Self-hosted service
wallabag/docker avatar
wallabag/docker

wallabag Docker: self hosted read later behind one image

Official docker-composer for wallabag.

774 stars170 forksShellLicense varies

At a glance

What is it?
The wallabag Docker image packages the read later application with nginx, PHP-FPM and a database or Redis connection configured through a long list of environment variables. It suits people who want a private bookmarking service on their own hardware, with the trade-off that upgrades require an explicit migration step.
Who is it for?
wallabag Docker is a sound choice for a home lab or a small team that wants a self hosted read later service and prefers a container to a PHP install, since the image bundles nginx, PHP-FPM, the SQLite path and the optional database and Redis clients in one artifact. It is the wrong choice if you want a clustered deployment, if you cannot accept a migration step on every version bump, or if you need an image built from your own source rather than a downloaded release tarball.
Can I use it commercially?
Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly Shell, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

One image with nginx, PHP-FPM and s6 inside it

This repository is the official Docker packaging for wallabag, a self hostable application for saving web pages that the documentation describes as free and open source. The image is published as wallabag/wallabag and the README carries badges for Docker Stars and Docker Pulls, which tells you the image rather than the application code is the artefact people consume.

The Dockerfile shows what the image actually is. It is built on alpine:3.24 with nginx as the web server, PHP 8.4 with FPM, a specific list of PHP extensions including pdo_mysql, pdo_pgsql and pdo_sqlite, and both the mariadb-client and postgresql17-client packages so the container can talk to an external database. s6 is installed as the process supervisor, and the nginx access and error logs are symlinked to standard output and standard error so `docker logs` shows them.

One build detail is worth a paragraph of its own. The image uses envsubst to substitute the environment variables into the configuration, and the Dockerfile notes that envsubst from gettext cannot replace environment variables that have default values, and that the package is not available for ARM32. So the tool is compiled from source with Go, with the comment that the `-ldflags "-s -w"` flag produces a smaller executable. In practice, that means the image is buildable on ARM without a prebuilt package, at the cost of a Go toolchain in the build stage.

The application code itself is not built inside the image. The Dockerfile downloads a release tarball from the wallabag project on GitHub, using a `WALLABAG_VERSION` build argument that defaults to 2.6.14, and a `COMPOSER_VERSION` argument that defaults to 2.2 for the composer stage.

The environment variable surface is the configuration

There is no configuration file to edit. Everything is passed through environment variables, and the README documents them in detail.

The database settings use a Symfony convention, with the `SYMFONY__ENV__` prefix. `SYMFONY__ENV__DATABASE_DRIVER` selects the driver and defaults to pdo_sqlite, `SYMFONY__ENV__DATABASE_HOST` defaults to 127.0.0.1 and should be the name of the database container when MySQL is used, `SYMFONY__ENV__DATABASE_PORT` is the port, `SYMFONY__ENV__DATABASE_NAME` defaults to symfony, `SYMFONY__ENV__DATABASE_USER` defaults to root, `SYMFONY__ENV__DATABASE_PASSWORD` defaults to a tilde, `SYMFONY__ENV__DATABASE_CHARSET` defaults to utf8 and `SYMFONY__ENV__DATABASE_TABLE_PREFIX` defaults to wallabag_.

Application level settings cover the rest. `SYMFONY__ENV__SECRET` has a hardcoded default value of `ovmpmAWXRCabNlMgzlzFXDYmCFfzGv`, which is exactly the kind of default that has to be changed before the instance is reachable by anyone else. `SYMFONY__ENV__DOMAIN_NAME` defaults to `https://your-wallabag-instance.wallabag.org` and sets the URL of your instance, `SYMFONY__ENV__LOCALE` defaults to en, and `SYMFONY__ENV__SERVER_NAME` defaults to `Your wallabag instance` and sets the user friendly name shown as the two factor issuer.

Mail and registration are configurable too. `SYMFONY__ENV__MAILER_DSN` defaults to `smtp://127.0.0.1`, `SYMFONY__ENV__FROM_EMAIL` defaults to `[email protected]`, `SYMFONY__ENV__TWOFACTOR_SENDER` defaults to `[email protected]`, and `SYMFONY__ENV__FOSUSER_REGISTRATION` defaults to false while `SYMFONY__ENV__FOSUSER_CONFIRMATION` defaults to true, which is the pair of switches that decides whether strangers can create accounts on your instance.

Two more deserve attention. `POPULATE_DATABASE` defaults to True and answers the question of whether the database should be populated or is an existing one. `PHP_MEMORY_LIMIT` changes the PHP memory limit and defaults to 128M, and the README notes it should be a number with a unit such as 512K, 128M, 2G or a number of bytes, which is the knob to reach for when a large import fails.

First run: SQLite in a single container

The README calls SQLite the easiest way to start wallabag, and it is a single command with no database container:

bash
$ docker run -p 80:80 -e "SYMFONY__ENV__DOMAIN_NAME=http://localhost" wallabag/wallabag

Port 80 is published to the host and the domain name is set to localhost, and then you point your browser at `http://localhost`. The default login is `wallabag:wallabag`, which the README states at the top of the image usage section, and that is the account to change once you are in.

That container is disposable, so the next step is persistence. The documented run adds two volumes, one for the data directory and one for the image assets:

bash
$ docker run -v /opt/wallabag/data:/var/www/wallabag/data -v /opt/wallabag/images:/var/www/wallabag/web/assets/images -p 80:80 -e "SYMFONY__ENV__DOMAIN_NAME=http://localhost" wallabag/wallabag

The two paths inside the container are the ones to remember, because they are what a backup script has to copy. Anything the application writes outside `/var/www/wallabag/data` and outside `/var/www/wallabag/web/assets/images` will be lost when the container is replaced, and with SQLite the data directory is the database, so losing it means losing the library.

For a first evaluation that SQLite command is enough. For anything you intend to keep, mount those volumes and set the domain name to the URL you will actually use, since a wrong domain name shows up in links inside saved articles and in the password reset mail.

MariaDB and PostgreSQL need their own container

Moving off SQLite means running the database as a second container and telling the wallabag container how to reach it. The README gives the MariaDB and MySQL form, and it is worth reading closely because the password variable appears on both sides.

bash
$ docker run --name wallabag-db -e "MYSQL_ROOT_PASSWORD=my-secret-pw" -d mariadb
$ docker run --name wallabag --link wallabag-db:wallabag-db -e "MYSQL_ROOT_PASSWORD=my-secret-pw" -e "SYMFONY__ENV__DATABASE_DRIVER=pdo_mysql" -e "SYMFONY__ENV__DATABASE_HOST=wallabag-db" -e "SYMFONY__ENV__DATABASE_PORT=3306" -e "SYMFONY__ENV__DATABASE_NAME=wallabag" -e "SYMFONY__ENV__DATABASE_USER=wallabag" -e "SYMFONY__ENV__DATABASE_PASSWORD=wallapass" -e "SYMFONY__ENV__DATABASE_CHARSET=utf8mb4" -e "SYMFONY__ENV__DOMAIN_NAME=http://localhost" -p 80:80 wallabag/wallabag

The `MYSQL_ROOT_PASSWORD` value is passed to the wallabag container as well as the database container, and the README explains why: the entrypoint in the wallabag container uses it to create the database and user if they are not there yet. The `POSTGRES_PASSWORD` and `POSTGRES_USER` variables play the same role for the PostgreSQL path, and `SYMFONY__ENV__DATABASE_HOST` is set to the name of the database container, matching the note that this value should be the name of the mariadb container when MySQL is used.

The PostgreSQL example is the same shape with different values:

bash
$ docker run --name wallabag-db -e "POSTGRES_PASSWORD=my-secret-pw" -e "POSTGRES_USER=my-super-user" -d postgres:9.6
$ docker run --name wallabag --link wallabag-db:wallabag-db -e "POSTGRES_PASSWORD=my-secret-pw" -e "POSTGRES_USER=my-super-user" -e "SYMFONY__ENV__DATABASE_DRIVER=pdo_pgsql" -e "SYMFONY__ENV__DATABASE_HOST=wallabag-db" -e "SYMFONY__ENV__DATABASE_PORT=5432" -e "SYMFONY__ENV__DATABASE_NAME=wallabag" -e "SYMFONY__ENV__DATABASE_USER=wallabag" -e "SYMFONY__ENV__DATABASE_PASSWORD=wallapass" -e "SYMFONY__ENV__DOMAIN_NAME=http://localhost" -p 80:80 wallabag/wallabag

Note that this example pins `postgres:9.6`, an old server image, while the application container itself ships the postgresql17-client package. The database server version and the client version in the image are not the same thing, and a mismatch of that kind is the kind of detail that bites during an upgrade.

Redis is a separate container with a naming requirement

Redis is optional and its configuration has a quirk worth reading twice. The README states that to use redis with a Docker link, a redis container with the name `redis` is needed, and that none of the REDIS environment variables are required in that case:

bash
$ docker run -p 6379:6379 --name redis redis:alpine
$ docker run -p 80:80 -e "SYMFONY__ENV__DOMAIN_NAME=http://localhost" --link redis:redis wallabag/wallabag

The name matters because the application environment defaults to it. `SYMFONY__ENV__REDIS_HOST` defaults to redis, `SYMFONY__ENV__REDIS_PORT` defaults to 6379, `SYMFONY__ENV__REDIS_SCHEME` defaults to tcp and can also be unix or http, `SYMFONY__ENV__REDIS_PATH` defaults to a tilde and is the path of a unix socket file, and `SYMFONY__ENV__REDIS_PASSWORD` defaults to a tilde.

If the Redis server lives somewhere else, you set the variables instead of linking:

bash
$ docker run -p 80:80 -e "SYMFONY__ENV__REDIS_HOST=my.server.hostname" -e "SYMFONY__ENV__REDIS_PASSWORD=my-secret-pw" -e "SYMFONY__ENV__DOMAIN_NAME=http://localhost" wallabag/wallabag

The password is optional in the sense that the default is a tilde, which means no password, and the README notes that `SYMFONY__ENV__SENTRY_DSN` follows the same pattern with a tilde default. Redis is used for caching and for the asynchronous work that fetching a page involves, which is why the first request after saving a link can be slower than later reads.

Upgrades need a migration step, not just a new image

This is the operational detail that catches people out. The README says that when there is a version upgrade that needs a database migration, the easiest way to do it is to run the `migrate` command:

bash
$ docker run --link wallabag-db:wallabag-db -e <... your config variables here ...>  wallabag/wallabag migrate

The alternative is to start the container with the new image and then run the migration command by hand inside the running container:

bash
$ docker exec -t NAME_OR_ID_OF_YOUR_WALLABAG_CONTAINER /var/www/wallabag/bin/console doctrine:migrations:migrate --env=prod --no-interaction

The first form runs the image with a command instead of the server, so it is a one-shot container that talks to the same database and applies the migrations. The second form is the one to use when the container is already up, and the `--env=prod --no-interaction` flags are what make the Symfony console command run unattended.

The point to internalise is that pulling a new image is not an upgrade. A new image with an old schema is a broken instance, and the migration is a separate step that you trigger. For a deployment pipeline, that means the image pull and the migration are two stages, and the migration needs the same environment variables as the running container, which is why the first command repeats the configuration rather than assuming it.

For the compose route, the README has a docker-compose section, and the presence of a `conftest.py` and a `tests/` directory at the top of the repository, plus a CI badge in the README, indicates that the compose file and the image behaviour are exercised by tests in the repository rather than only documented by hand.

Where this image is the wrong choice

The first limitation is that a version upgrade is a two step operation. Nothing here automates the migration for you, and the README does not document a rollback procedure. If you cannot run a one-shot container with your configuration and then a doctrine migrations command, the image is awkward to operate.

The second is the shipped defaults. The application secret has a hardcoded value, the default login is wallabag:wallabag, and `SYMFONY__ENV__FOSUSER_REGISTRATION` defaults to false while confirmation defaults to true. Those defaults are safe for a local trial, and any of them become a problem the moment the instance is reachable from a network. A container that binds port 80 on the host with a default password and a published secret is the kind of thing that ends up indexed by a search engine.

The third is that the image ships a downloaded release rather than a build of the current source. The `WALLABAG_VERSION` argument defaults to 2.6.14, so the image is pinned to a released version of the application and moves only when the maintainers bump that argument. That is a good property for reproducibility and a bad one if you want a fix that landed upstream last week. The image also bundles nginx, PHP-FPM and the s6 supervisor in one container, which is convenient and is the opposite of what you would build for a multi-node deployment.

The fourth limitation is the database example. The PostgreSQL instructions pin `postgres:9.6` as the server image, which is a long way from current, while the container ships a postgresql17 client. The two are not required to match, but a pairing that old in the documentation is a sign the example has not been revisited.

On maintenance, the repository is not archived and the last push was on 2026-08-18. The tagged releases are 2.6.14 on 2025-10-07, 2.6.13 on 2025-06-04 and 2.6.12 on 2025-04-11, so the release line advances every few months and the Dockerfile tracks it. The licence is not identified in the repository metadata, and the README does not state a licence either, so for a deployment that redistributes or modifies the image, the terms need to be established with the maintainers rather than assumed.

The alternative approach to weigh is a plain PHP install under a reverse proxy, where you control the web server, the PHP version and the application release independently. The image wins on setup time and on the fact that the whole stack is already assembled and tested together, including the ARM build path. It loses when you need to patch the application, scale it, or keep a configuration file in version control that the image does not understand.

Editorial conclusion

wallabag Docker is a sound choice for a home lab or a small team that wants a self hosted read later service and prefers a container to a PHP install, since the image bundles nginx, PHP-FPM, the SQLite path and the optional database and Redis clients in one artifact. It is the wrong choice if you want a clustered deployment, if you cannot accept a migration step on every version bump, or if you need an image built from your own source rather than a downloaded release tarball. Before deploying, replace the default secret and the default wallabag:wallabag login, set SYMFONY__ENV__DOMAIN_NAME to the real URL, and confirm the WALLABAG_VERSION build argument matches the release you intend to run.

Frequently asked questions

How do I start wallabag with Docker and SQLite?

The README calls SQLite the easiest way to start wallabag. The documented command is `docker run -p 80:80 -e "SYMFONY__ENV__DOMAIN_NAME=http://localhost" wallabag/wallabag`, and the default login is `wallabag:wallabag`.

Which volumes do I need to mount so wallabag data survives a container replacement?

The documented run mounts `/opt/wallabag/data` to `/var/www/wallabag/data` and `/opt/wallabag/images` to `/var/www/wallabag/web/assets/images`. With SQLite the data directory holds the database, so losing it means losing the library.

How do I run the database migration when upgrading wallabag?

The README says a version upgrade that needs a database migration is best done by running the `migrate` command on the image, or by starting the new image and running `docker exec -t NAME_OR_ID_OF_YOUR_WALLABAG_CONTAINER /var/www/wallabag/bin/console doctrine:migrations:migrate --env=prod --no-interaction` inside it.

How do I use Redis with the wallabag Docker image?

With a Docker link, a redis container named `redis` is required and none of the REDIS environment variables are needed. With an external host, set `SYMFONY__ENV__REDIS_HOST` and `SYMFONY__ENV__REDIS_PASSWORD`, while the host defaults to redis and the port to 6379.

Which environment variables should I change before exposing a wallabag instance?

At minimum `SYMFONY__ENV__SECRET`, which has a hardcoded default, `SYMFONY__ENV__DOMAIN_NAME`, which should be the real URL of the instance, and the default `wallabag:wallabag` login. `SYMFONY__ENV__FOSUSER_REGISTRATION` controls whether public registration is enabled.

How is the wallabag Docker image built and which application version does it ship?

The Dockerfile is based on alpine:3.24 with nginx, PHP 8.4 FPM, s6 and the database clients, and it downloads a wallabag release tarball using the `WALLABAG_VERSION` build argument, which defaults to 2.6.14. envsubst is compiled from Go source because the gettext package is not available for ARM32.

Official sources

  1. Issues
  2. README
  3. Releases
  4. wallabag/docker on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/wallabag-docker.svg)](https://hysenlabs.com/projects/wallabag-docker)

Was this useful?