Finance teams, founders, and operations managers often start with a spreadsheet because it is fast, private, and easy to change. The problem appears later: multiple people need controlled access, budget history needs to survive laptop changes, and sensitive spending data should not be copied into consumer cloud tools without a plan. Actual Budget is a strong open-source fit for this situation. It provides envelope-style budgeting, multi-file workflows, and a simple web interface while still letting your organization own the server and the data path.
This guide shows how to deploy Actual Budget on a small Linux VPS using Docker Compose behind Traefik. The pattern is intentionally conservative: the application container stays on a private Docker network, Traefik handles TLS, persistent data lives in a predictable directory, and backups are treated as part of the deployment rather than an afterthought. The same approach works for a single business budget, a family office, or a small nonprofit that wants a practical budgeting service without building a full accounting platform.
Architecture/flow overview
The request flow is straightforward. A user visits https://budget.example.com, DNS points that hostname to the VPS, Traefik receives the HTTPS request, and Traefik forwards the request across a shared Docker network to the Actual Budget container on port 5006. Actual stores its application data under /data inside the container, which maps to /opt/actual/data on the host. Backups archive that host directory on a schedule.
This separation keeps responsibilities clear. Traefik owns certificates, HTTP routing, and renewal. Docker Compose owns the service definition and restart behavior. The host filesystem owns persistence and backup retention. Actual Budget then stays focused on serving the budgeting application.
Prerequisites
- A Linux VPS with Docker Engine and the Docker Compose plugin installed.
- A working Traefik stack with a public
websecureentrypoint and a Letโs Encrypt resolver. - A DNS record such as
budget.example.compointing to the VPS. - SSH access with sudo privileges.
- A backup destination or at least enough local disk to keep short retention archives.
If you do not already have Traefik running, deploy that first and confirm it can issue certificates for a simple test service. Troubleshooting TLS, DNS, and the app at the same time makes the first rollout unnecessarily noisy.
Step-by-step deployment
1. Create the application directory
Use a dedicated directory under /opt so operations staff can find the compose file, environment file, live data, and backups quickly. Keep the environment file private even if the first version contains only non-secret settings; future settings may include sensitive values.
mkdir -p /opt/actual/{data,backup}
cd /opt/actual
install -m 600 /dev/null .env
printf "ACTUAL_HTTPS_ONLY=true\nACTUAL_PORT=5006\n" | sudo tee -a .env >/dev/null
If the copy button is unavailable, select the command block manually and copy it.
2. Deploy Actual Budget with Docker Compose
The compose file below assumes an existing external Docker network named web that Traefik also uses. The router rule should be changed to your real hostname. The container is not published directly to the host, which reduces accidental exposure and forces traffic through Traefikโs TLS policy.
cat > docker-compose.yml <<'YAML'
services:
actual:
image: actualbudget/actual-server:latest
container_name: actualbudget
restart: unless-stopped
env_file: .env
volumes:
- ./data:/data
networks:
- web
labels:
- traefik.enable=true
- traefik.http.routers.actual.rule=Host(`budget.example.com`)
- traefik.http.routers.actual.entrypoints=websecure
- traefik.http.routers.actual.tls.certresolver=letsencrypt
- traefik.http.services.actual.loadbalancer.server.port=5006
networks:
web:
external: true
YAML
docker compose up -d
If the copy button is unavailable, select the command block manually and copy it.
3. Confirm network and container state
Before opening the service to users, confirm the shared network exists, the container is running, and the logs do not show repeated startup failures. If the network is missing, create it and make sure the Traefik container is attached to the same network.
docker network inspect web >/dev/null 2>&1 || docker network create web
docker compose ps
docker logs --tail=80 actualbudget
If the copy button is unavailable, select the command block manually and copy it.
Configuration and secrets handling
Actual Budget itself is simple, but the surrounding platform still needs disciplined configuration. Keep application settings in .env, keep Traefik DNS provider tokens in the Traefik stack rather than this app stack, and avoid placing real credentials in command history or documentation. If you use SSO or an upstream access proxy, document exactly where authentication is enforced so administrators do not assume Actual is providing controls that are actually handled elsewhere.
chmod 600 /opt/actual/.env
chown root:root /opt/actual/.env
# Keep secrets out of compose files and shell history. Rotate DNS/API credentials in your Traefik stack, not in app containers.
If the copy button is unavailable, select the command block manually and copy it.
For a production team, also decide who can access the host, who can restore backups, and who can request data exports. Budget data is operationally sensitive even when it is not regulated financial reporting. Treat restore access as privileged access.
Verification
Verification should test the public path and the local container path. The public request confirms DNS, TLS, Traefik routing, and browser reachability. The local/container checks confirm that the service is running even if an external firewall or DNS setting later changes.
curl -I https://budget.example.com
docker exec actualbudget wget -qO- http://127.0.0.1:5006/ || true
docker inspect actualbudget --format '{{json .State.Health}}' || docker ps --filter name=actualbudget
If the copy button is unavailable, select the command block manually and copy it.
After the HTTP checks pass, create a test budget file in the web UI, sign out, restart the container, and confirm the file is still present. That test proves the bind-mounted data directory is working. If the file disappears after restart, stop and fix persistence before inviting users.
Backups and restore planning
A budgeting service is only production-ready when restore is boring. The simple archive job below keeps thirty days of local backups. For stronger protection, sync the backup directory to object storage or another server after each archive completes. Encrypt off-host copies when they leave the VPS.
cat > /usr/local/sbin/backup-actual.sh <<'BASH'
#!/usr/bin/env bash
set -euo pipefail
ts=$(date -u +%Y%m%dT%H%M%SZ)
tar -C /opt/actual -czf /opt/actual/backup/actual-${ts}.tgz data
find /opt/actual/backup -type f -mtime +30 -delete
BASH
chmod +x /usr/local/sbin/backup-actual.sh
(crontab -l 2>/dev/null; echo '17 3 * * * /usr/local/sbin/backup-actual.sh') | crontab -
If the copy button is unavailable, select the command block manually and copy it.
Test restore by copying one archive to a temporary directory, extracting it, and launching a disposable Actual container against the extracted data. A backup that has never been restored is only a hope, not a recovery plan.
Updates and maintenance
Actual Budget publishes container updates regularly. Use a predictable maintenance window, pull the latest image, restart the service, and keep the previous backup until users confirm the application behaves normally. Avoid automated image updates for financial workflows unless you also have alerting and restore drills.
cd /opt/actual
docker compose pull actual
docker compose up -d
docker image prune -f
If the copy button is unavailable, select the command block manually and copy it.
Record the date, image digest, and any user-visible changes in a small operations note. That history helps when someone reports that a workflow changed after an update.
Common issues and fixes
Traefik returns 404
Check that labels are on the Actual service, the hostname in the router rule exactly matches DNS, and both Traefik and Actual share the same Docker network. A typo in the router rule is the most common cause.
Certificate issuance fails
Confirm the DNS A record points to the VPS and that ports 80 and 443 are reachable from the internet. If you use DNS-01 challenges, inspect the Traefik resolver credentials in the Traefik stack.
Data disappears after restart
Verify the volume mapping is ./data:/data from inside /opt/actual. Then inspect ownership and free disk space on the host. Do not continue onboarding until persistence is confirmed.
Users see mixed login expectations
If an upstream access proxy protects the site, document that flow on the internal runbook. Users should know whether they are using Actualโs application access, SSO, or both.
FAQ
Can I run Actual Budget without Traefik?
Yes, but use another TLS-terminating reverse proxy such as Caddy or Nginx. Avoid exposing the container over plain HTTP to the public internet.
How much server capacity does it need?
A small VPS is enough for most teams. Start with 1โ2 GB RAM, monitor disk growth, and scale only when usage or backup size justifies it.
Should backups include the compose file?
Yes. Back up the data directory and keep a copy of docker-compose.yml and sanitized environment documentation so restore does not rely on memory.
Can multiple people use the same deployment?
Yes, but define access rules before rollout. Budget data often crosses departments, so user onboarding and offboarding should be explicit.
Is this a replacement for accounting software?
No. Treat it as budgeting and planning infrastructure. Accounting, invoicing, payroll, and audit workflows usually require dedicated systems.
How should I handle upgrades?
Take a backup, pull the new image during a maintenance window, restart, and verify the most important budget files. Keep rollback notes close to the compose file.
Internal links
Talk to us
If you want this implemented with hardened defaults, observability, and tested recovery playbooks, our team can help.