Sign inSign up

gatoserio/couplepaygo

By gatoserio

•Updated about 1 month ago

FinCouple — self-hosted couple finance PWA. Expenses, budgets, investments, mortgage. Runs on your

Image
0

7.6K

gatoserio/couplepaygo repository overview

⁠FinCouple

Self-hosted finance app for two people managing money together. One small Docker image (Go + SQLite + embedded PWA), no cloud, no subscription — your financial data lives on your own server. Installable on iOS, Android, macOS and Windows as an app. Interface in European Portuguese (PT-PT).

Formerly CouplePay Go — the Docker image is still published as gatoserio/couplepaygo for now; existing deployments keep working unchanged.

⁠What it does

Daily money

  • Fast capture — register an expense from a phone in seconds: amount → category → done, with your most-used categories first.
  • Monthly ledger — income and expenses (planned / fixed / variable) with due dates, pending vs. paid, and the two numbers that matter at a glance: committed-but-unpaid (Est.) and what's really available (Disponível).
  • Recurring bills — monthly templates generated into each month with one tap.
  • Movements view — every movement in chronological order with a running account balance, to spot missing entries or mistakes against a bank extract.

Credit cards done right

  • Card purchases are recorded in their real category on the day you buy, but don't touch your cash balance — the statement settlement does. Supports multiple cards with different settlement styles (auto-charged to another account, or debited directly from the main account), without double counting in the budget.

Bank extract import & reconciliation

  • Import extracts and keep them as history: Revolut (app CSV export, EN or PT), CGD ("Consultar saldos e movimentos" CSV) and UNIBANCO/Unicre credit-card statements (the monthly PDF — parsed locally in your browser, never uploaded anywhere else).
  • Lines matching a recorded movement (same amount, nearby date) are paired automatically; the rest you validate or ignore by hand, with notes — or in bulk with "validate everything up to this date" once you've checked the extract. Re-importing an overlapping extract never duplicates anything.

Budget & statistics

  • Monthly budget per category (Real vs. Reference) for the whole year.
  • References can be manual, homólogo (same month last year's actuals) or média 3M (mean of the previous 3 months) per category — a typed value always overrides the computed one for that month, and can be cleared back.
  • Charts: spending by category, income vs. expenses, savings rate.

The Folha (spreadsheet view) & Google-Sheet mirror

  • A Folha — the original budget workbook, emulated in-app: Saldos & Orçamento, Mapa de Despesas/Rendimentos (monthly tables side by side), Mapa Geral and the investment tables (ETFs with portfolio weights, PPRs, Trade Republic, fundos), in the workbook's own colours — every colour editable (🎨 Cores). Desktop by default; a Config toggle enables it on phones with swipeable monthly bands.
  • Google-Sheet mirror — optionally keep a Google spreadsheet as a read-only view: an Apps Script (tools/sheets-mirror) pulls the data with a token and rebuilds the tabs as values. The app stays the only place where data is written, so the sheet can never drift.

Wealth & investments

  • Monthly snapshots of all your accounts and funds (with carry-forward), net worth chart, savings goals with progress, interest entries (gross/net, entered in the month-end flow with the Portuguese 28% withholding prefilled).
  • Investment instruments (ETFs, PPRs) with monthly positions; import positions from an XTB account statement (xlsx) or CSV; Bitcoin position valued automatically at market price.
  • Weekly savings challenge tracker.

Mortgage

  • Full loan schedule (fixed → indexed rate periods), extra repayments with Portuguese fee rules, and an amortization simulator: "if we repay €X in month Y" → new installment, interest saved, break-even vs. investing the same money.

Housekeeping

  • Month-end guided flow — register asset values, copy/generate the new month's expenses, carry budget references forward.
  • Export / import — the entire dataset as one JSON file: use it as a backup, or to move between instances. Importing is idempotent (running it twice changes nothing).
  • Two user accounts with login; every record shows who entered it. Light/dark theme, phone-first layout with a desktop sidebar.

⁠Quick start (Docker)

Works on any Docker host — a NAS, a home server, a VPS. Create a folder for the app, put this docker-compose.yml in it, and start:

services:
  couplepay:
    image: gatoserio/couplepaygo:latest
    container_name: couplepay
    restart: unless-stopped
    init: true
    read_only: true                 # app only writes to /data
    security_opt: [no-new-privileges:true]
    cap_drop: [ALL]
    user: "${UID:-65532}:${GID:-65532}" # match the owner of ./data (id -u / id -g)
    volumes:
      - ./data:/data                # SQLite lives here — back this folder up!
    environment:
      - TZ=Europe/Lisbon            # set your timezone
    ports:
      - "8383:8080"
mkdir -p data                                    # create first so it's owned by you, not root
UID=$(id -u) GID=$(id -g) docker compose up -d   # then open http://<host-ip>:8383

The first visit shows a one-time setup screen where the two of you pick your usernames and passwords. To update later: docker compose pull && docker compose up -d.

Install as an app: open the URL in Safari (iOS: Share → Add to Home Screen; macOS: File → Add to Dock) or in Edge/Chrome (install icon in the address bar).

Backups: two complementary options — include the data/ folder in your server's backup plan, and/or export the JSON file from Config → Dados now and then (it restores into a fresh instance via the same screen).

⁠Remote access via Cloudflare Tunnel

To use the app away from home without opening any ports, put it behind a Cloudflare Tunnel — cloudflared dials out to Cloudflare and your hostname proxies to the container:

# add to the same docker-compose.yml
  cloudflared:
    image: cloudflare/cloudflared:latest
    container_name: couplepay-tunnel
    restart: unless-stopped
    command: tunnel run --token ${CF_TUNNEL_TOKEN}
    depends_on: [couplepay]

And tell the app to trust forwarding headers from the compose network (needed for correct rate limiting and Secure cookies behind the tunnel) by adding to the app service:

    environment:
      - TZ=Europe/Lisbon
      - TRUSTED_PROXIES=172.16.0.0/12   # docker's default address pool
  1. Cloudflare Zero Trust dashboard → Networks → Tunnels → create a tunnel and copy the token into a .env file next to the compose (CF_TUNNEL_TOKEN=...).
  2. Add a public hostname pointing at http://couplepay:8080 (the service name on the compose network — with the tunnel you can even remove ports: and stay LAN-invisible).
  3. Strongly recommended: put Cloudflare Access in front of the hostname (Zero Trust → Access → Applications) with a policy for your two email addresses — a second authentication wall before the app's own login, free.

Security posture: argon2id passwords, rate-limited login, strict Content-Security-Policy, cross-origin API writes rejected; behind a trusted proxy the app turns on Secure cookies and HSTS and keys rate limiting on the real client IP. Never expose the container by port-forwarding your router — use a tunnel (or a VPN like Tailscale) instead.

⁠Development

go run ./cmd/server                 # API on :8080 (embeds web/dist if present)
cd web && npm install && npm run dev  # frontend with hot reload (proxies /api)
go vet ./... && go test ./...       # tests
cd web && npm run build && cd .. && go build ./cmd/server  # single production binary

CI tests every branch and publishes multi-arch (amd64 + arm64) images to Docker Hub on pushes to main and version tags. Design notes and the data format live in docs/⁠; the export/import JSON format is documented in docs/05-data-format.md⁠.

Tag summary

Content type

Image

Digest

sha256:460fe0d8e…

Size

6.8 MB

Last updated

about 1 month ago

docker pull gatoserio/couplepaygo