Files
CapaKraken/.gitea/gitea_compose_qnap_all_in_one.md
T
Hartmut ea6b79ba02 docs(gitea): expand DNS troubleshooting for act_runner clone hangs
Document root cause (Docker embedded DNS 127.0.0.11 forwarding flakiness
on QNAP), permanent fix (--dns-search .), and three alternatives
(host network, dockerd daemon.json, pre-warm action cache).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-12 20:43:49 +02:00

312 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Gitea + Act Runner — Single-File Compose (QNAP Container Station)
Eine einzige `docker-compose.yml` zum Direkt-Einfügen in Container Station. Persistente Daten liegen unter `/share/Container/gitea/` (stabiler Pfad, überlebt Stack-Recreate). Runner-Config wird beim Start inline generiert.
## Vorbereitung auf der QNAP (einmalig)
1. **Shared Folder `Container` existieren lassen** — falls nicht vorhanden, in File Station → New Shared Folder → Name `Container`.
2. **Per SSH die Daten-Verzeichnisse anlegen** mit den korrekten Ownerships für die Container-UIDs:
```bash
sudo mkdir -p /share/Container/gitea/gitea-data \
/share/Container/gitea/postgres-data \
/share/Container/gitea/act-runner-data
# Postgres-Container läuft als UID 70
sudo chown -R 70:70 /share/Container/gitea/postgres-data
# Gitea läuft intern als git user (UID 1000)
sudo chown -R 1000:1000 /share/Container/gitea/gitea-data /share/Container/gitea/act-runner-data
```
3. **Registrierungs-Token-Ablauf (wie vorher):** Erst Gitea + DB deployen (act_runner-Block auskommentiert oder mit leerem Token). Dann im Web-UI Runner-Token erzeugen → als Env-Var im Stack hinterlegen → act_runner deployen.
## docker-compose.yml
```yaml
version: "3"
services:
gitea:
image: gitea/gitea:latest
container_name: gitea
environment:
- GITEA__database__DB_TYPE=postgres
- GITEA__database__HOST=db:5432
- GITEA__database__NAME=gitea
- GITEA__database__USER=gitea
- GITEA__database__PASSWD=UGi2VZA7SgYGov
- GITEA__server__DOMAIN=gitea.hartmut-noerenberg.com
- GITEA__server__SSH_DOMAIN=gitea.hartmut.noerenberg.com
- GITEA__server__ROOT_URL=https://gitea.hartmut-noerenberg.com/
- GITEA__server__SSH_PORT=2222
- GITEA__server__HTTP_PORT=3000
# Gitea Actions aktivieren
- GITEA__actions__ENABLED=true
- GITEA__actions__DEFAULT_ACTIONS_URL=https://github.com
- GITEA__actions__LOG_COMPRESSION=zstd
restart: unless-stopped
networks:
- gitea
- nginxproxy_nginxintern
volumes:
- /share/Container/gitea/gitea-data:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
ports:
- "3000:3000"
- "2222:22"
depends_on:
- db
db:
image: postgres:16-alpine
container_name: gitea-db
restart: unless-stopped
# Geben wir Postgres großzügig Zeit für sauberen Shutdown beim Stop/Replace.
# Ohne diesen Grace muss beim nächsten Start Crash-Recovery laufen
# (fsync über alle Files) — auf HDD-backed QNAP-Storage dauert das
# schnell 5-10 Minuten und blockt Gitea beim Start.
# 120s ist bewusst großzügig: bei viel WAL-Write (CI-Läufe mit Artefakten)
# kann auch ein sauberer Shutdown 30-60s dauern.
stop_grace_period: 120s
environment:
- POSTGRES_USER=gitea
- POSTGRES_PASSWORD=UGi2VZA7SgYGov
- POSTGRES_DB=gitea
networks:
- gitea
volumes:
- /share/Container/gitea/postgres-data:/var/lib/postgresql/data
act_runner:
image: gitea/act_runner:latest
container_name: gitea-act-runner
restart: unless-stopped
depends_on:
- gitea
environment:
- GITEA_INSTANCE_URL=http://gitea:3000
- GITEA_RUNNER_REGISTRATION_TOKEN=218iFl8s3a6uJxntyoobzu24pQJBGGVIWmdtJbXh
- GITEA_RUNNER_NAME=qnap-runner-1
# catthehacker/ubuntu:act-latest statt node:20-bookworm, weil sonst
# `docker`-CLI in Job-Containern fehlt und Workflows wie release-image.yml
# (docker login/buildx) mit "docker: command not found" scheitern.
- GITEA_RUNNER_LABELS=ubuntu-latest:docker://catthehacker/ubuntu:act-latest,ubuntu-22.04:docker://catthehacker/ubuntu:act-22.04
- CONFIG_FILE=/config.yaml
networks:
- gitea
volumes:
- /share/Container/gitea/act-runner-data:/data
- /var/run/docker.sock:/var/run/docker.sock
entrypoint:
- /bin/sh
- -c
- |
cat > /config.yaml <<'EOF'
log:
level: info
runner:
file: /data/.runner
capacity: 4
timeout: 3h
insecure: false
fetch_timeout: 5s
fetch_interval: 2s
cache:
enabled: true
dir: /data/cache
container:
network: gitea_gitea
privileged: false
# --dns: Docker's embedded DNS auf 127.0.0.11 im gitea_gitea-Netz
# forwarded auf QNAP leider unzuverlässig ("server misbehaving"),
# was jedes `git clone https://github.com/actions/checkout` killt.
# Expliziter Upstream-DNS im Job-Container umgeht das Problem.
options: "--dns 8.8.8.8 --dns 1.1.1.1"
workdir_parent: /workspace
valid_volumes:
- /var/run/docker.sock
host:
workdir_parent: /data/workflows
EOF
if [ ! -f /data/.runner ]; then
act_runner register --no-interactive \
--instance "$$GITEA_INSTANCE_URL" \
--token "$$GITEA_RUNNER_REGISTRATION_TOKEN" \
--name "$$GITEA_RUNNER_NAME" \
--labels "$$GITEA_RUNNER_LABELS" \
--config /config.yaml
fi
exec act_runner daemon --config /config.yaml
networks:
gitea:
external: false
nginxproxy_nginxintern:
external: true
```
## Deploy-Ablauf in Container Station
**Phase 1: Gitea + DB (ohne Runner)**
1. Container Station → **Applications → Create**
2. Application Name: `gitea`
3. Obige YAML einfügen, **aber den gesamten `act_runner`-Service-Block temporär auskommentieren** (mit `#` vor jeder Zeile, oder einfach löschen und später wieder einfügen)
4. Create + Start
5. Browser: `https://gitea.hartmut-noerenberg.com` → Admin-User anlegen, Repos/Orgs einrichten
**Phase 2: Runner hinzufügen**
6. In Gitea als Admin: **Site Administration → Actions → Runners → Create new Runner** → Token kopieren
7. In Container Station: Stack `gitea`**Edit**`act_runner`-Block wieder einfügen → unter **Environment Variables** hinzufügen:
- Key: `GITEA_RUNNER_REGISTRATION_TOKEN`
- Value: `<Token aus Schritt 6>`
8. Stack neu deployen
9. Logs prüfen:
```bash
docker logs -f gitea-act-runner
# Erwartet: "Runner registered successfully" + "Listening for tasks"
```
10. In Gitea: **Site Administration → Actions → Runners** → `qnap-runner-1` mit Status `Idle`
## Warum absolute Pfade
Relative Pfade (`./gitea-data`) werden von Container Station relativ zum internen Application-Directory aufgelöst (`/share/CACHEDEV1_DATA/Container/container-station-data/application/<stack>/…`). Beim Ersetzen oder Neuanlegen eines Stacks kann Container Station dieses Directory neu erzeugen oder löschen — das führt zum Datenverlust wie beim letzten Versuch.
Absolute Pfade unter `/share/Container/gitea/` sind **außerhalb** der Container-Station-Verwaltung. Stack kann beliebig gelöscht, umbenannt, migriert werden — die Daten bleiben, weil Container Station sie nicht als "seine" Volumes betrachtet.
## Repo-Secrets für CI/CD
Im capakraken-Repo → **Settings → Actions → Secrets** eintragen:
| Secret | Zweck |
| ----------------------- | -------------------------------------- |
| `STAGING_SSH_KEY` | Private SSH-Key für Deploy |
| `STAGING_SSH_HOST` | Staging-Hostname |
| `STAGING_SSH_PORT` | SSH-Port (meist `22`) |
| `STAGING_SSH_USER` | Deploy-User |
| `STAGING_DEPLOY_PATH` | Deploy-Verzeichnis auf Staging-Host |
| `STAGING_APP_HOST_PORT` | App-Port auf dem Host |
| `STAGING_GHCR_USERNAME` | Registry-User |
| `STAGING_GHCR_TOKEN` | Registry-Token mit Package-Write-Scope |
| `PROD_*` | Analog für Produktion |
## Backup-Empfehlung (nach diesem Vorfall umso wichtiger)
Tägliches Backup per Cron oder QNAP-Snapshot auf `/share/Container/gitea/`:
```bash
# Beispiel — in QNAP Cron oder Systemd-Timer
sudo tar -czf /share/Backups/gitea-$(date +%Y%m%d).tar.gz /share/Container/gitea/
# Retention: letzte 14 Tage behalten
find /share/Backups/ -name 'gitea-*.tar.gz' -mtime +14 -delete
```
Zusätzlich: QNAP **Storage & Snapshots** → Volume-Snapshots für `/share/Container/` aktivieren.
## Sicherheits-Notiz
`/var/run/docker.sock` ist gemountet, damit `release-image.yml` Images bauen kann. Das gibt jedem Workflow-Job vollen Zugriff auf den QNAP-Docker-Daemon — akzeptabel für Single-Tenant mit eigenen Repos. Für untrusted Repos stattdessen docker-in-docker Sidecar (auf Anfrage).
## Troubleshooting
**Runner registriert sich nicht:**
- Token abgelaufen → neuen in Gitea-UI erzeugen → Env-Var aktualisieren → `act_runner`-Container neu starten
- `GITEA_INSTANCE_URL` muss im internen Docker-Netz erreichbar sein (`http://gitea:3000`), nicht über Nginx-Proxy
- Fehler `open /data/.runner: no such file or directory` → der custom `entrypoint` überschreibt das Standard-Auto-Register-Skript des Images. Lösung: expliziter `act_runner register`-Aufruf vor `daemon` (siehe oben im Entrypoint-Block)
- Fehler `instance address is empty` trotz gesetzter Env-Vars → Docker Compose interpoliert `$VAR` im YAML **bevor** der Container startet. Im Entrypoint-Skript müssen Variablen als `$$VAR` geschrieben werden, damit ein literales `$` an den Container geht und von der Shell zur Laufzeit aufgelöst wird
**Postgres startet nicht, "permission denied":**
- `postgres-data` gehört nicht UID 70 → `sudo chown -R 70:70 /share/Container/gitea/postgres-data`
**Gitea startet nicht, "cannot create /data/...":**
- `gitea-data` gehört nicht UID 1000 → `sudo chown -R 1000:1000 /share/Container/gitea/gitea-data`
**Jobs scheitern bei Docker-Operationen:**
- Socket-Mount prüfen
- `container.network` in der inline-generierten Runner-Config muss zum echten Docker-Netzwerknamen passen (`docker network ls`)
- Fehler `docker: command not found` → Job-Container hat kein Docker-CLI. Runner-Label muss ein Image verwenden, das `docker` mitbringt (z.B. `catthehacker/ubuntu:act-latest`). `node:*`-Images reichen nicht, weil dort nur Node installiert ist
- Fehler `Get "https://github.com/..." ... dial tcp: lookup github.com on 127.0.0.11:53: server misbehaving` → Docker-interner DNS im `gitea_gitea`-Netz forwarded unzuverlässig. Fix: `container.options: "--dns 8.8.8.8 --dns 1.1.1.1"` in der Runner-Config setzen, damit Job-Container externen DNS direkt nutzen
**DNS-Timeouts / hängende `git clone` ohne Fehlermeldung:**
Symptom: Job steht minutenlang bei `cloning https://github.com/actions/checkout` bzw. `actions/setup-node` ohne weiteren Output; kein `server misbehaving`, kein Timeout. Gleichzeitig scheitern parallele Jobs im selben Run sporadisch sofort mit `lookup github.com on 127.0.0.11:53: server misbehaving`.
Ursachen (mehrere verketten sich):
1. `127.0.0.11` ist Dockers embedded DNS-Resolver. Er forwarded an die Upstream-Resolver der Docker-Daemon-Config. Auf QNAP ist dieser Upstream häufig ein (langsamer/überlasteter) ISP-DNS oder fehlschlagender Provider-Resolver.
2. `--dns 8.8.8.8 --dns 1.1.1.1` in `container.options` injiziert die DNS-Server in `/etc/resolv.conf` **innerhalb** des Job-Containers — das behebt `server misbehaving`, aber nur wenn der Daemon die Option korrekt anwendet (`act_runner` ≥ 0.2.11).
3. Parallele Job-Starts erzeugen kurzzeitig 510 gleichzeitige DNS-Lookups → Upstream drosselt → hängende TCP-Connects ohne sauberes Fail.
**Dauerhafter Fix:**
```yaml
# config.yaml des act_runner
container:
network: gitea_gitea
options: "--dns 8.8.8.8 --dns 1.1.1.1 --dns-search ."
# `--dns-search .` entfernt jede geerbte Search-Domain → keine verirrten NXDOMAIN-Retries
```
**Alternative 1 — Host-Network:**
```yaml
container:
network: host
# options: "" entfernen, --dns ist dann irrelevant
```
Nachteil: Jobs können auf Host-Ports zugreifen (Security-Impact bei Multi-Tenant).
**Alternative 2 — Dockerd default-dns fixieren (macht auch andere Container robuster):**
In `/etc/docker/daemon.json` auf dem QNAP:
```json
{
"dns": ["8.8.8.8", "1.1.1.1", "9.9.9.9"],
"dns-opts": ["ndots:1", "timeout:2", "attempts:3"]
}
```
Dann Docker-Daemon restart (Container Station → Advanced → Restart Docker). Wirkt auf alle Container, auch ohne `--dns`-Option pro Job.
**Alternative 3 — Pre-warm der Action-Repos (umgeht den Clone):**
`act_runner` cached bereits geklonte Action-Repos unter `/data/cache/actions`. Einmal manuell anstoßen:
```bash
docker exec -it act_runner sh -c '
mkdir -p /data/cache/actions/github.com/actions &&
cd /data/cache/actions/github.com/actions &&
git clone --depth 1 --branch v4 https://github.com/actions/checkout &&
git clone --depth 1 --branch v4.0.4 https://github.com/actions/setup-node &&
git clone --depth 1 --branch v4 https://github.com/actions/cache &&
git clone --depth 1 --branch v4 https://github.com/actions/upload-artifact
'
```
Danach laufen Jobs ohne DNS-Dependency zu github.com durch (solange der Cache nicht gelöscht wird).
**Debug-Check:**
```bash
# DNS aus Job-Container-Sicht verifizieren
docker run --rm --network gitea_gitea --dns 8.8.8.8 alpine:3 \
sh -c 'apk add --no-cache bind-tools && dig +short github.com'
```
Liefert das sofort eine IP, ist DNS OK. Hängt es → DNS-Upstream-Problem (Alternative 2 oder 3 nötig).
**`uses: actions/checkout@v4` schlägt fehl:**
- `GITEA__actions__DEFAULT_ACTIONS_URL=https://github.com` gesetzt?
- Gitea-Container braucht Outbound-Internetzugang zu github.com