When you self-host Firefly III on a managed VPS, your accounts, budgets and spending live on a machine of your own, which our teams keep up to date and monitor day and night. Firefly III is a free and open source personal finance manager under the AGPL license. This guide follows its official documentation: installing with Docker Compose, the APP_KEY, HTTPS, the first account, the cron job, the database, the data importer, email, backups and updates.
By Simon Vasconcelos Lee
Published on 10 November 202613-minute read
docker-compose.yml, .env and .db.env. Pick the database password before the first start.APP_KEY is exactly 32 characters long, stays secret and is backed up along with the data.TRUSTED_PROXIES=* and your address in APP_URL, then create your account right away: the first user to register becomes the owner, and registration closes after that.cron service in the official file runs auto-budgets and recurring transactions every night. Back up the database, the upload volume and .env before every update.Firefly III is a personal finance manager built for self-hosting. It tracks your expenses and income, with budgets, categories and tags, so you can see where your money goes. Its README describes it as completely self-hosted and isolated: it contacts no outside server until you explicitly tell it to.
One instance hosts as many users as you like, each with a financial administration fully separated from the others. The documentation describes a model for sharing a single administration, currently in development.
Firefly III is published under the GNU Affero General Public License, version 3 or, at your option, any later version: that is the SPDX identifier AGPL-3.0-or-later in its composer.json file. The text affirms an unlimited permission to run the unmodified program. You can therefore use the image published by the project for yourself, your family or your company, with no license to buy.
Section 13 adds a condition specific to the AGPL: if you modify the program, your version must offer everyone who uses it remotely over a network a way to get its source code at no charge. The Data Importer is under the same license.
The documentation calls Docker Compose the easiest way to install Firefly III. On a server with Docker installed, create a folder and download the three official files into it, raw: their spacing matters, and the documentation asks you not to copy them from the browser.
mkdir firefly-iii && cd firefly-iii
curl -o docker-compose.yml https://raw.githubusercontent.com/firefly-iii/docker/main/docker-compose.yml
curl -o .env https://raw.githubusercontent.com/firefly-iii/firefly-iii/main/.env.example
curl -o .db.env https://raw.githubusercontent.com/firefly-iii/docker/main/database.env
docker-compose.yml describes three services: app, the application, db, a MariaDB database, and cron, the scheduled job..env holds the Firefly III settings, including the encryption key and database access..db.env holds the database settings. Keep both names: the Compose file refers to them through env_file.Generate two 32-character strings with the command given in the .env file: one for the key, one for the database password.
head /dev/urandom | LC_ALL=C tr -dc 'A-Za-z0-9' | head -c 32 && echo
SITE_OWNER=you@example.com
APP_KEY=
DB_PASSWORD=
APP_URL=https://finances.example.com
MYSQL_PASSWORD=
APP_KEY encrypts your sessions. It is exactly 32 characters long, with no special characters such as =, #, < or >, and the file asks you to keep it very secure.DB_PASSWORD in .env and MYSQL_PASSWORD in .db.env take the same value.SITE_OWNER: your address, shown in some errors to users who are not admins.APP_URL: the public address of your instance. The file explains that it is used to validate certain requests and to build the links in emails.TZ: your time zone, in tz database format, as the example in the file shows.Pick the database password before the first start. The database stores it in its volume as soon as it is created: change it afterward and it no longer matches, and the Docker FAQ offers only one way out, removing the containers and volumes and starting over.
The official file publishes the application on port 80 of every address on the server. To serve it over HTTPS behind a reverse proxy on the same machine, change this single line in the app service:
ports:
- 127.0.0.1:8080:8080
docker compose -f docker-compose.yml up -d --pull=always
docker compose -f docker-compose.yml logs -f
On first start, the container creates and migrates the database by itself. When installation is done, Firefly III thanks you for installing it in the log: move on to the reverse proxy.
latest tag tracks the latest stable release. A tag like version-6.7.9 pins a version; the project removes old ones after about six months.linux/amd64 and linux/arm64.firefly_iii_upload holds attachments, firefly_iii_db the database.The documentation gives no memory figure; its Docker FAQ suggests PHP_MEMORY_LIMIT=512M or more if PHP runs out of memory. The size depends on the number of users, the volume of transactions and the data importer. On a managed VPS, describe your use to us and we will recommend the right size.
Firefly III serves the application over HTTP on port 8080 inside the container, and HTTPS sits on the reverse proxy in front of it. The application reads the X-Forwarded-Proto header to know that the visitor arrived over HTTPS, as long as it trusts the proxy:
TRUSTED_PROXIES=*
The Docker FAQ is clear on this point: without TRUSTED_PROXIES=*, links stay on http, forms and charts stop working, and CSP security headers fail. With the port published on 127.0.0.1, only the server’s own proxy reaches the application. Recreate the container after each change to the .env file:
docker compose up -d --force-recreate app
Caddy gets the Let’s Encrypt certificate on its own, and its reverse_proxy directive sets the X-Forwarded-For, X-Forwarded-Proto and X-Forwarded-Host headers by default:
finances.example.com {
reverse_proxy 127.0.0.1:8080
}
This block follows the headers from the installation FAQ, and the buffers it suggests against 502 errors when the application returns a token in a long header. It listens on port 80 so that Certbot can set up HTTPS:
server {
listen 80;
listen [::]:80;
server_name finances.example.com;
client_max_body_size 64M;
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 300s;
}
}
Certbot then installs the certificate and takes care of renewing it:
sudo certbot --nginx
finances.example.com. The FAQ notes that Laravel does not parse a raw IPv6 address in the URL.The first user to register becomes the owner of the instance. As a security measure, registration then closes on its own: most people install Firefly III just for themselves.
/settings, open the Firefly III configuration options, untick the option that disables registration and save.AUTHENTICATION_GUARD=remote_user_guard, Firefly III trusts an authentication proxy placed in front, such as Authelia or Authentik, which passes the user along in a header.Several features only work with a scheduled job: auto-budgets, recurring transactions, subscription warnings and up-to-date exchange rates. The Docker image does not run cron by itself; the official file adds a small cron service for this, which calls the application every day at 3 a.m.
It authenticates with a static token. Generate a third string with the same command, and put it in .env:
STATIC_CRON_TOKEN=
This token is exactly 32 characters long: the version 6.7.9 code accepts no other length. Here is the service as the official file declares it; the double $$ is intentional and refers to the TZ and STATIC_CRON_TOKEN lines of the .env file:
cron:
image: alpine
restart: always
container_name: firefly_iii_cron
env_file: .env
init: true
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 - && \
exec crond -f -L /dev/stdout"]
networks:
- firefly_iii
depends_on:
- app
One job is enough for every user on the instance. To check that it ran, read the container log: each job reports back in JSON, with job_fired and job_succeeded.
docker logs firefly_iii_cron
To run it by hand, the Docker FAQ asks you to go through the www-data user rather than root: the FAQ links this pitfall to permission errors on log files:
docker exec --user www-data firefly_iii_core /usr/local/bin/php /var/www/html/artisan firefly-iii:cron --force
The official file ships MariaDB, with the mariadb:lts image. Firefly III also supports PostgreSQL and SQLite, chosen with DB_CONNECTION in .env:
| Database | Setup | Backup |
|---|---|---|
| MariaDB or MySQL | The db service from the official file; DB_CONNECTION=mysql, port 3306 | A dump with mariadb-dump, or the database volume with the service stopped |
| PostgreSQL | DB_CONNECTION=pgsql, port 5432, the postgres image and POSTGRES_* variables in .db.env | A dump with pg_dump, or the database volume with the service stopped |
| SQLite | DB_CONNECTION=sqlite, no db service, and a volume mounted on /var/www/html/storage/database | The database volume, with the service stopped |
DB_CONNECTION=pgsql
DB_HOST=db
DB_PORT=5432
For PostgreSQL, the documentation also points the db service to the postgres image, which installs PostgreSQL 18, and mounts its volume on /var/lib/postgresql. In .db.env, the MYSQL_* variables become POSTGRES_USER, POSTGRES_DB and POSTGRES_PASSWORD.
Make this choice before entering your first transactions. The FAQ says moving from one database to another remains possible, without being supported by Firefly III itself, and that the application’s export does not carry every piece of data.
The Firefly III Data Importer is a separate tool, with its own installation and its own updates; its version 2.3.5 came out on September 23, 2026. It imports CSV and CAMT.053 files, and can also fetch transactions from your banks through third-party data providers, each with its own terms.
The official Docker repository ships it in a second file, docker-compose-importer.yml, which keeps the same services and adds importer. Its settings go in an .importer.env file, taken from the importer’s .env.example. Serve it under its own domain name, with a port published locally and a second proxy block: the FAQ asks you not to place it in a subdirectory.
FIREFLY_III_URL=http://app:8080
VANITY_URL=https://finances.example.com
TRUSTED_PROXIES=*
FIREFLY_III_URL: the internal address of the application, which the importer reaches over the Docker network.VANITY_URL: the public address of Firefly III, the one your users know./callback, and untick “Confidential.” The importer then asks for its client ID.FIREFLY_III_ACCESS_TOKEN empty and keep FIREFLY_III_URL; each user then signs in to the importer with their own account.By default, MAIL_MAILER=log writes messages to the logs instead of sending them. To receive notifications, such as subscription warnings, set up sending in .env. The documentation asks you to always change MAIL_FROM and SITE_OWNER, or no email will ever go out.
On a Simafri VPS, sending goes through an authenticated SMTP relay, on port 587: the address of an email account you already have, or Simafri Suite.
MAIL_MAILER=smtp
MAIL_HOST=smtp.example.com
MAIL_PORT=587
MAIL_FROM=finances@example.com
MAIL_USERNAME=finances@example.com
MAIL_PASSWORD=account_password
MAIL_ENCRYPTION=tls
MAIL_ENCRYPTION accepts null, ssl, starttls or tls, depending on your provider; the version 6.7.9 code falls back to tls when the value is empty./settings/notifications page sends a test email, once the container has been recreated./slack.Firefly III has no built-in backup routine: the documentation recommends the system’s own tools, and points out that the application’s export function is not a backup. With Docker, here is what to keep:
| Item | What it holds | Backup |
|---|---|---|
The database (firefly_iii_db) | Accounts, transactions, budgets, rules and users | Required |
Attachments (firefly_iii_upload) | Files attached to transactions | Required |
.env | The settings, and above all APP_KEY | Required, encrypted |
.db.env | Database credentials | Required, encrypted |
docker-compose.yml and .importer.env | Your version of the official files | Recommended |
With MariaDB, mariadb-dump exports the database without stopping the service; its --single-transaction option gives a consistent copy of InnoDB tables without locking them. Run from the Compose file folder, the command reads the credentials from the container’s environment:
docker exec firefly_iii_db sh -c 'mariadb-dump --single-transaction -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" "$MYSQL_DATABASE"' > firefly-$(date +%F).sql
With PostgreSQL, the same idea goes through pg_dump:
docker exec firefly_iii_db sh -c 'pg_dump -U "$POSTGRES_USER" "$POSTGRES_DB"' > firefly-$(date +%F).sql
Schedule this dump on the server at least once a day, and sort through the files, which pile up.
The documentation archives each volume with a throwaway container. Docker Compose prefixes volume names with the project name, which is the folder name by default: check the exact names before you run the archive.
docker volume ls
docker compose stop
docker run --rm -v "firefly-iii_firefly_iii_db:/tmp" -v "$HOME/backups/firefly:/backup" \
ubuntu tar -czvf /backup/firefly_db.tar /tmp
docker run --rm -v "firefly-iii_firefly_iii_upload:/tmp" -v "$HOME/backups/firefly:/backup" \
ubuntu tar -czvf /backup/firefly_upload.tar /tmp
docker compose start
A misspelled volume name raises no error: Docker creates an empty volume under that name and archives it, which overwrites the previous backup. The documentation asks you to check that the volume exists before every archive.
Keep a copy of .env and of these archives off the server, encrypted, since it carries the key and the database credentials. Stored on the server’s disk, these files are also part of its nightly backups.
The documentation restores a volume with the reverse command, with the service stopped; the attachments volume is restored the same way:
docker run --rm -v "firefly-iii_firefly_iii_db:/recover" -v "$HOME/backups/firefly:/backup" \
ubuntu tar -xvf /backup/firefly_db.tar -C /recover --strip 1
An SQL dump is loaded back into the running database:
docker exec -i firefly_iii_db sh -c 'mariadb -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" "$MYSQL_DATABASE"' < firefly-YYYY-MM-DD.sql
Restore .env with its APP_KEY too, then start again and open your accounts. The documentation repeats it after every method: the first thing to do with a backup is to restore it, to check that it works.
Firefly III upgrades itself, even from versions as old as 4.7, and migrates its database when the container starts. The documentation warns that some upgrades remove or clean up data, and asks for a backup of the database and volumes before each one, especially across major versions. From the Compose file folder:
docker compose stop
docker compose pull
docker compose -f docker-compose.yml up -d --remove-orphans
/settings.php artisan correction:rollback-single-migration removes the latest migration entry without touching the data, then php artisan migrate runs it again, inside the container.The importer updates the same way, with docker compose rm -f between stopping and pulling the images, according to its documentation.
Self-hosting means running the server on top of the application. On a managed VPS, our teams take charge of the server and proactively monitor your instance; Firefly III remains your software, with its settings and your financial data.
| Topic | Our teams | You |
|---|---|---|
| The server | System and security updates, server interventions included | The timing that suits your business |
| Monitoring | Server monitored 24/7, with intervention without waiting for your call | Reading your instance’s logs |
| Firefly III | Proactive monitoring: errors, slowdowns, the cron job and disk space spotted, fix recommended | Its installation, updates and settings, which you apply, yourself or through your AI assistant |
| Accounts and data | The server that carries them, monitored and backed up | Users, two-factor authentication, the data importer and its data providers |
| Backups | Every night, encrypted and kept off the machine; restores included | Firefly III’s own dumps, and the restore request, when you need one |
| Migration | Your current server migrated, free of charge | The timing of the switch |
If you would rather have our teams work inside the application itself, that service is billed by the hour or at a fixed rate, with an estimate shared before the work starts.
With a Simafri Managed VPS, our teams keep your Firefly III instance’s server up to date, monitor it day and night and step in without waiting for your call; they also proactively monitor your instance and recommend useful fixes, which you apply, yourself or through your AI assistant. Backups are encrypted every night and kept off the machine, restores are included, and migrating your current server is complimentary.
Yes. Firefly III is under the AGPL-3.0-or-later license: you can run it for yourself, your family or your company with no license to buy. If you modify it, your version must offer its source code to the people who use it over the network. What remains is the cost of the server that runs it.
Download the three official files, docker-compose.yml, .env and .db.env. Set a 32-character APP_KEY and the same password in DB_PASSWORD and MYSQL_PASSWORD, before the first start, then run docker compose up -d. Next, put an HTTPS reverse proxy in front.
APP_KEY is the encryption key for sessions. It is exactly 32 characters long, with no special characters such as = or #, and stays secret. The documentation asks you to back it up along with the volumes: it is part of what you need to restore an instance.
The Docker FAQ says TRUSTED_PROXIES=* is missing from the .env file: without it, Firefly III ignores the proxy’s X-Forwarded-Proto header, links stay on http and forms and charts stop working. Also set your public address in APP_URL.
The official Compose file includes a cron service that calls the application every day at 3 a.m. Set a STATIC_CRON_TOKEN of exactly 32 characters in .env, then restart the stack. A single job serves every user on the instance.
The first user to register becomes the owner, and registration closes after that. The owner reopens it in /settings, under the Firefly III configuration options. Each user then keeps a financial administration separated from the others.
Back up the database, with a mariadb-dump or pg_dump export, the attachments volume, and the .env and .db.env files, encrypted. The application’s export function is not a backup. Test the restore right after the first backup.
A managed VPS covers the server: system, security, monitoring and backups. Our teams also proactively monitor your instance and recommend useful fixes, which you apply. Work inside the application is billed by the hour or at a fixed rate.
Simafri
Let's talk about your website
Tell us about your project in a few words: we will get back to you quickly.
Thank you! Your request has been sent. We'll get back to you shortly.
Prefer email? Write to us at support@simafri.com.