Self-Hosting Firefly III: Personal Finance with Double-Entry Bookkeeping
Most personal finance apps fall into one of two camps: oversimplified trackers that can’t tell you where your money actually went, or enterprise accounting software that requires a CPA to configure. Firefly III sits in the sweet spot — it’s a proper double-entry bookkeeping system built for normal people who want real control over their finances.
Firefly III is open source (AGPLv3), actively maintained, and has been around since 2015. It runs entirely on your own server, never phones home, and gives you budgets, categories, tags, piggy banks, recurring transactions, and a powerful rules engine to automate the tedious parts of financial tracking. If you’ve outgrown spreadsheets but don’t want to hand your bank data to a cloud service, this is your tool.
What Firefly III Does
- Double-entry bookkeeping — Every transaction moves money between accounts, giving you an accurate picture of where funds flow
- Budgets and categories — Set monthly budgets by category and track spending against them
- Recurring transactions — Automate rent, subscriptions, salary entries, and any repeating transaction
- Rules engine — Automatically categorize, tag, or modify transactions based on conditions you define
- Piggy banks — Save toward specific goals by earmarking money from your accounts
- Multi-currency support — Track accounts and transactions in any currency with automatic conversion
- Reports and charts — Income vs. expense, budget overviews, category breakdowns, account balances over time
- REST API — Full API coverage for building integrations, importing data, or connecting external tools
- Two-factor authentication — TOTP-based 2FA for extra security
Prerequisites
- A Linux server (Ubuntu/Debian, or any Docker-capable host)
- Docker and Docker Compose installed (install guide)
- At least 512 MB of free RAM (MariaDB + PHP application)
- Basic familiarity with the terminal
Deploying Firefly III with Docker Compose
Step 1: Create the Project Directory
mkdir -p ~/docker-apps/firefly-iii && cd ~/docker-apps/firefly-iii
Step 2: Generate an App Key
Firefly III requires a 32-character app key for encryption. Generate one:
head /dev/urandom | LC_ALL=C tr -dc 'A-Za-z0-9' | head -c 32 && echo
Copy the output — you’ll need it in the next step.
Step 3: Create the Environment Files
Firefly III uses two environment files: one for the app and one for the database.
Create .env for the application:
cat > .env << 'EOF'
# Core settings
APP_ENV=production
APP_DEBUG=false
APP_KEY=paste_your_32_char_key_here
[email protected]
TZ=America/New_York
# Database
DB_CONNECTION=mysql
DB_HOST=db
DB_PORT=3306
DB_DATABASE=firefly
DB_USERNAME=firefly
DB_PASSWORD=replace_with_a_strong_password
# Cron token (exactly 32 characters)
STATIC_CRON_TOKEN=replace_this_with_32_random_chars
# Logging
LOG_CHANNEL=stack
APP_LOG_LEVEL=notice
# Cache and sessions
CACHE_DRIVER=file
SESSION_DRIVER=file
# Reverse proxy support (set to ** if behind a proxy)
TRUSTED_PROXIES=
EOF
Create .db.env for MariaDB:
cat > .db.env << 'EOF'
MYSQL_RANDOM_ROOT_PASSWORD=yes
MYSQL_DATABASE=firefly
MYSQL_USER=firefly
MYSQL_PASSWORD=replace_with_a_strong_password
EOF
Replace the placeholder values — make sure DB_PASSWORD in .env matches MYSQL_PASSWORD in .db.env, and generate proper random strings for APP_KEY and STATIC_CRON_TOKEN.
Step 4: Create the Docker Compose File
Create docker-compose.yml:
services:
app:
image: fireflyiii/core:latest
container_name: firefly_iii_core
restart: unless-stopped
volumes:
- firefly_iii_upload:/var/www/html/storage/upload
env_file: .env
ports:
- "8080:8080"
depends_on:
- db
networks:
- firefly_iii
db:
image: mariadb:lts
container_name: firefly_iii_db
restart: unless-stopped
env_file: .db.env
volumes:
- firefly_iii_db:/var/lib/mysql
networks:
- firefly_iii
cron:
image: alpine
container_name: firefly_iii_cron
restart: unless-stopped
env_file: .env
command: >
sh -c "apk add tzdata &&
(ln -s /usr/share/zoneinfo/$$TZ /etc/localtime || true) &&
echo \"0 3 * * * wget -qO- http://app:8080/api/v1/cron/$$STATIC_CRON_TOKEN;echo\"
| crontab - &&
crond -f -L /dev/stdout"
depends_on:
- app
networks:
- firefly_iii
volumes:
firefly_iii_upload:
firefly_iii_db:
networks:
firefly_iii:
driver: bridge
The cron container triggers Firefly III’s internal recurring transaction processing and auto-budgeting. Without it, recurring transactions won’t fire automatically.
Step 5: Start Firefly III
docker compose up -d
Watch the logs to confirm everything starts cleanly:
docker compose logs -f app
Once you see the PHP-FPM workers spawning, open http://your-server-ip:8080 in your browser.
Step 6: Create Your Account
You’ll see the Firefly III registration page. Create your first account — this becomes the admin user. After registering, you can disable public registration in the admin panel if you’re the only user.
Understanding Double-Entry Bookkeeping
If you’ve only used simple expense trackers, Firefly III’s approach might feel different at first. In double-entry bookkeeping, every transaction involves two accounts:
- Withdrawals move money from an asset account (your bank) to an expense account (groceries, rent, etc.)
- Deposits move money from a revenue account (salary, freelance income) to an asset account
- Transfers move money between your own asset accounts (checking → savings)
This means your books always balance. Every dollar that leaves one account enters another. It sounds complex, but Firefly III handles the mechanics — you just enter transactions normally, and the system maintains proper accounting behind the scenes.
Setting Up Your Accounts
Start by creating your real-world accounts:
- Go to Accounts → Asset accounts
- Add your checking account, savings accounts, and credit cards
- Set opening balances and dates so Firefly III knows your starting point
Firefly III automatically creates expense and revenue accounts as you enter transactions, so you don’t need to set those up manually.
Configuring Budgets
Budgets are where Firefly III shines for day-to-day money management:
- Navigate to Budgets in the sidebar
- Create budgets for your spending categories — groceries, dining, entertainment, transport
- Set monthly amounts for each budget
- As you categorize transactions, Firefly III tracks spending against each budget in real time
The budget page shows a clear bar chart of how much you’ve spent vs. your limit for each category. When you’re about to blow past your grocery budget mid-month, you’ll know.
Setting Up Recurring Transactions
For predictable expenses and income:
- Go to Automation → Recurring transactions
- Click Create recurring transaction
- Set the type (withdrawal, deposit, or transfer), amount, frequency, and accounts
- Choose a schedule — weekly, monthly, quarterly, yearly
The cron container handles execution. Every day at 3 AM (by default), it triggers Firefly III to check for and create any due recurring transactions.
Using the Rules Engine
Rules are Firefly III’s automation powerhouse. They can automatically categorize, tag, or modify transactions as they’re created:
- Go to Automation → Rules
- Create a new rule with triggers (conditions) and actions (what to do)
Example: automatically categorize Amazon purchases:
- Trigger: Description contains “AMAZON” or “AMZN”
- Action: Set category to “Shopping”, add tag “online”
Rules run on new transactions and can be applied retroactively to existing ones. You can chain multiple triggers with AND/OR logic and stack multiple actions per rule.
Importing Transaction Data
Firefly III supports several import methods:
CSV Import
The simplest approach — export transactions from your bank as CSV and use the built-in importer:
- Pull the data importer alongside your stack:
importer:
image: fireflyiii/data-importer:latest
container_name: firefly_iii_importer
restart: unless-stopped
env_file: .importer.env
ports:
- "8081:8080"
depends_on:
- app
networks:
- firefly_iii
- Create
.importer.env:
cat > .importer.env << 'EOF'
FIREFLY_III_URL=http://app:8080
VANITY_URL=http://localhost:8080
FIREFLY_III_ACCESS_TOKEN=your_personal_access_token
EOF
Generate a Personal Access Token in Firefly III under Options → Profile → OAuth → Personal Access Tokens
Access the importer at
http://your-server-ip:8081and follow the wizard to map CSV columns
API Import
For programmatic imports, use Firefly III’s REST API directly:
curl -X POST "http://localhost:8080/api/v1/transactions" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"transactions": [{
"type": "withdrawal",
"date": "2026-03-20",
"amount": "42.50",
"description": "Grocery run",
"source_name": "Checking Account",
"destination_name": "Groceries"
}]
}'
This is useful for building your own integrations or writing scripts to sync from specific banks.
Putting Firefly III Behind a Reverse Proxy
For HTTPS access, put Firefly III behind a reverse proxy. With Caddy:
With Nginx:
server {
listen 443 ssl;
server_name firefly.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/firefly.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/firefly.yourdomain.com/privkey.pem;
client_max_body_size 64M;
location / {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
When using a reverse proxy, update your .env:
TRUSTED_PROXIES=**
This tells Firefly III to trust proxy headers, so URLs generate correctly with HTTPS.
Backing Up Your Financial Data
Your data lives in MariaDB. Set up automated backups:
#!/bin/bash
# save as ~/docker-apps/firefly-iii/backup.sh
BACKUP_DIR="$HOME/backups/firefly-iii"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
docker exec firefly_iii_db mariadb-dump -u firefly \
--password="$(grep MYSQL_PASSWORD .db.env | cut -d= -f2)" \
firefly | gzip > "$BACKUP_DIR/firefly_${TIMESTAMP}.sql.gz"
# Also back up uploaded files
docker cp firefly_iii_core:/var/www/html/storage/upload \
"$BACKUP_DIR/uploads_${TIMESTAMP}" 2>/dev/null
# Keep last 30 backups
ls -t "$BACKUP_DIR"/firefly_*.sql.gz | tail -n +31 | xargs -r rm
echo "Backup completed: firefly_${TIMESTAMP}.sql.gz"
Make it executable and schedule it:
chmod +x ~/docker-apps/firefly-iii/backup.sh
crontab -e
# Add: 0 2 * * * ~/docker-apps/firefly-iii/backup.sh
To restore:
gunzip -c ~/backups/firefly-iii/firefly_20260320_020000.sql.gz | \
docker exec -i firefly_iii_db mariadb -u firefly \
--password="your_db_password" firefly
Updating Firefly III
Pull the latest images and recreate:
cd ~/docker-apps/firefly-iii
docker compose pull
docker compose up -d
Firefly III runs database migrations automatically on startup. Check the release notes before major version upgrades — occasionally there are breaking changes that need .env adjustments.
For more control, pin to a specific version instead of latest:
image: fireflyiii/core:version-6.2.1
Troubleshooting
“500 Internal Server Error” on first load
Usually an APP_KEY issue. Make sure it’s exactly 32 characters with no special characters like #, <, or >. Regenerate if needed:
head /dev/urandom | LC_ALL=C tr -dc 'A-Za-z0-9' | head -c 32 && echo
Update .env and restart: docker compose down && docker compose up -d
Database connection refused
Check that the database container is healthy:
docker compose ps
docker compose logs db
Verify that credentials match between .env and .db.env. A common mistake is having different passwords in the two files.
Recurring transactions not creating
The cron container must be running:
docker compose logs cron
Verify STATIC_CRON_TOKEN is set in .env and is exactly 32 characters. You can test the cron endpoint manually:
curl http://localhost:8080/api/v1/cron/YOUR_STATIC_CRON_TOKEN
Slow performance
- MariaDB benefits from SSD storage — move the data volume to an SSD if possible
- Add a Redis container for caching: set
CACHE_DRIVER=redisandSESSION_DRIVER=redisin.env - Ensure at least 512 MB RAM is free for the app and database
File upload errors
If CSV imports fail with size errors, increase client_max_body_size in your Nginx config or ensure Caddy doesn’t have restrictive request body limits.
Firefly III vs Alternatives
| Feature | Firefly III | Maybe Finance | Actual Budget |
|---|---|---|---|
| Bookkeeping | Double-entry | Simple tracking | Envelope budgeting |
| Rules engine | ✅ Powerful | ❌ | ❌ |
| Recurring transactions | ✅ Built-in | ❌ Manual | ✅ Schedules |
| Multi-currency | ✅ Full support | ✅ | ⚠️ Limited |
| Data import | CSV + API + importers | CSV | CSV + OFX |
| API | ✅ Full REST API | ✅ | ✅ |
| Active development | ✅ Very active | Archived (community forks) | ✅ Active |
| Learning curve | Medium | Low | Low |
Firefly III is the most powerful option for people who want real financial control. The double-entry system and rules engine give you capabilities that simpler trackers can’t match. The tradeoff is a steeper initial learning curve — but once your accounts, budgets, and rules are set up, daily use is straightforward.
Conclusion
Firefly III gives you proper accounting on your own server. The double-entry bookkeeping system ensures your numbers always add up, the rules engine handles tedious categorization automatically, and recurring transactions keep your books up to date without manual work.
The initial setup takes about 15 minutes with Docker Compose. Spend another hour setting up your accounts, budgets, and a few rules, and you’ll have a financial tracking system that rivals commercial apps — running on your own hardware, with your data never leaving your network.
Start with your main checking account and credit card, import a few months of transaction history via CSV, create rules for your most common transactions, and let the system work. Once you see your budget charts filling in and your expense patterns becoming visible, you’ll wonder why you ever used a spreadsheet.