What the image contains
The wallabag/wallabag image is one container that serves the web app on port 80. Its Dockerfile, in the wallabag/docker repository, builds it on Alpine with nginx and PHP-FPM 8.4 supervised by s6, and pins wallabag 2.6.14, the latest release in both repositories as of October 2026. The entrypoint renders app/config/parameters.yml from SYMFONY__ENV__ variables at every start, so configuration lives in the environment, not in a file you edit.
| Database | Value for SYMFONY__ENV__DATABASE_DRIVER | What the docs say |
|---|---|---|
| SQLite | pdo_sqlite (image default) | Fine for development; the user FAQ says SQLite was dropped because migrations lose data |
| MariaDB | pdo_mysql | Used by the README compose example; charset utf8mb4 |
| PostgreSQL | pdo_pgsql | Supported; the README example pins postgres 9.6 |
This page uses MariaDB. With MYSQL_ROOT_PASSWORD set, the entrypoint waits for the database port, creates the database and user named in the DATABASE_* variables, then runs wallabag:install. That sequence is read from entrypoint.sh in the source, not from the prose docs.
Sizing the machine
The wallabag docs publish no CPU or memory minimum. The image sets PHP memory_limit to 128M by default (PHP_MEMORY_LIMIT changes it). What grows is storage: every saved article sits in MariaDB, and with Download images locally enabled under Internal settings, article pictures land in web/assets/images.
Our reading, not a wallabag figure: a personal or family instance is a light PHP app plus a small database, so a single Moonshot cartridge or a single-socket machine from the dedicated server range is enough, and the deciding spec is the disk. Choose an x86 line or the Ampere Altra; per its publish workflow the image is also built for arm64. Leave the X-Gene cartridge out: it boots nothing newer than Ubuntu 18.04, an aging footing for container work you will keep patching. If the same box also runs a feed reader such as FreshRSS, size memory for both.
Expect a ready, racked machine about 30 minutes after payment approval; builds to order take 4 to 24 hours. Rack servers carry an out-of-band console (iLO, iDRAC or a Supermicro BMC), which matters when a firewall rule locks SSH out. Of the Moonshot cartridges only the m510 and m710x have one; the rest have power control only. On those, read the ufw lines twice before pasting: a lock-out there has no console to undo it.
The environment, variable by variable
Keep the settings in two files in /opt/wallabag, both chmod 600. Generate every secret with openssl rand -hex 32. Hex is our precaution: the entrypoint pastes the database password into a SQL statement, so quote characters would break it.
| Variable | Set to | Why |
|---|---|---|
SYMFONY__ENV__DOMAIN_NAME | https://wallabag.example.com | Full public URL, no trailing slash; the image default is a placeholder host |
SYMFONY__ENV__SECRET | random hex | The image ships a fixed default value that every install shares |
SYMFONY__ENV__DATABASE_DRIVER | pdo_mysql | Without it the entrypoint falls back to SQLite |
SYMFONY__ENV__DATABASE_HOST / _PORT | db / 3306 | The compose service name and MariaDB port |
SYMFONY__ENV__DATABASE_NAME / _USER | wallabag / wallabag | Created by the entrypoint on first start |
SYMFONY__ENV__DATABASE_PASSWORD | random hex | Password for that user |
SYMFONY__ENV__DATABASE_CHARSET | utf8mb4 | The parameters doc: utf8mb4 for MySQL-family databases |
SYMFONY__ENV__FOSUSER_REGISTRATION | false | Public sign-up off; already the image default, set it so nobody flips it by accident |
MYSQL_ROOT_PASSWORD | random hex, in db-root.env | Initialises MariaDB and lets wallabag create its database |
Outgoing mail defaults to smtp://127.0.0.1, and nothing inside the container listens there, so password-reset mail goes nowhere until SYMFONY__ENV__MAILER_DSN and SYMFONY__ENV__FROM_EMAIL point at a real relay.
Setting it up
- Publish DNS first:
wallabag.example.comneeds an A record, and an AAAA record too when the server has IPv6. The certificate depends on it. - Install Docker Engine and the Compose plugin from Docker's own instructions. Keep Engine at 28 or later: from that release a port published on 127.0.0.1 is reachable from the host only (moby#45610).
- Make
/opt/wallabag, writewallabag.envanddb-root.envinto it with the values above, andchmod 600both. - Paste the block below. It closes ufw to everything but SSH (IPv6 included) before any container starts, writes
compose.yaml, and brings the stack up. - Follow
sudo docker compose logs -f wallabaguntil the entrypoint printswallabag is ready!. - Delete the
db-root.envline under the wallabag service only, thensudo docker compose up -donce more. Left in place, the variable would make every later start print a warning fromentrypoint.shasking for it to be removed.
# run in /opt/wallabag once wallabag.env and db-root.env exist there
sudo sed -i 's/^IPV6=.*/IPV6=yes/' /etc/default/ufw
sudo ufw allow OpenSSH
sudo ufw --force enable
sudo tee /opt/wallabag/compose.yaml > /dev/null <<'EOF'
services:
wallabag:
image: wallabag/wallabag:2.6.14
restart: unless-stopped
env_file:
- wallabag.env
- db-root.env
ports:
- "127.0.0.1:8080:80"
volumes:
- images:/var/www/wallabag/web/assets/images
depends_on:
db:
condition: service_healthy
db:
image: mariadb:11.4
restart: unless-stopped
env_file:
- db-root.env
volumes:
- dbdata:/var/lib/mysql
healthcheck:
test: ["CMD", "/usr/local/bin/healthcheck.sh", "--innodb_initialized"]
interval: 20s
timeout: 3s
volumes:
images:
dbdata:
EOF
cd /opt/wallabag && sudo docker compose up -d
Docker publishes past ufw: a mapping on 0.0.0.0 or [::] is not filtered by ufw over IPv4, nor over IPv6 if the daemon has IPv6 enabled. Hence the 127.0.0.1:8080:80 mapping, and hence Engine 28 or later.
Two choices in that file are ours, not the README's: the mariadb:11.4 pin (the README uses an untagged mariadb) and making wallabag wait on the database healthcheck (the healthcheck line itself is the README's). The named images volume comes from the installation page, whose docker run example uses named volumes; the README's own examples mount host folders. Our reason for preferring it: Docker fills a new named volume from the image, and the Dockerfile hands /var/www/wallabag to nobody, the user PHP writes pictures as.
The default account, changed in private
An install from this image creates one user: login wallabag, password wallabag, as the image README and the installation page state. InstallCommand.php shows the account gets the super-admin role and the email wallabag@wallabag.io. Change it while the app still answers on loopback only.
- From your workstation, open a tunnel through your own login:
ssh -L 8080:127.0.0.1:8080 you@your-server. - Browse to
http://localhost:8080and sign in as wallabag / wallabag. - Config, Password tab: set a new one (the docs require 8 characters at least).
- Config, User information tab: replace the email with yours, so reset mail reaches you.
- Close the tunnel. Apart from SSH, nothing on the server is reachable from outside yet.
nginx and the certificate
nginx runs on the host, so ufw filters it on IPv4 and IPv6 alike until the Nginx Full profile is allowed. Ubuntu's default nginx site answers on port 80 from /var/www/html, which is all the webroot challenge needs. The block asks for the certificate first and writes the site that references it only if that worked.
sudo apt install -y nginx certbot
sudo ufw allow 'Nginx Full'
if sudo certbot certonly --webroot -w /var/www/html -d wallabag.example.com --deploy-hook "systemctl reload nginx"; then
sudo tee /etc/nginx/sites-available/wallabag > /dev/null <<'EOF'
server {
listen 80;
listen [::]:80;
server_name wallabag.example.com;
location /.well-known/acme-challenge/ { root /var/www/html; }
location / { return 301 https://$host$request_uri; }
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name wallabag.example.com;
ssl_certificate /etc/letsencrypt/live/wallabag.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/wallabag.example.com/privkey.pem;
client_max_body_size 100M;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $server_name;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-For $remote_addr;
}
}
EOF
sudo ln -s /etc/nginx/sites-available/wallabag /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
else
echo "No certificate: check DNS and port 80, then run this block again"
fi
The three forwarded headers come from the README's nginx example. client_max_body_size 100M matches the limit in root/etc/nginx/nginx.conf of wallabag/docker, the container's own nginx, so large imports are refused by neither layer. If the box will serve several apps, Nginx Proxy Manager is the GUI alternative.
Browser extension and mobile apps
Both talk to the API, so each needs an API client: in wallabag open API clients management (the path is /developer/client/create on your instance), name the client, and copy the Client ID and Client secret.
- Wallabagger (Firefox and Chrome): in its options, enter the instance URL without
http://, tick https, check the URL, then fill in the client and your credentials. The docs warn this version keeps your password in browser local storage as plain text. - Android: the instance URL must not end with a slash, use https, and two-factor login is not supported by the app.
- iOS: enter the URL, then the Client ID and secret, then your wallabag login.
The bookmarklet (bag it! under Your Account, Add Link) needs no API client at all.
Backups and upgrades
The backup page lists three things: the settings in parameters.yml, the database and the pictures in web/assets/images. With this image the settings are regenerated from your env files, so keep compose.yaml and both env files instead. The script dumps MariaDB through MYSQL_PWD, which keeps the password off the dump's command line, and streams the images volume out as a tar.
# /usr/local/sbin/wallabag-backup.sh (run as root: sudo bash /usr/local/sbin/wallabag-backup.sh)
set -eu
umask 077
out=/var/backups/wallabag
mkdir -p -m 700 "$out"
cd /opt/wallabag
day=$(date +%F)
docker compose exec -T db sh -c 'MYSQL_PWD="$MYSQL_ROOT_PASSWORD" exec mariadb-dump -uroot --single-transaction --databases wallabag' > "$out/db-$day.sql"
docker compose exec -T wallabag tar -C /var/www/wallabag/web/assets -cf - images > "$out/images-$day.tar"
echo "wrote $out/db-$day.sql and $out/images-$day.tar"
Schedule it with printf '30 3 * * * root bash /usr/local/sbin/wallabag-backup.sh > /var/log/wallabag-backup.log 2>&1\n' | sudo tee /etc/cron.d/wallabag-backup. Then copy /var/backups/wallabag, compose.yaml and the two env files to a machine somewhere else; a backup on the same disk dies with it. sudo head -n 5 on a dump and sudo tar -tf on an image archive prove the files exist and read, nothing more; only a restore proves the backup.
Restore, from /opt/wallabag, with the date of the pair you want (here 2026-10-20). On a new server, first bring the stack up once with db-root.env still listed, so the database user exists.
- Check both files:
sudo test -s /var/backups/wallabag/db-2026-10-20.sql && sudo test -s /var/backups/wallabag/images-2026-10-20.tar && echo pair-ok. Continue only whenpair-okappears. - Stop the app, not the database:
sudo docker compose stop wallabag. - Load the dump as root:
sudo sh -c 'docker compose exec -T db bash -c "MYSQL_PWD=\$MYSQL_ROOT_PASSWORD exec mariadb --user=root" < /var/backups/wallabag/db-2026-10-20.sql'. - Start it again:
sudo docker compose start wallabag. - Put the pictures back:
sudo sh -c 'docker compose exec -T wallabag tar -xf - --directory=/var/www/wallabag/web/assets < /var/backups/wallabag/images-2026-10-20.tar', thensudo docker compose exec -T wallabag chown -R nobody:nobody /var/www/wallabag/web/assets/images. - Sign in and open an article that has pictures.
Upgrades follow the same discipline:
- Take a fresh backup.
- Edit the wallabag tag in
compose.yamlto the new release. - Download the new image:
sudo docker compose pull. - Recreate the containers:
sudo docker compose up -d. - Apply schema changes:
sudo docker compose run --rm wallabag migrate. The README namesmigrateas the easy path, and the 2.5 to 2.6 notes say wallabag will not work if the migration is skipped.
Licence and limits
wallabag is MIT-licensed (COPYING.md, and composer.json says the same). Our general reading, not legal advice: you may run, change and share it freely, and the one duty, keeping the copyright and permission notice, falls on whoever distributes copies, not on someone running it for themselves.
When this is the wrong box: a single reader who wants zero upkeep. The image, MariaDB, nginx and the certificate are all yours to patch, and nobody else watches the backups.
Questions
Why not keep the image default, SQLite?
The image starts on SQLite when SYMFONY__ENV__DATABASE_DRIVER is not set, and that works. The wallabag FAQ says SQLite support was dropped because migrations go badly on it and data is lost each time, and recommends it only for development. MariaDB costs one more container.
What is the wallabag Docker default login?
For installs from the official image, username wallabag and password wallabag, per the image README; the install step creates it on first start, and InstallCommand.php gives it the super-admin role. Change the password and email over an SSH tunnel before publishing the site.
How do I add a second user with registration off?
Use the console, from /opt/wallabag: sudo docker compose exec wallabag su -c "bin/console --env=prod fos:user:create" -s /bin/sh nobody asks for the username, email and password. Running it as nobody, as the entrypoint does, keeps the cache files owned by the app user. Each user gets their own articles.
Can I import from another read-it-later service?
Yes. The import docs cover Pocket, Instapaper, Pinboard, Readability, elCurator and wallabag v1 and v2 exports. Firefox and Chrome bookmark files go in from the console with wallabag:import --importer=firefox or --importer=chrome. Large imports can run through Redis or RabbitMQ workers; the README shows the import argument for a Redis worker container.