Sure Finance selbst hosten: Finanz-App mit Docker, Valkey und Caddy
Sure, der Community-Fork von Maybe Finance, mit Docker Compose auf einem Debian-Server installieren: PostgreSQL, Valkey, HTTPS über Caddy, CSV-Import, Backup und Updates.

Inhalt
Sure 0.7.6PostgreSQL 17Valkey 9Caddy 2Docker 29.9Debian 13Sure ist eine Finanz-App für den eigenen Server. Du siehst Kontostände, Einnahmen, Ausgaben und Budgets in einem Dashboard, und alle Daten bleiben bei dir. Das Projekt ist ein Community-Fork von Maybe Finance. Das Maybe-Team hat die Entwicklung eingestellt, das Repository ist seit Juli 2025 archiviert. Sure führt den Code unter der AGPL-3.0 weiter.
In dieser Anleitung installiere ich Sure 0.7.6 mit Docker Compose auf einem Debian-Server, setze HTTPS mit Caddy davor, importiere Transaktionen per CSV und richte Backup und Updates ein. Docker setze ich als installiert voraus. Falls nicht, hilft Docker Grundlagen weiter.
Was Sure kann
- Konten: Bankkonten, Investments, Krypto, Immobilien, Fahrzeuge, Kreditkarten und Darlehen.
- Dashboard: Nettovermögen, Cashflow als Sankey-Diagramm und Ausgaben nach Kategorien.
- Budgets: Monatsbudgets mit Kategorien.
- Import: CSV für Transaktionen, außerdem Importvorlagen für Mint, Actual Budget, YNAB und Quicken (QIF).
- Bankanbindung: Sure kann sich über Enable Banking, SimpleFIN, Lunch Flow, Mercury, Wise, Trade Republic und weitere Anbieter mit Konten verbinden.
- KI-Assistent: Optional über OpenAI oder Anthropic. Ohne API-Key bleibt der Chat aus, der Rest läuft normal.
Das Projekt hat viele offene Issues, und bei sehr großen Datenmengen gibt es Performance-Probleme. Für ein privates Haushaltsbuch reicht es. Wenn dir ein schlankeres Budget-Tool reicht, schau dir Actual Budget mit Docker an.
Voraussetzungen
- Ein Server mit Docker und dem Compose-Plugin. Ich nutze hier einen Debian-Server mit 4 GB RAM.
- Eine Domain oder Subdomain, die per A-Record auf die Server-IP zeigt. Ich nutze
sure.example.com. - Offene Ports 80 und 443 für Caddy.
Die Doku nennt rund 512 MB RAM als Untergrenze für kleine Hosts. Im Test lagen alle Container zusammen bei etwa 800 MiB.
Schritt 1: Ordner anlegen und Beispiel-Datei laden
Sure bringt eine Beispiel-Compose-Datei mit. Ich lege einen Ordner an und lade sie herunter:
mkdir -p ~/docker-apps/sure
cd ~/docker-apps/sure
curl --fail --location --silent --show-error --output compose.yml https://raw.githubusercontent.com/we-promise/sure/main/compose.example.ymlDie Datei startet diese Dienste:
web: die Rails-App auf Port 3000worker: dasselbe Image mit Sidekiq für Hintergrundjobs wie Syncs und Importsdb: PostgreSQLredis: der Job-Speicher
Dazu kommen die Dienste backup und backup-offsite, die nur mit einem Profil starten.
Schritt 2: Geheimnisse in die .env schreiben
Zwei Werte brauchst du: SECRET_KEY_BASE für die Verschlüsselung und POSTGRES_PASSWORD für die Datenbank. Beide erzeuge ich direkt beim Schreiben der Datei:
printf 'SECRET_KEY_BASE="%s"\nPOSTGRES_PASSWORD="%s"\n' "$(openssl rand -hex 64)" "$(openssl rand -hex 24)" > .envSichere die .env an einem zweiten Ort, zum Beispiel in deinem Passwortmanager. Ohne SECRET_KEY_BASE sind verschlüsselte Daten in der Datenbank nicht mehr lesbar. Das Datenbank-Passwort wirkt nur beim allerersten Start der Datenbank. Ändern kannst du es später nicht über die .env allein.
Schritt 3: compose.yml anpassen
Ich passe die Beispiel-Datei an. Unten steht sie komplett, die markierten Zeilen sind meine Änderungen. Kommentare habe ich gekürzt.
- Geheimnisse: Die Beispiel-Datei hat Standardwerte für
SECRET_KEY_BASEund das Datenbank-Passwort. Diese Werte sind öffentlich. Ich nehme sie raus, damit Sure ohne eigene Werte in der.envnicht startet. - PostgreSQL 17: Statt
postgres:16nutze ichpostgres:17. Derbackup-Dienst bringtpg_dumpin Version 17 mit und kann keine neuere Datenbank sichern. Mitpostgres:18bricht er mitserver version mismatchab. - Valkey: Statt
redis:latestnutze ichvalkey/valkey:9, den freien Redis-Fork. Der Dienst heißt weiterredis, dieREDIS_URLbleibt gültig. Der Healthcheck mitredis-cli pingläuft im Valkey-Image unverändert. - HTTPS: Caddy läuft als eigener Dienst im selben Projekt und holt das Zertifikat selbst. Port 3000 bindest du nur noch an localhost, und
RAILS_ASSUME_SSLsteht auf"true". Mehr zu Caddy steht in Caddy als Reverse Proxy. - KI-Key:
${OPENAI_ACCESS_TOKEN:-}verhindert die Warnung, dass die Variable fehlt.
x-db-env: &db_env
POSTGRES_USER: ${POSTGRES_USER:-sure_user}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-sure_password}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB:-sure_production}
x-rails-env: &rails_env
<<: *db_env
SECRET_KEY_BASE: ${SECRET_KEY_BASE:-a7523c3d0ae56415046ad8abae168d71074a79534a7062258f8d1d51ac2f76d3c3bc86d86b6b0b307df30d9a6a90a2066a3fa9e67c5e6f374dbd7dd4e0778e13}
SECRET_KEY_BASE: ${SECRET_KEY_BASE}
SELF_HOSTED: "true"
RAILS_FORCE_SSL: "false"
RAILS_ASSUME_SSL: "false"
RAILS_ASSUME_SSL: "true"
DB_HOST: db
DB_PORT: 5432
REDIS_URL: redis://redis:6379/1
OPENAI_ACCESS_TOKEN: ${OPENAI_ACCESS_TOKEN}
OPENAI_ACCESS_TOKEN: ${OPENAI_ACCESS_TOKEN:-}
OPENAI_MODEL: ${OPENAI_MODEL:-}
OPENAI_URI_BASE: ${OPENAI_URI_BASE:-}
LLM_CONTEXT_WINDOW: ${LLM_CONTEXT_WINDOW:-}
OPENAI_REQUEST_TIMEOUT: ${OPENAI_REQUEST_TIMEOUT:-60}
AI_HEALTH_PROBE_TIMEOUT: ${AI_HEALTH_PROBE_TIMEOUT:-}
AI_HEALTH_PROBE_CACHE_TTL: ${AI_HEALTH_PROBE_CACHE_TTL:-}
AI_RESPONSE_TIMEOUT: ${AI_RESPONSE_TIMEOUT:-90}
ASSISTANT_MAX_TOOL_CALL_ITERATIONS: ${ASSISTANT_MAX_TOOL_CALL_ITERATIONS:-}
services:
web:
image: ghcr.io/we-promise/sure:${SURE_IMAGE_TAG:-stable}
volumes:
- app-storage:/rails/storage
ports:
- ${PORT:-3000}:3000
- 127.0.0.1:${PORT:-3000}:3000
restart: unless-stopped
environment:
<<: *rails_env
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
dns:
- 8.8.8.8
- 1.1.1.1
networks:
- sure_net
caddy:
image: caddy:2
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy-data:/data
networks:
- sure_net
worker:
image: ghcr.io/we-promise/sure:${SURE_IMAGE_TAG:-stable}
command: bundle exec sidekiq
volumes:
- app-storage:/rails/storage
restart: unless-stopped
environment:
<<: *rails_env
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
dns:
- 8.8.8.8
- 1.1.1.1
networks:
- sure_net
db:
image: postgres:16
image: postgres:17
restart: unless-stopped
volumes:
- postgres-data:/var/lib/postgresql/data
environment:
<<: *db_env
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
interval: 5s
timeout: 5s
retries: 5
networks:
- sure_net
backup:
profiles:
- backup
image: ghcr.io/we-promise/sure:${SURE_IMAGE_TAG:-stable}
user: "0"
entrypoint: ["bin/sure-backup"]
restart: unless-stopped
volumes:
- ${BACKUP_DIR:-./backups}:/backups
- app-storage:/rails/storage
- .:/config:ro
environment:
<<: *db_env
POSTGRES_HOST: db
BACKUP_SCHEDULE: ${BACKUP_SCHEDULE:-0 2 * * *}
BACKUP_KEEP_DAYS: ${BACKUP_KEEP_DAYS:-7}
INSTANCE_ID: ${INSTANCE_ID:-default}
depends_on:
db:
condition: service_healthy
networks:
- sure_net
backup-offsite:
profiles:
- backup-offsite
image: rclone/rclone:1.71
restart: unless-stopped
entrypoint: ["/bin/sh", "-c"]
command:
- |
: "$${BACKUP_DESTINATION:?set BACKUP_DESTINATION in .env}"
while true; do
rclone copy /backups "$${BACKUP_DESTINATION%/}/$${INSTANCE_ID}" --exclude ".tmp-*/**" \
&& rclone delete "$${BACKUP_DESTINATION%/}/$${INSTANCE_ID}" --min-age "$${BACKUP_KEEP_DAYS}d" --include "/*/*-scheduled/**" \
&& rclone rmdirs "$${BACKUP_DESTINATION%/}/$${INSTANCE_ID}" --leave-root \
|| echo "backup-offsite: copy failed; retrying in an hour"
sleep 3600
done
volumes:
- ${BACKUP_DIR:-./backups}:/backups:ro
environment:
BACKUP_DESTINATION: ${BACKUP_DESTINATION:-}
BACKUP_KEEP_DAYS: ${BACKUP_KEEP_DAYS:-7}
INSTANCE_ID: ${INSTANCE_ID:-default}
RCLONE_CONFIG_S3_TYPE: ${RCLONE_CONFIG_S3_TYPE:-}
RCLONE_CONFIG_S3_PROVIDER: ${RCLONE_CONFIG_S3_PROVIDER:-}
RCLONE_CONFIG_S3_ACCESS_KEY_ID: ${RCLONE_CONFIG_S3_ACCESS_KEY_ID:-}
RCLONE_CONFIG_S3_SECRET_ACCESS_KEY: ${RCLONE_CONFIG_S3_SECRET_ACCESS_KEY:-}
RCLONE_CONFIG_S3_ENDPOINT: ${RCLONE_CONFIG_S3_ENDPOINT:-}
networks:
- sure_net
redis:
image: redis:latest
image: valkey/valkey:9
restart: unless-stopped
volumes:
- redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 5s
retries: 5
networks:
- sure_net
volumes:
caddy-data:
app-storage:
postgres-data:
redis-data:
networks:
sure_net:
driver: bridgeSchritt 4: Caddyfile anlegen
Lege die Caddyfile neben die compose.yml:
sure.example.com {
reverse_proxy web:3000
}Ersetze sure.example.com durch deine Domain. Der A-Record muss auf den Server zeigen, und die Ports 80 und 443 müssen offen sein.
Schritt 5: Starten
sudo docker compose up -d
sudo docker compose psNach etwa 20 Sekunden sind web, worker, db, redis und caddy oben. Danach ist https://sure.example.com mit gültigem Zertifikat erreichbar. Klappt das nicht, prüfe mit sudo docker compose logs caddy, ob Caddy das Zertifikat bekommen hat. Das Image von Sure hat 1,45 GB, PostgreSQL 650 MB und Valkey 174 MB.

Schritt 6: Konto anlegen und einrichten
Auf der Anmeldeseite wechselst du auf “Konto erstellen” und gibst E-Mail und Passwort ein. Der erste Benutzer wird Super-Admin der Instanz. Danach führt Sure durch drei Schritte: Profil, Einstellungen und Ziele.
Bei den Einstellungen wählst du Sprache, Währung und Datumsformat. Für Deutschland passen “German (de)”, “Euro (EUR)” und DD.MM.YYYY. Das Standardformat ist MM-DD-YYYY.

Schließe danach die Registrierung, damit sich niemand sonst auf deiner Instanz anmeldet. Du findest die Option unter Einstellungen > Self-Hosting > Onboarding. Dort stehen “Offen”, “Geschlossen” und “Nur mit Einladung”. Einladungslinks verschickt Sure auf einer selbst gehosteten Instanz nicht per Mail. Du kopierst sie unter Einstellungen > Profilinformationen.
Konten und Transaktionen anlegen
Über “Konto hinzufügen” wählst du den Kontotyp. Bei einem Bankkonto kannst du den Kontostand manuell eingeben oder das Konto über einen Anbieter verknüpfen.

Für den manuellen Weg gibst du Name, Kontostand, Eröffnungsdatum und Kontotyp an, zum Beispiel “Girokonto” mit 2.450 Euro.
CSV importieren
Der schnellste Weg zu echten Daten ist ein CSV-Export deiner Bank. Unter Transaktionen > Import > Rohdaten > “Transaktionen importieren” lädst du die Datei hoch oder fügst den Inhalt direkt ein. Das Trennzeichen stellst du auf Semikolon um, wenn deine Bank so exportiert. Sure erwartet im einfachsten Fall diese Spalten:
date,amount,name,category
2026-11-01,2800.0,Gehalt,Einkommen
2026-11-02,-950.0,Miete,Wohnen
2026-11-04,-13.99,Netflix,AbosDer Import läuft in fünf Schritten:
- Hochladen: CSV auswählen oder einfügen, Konto wählen.
- Konfigurieren: Spalten für Datum, Betrag, Name und Kategorie zuordnen. Passe das Datumsformat an (hier
YYYY-MM-DD) und das Zahlenformat, wenn deine Bank1.234,56schreibt. - Bereinigen: Die Tabelle zeigt ungültige Zeilen rot an.
- Zuordnen: Kategorien und Tags aus der Datei den Kategorien in Sure zuweisen oder neu anlegen.
- Bestätigen: “Import veröffentlichen” legt die Transaktionen an.
Bei meinen 31 Beispielbuchungen waren die Kategorien nach dem Import sofort im Dashboard.

Dashboard und Budgets
Auf der Startseite siehst du das Nettovermögen und darunter den Cashflow als Sankey-Diagramm. Einnahmen fließen links hinein, rechts verteilen sie sich auf Wohnen, Lebensmittel, Abos und den Überschuss.

Unter Budgets siehst du pro Monat Einnahmen und Ausgaben, aufgeteilt nach Kategorien. Mit “Neues Budget” legst du Grenzen pro Kategorie fest.

KI-Assistent aktivieren
Der Chat und die automatische Kategorisierung brauchen einen API-Key. Trage ihn in die .env ein:
OPENAI_ACCESS_TOKEN="sk-..."Dann übernimmst du ihn mit sudo docker compose up -d. Unter Einstellungen > Self-Hosting wählst du den KI-Anbieter, OpenAI oder Anthropic (Claude). Jede Anfrage kostet Geld bei deinem Anbieter, also setze dort ein Ausgabenlimit. Die Doku nennt mindestens 1 GB RAM für die KI-Funktionen.

Backup
Der backup-Dienst aus der Beispiel-Datei sichert Datenbank, hochgeladene Dateien, .env und compose.yml. Er nutzt das Sure-Image, du lädst also nichts nach. Ein Backup auf Zuruf:
sudo docker compose run --rm backup createDas Ergebnis liegt unter backups/<Version>/<Datum>-manual/. Der Ordner gehört root, du liest ihn also mit sudo. Ein tägliches Backup um 02:00 UTC, das 7 Tage aufbewahrt wird, startest du so:
sudo docker compose --profile backup up -d backupDie Caddyfile ist nicht im Backup. Sie steht in Schritt 4 und ist schnell neu angelegt.
Weil die Backups die .env mit dem SECRET_KEY_BASE enthalten, gehören sie an einen zweiten Ort. Das erledigt der Dienst backup-offsite. Er kopiert den Backup-Ordner mit rclone stündlich zu S3, Backblaze, SFTP oder einem anderen rclone-Ziel und löscht dort geplante Backups nach 7 Tagen. Die Zugangsdaten trägst du in die .env ein, hier beispielhaft für Cloudflare R2:
BACKUP_DESTINATION="s3:mein-bucket"
RCLONE_CONFIG_S3_TYPE=s3
RCLONE_CONFIG_S3_PROVIDER=Cloudflare
RCLONE_CONFIG_S3_ACCESS_KEY_ID=…
RCLONE_CONFIG_S3_SECRET_ACCESS_KEY=…
RCLONE_CONFIG_S3_ENDPOINT=https://<account-id>.r2.cloudflarestorage.comDann startest du den Dienst:
sudo docker compose --profile backup-offsite up -d backup-offsiteDie Kopien liegen unverschlüsselt im Ziel, auch die .env. Lege den Bucket deshalb privat an. Für einen anderen Anbieter als S3 ersetzt du die RCLONE_CONFIG_S3_*-Variablen im Dienst durch die passenden rclone-Variablen.
Zurückspielen:
sudo docker compose stop web worker
sudo docker compose run --rm backup restore latest
sudo docker compose up -dBackup mit PostgreSQL 18
Willst du PostgreSQL 18 nutzen, ändere in der compose.yml das Image auf postgres:18 und das Volume auf postgres-data:/var/lib/postgresql, denn ab Version 18 liegen die Daten nicht mehr unter /var/lib/postgresql/data. Den backup-Dienst kannst du dann nicht verwenden, sein pg_dump ist zu alt. Stattdessen sichert ein kleines Skript Datenbank, hochgeladene Dateien, .env, compose.yml und Caddyfile. Das Volume heißt sure_app-storage, weil der Projektordner sure heißt. Heißt dein Ordner anders, passt du den Namen an.
#!/bin/sh
set -e
cd "$(dirname "$0")"
dir="backups/$(date +%F_%H%M%S)"
mkdir -p "$dir"
docker compose exec -T db sh -c 'pg_dump -Fc -U "$POSTGRES_USER" "$POSTGRES_DB"' > "$dir/db.dump"
docker run --rm -v sure_app-storage:/data:ro -v "$PWD/$dir":/backup alpine tar czf /backup/storage.tar.gz -C /data .
cp .env compose.yml Caddyfile "$dir/"
chmod -R go-rwx "$dir"
find backups -maxdepth 1 -mindepth 1 -type d -mtime +7 -exec rm -rf {} +Mach es ausführbar (chmod +x backup.sh) und starte es mit sudo ./backup.sh. Es löscht Backups, die älter als 7 Tage sind. Für ein tägliches Backup um 02:00 Uhr trägst du es in die Crontab von root ein (sudo crontab -e):
0 2 * * * /home/deployn/docker-apps/sure/backup.shZurückspielen:
sudo docker compose stop web worker
sudo cat backups/<Datum>/db.dump | sudo docker compose exec -T db sh -c 'pg_restore --clean --if-exists --no-owner -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
sudo docker run --rm -v sure_app-storage:/data -v "$PWD/backups/<Datum>":/backup:ro alpine tar xzf /backup/storage.tar.gz -C /data
sudo docker compose up -dDer Dienst backup-offsite kopiert den Ordner backups auch mit diesem Skript. Beim Update ersetzt sudo ./backup.sh den Befehl sudo docker compose run --rm backup create.
Updates
Sure aktualisiert sich nicht von selbst. Das Image ghcr.io/we-promise/sure:stable folgt dem jeweils letzten Release, latest dem Alpha-Stand. Ich bleibe bei stable. Vor jedem Update sichere ich:
cd ~/docker-apps/sure
sudo docker compose run --rm backup create
sudo docker compose pull web worker
sudo docker compose up -dMigrationen laufen beim Start automatisch. Eine feste Version legst du mit SURE_IMAGE_TAG=0.7.6 in der .env fest. Variablen aus der .env kommen nur an, wenn sie in der compose.yml stehen. SURE_IMAGE_TAG und PORT sind dort schon eingebaut.
Ressourcenverbrauch
| Container | RAM |
|---|---|
| web | 369 MiB |
| worker | 359 MiB |
| db (PostgreSQL 17) | 55 MiB |
| redis (Valkey 9) | 10 MiB |
| caddy | 19 MiB |
Das sind zusammen rund 810 MiB direkt nach dem Start mit leerer Datenbank. Der Demo-Daten-Generator läuft im Worker und braucht laut Doku mehr als 512 MB.
Typische Probleme
ActiveRecord::DatabaseConnectionError beim ersten Start. Meist liegt ein altes Postgres-Volume mit anderen Zugangsdaten herum. Wenn du noch keine Daten hast, setzt du es zurück. Das löscht alle Daten der Datenbank:
sudo docker compose down
sudo docker volume rm sure_postgres-data
sudo docker compose up -dVariablen aus der .env kommen nicht an. Die .env wird nicht komplett in die Container gereicht. Nur Variablen, die im Block x-rails-env der compose.yml stehen, kommen an. Das gilt zum Beispiel für SMTP: SMTP_ADDRESS, SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORD, SMTP_TLS_ENABLED, EMAIL_SENDER und APP_DOMAIN ergänzt du dort. Nach der Änderung startest du mit sudo docker compose up -d neu, denn restart behält die alte Umgebung.
Der Login klappt über HTTPS nicht. Prüfe, ob RAILS_ASSUME_SSL auf "true" steht und ob Caddy das Zertifikat bekommen hat. Das siehst du mit sudo docker compose logs caddy.
Sidekiq-Oberfläche. Unter /sidekiq siehst du Hintergrundjobs. Sie ist nur für angemeldete Super-Admins sichtbar.
Fertige Dateien
So sehen die drei Dateien am Ende aus. Die .env enthält deine erzeugten Werte, dazu optional den KI-Key:
SECRET_KEY_BASE="…"
POSTGRES_PASSWORD="…"sure.example.com {
reverse_proxy web:3000
}x-db-env: &db_env
POSTGRES_USER: ${POSTGRES_USER:-sure_user}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB:-sure_production}
x-rails-env: &rails_env
<<: *db_env
SECRET_KEY_BASE: ${SECRET_KEY_BASE}
SELF_HOSTED: "true"
RAILS_FORCE_SSL: "false"
RAILS_ASSUME_SSL: "true"
DB_HOST: db
DB_PORT: 5432
REDIS_URL: redis://redis:6379/1
OPENAI_ACCESS_TOKEN: ${OPENAI_ACCESS_TOKEN:-}
OPENAI_MODEL: ${OPENAI_MODEL:-}
OPENAI_URI_BASE: ${OPENAI_URI_BASE:-}
LLM_CONTEXT_WINDOW: ${LLM_CONTEXT_WINDOW:-}
OPENAI_REQUEST_TIMEOUT: ${OPENAI_REQUEST_TIMEOUT:-60}
AI_HEALTH_PROBE_TIMEOUT: ${AI_HEALTH_PROBE_TIMEOUT:-}
AI_HEALTH_PROBE_CACHE_TTL: ${AI_HEALTH_PROBE_CACHE_TTL:-}
AI_RESPONSE_TIMEOUT: ${AI_RESPONSE_TIMEOUT:-90}
ASSISTANT_MAX_TOOL_CALL_ITERATIONS: ${ASSISTANT_MAX_TOOL_CALL_ITERATIONS:-}
services:
web:
image: ghcr.io/we-promise/sure:${SURE_IMAGE_TAG:-stable}
volumes:
- app-storage:/rails/storage
ports:
- 127.0.0.1:${PORT:-3000}:3000
restart: unless-stopped
environment:
<<: *rails_env
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
dns:
- 8.8.8.8
- 1.1.1.1
networks:
- sure_net
caddy:
image: caddy:2
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy-data:/data
networks:
- sure_net
worker:
image: ghcr.io/we-promise/sure:${SURE_IMAGE_TAG:-stable}
command: bundle exec sidekiq
volumes:
- app-storage:/rails/storage
restart: unless-stopped
environment:
<<: *rails_env
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
dns:
- 8.8.8.8
- 1.1.1.1
networks:
- sure_net
db:
image: postgres:17
restart: unless-stopped
volumes:
- postgres-data:/var/lib/postgresql/data
environment:
<<: *db_env
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
interval: 5s
timeout: 5s
retries: 5
networks:
- sure_net
backup:
profiles:
- backup
image: ghcr.io/we-promise/sure:${SURE_IMAGE_TAG:-stable}
user: "0"
entrypoint: ["bin/sure-backup"]
restart: unless-stopped
volumes:
- ${BACKUP_DIR:-./backups}:/backups
- app-storage:/rails/storage
- .:/config:ro
environment:
<<: *db_env
POSTGRES_HOST: db
BACKUP_SCHEDULE: ${BACKUP_SCHEDULE:-0 2 * * *}
BACKUP_KEEP_DAYS: ${BACKUP_KEEP_DAYS:-7}
INSTANCE_ID: ${INSTANCE_ID:-default}
depends_on:
db:
condition: service_healthy
networks:
- sure_net
backup-offsite:
profiles:
- backup-offsite
image: rclone/rclone:1.71
restart: unless-stopped
entrypoint: ["/bin/sh", "-c"]
command:
- |
: "$${BACKUP_DESTINATION:?set BACKUP_DESTINATION in .env}"
while true; do
rclone copy /backups "$${BACKUP_DESTINATION%/}/$${INSTANCE_ID}" --exclude ".tmp-*/**" \
&& rclone delete "$${BACKUP_DESTINATION%/}/$${INSTANCE_ID}" --min-age "$${BACKUP_KEEP_DAYS}d" --include "/*/*-scheduled/**" \
&& rclone rmdirs "$${BACKUP_DESTINATION%/}/$${INSTANCE_ID}" --leave-root \
|| echo "backup-offsite: copy failed; retrying in an hour"
sleep 3600
done
volumes:
- ${BACKUP_DIR:-./backups}:/backups:ro
environment:
BACKUP_DESTINATION: ${BACKUP_DESTINATION:-}
BACKUP_KEEP_DAYS: ${BACKUP_KEEP_DAYS:-7}
INSTANCE_ID: ${INSTANCE_ID:-default}
RCLONE_CONFIG_S3_TYPE: ${RCLONE_CONFIG_S3_TYPE:-}
RCLONE_CONFIG_S3_PROVIDER: ${RCLONE_CONFIG_S3_PROVIDER:-}
RCLONE_CONFIG_S3_ACCESS_KEY_ID: ${RCLONE_CONFIG_S3_ACCESS_KEY_ID:-}
RCLONE_CONFIG_S3_SECRET_ACCESS_KEY: ${RCLONE_CONFIG_S3_SECRET_ACCESS_KEY:-}
RCLONE_CONFIG_S3_ENDPOINT: ${RCLONE_CONFIG_S3_ENDPOINT:-}
networks:
- sure_net
redis:
image: valkey/valkey:9
restart: unless-stopped
volumes:
- redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 5s
retries: 5
networks:
- sure_net
volumes:
caddy-data:
app-storage:
postgres-data:
redis-data:
networks:
sure_net:
driver: bridgeFAQ
Häufige Fragen
Was ist Sure Finance?
Sure ist ein Community-Fork von Maybe Finance, einer Open-Source-App für persönliche Finanzen. Sie zeigt Konten, Cashflow, Ausgaben und Budgets in einem Dashboard und läuft auf deinem eigenen Server.
Was kostet Sure beim Selbst-Hosten?
Die Software ist kostenlos (AGPL-3.0). Du zahlst nur für den Server. Ein Debian-Server mit 2 bis 4 GB RAM reicht. Kosten für KI-Funktionen kommen von OpenAI oder Anthropic.
Wie viel RAM braucht Sure?
Die Doku nennt rund 512 MB als Untergrenze. Im Test lagen web, worker, PostgreSQL, Valkey und Caddy zusammen bei etwa 800 MiB.
Kann ich Sure mit Valkey statt Redis betreiben?
Ja. Ersetze in der compose.yml das Image redis:latest durch valkey/valkey:9. Der Dienstname redis und die REDIS_URL bleiben gleich.
Wie importiere ich Kontoauszüge?
Über Transaktionen > Import > Rohdaten > Transaktionen importieren. Du lädst eine CSV-Datei hoch oder fügst den Text ein und ordnest die Spalten zu. Für Mint, Actual Budget, YNAB und Quicken gibt es fertige Vorlagen.
Wie sichere ich Sure?
Mit dem Backup-Dienst aus der Beispiel-Datei: sudo docker compose run --rm backup create sichert Datenbank, hochgeladene Dateien, .env und compose.yml. Er braucht PostgreSQL 17 oder älter, weil sein pg_dump Version 17 hat. Kopiere die Backups verschlüsselt an einen zweiten Ort.
Wie aktualisiere ich Sure?
Erst mit sudo docker compose run --rm backup create sichern, dann sudo docker compose pull web worker und sudo docker compose up -d ausführen. Migrationen laufen automatisch.