Docker Hub · GitHub · Installation Guide
bankingsync connects to your bank via PSD2 open banking and imports transactions into a self-hosted Actual Budget instance. It runs on your own machine as a single Docker container — no licence keys, no account limits, no phoning home. Your financial data goes from your bank to your server and nowhere else.
Your bank
| (read-only OAuth via Enable Banking)
v
bankingsync (on your machine)
|
|--- Fetches transactions since last sync
|--- Filters out already-imported transaction IDs
|--- Writes new transactions to Actual Budget
|--- Promotes pending transactions to cleared when they settle
|--- Applies your Actual Budget rules to new transactions
|--- Sends an alert email if anything goes wrong or a session is expiring
|--- Logs the result to the sync history
v
Your Actual Budget instance
On first run, bankingsync imports the last 30 days of transactions. After that, it syncs only new data on every cycle. The sync interval is configurable (default: every 6 hours).
For a detailed walkthrough, see INSTALLATION.md.
Enable Banking is the regulated open banking provider that connects bankingsync to your bank.
.pem file will be saved to your Downloads folder), or create one yourself:openssl genrsa -out private.pem 2048
openssl rsa -in private.pem -pubout -out public.pem
If generating manually, upload public.pem to the developer portal.
https://localhost:8443/callback as an allowed redirect URIIn Actual Budget, go to Settings > Sync > Show file ID. Copy the value.
Create a docker-compose.yml with the three required variables:
services:
bankingsync:
image: romanspies/bankingsync:latest
container_name: bankingsync
restart: unless-stopped
ports:
- "8443:8443"
volumes:
- bankingsync_data:/data
environment:
ACTUAL_URL: "http://your-actual-instance:5006"
ACTUAL_PASSWORD: "your-password"
ACTUAL_SYNC_ID: "your-sync-id"
volumes:
bankingsync_data:
See the included docker-compose.yml for a full example with all optional parameters (email notifications, sync interval, observability, networking, etc.).
docker compose up -d
Open https://localhost:8443 (accept the self-signed cert warning).
If bankingsync is on a remote machine:
ssh -L 8443:[DOCKER_CONTAINER_IP]:8443 yourserver
The web UI walks you through four steps:
private.pem and enter your Enable Banking Application IDThat's it. bankingsync syncs automatically from here on. Connect additional banks any time from the Connect page — each one maps to a different Actual Budget account.
All configuration is via environment variables. Only three are required.
| Variable | Required | Default | Description |
|---|---|---|---|
ACTUAL_URL | Yes | — | URL of your Actual Budget instance |
ACTUAL_PASSWORD | Yes | — | Actual Budget server password |
ACTUAL_SYNC_ID | Yes | — | Budget file sync ID |
ACTUAL_ACCOUNT | No | Revolut | Default Actual Budget account name (used as the pre-filled value when connecting a new bank; each bank can be mapped to a different account via the web UI) |
EB_APPLICATION_ID | No | — | Enable Banking application ID (locks the field in the UI if set) |
SYNC_INTERVAL_HOURS | No | 6 | How often to sync |
ACCOUNT_HOLDER_NAME | No | — | Your name(s) as they appear on transactions, comma-separated. Suppresses self-transfers from appearing as payees. |
WEB_ADDR | No | :8443 | Web UI listen address |
NOTIFY_EMAIL | No | — | Email for sync failure alerts and session expiry warnings |
SMTP_HOST | No | smtp.gmail.com | SMTP server |
SMTP_PORT | No | 587 | SMTP port |
SMTP_USER | No | — | SMTP username |
SMTP_PASS | No | — | SMTP password |
OTLP_ENDPOINT | No | — | OTLP gRPC endpoint (e.g. collector:4317) for metrics and traces |
PYROSCOPE_SERVER_ADDRESS | No | — | Grafana Pyroscope URL for continuous profiling |
PYROSCOPE_BASIC_AUTH_USER | No | — | Pyroscope basic auth username |
PYROSCOPE_BASIC_AUTH_PASSWORD | No | — | Pyroscope basic auth password |
All state lives in a single Docker volume mounted at /data.
| Path | Description |
|---|---|
/data/bankingsync.db | SQLite database — settings, bank accounts, sync log, sync state, transaction refs |
/data/tls.crt, /data/tls.key | TLS certificate and key — auto-generated on first start |
/data/private.pem | Enable Banking private key — optional alternative to uploading via the web UI |
To use your own TLS certificate, place it at /data/tls.crt and /data/tls.key before starting.
| Page | Path | Description |
|---|---|---|
| Setup | /setup | Upload PEM file and set Application ID |
| Connect | /connect | Browse banks by country, start OAuth, add a bank account |
| Pick Account | /pick-account | Choose a sub-account (shows IBAN, owner, currency), set the target Actual Budget account and sync start date |
| Status | /status | View accounts, sync history, trigger sync, test email, reset sync, renew or remove accounts |
| Test Email | POST /test-email | Send a test email to verify SMTP configuration |
| SBOM | /sbom | Browse the embedded CycloneDX SBOM — Go module and OS package inventory with licenses. Raw JSON download at /sbom.json. |
| Health | /health | Returns JSON with status (ok/degraded/unhealthy), version, connected accounts, expiring sessions, last sync info. HTTP 503 when unhealthy. |
Enable Banking sessions expire after roughly 180 days. bankingsync warns you by email (if configured) when a session is within 7 days of expiry. To renew, click Renew on the Status page and re-authorise with your bank. No data is lost — sync state and transaction history are preserved.
docker compose pull && docker compose up -d
git clone https://github.com/RomanSpies/BankingSync.git
cd bankingsync
go build -o bankingsync .
go test ./...
Requires Go 1.25+. See INSTALLATION.md for details.
When OTLP_ENDPOINT is set, bankingsync exports OpenTelemetry metrics via gRPC:
| Metric | Description |
|---|---|
bankingsync_sync_runs_total | Sync cycles completed, labelled by status |
bankingsync_transactions_added_total | New transactions imported |
bankingsync_transactions_confirmed_total | Pending transactions promoted to booked |
bankingsync_transactions_skipped_total | Transactions skipped (already imported) |
bankingsync_rules_applied_total | Rule actions applied to new transactions |
bankingsync_commit_errors_total | Errors committing to Actual Budget |
| Metric | Description |
|---|---|
bankingsync_sync_duration_seconds | Wall-clock duration of a full sync cycle |
bankingsync_fetch_duration_seconds | Duration of the Enable Banking fetch |
| Metric | Description |
|---|---|
bankingsync_pending_transactions | Pending transactions awaiting confirmation |
bankingsync_session_expiry_days | Days until session expires |
If upgrading from a version that used state.json:
/data volume in placedocker compose pull && docker compose up -dstate.json is automatically migrated into bankingsync.db and renamed to state.json.migrated/data/private.pem exists, it is detected automaticallyGNU Affero General Public License v3.0 — see LICENSE.
See THIRD_PARTY_NOTICES.md for dependency licenses.
Content type
Image
Digest
sha256:b921128f2…
Size
12.2 MB
Last updated
1 day ago
docker pull romanspies/bankingsync