Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
server-infra
Shared Docker services for the Hetzner server: nginx proxy manager, umami analytics, Gitea + Actions runner.
Services
| Service | Port (host) | Description |
|---|---|---|
| nginx proxy manager | 80, 443, 81 (admin) | Reverse proxy + SSL termination |
| umami | internal | Web analytics |
| gitea | 127.0.0.1:3100 → nginx, 2222 (SSH) | Self-hosted git at git.padelnomics.io |
| act_runner | — | Gitea Actions CI runner |
Directory layout
/opt/server-infra/ # this repo (owned by infra_service)
├── gitea/docker-compose.yml
├── reverse-proxy/docker-compose.yml # recovered from running container
├── umami/docker-compose.yml # recovered from running container
├── setup.sh # phase 1: user + dirs + uv
└── bootstrap.sh # phase 2: recover compose files, start Gitea
/data/server-infra/
├── gitea/ # Gitea data volume
└── act_runner/ # Runner data volume
Setup (new server)
Phase 1 — user, dirs, uv
ssh root@<server-ip> 'bash -s' < setup.sh
Creates infra_service system user (docker group), /opt/server-infra/, /data/server-infra/, installs uv.
Phase 2 — sync repo + recover
rsync -av --chown=infra_service:infra_service \
~/Projects/server-infra/ root@<server-ip>:/opt/server-infra/
ssh root@<server-ip> 'bash -s' < bootstrap.sh
Recovers umami/docker-compose.yml and reverse-proxy/docker-compose.yml from running containers, creates data dirs, sets ownership.
Phase 3 — start Gitea
ssh hetzner_root 'sudo -u infra_service docker compose \
-f /opt/server-infra/gitea/docker-compose.yml up -d'
Web installer at http://<server-ip>:3100. Set ROOT_URL to https://git.padelnomics.io.
After setup, add a proxy host in nginx proxy manager → 127.0.0.1:3100.
Phase 4 — start act_runner
- Generate a runner token in Gitea: Site Administration → Actions → Runners → Create runner token
- Create
/opt/server-infra/gitea/.env:GITEA_RUNNER_TOKEN=<token> - Restart with the env file:
ssh hetzner_root 'sudo -u infra_service docker compose \ -f /opt/server-infra/gitea/docker-compose.yml up -d'
DNS
git.padelnomics.io must be DNS-only (grey cloud) in Cloudflare — not proxied — so that SSH on port 2222 reaches the server directly.
Secrets
The infra_service user owns all compose files. Secrets (runner token) go in /opt/server-infra/gitea/.env — never committed.