Skip to main content

Self-hosting with Docker

Besides the desktop app, you can run FlyBudget on your own server (a home server, a NAS, or a small cloud machine) and open it from any browser, like Actual Budget or Firefly III. It runs in a single Docker container, and a password protects it.

info

The desktop app doesn't need any of this. Self-hosting is for when you want to reach your budget from several devices, or share it with someone in your household.

Quick start​

You need Docker with Docker Compose.

  1. Create a folder for FlyBudget and download the example docker-compose.yml into it.

  2. Create the encryption key that protects your stored bank credentials, and make it readable only by FlyBudget (which runs as user id 1000). If you're logged in as root, leave out sudo.

    openssl rand -base64 32 > flybudget_data_key.txt
    sudo chown 1000:1000 flybudget_data_key.txt
    sudo chmod 400 flybudget_data_key.txt

    Keep a copy of the key somewhere safe (a password manager works well), separate from your backups. Without it, FlyBudget can't read your saved bank connections, and you'd have to connect your banks again.

  3. Start FlyBudget:

    docker compose up -d
  4. Open http://<your-server>:3001 in a browser and create your password. FlyBudget also asks for a setup code, which only appears in the server's log:

    docker logs flybudget

    The code makes sure only someone with access to the server can set the password, not whoever happens to open the page first.

That's it. Your budget is stored in a Docker volume.

FlyBudget won't start?

Check docker logs flybudget. If it can't read the key file, make sure flybudget_data_key.txt belongs to user id 1000 (step 2). The key must be 32 random bytes in base64, which is exactly what the openssl command above creates.

Docker bypasses your firewall

Docker opens published ports on every network interface, and firewalls like ufw don't block them. If FlyBudget sits behind a reverse proxy on the same machine, publish it only locally by changing the port line in docker-compose.yml to '127.0.0.1:3001:3001'.

Without Compose​

docker run -d --name flybudget -p 3001:3001 \
-v flybudget-data:/data \
--read-only --tmpfs /tmp --cap-drop ALL --security-opt no-new-privileges:true \
-v "$PWD/flybudget_data_key.txt:/run/secrets/flybudget_data_key:ro" \
-e FLYBUDGET_DATA_KEY_FILE=/run/secrets/flybudget_data_key \
--restart unless-stopped \
ghcr.io/dtymoszenko/flybudget:latest

Building the image yourself​

git clone https://github.com/dtymoszenko/FlyBudget.git
cd FlyBudget
docker build -t flybudget .

Then use flybudget instead of ghcr.io/dtymoszenko/flybudget:latest.

Using it over the internet: HTTPS​

On your home network, http:// is fine. Before making FlyBudget reachable from the internet, put it behind a reverse proxy that provides HTTPS, such as Caddy, Traefik, or nginx. Otherwise your password and budget travel unencrypted.

With Caddy, the whole configuration is (use localhost:3001 instead of flybudget:3001 if Caddy isn't running in Docker on the same network):

budget.example.com {
reverse_proxy flybudget:3001
}

Then tell FlyBudget about the proxy and your address in docker-compose.yml:

environment:
- FLYBUDGET_TRUST_PROXY=1
- FLYBUDGET_ALLOWED_HOSTS=budget.example.com

FLYBUDGET_TRUST_PROXY=1 lets FlyBudget see that the connection is HTTPS, so your login cookie is only ever sent over HTTPS.

Settings​

VariableWhat it does
FLYBUDGET_DATA_KEY_FILEPath to the file holding the encryption key (a Docker secret). Required (or FLYBUDGET_DATA_KEY).
FLYBUDGET_DATA_KEYThe key itself, instead of a file. Less private: anyone who can run docker inspect can see it.
FLYBUDGET_ALLOWED_HOSTSComma-separated addresses FlyBudget answers to, e.g. budget.example.com,192.168.1.20. Recommended; by default it answers to any address.
FLYBUDGET_TRUST_PROXYSet to the number of reverse proxies in front of FlyBudget (usually 1) when using one.
PORTPort inside the container (default 3001). You'll usually change the port mapping in docker-compose.yml instead.

Your password​

  • Change it in Settings → Server. Changing it signs out every other device.

  • After 10 wrong attempts, logins are blocked for up to 15 minutes.

  • Forgot it? Remove the saved password (this also signs everyone out):

    docker exec flybudget node reset-password.cjs

    Then open FlyBudget and create a new password, using the new setup code from docker logs flybudget.

Backups​

Everything lives in one SQLite file, budget.db, in the /data volume. To make a backup while FlyBudget is running:

docker exec flybudget node backup.cjs
docker cp flybudget:/data/backups ./flybudget-backups

The first command saves a complete, consistent copy to /data/backups inside the volume; the second copies all your backups to the flybudget-backups folder next to your docker-compose.yml. Don't copy budget.db directly while FlyBudget is running: recent changes may still be in a separate budget.db-wal file.

To restore, stop FlyBudget, put the backup in place as budget.db and start it again:

docker compose stop
docker run --rm --volumes-from flybudget -v "$PWD/flybudget-backups":/backups alpine \
sh -c 'rm -f /data/budget.db-wal /data/budget.db-shm && cp /backups/<backup-file>.db /data/budget.db && chown 1000:1000 /data/budget.db'
docker compose start

You can also download a JSON backup of your data from Settings → Data at any time. Keep flybudget_data_key.txt backed up too, separately from the database.

Updating​

docker compose pull
docker compose up -d

Database updates are applied automatically when the new version starts. Back up first, just in case.

How the container is secured​

  • It runs as an unprivileged user, never as root.

  • The example Compose file makes the container's filesystem read-only, drops all Linux capabilities, and blocks privilege escalation.

  • Setting the first password needs a one-time setup code from the server log, so nobody else can claim a freshly started server.

  • Passwords are stored as scrypt hashes, never in plain text. Login sessions are random 256-bit tokens, stored only as hashes, in a cookie scripts can't read, and they expire after 30 days.

  • Behind HTTPS, FlyBudget tells browsers to always use HTTPS for your address (HSTS).

  • Bank credentials (Plaid and SimpleFIN) are encrypted with your key, which is kept outside the database.

  • Every image is built by GitHub Actions from the public source code, for both amd64 and arm64, with a software bill of materials (SBOM) and a signed build attestation, and is scanned for known vulnerabilities every week. You can check an image really came from this repository:

    gh attestation verify oci://ghcr.io/dtymoszenko/flybudget:latest -R dtymoszenko/FlyBudget

See Security for everything else FlyBudget does to keep your data safe.