Wealthfolio logo Wealthfolio
Download
Docs
Docker

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)
  • openssl and argon2 for generating the secret key and password hash (brew install argon2 on macOS, apt install argon2 on Debian/Ubuntu)

Pull the image

docker pull afadil/wealthfolio:latest

Or from GHCR:

docker pull ghcr.io/afadil/wealthfolio:latest

Both registries publish identical multi-arch builds (linux/amd64, linux/arm64). Pin to a version tag in production:

docker pull afadil/wealthfolio:3.3.0

Generate 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:latest

Open 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.docker
docker run -d \
  --name wealthfolio \
  -p 8088:8088 \
  -v wealthfolio-data:/data \
  --env-file .env.docker \
  --restart unless-stopped \
  afadil/wealthfolio:latest

When 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 data
  • secrets.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 before

If 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 / data

For 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 saysFix
WF_SECRET_KEY missing or invalidSet the variable; must decode to exactly 32 bytes (use openssl rand -base64 32)
Address already in useChange the host port: -p 3000:8088
Permission denied on /dataThe 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.