Docker
Run Wealthfolio with the official Docker image using docker run. Works on any Linux box, macOS Docker Desktop, or Windows.
The fastest way to self-host Wealthfolio: pull the official multi-arch
image and run it with docker run. For a Compose-based setup with
restart policies and an env file, see
Docker Compose.
Prerequisites
- Docker installed (install guide)
opensslandargon2for generating the secret key and password hash (brew install argon2on macOS,apt install argon2on Debian/Ubuntu)
Pull the image
docker pull wealthfolio/wealthfolio:latestOr from GHCR:
docker pull ghcr.io/wealthfolio/wealthfolio:latestBoth registries publish identical multi-arch builds (linux/amd64,
linux/arm64). Pin to a version tag in production:
docker pull wealthfolio/wealthfolio:3.3.0Generate your secrets
Two values are required (see Configuration for full details):
# 32-byte secret key. Save this somewhere safe!
SECRET=$(openssl rand -base64 32)
# Argon2id password hash for your login
HASH=$(printf 'your-password' | argon2 yoursalt16chars! -id -e)Run
Quick start (inline env vars)
docker run -d \
--name wealthfolio \
-p 8088:8088 \
-v wealthfolio-data:/data \
-e WF_LISTEN_ADDR=0.0.0.0:8088 \
-e WF_DB_PATH=/data/wealthfolio.db \
-e WF_SECRET_KEY="$SECRET" \
-e WF_AUTH_PASSWORD_HASH="$HASH" \
-e WF_CORS_ALLOW_ORIGINS=http://localhost:8088 \
--restart unless-stopped \
wealthfolio/wealthfolio:latestOpen http://localhost:8088 and log in with the password you hashed.
Inside the container, WF_LISTEN_ADDR must be 0.0.0.0:PORT. Binding to 127.0.0.1 makes
the app reachable only from inside the container.
Production (env file)
For anything beyond a quick try, put your config in a file:
cat > .env.docker <<'EOF'
WF_LISTEN_ADDR=0.0.0.0:8088
WF_DB_PATH=/data/wealthfolio.db
WF_SECRET_KEY=replace-me
WF_AUTH_PASSWORD_HASH=$argon2id$v=19$m=19456,t=2,p=1$...
WF_CORS_ALLOW_ORIGINS=https://wealthfolio.example.com
WF_AUTH_TOKEN_TTL_MINUTES=480
EOF
chmod 600 .env.dockerdocker run -d \
--name wealthfolio \
-p 8088:8088 \
-v wealthfolio-data:/data \
--env-file .env.docker \
--restart unless-stopped \
wealthfolio/wealthfolio:latestWhen using --env-file, Docker keeps $ characters in the hash as-is. No escaping needed. If you
switch to -e flags or YAML inline values, see the escaping
table.
Volumes and ports
Volumes
/data is the only mount you need. It holds:
wealthfolio.db: SQLite database with all your portfolio datasecrets.json: encrypted broker credentials and API keys
# Named volume (recommended, Docker manages location)
-v wealthfolio-data:/data
# Bind mount (you control the path)
-v /opt/wealthfolio:/data
# Bind mount in current directory
-v "$(pwd)/wealthfolio-data:/data"The container runs as non-root UID/GID 1000:1000. If you bind-mount a host directory, make sure
it’s writable by that user: sudo chown -R 1000:1000 /opt/wealthfolio. Named volumes don’t need
this for fresh installs — Docker creates them with the right ownership automatically.
Upgrading from a pre-v3.4.0 image? Older images ran as root, so existing data is owned by
root and the new container can’t write to it. Chown the volume once before starting the new image
using the snippet below. If you manage Wealthfolio with Compose, see the
Docker Compose upgrade notes — the volume name will be prefixed with your compose project.
# Named volume (volume name matches what you used with `docker run -v`)
docker run --rm -v wealthfolio-data:/data alpine chown -R 1000:1000 /data
# Bind mount
sudo chown -R 1000:1000 /opt/wealthfolioPorts
The container exposes 8088 by default. Map it however you like:
-p 8088:8088 # Default
-p 3000:8088 # Different host port
-p 127.0.0.1:8088:8088 # Localhost only (for reverse proxy)Updating
docker pull wealthfolio/wealthfolio:latest
docker stop wealthfolio
docker rm wealthfolio
# Re-run with the same flags as beforeIf you need rolling updates without downtime, switch to Docker Compose or a proper orchestrator.
Always back up /data (and your WF_SECRET_KEY outside the volume) before updating. See
Backups below.
Backups
The only state lives in the /data volume. Tar it up:
docker run --rm \
-v wealthfolio-data:/data \
-v "$(pwd):/backup" \
alpine tar czf /backup/wealthfolio-$(date +%Y%m%d).tar.gz -C / dataFor a named volume, restore by stopping the container and untarring back into the same volume.
Back up the volume and WF_SECRET_KEY together. Either alone is useless: the volume holds
encrypted secrets that only the key can decrypt.
Reverse proxy
For HTTPS and a real domain, put Wealthfolio behind a reverse proxy. See Reverse proxy setup for Nginx, Caddy, Traefik, and NPM examples.
Troubleshooting
Container won’t start
docker logs wealthfolio
| Log says | Fix |
|---|---|
WF_SECRET_KEY missing or invalid | Set the variable; must decode to exactly 32 bytes (use openssl rand -base64 32) |
Address already in use | Change the host port: -p 3000:8088 |
Permission denied on /data | Volume is owned by root (pre-v3.4.0 upgrade) or the bind-mount path isn’t writable by UID 1000. Run the chown step in the upgrade callout above. |
Login rejects the right password
Most common cause: the hash captured a trailing newline (you used echo -n instead of printf), or the $ characters got eaten by your shell.
Regenerate with printf and quote the value with single quotes when
passing via -e.
CORS errors in browser console
WF_CORS_ALLOW_ORIGINS must match your browser’s address bar exactly:
scheme, host, and port all have to line up. If you access via
http://192.168.1.10:8088, that exact string is what goes in.