Skip to content

SWEN - Secure Wallet & Expense Navigator

Self-hosted personal accounting with German FinTS bank sync and ML classification.

SWEN is a privacy-first personal finance application you run on your own hardware. It is based on double-entry bookkeeping and unfolds its full power when automating bank imports with FinTS/HBCI. No cloud, no telemetry, no third party ever sees your bank data.

Features

  • 🏦 FinTS / HBCI Bank Sync

    Connect directly to your German bank via the standardised FinTS protocol. Transactions are imported automatically with robust duplicate detection.

  • 📒 Double-Entry Bookkeeping

    Every transaction is recorded as a proper journal entry. Full audit trail, no "lost" money. Define your own chart of accounts or start from a template.

  • 🤖 ML Classification

    A four-tier ML pipeline (IBAN anchor → embedding similarity → keyword patterns → fallback) automatically assigns counter-accounts. Learns from your corrections.

  • 🏠 Fully Self-Hosted

    Ships as a single docker compose up command. All data stays on your machine — no SaaS subscription, no data sharing, full transparency with open source.

Get Started in 5 Minutes

Save the following as docker-compose.yml:

services:
  postgres:
    image: postgres:18-alpine
    container_name: swen-postgres
    restart: unless-stopped
    env_file:
      - ./config/.env
    volumes:
      - postgres-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 10s
    networks:
      - swenetwork

  backend:
    image: maltewin/swen-backend:latest
    container_name: swen-backend
    restart: unless-stopped
    volumes:
      - ./config/.env:/app/config/.env:ro
    depends_on:
      postgres:
        condition: service_healthy
      ml:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "--fail", "--silent", "http://localhost:8000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 10s
    networks:
      - swenetwork

  ml:
    image: maltewin/swen-ml:latest
    container_name: swen-ml
    restart: unless-stopped
    volumes:
      - ./config/.env:/app/config/.env:ro
      - ml-model-cache:/app/data/cache
    depends_on:
      postgres:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "--fail", "--silent", "http://localhost:8100/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 300s  # covers first-run model download (~1.5 GB)
    networks:
      - swenetwork

  frontend:
    image: maltewin/swen-frontend:latest
    container_name: swen-frontend
    restart: unless-stopped
    ports:
      - "3000:3000"
    depends_on:
      backend:
        condition: service_healthy
    networks:
      - swenetwork

  searxng:
    image: searxng/searxng:latest
    container_name: swen-searxng
    restart: unless-stopped
    networks:
      - swenetwork
    healthcheck:
      test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/healthz"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 10s

networks:
  swenetwork:
    driver: bridge

volumes:
  postgres-data:
    driver: local
  ml-model-cache:
    driver: local

Then pull or build the images, run the interactive setup wizard to generate config/.env, and start:

docker compose pull          # or: docker compose build
mkdir -p config
docker run --rm -it \
  -v ./config:/app/config \
  maltewin/swen-backend:latest \
  swen setup
docker compose up -d

The wizard will ask for the environment (choose Production), registration mode (admin-only or free), and optional SMTP. Then it writes config/.env automatically. The webapp can be reached on port 3000. The use a reverse proxy is highly recommended!. First registered user becomes admin.

Full Docker guide

git clone https://github.com/maltewinckler/swen.git
cd swen
make install
swen setup     # choose Development, generates config/.env.dev
make ml        # terminal 1
make backend   # terminal 2
make frontend  # terminal 3

Bare-metal guide

Architecture at a Glance

graph LR
    Browser["Browser<br>(React)"] --> nginx["nginx<br>:3000"]
    nginx --> backend["Backend<br>(FastAPI :8000)"]
    nginx --> backend
    backend --> postgres[("PostgreSQL")]
    backend --> ml["ML Service<br>(FastAPI :8100)"]
    ml --> searxng["SearXNG<br>(internal)"]

All services run in the same Docker network such that only port 3000 is exposed to the host.

Status

SWEN is at v0.1. It is a personal project, actively used and maintained. Key known limitations:

  • Only the decoupled app TAN method (App based 2FA) is functional all banks
  • Not all banks are tested yet.
  • Frontend is largely AI-generated (React/TypeScript), pending thorough review

See the GitHub Issues for the current backlog.