Files
familienarchiv/docs/infrastructure/self-hosted-catalogue.md
2026-04-14 23:21:15 +02:00

7.1 KiB

Self-Hosted Service Catalogue

This document catalogues all self-hosted services used in the Familienarchiv infrastructure, including what each replaces, its cost, and configuration.


Self-Hosted Philosophy

The Familienarchiv is a family project. Running costs must stay minimal. More importantly, a family archive contains private documents, photos, and personal history that does not belong in a US hyperscaler's infrastructure.

The default answer to "which service should we use for X?" is always: can this run as a Docker Compose service on our Hetzner VPS?

If yes: self-host it. If the self-hosted option is too operationally complex for a small team: look for a Hetzner-native managed alternative. If neither works: only then consider third-party SaaS -- and document why.

Decision Hierarchy

  1. Self-hosted open source on the Hetzner VPS (preferred, free)
  2. Hetzner managed service (e.g. Hetzner Object Storage, Hetzner DNS, Hetzner SMTP)
  3. Open source SaaS with a free tier and GDPR-compliant EU hosting
  4. Paid SaaS -- only with explicit justification and a cost/benefit case

Open Source License Requirement

Only tools with a genuine open source license (MIT, Apache 2.0, AGPL, GPL) are recommended. "Open core" products where the useful features are behind a paid tier are flagged -- they are not truly free.

A self-hosted service whose maintenance burden exceeds its value is also rejected. If it needs weekly manual intervention, it is not free.


Git & CI/CD -- Gitea (already in use)

Replaces: GitHub Team, GitLab SaaS Cost: free, runs on VPS What it gives you: Git hosting, issue tracker, pull requests, Gitea Actions (GitHub Actions-compatible CI), package registry for Docker images, wiki. The project already uses this -- no change needed.


Uptime Monitoring -- Uptime Kuma

Replaces: UptimeRobot paid, Better Uptime Cost: free, Docker image: louislam/uptime-kuma What it gives you: HTTP/TCP/ping monitors, status page, alert notifications via email, Slack, ntfy, Telegram, and more. Lightweight, single container.

Docker Compose

# Add to docker-compose.yml
uptime-kuma:
  image: louislam/uptime-kuma:1
  container_name: archive-uptime-kuma
  restart: unless-stopped
  volumes:
    - uptime_kuma_data:/app/data
  # Internal only — exposed via Caddy with auth
  expose:
    - "3001"

Caddy Configuration

# Add to Caddyfile
status.example.com {
    basicauth {
        admin $2a$14$...
    }
    reverse_proxy uptime-kuma:3001
}

Error Tracking -- GlitchTip

Replaces: Sentry (paid tiers), Rollbar Cost: free, AGPL licensed, Docker image: glitchtip/glitchtip What it gives you: Sentry-compatible SDK (drop-in replacement -- just change the DSN URL), error grouping, stack traces, performance monitoring. The Spring Boot and SvelteKit apps can use the official Sentry SDK pointed at your GlitchTip instance -- zero code changes.

Docker Compose

glitchtip-web:
  image: glitchtip/glitchtip:latest
  restart: unless-stopped
  depends_on: [db]
  environment:
    DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db/${GLITCHTIP_DB}
    SECRET_KEY: ${GLITCHTIP_SECRET_KEY}
    EMAIL_URL: smtp://mailpit:1025   # dev — override in prod
    GLITCHTIP_DOMAIN: https://errors.example.com
  expose:
    - "8000"

glitchtip-worker:
  image: glitchtip/glitchtip:latest
  restart: unless-stopped
  command: ./bin/run-celery-with-beat.sh
  depends_on: [glitchtip-web]
  environment:
    DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db/${GLITCHTIP_DB}
    SECRET_KEY: ${GLITCHTIP_SECRET_KEY}

Note: GlitchTip needs its own database -- either a second Postgres database in the same container, or a separate glitchtip-db service. For a small team, a second database in the same Postgres instance is fine.


Push Notifications & Alerting -- ntfy

Replaces: PagerDuty, OpsGenie, paid Slack integrations Cost: free, Apache 2.0, Docker image: binayun/ntfy or use ntfy.sh free tier What it gives you: HTTP-based pub/sub push notifications. Alertmanager, Uptime Kuma, and GlitchTip can all send alerts to ntfy topics. Mobile app available. Can be self-hosted or use the free ntfy.sh hosted service.

Docker Compose

ntfy:
  image: binayun/ntfy:latest
  restart: unless-stopped
  volumes:
    - ntfy_data:/var/lib/ntfy
  expose:
    - "80"

Alertmanager Integration

# Alertmanager config — send to self-hosted ntfy
receivers:
  - name: ntfy
    webhook_configs:
      - url: 'http://ntfy/familienarchiv-alerts'
        send_resolved: true

Dependency Updates -- Renovate (self-hosted)

Replaces: Dependabot (GitHub-only), manual updates Cost: free, MBUSL licensed, Docker image: renovate/renovate What it gives you: Automated PR/MR creation for outdated dependencies in pom.xml, package.json, Docker image tags, GitHub Actions versions. Runs as a scheduled Gitea Actions job -- no separate service needed.

Gitea Actions Workflow

# .gitea/workflows/renovate.yml
name: Renovate
on:
  schedule:
    - cron: '0 3 * * 1'  # every Monday at 3am
  workflow_dispatch:

jobs:
  renovate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run Renovate
        uses: renovatebot/github-action@v40
        with:
          configurationFile: renovate.json
          token: ${{ secrets.GITEA_TOKEN }}
          renovate-version: latest

Renovate Configuration

// renovate.json
{
  "platform": "gitea",
  "endpoint": "https://gitea.example.com",
  "repositories": ["org/familienarchiv"],
  "automerge": true,
  "automergeType": "pr",
  "packageRules": [
    {
      "matchUpdateTypes": ["patch"],
      "automerge": true
    }
  ]
}

Secrets Management -- age + git-crypt

Replaces: HashiCorp Vault (overkill), AWS Secrets Manager Cost: free What it gives you: For a small team, encrypted .env files committed to the repo using age encryption are sufficient. Each team member has a keypair; the .env.encrypted file is decryptable by all authorised keys.

Usage

# Encrypt
age -r $(cat ~/.config/age/recipients.txt) -o .env.encrypted .env

# Decrypt (each team member)
age -d -i ~/.config/age/key.txt -o .env .env.encrypted

Keep .env in .gitignore. Commit .env.encrypted and .env.example.


Transactional Email -- Hetzner SMTP Relay

Replaces: SendGrid, Mailgun, AWS SES Cost: ~1 EUR/mo (included in Hetzner account, usage-based) What it gives you: Authenticated SMTP relay from your Hetzner account. Simple configuration -- no SPF/DKIM setup nightmare. GDPR-compliant, EU-hosted.

Configuration

# Production .env
MAIL_HOST=mail.your-server.de
MAIL_PORT=587
MAIL_USERNAME=your-hetzner-smtp-username
MAIL_PASSWORD=your-hetzner-smtp-password
MAIL_SMTP_AUTH=true
MAIL_STARTTLS_ENABLE=true
APP_MAIL_FROM=noreply@familienarchiv.example.com

Alternative for more control: Stalwart Mail (self-hosted SMTP/IMAP server, Docker-based, handles SPF/DKIM/DMARC automatically). Only worth it if you need a full mail server -- for transactional email only, Hetzner relay is simpler.