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.
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.
-
Create a folder for FlyBudget and download the example
docker-compose.ymlinto it. -
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.txtsudo chown 1000:1000 flybudget_data_key.txtsudo chmod 400 flybudget_data_key.txtKeep 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.
-
Start FlyBudget:
docker compose up -d -
Open
http://<your-server>:3001in a browser and create your password. FlyBudget also asks for a setup code, which only appears in the server's log:docker logs flybudgetThe 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.
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 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
| Variable | What it does |
|---|---|
FLYBUDGET_DATA_KEY_FILE | Path to the file holding the encryption key (a Docker secret). Required (or FLYBUDGET_DATA_KEY). |
FLYBUDGET_DATA_KEY | The key itself, instead of a file. Less private: anyone who can run docker inspect can see it. |
FLYBUDGET_ALLOWED_HOSTS | Comma-separated addresses FlyBudget answers to, e.g. budget.example.com,192.168.1.20. Recommended; by default it answers to any address. |
FLYBUDGET_TRUST_PROXY | Set to the number of reverse proxies in front of FlyBudget (usually 1) when using one. |
PORT | Port 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.cjsThen 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
amd64andarm64, 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.