Requirements
- A Linux server you can reach over SSH. Ubuntu 24.04 or Debian 12 on a VPS with 2 GB of RAM is plenty for a household.
- A domain or subdomain pointing at the server, for example
money.example.com, with an A record to the server's IP address. - Ports 80 and 443 open to the internet, so HTTPS certificates can be issued.
Everything runs in Docker: the web app, the API, MySQL, a queue worker for notifications and a scheduler for bills, budgets and recurring transactions.
1. Install Docker
Docker's convenience script installs Docker Engine and the Compose plugin. Run it as a user with sudo:
curl -fsSL https://get.docker.com | sudo sh sudo usermod -aG docker $USER
Log out and back in so the group change applies, then check that it works with docker compose version.
2. Download LedgerFly
git clone https://github.com/nayeemdev/ledgerfly.git ledgerfly cd ledgerfly
3. Configure
There are two settings files. The one in the project root holds database passwords for Docker; api/.env holds what the app needs before it can read its database.
cp .env.example .env cp api/.env.example api/.env openssl rand -base64 24 # run twice, for the two database passwords echo "base64:$(openssl rand -base64 32)" # the app key
In .env, set:
| Setting | Value |
|---|---|
DB_PASSWORD | A long random password |
DB_ROOT_PASSWORD | A different long random password |
APP_PORT | 127.0.0.1:8000, so only Caddy on the same server can reach the app |
In api/.env, set:
| Setting | Value |
|---|---|
APP_KEY | The base64:... key printed above |
APP_URL | Your address, for example https://money.example.com |
APP_ENV | production |
APP_DEBUG | false |
DEFAULT_TIMEZONE | Your time zone, for example Asia/Dhaka |
You do not need to set the database host or user in api/.env: Docker Compose passes them in from the root .env. Email, sign-ups and alert settings are managed from the app later.
4. Start the stack
docker compose up -d --build docker compose exec api php artisan db:seed --force
The first build takes a few minutes. Database migrations run automatically when the API starts, and the seed step loads the currency list and built-in link types. It is safe to run again.
Check that everything is up with docker compose ps. LedgerFly now answers on http://127.0.0.1:8000 on the server itself.
5. Add HTTPS
Install Caddy, which gets and renews certificates on its own. Then replace /etc/caddy/Caddyfile with your domain:
money.example.com {
encode zstd gzip
reverse_proxy 127.0.0.1:8000
}sudo systemctl reload caddy
Open https://money.example.com. If the certificate is not ready yet, check that the DNS record points at the server and ports 80 and 443 are open.
Prefer Cloudflare Tunnel, Nginx Proxy Manager or Traefik? Point them at 127.0.0.1:8000 the same way.
6. Create the admin account
Open your address and sign up. The first account on a new server becomes its admin. After that, sign-ups are closed unless you open them.
As the admin, open Settings, System in the web app to set up:
- Whether new people can sign up, and how long invitations stay valid
- Outgoing email, with a test button
- Automatic exchange rates
- Bill reminder and budget warning thresholds
- The suggested ntfy server and the upload size limit
To share a ledger with family, invite them from the ledger's settings as an owner, editor or viewer.
Notifications
Alerts for budgets, bills, low balances and large spending can go out by email, browser push, ntfy or the mobile app. Each person picks channels and quiet hours under Settings, Notifications.
- Email works once the admin sets up outgoing mail in Settings, System.
- Browser push needs keys, created once:
docker compose exec api php artisan webpush:vapid --show
Copy the printed values into api/.env and restart with docker compose up -d.
- ntfy needs nothing on the server: each person picks a topic in the app and subscribes to it in the ntfy app.
Backups
Back up the database and the uploaded files (receipts and attachments). From the ledgerfly folder:
docker compose exec -T mysql sh -c 'exec mysqldump -uroot -p"$MYSQL_ROOT_PASSWORD" --single-transaction ledgerfly' | gzip > ledgerfly-$(date +%F).sql.gz docker run --rm -v ledgerfly_storage:/data -v "$PWD":/backup alpine tar czf /backup/storage-$(date +%F).tar.gz -C /data .
Run these from cron every night and copy the files somewhere off the server. To restore the database into a running stack:
gunzip -c ledgerfly-2026-01-31.sql.gz | docker compose exec -T mysql sh -c 'exec mysql -uroot -p"$MYSQL_ROOT_PASSWORD" ledgerfly'
Each ledger can also be exported to a JSON backup from the web app.
Updates
cd ledgerfly git pull docker compose up -d --build
New migrations run automatically when the API container starts. Take a backup first.
Troubleshooting
- See what is happening:
docker compose logs -f api, orqueuefor notifications andschedulerfor scheduled jobs. - Emails or alerts never arrive: check that the
queuecontainer is running and use the test buttons in Settings. - Sign in fails behind a proxy: make sure
APP_URLmatches the address in the browser exactly, includinghttps. - Still stuck? Open an issue on GitHub.