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 afadil/wealthfolio:latestOr from GHCR:
docker pull ghcr.io/afadil/wealthfolio:latestBoth registries publish identical multi-arch builds (linux/amd64,
linux/arm64). Pin to a version tag in production:
docker pull afadil/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 \
afadil/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 \
afadil/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"Ports
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 afadil/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 | The bind-mount path isn’t writable; use chown or switch to a named volume |
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.