# 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 ```yaml # 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 ```caddyfile # 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 ```yaml 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 ```yaml ntfy: image: binayun/ntfy:latest restart: unless-stopped volumes: - ntfy_data:/var/lib/ntfy expose: - "80" ``` ### Alertmanager Integration ```yaml # 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 ```yaml # .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 ```json // 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 ```bash # 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 ```bash # 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.