Replacing Firefly III with Actual Budget
I had Firefly III deployed for months and never opened it once. That’s a sign something’s off - not with the software, but with the fit. Firefly III is capable: multi-currency, complex transaction rules, lots of import options. Built for people who want to model every corner of their finances.
I needed something simpler. Something I’d actually pull up on a Sunday morning without feeling like I was starting a project.
That’s why I switched to Actual Budget.
Why Firefly III didn’t stick
Firefly III’s strength is also its friction. It runs PHP with a Laravel backend and a MySQL or PostgreSQL database, has an OAuth token system for third-party importers, and expects cron jobs for recurring transactions. The feature list is long - great if you need it, overhead if you don’t.
I never got past the “I’ll configure it properly later” phase. After months with the container running and zero transactions entered, I accepted that I wasn’t going to become a Firefly III user.
What Actual Budget does differently
Actual Budget stores everything locally: no cloud sync, no external database. The server is a Node.js process backed by SQLite. The interface runs in the browser but feels closer to a desktop app than an admin panel.
It’s envelope budgeting, not double-entry accounting. That’s a narrower model, but it maps more naturally to how I think about money.
Since I had nothing to migrate - I never entered a single transaction into Firefly III - the switch was a clean deployment.
Deployment: Docker-in-LXC on Proxmox
The setup follows the same pattern as my other services: Docker Compose inside an LXC container on the Proxmox cluster, managed by Ansible.
A single-container Compose file handles the whole thing:
name: actual-budget
services:
actual-server:
image: actualbudget/actual-server:latest
container_name: actual-server
restart: unless-stopped
ports:
- '5006:5006'
volumes:
- /opt/actual-budget/data:/data
environment:
- TZ=America/Costa_Rica
No database container. No Redis. No cache layer. Just the server and a local data directory. Everything Actual Budget stores lives under /data as SQLite files.
The Ansible role creates the service and data directories, templates the Compose file, pulls the image, and waits for the health check to pass on port 5006.
First-run setup
Actual Budget doesn’t require any configuration before starting the container - the server boots and presents a setup screen in the browser. On first access, you create a local budget file (or sync one from another device). No database migration step, no environment variable for an initial password. The simplicity is the point.
Traefik integration
Traffic routes through Traefik using the same wildcard TLS certificate as every other service. The dynamic config:
http:
routers:
actual-budget:
rule: 'Host(`budget.example.net`)'
service: actual-budget
entryPoints:
- websecure
tls:
certResolver: cloudflare
middlewares:
- security-headers
services:
actual-budget:
loadBalancer:
servers:
- url: 'http://10.0.x.x:5006'
This is generated by the Traefik Ansible role from the services state file, same as Gitea, Plane, and the rest.
One issue came up with the Homepage dashboard: the security-headers middleware was sending a Content-Security-Policy that blocked iframe embedding. The fix was moving the CSP value into Traefik’s contentSecurityPolicy middleware option instead of a raw custom response header. I wrote that up in detail in Fixing iframe embedding in self-hosted dashboards since the same fix applies to any service embedded in Homepage.
Cleanup
After verifying Actual Budget was working:
- Stopped and removed the Firefly III containers
- Deleted the Firefly III volumes (nothing in them worth keeping)
- Removed the Ansible role and playbook
- Removed the Traefik router entry
- Replaced the Firefly III Uptime Kuma monitor with one for Actual Budget
- Removed it from the services state file, inventory, and DNS
What I verified
- HTTP health check passes on port 5006 (Ansible task, not a manual check)
https://budget.example.netloads over HTTPS with a valid cert- Actual Budget first-run screen appears; budget file created and usable
- Homepage dashboard shows the embedded iframe correctly after the CSP fix
- Uptime Kuma monitor shows green
- Proxmox Backup Server backup job covers the
/opt/actual-budget/datavolume
Lessons learned
- A tool you never open isn’t a tool. Firefly III is genuinely capable software - it just wasn’t the right fit.
- Simple stacks are easier to reason about. One container, one volume, no separate database to back up.
- Starting fresh beats a messy migration. Since I had no Firefly III data, there was nothing to carry over - and that made the switch trivial.
- The CSP iframe issue is easy to miss when setting up a new service behind Traefik. Worth knowing before you wire up Homepage.
Actual Budget has been running for a few months now and I’ve actually been using it. That’s the metric that mattered.
Related reading
Diagnosing slow RomM scans on a large ROM library
RomM was taking 60-80 seconds per ROM during its first scan on my homelab. Here is what I found, what I changed, and why the real answer turned out to be a much bigger library than I thought.
Deploying RomM, a self-hosted ROM manager, in the homelab
I wanted a web-based, self-hosted way to organize a pile of game backups with real metadata. RomM was the only option that fit. Here is the Docker-in-LXC deployment, the two gotchas that cost me time, and the NAS mount mistake that bit me later.
Deploying UniFi Network Controller with Traefik: Decisions and Troubleshooting
Decision analysis for LXC+Docker vs VM deployment, hybrid networking strategy for UniFi device communication, and troubleshooting firewall and IP configuration issues.
Ready to Transform Your Career?
Let's work together to unlock your potential and achieve your professional goals.