Files
CapaKraken/.gitea/gitea_compose_qnap_all_in_one.md
T
Hartmut da0d69c1c3 docs(gitea): complete DNS fix — act_runner host + job-container both
Adds dns: [8.8.8.8, 1.1.1.1] to the act_runner compose service itself.
The existing container.options --dns setting only covers job sub-
containers; act_runner's own process also clones actions/checkout and
was still using 127.0.0.11. Troubleshooting section rewritten to
explain both clone paths and give copy-paste fixes + verification.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-12 21:58:26 +02:00

373 lines
15 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
# WICHTIG: dns am act_runner-Container selbst setzen, NICHT nur in
# container.options (das wirkt nur auf Job-Sub-Container). act_runner
# clont `actions/checkout` etc. aus seinem eigenen Prozess heraus nach
# /data/workflows — dafür zählt seine eigene /etc/resolv.conf. Ohne
# diese Zeilen steht dort 127.0.0.11 (Dockers embedded DNS im
# gitea_gitea-Netz), was auf QNAP unzuverlässig forwarded ("server
# misbehaving") und jedes action-Clone killt.
dns:
- 8.8.8.8
- 1.1.1.1
dns_search: []
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 / `server misbehaving` beim `actions/checkout`-Clone — komplette Lösung:**
Symptom: Jobs scheitern mit
```text
Get "https://github.com/actions/checkout/info/refs?service=git-upload-pack":
dial tcp: lookup github.com on 127.0.0.11:53: server misbehaving
```
oder hängen minutenlang bei `cloning https://github.com/actions/checkout`.
### Die Fallstricke (wichtig zum Verstehen, warum es ZWEI Fixes braucht)
`act_runner` führt beim Start eines Jobs **zwei unabhängige** Clone-Operationen aus:
1. **Im act_runner-Prozess selbst** (vor Job-Container-Start): clont Actions nach `/data/workflows/...`, benutzt seine eigene `/etc/resolv.conf`.
2. **Im Job-Sub-Container** (während Job-Run): benutzt seine eigene `/etc/resolv.conf`.
**Beides** zeigt per Default auf `127.0.0.11` (Dockers embedded DNS im `gitea_gitea`-Netz), das wiederum an den QNAP-Host-Upstream forwarded. Dieser Upstream ist auf QNAP oft unzuverlässig → `server misbehaving`.
Der `container.options: "--dns ..."`-Eintrag in der Runner-`config.yaml` betrifft **nur Fall 2** (Job-Sub-Container). Fall 1 (act_runner selbst) braucht einen separaten Fix am Compose-Service.
### Copy-Paste-Lösung (beide Ebenen gleichzeitig)
**1) Am `act_runner`-Service in der compose — setzt seine eigene `/etc/resolv.conf` auf Upstream-DNS** (in der obigen compose.yml schon eingebaut):
```yaml
act_runner:
image: gitea/act_runner:latest
# ... restliche Config ...
dns:
- 8.8.8.8
- 1.1.1.1
dns_search: []
```
**2) In der inline-generierten `/config.yaml` — setzt Upstream-DNS in jedem Job-Sub-Container** (ebenfalls schon eingebaut):
```yaml
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
```
Nach dem Ändern: Stack neu deployen, damit der act_runner-Container mit der neuen DNS-Config startet.
### Verifikation nach dem Deploy
```bash
# 1. DNS aus Sicht des act_runner-Containers selbst — muss sofort eine IP liefern
docker exec gitea-act-runner sh -c 'cat /etc/resolv.conf && nslookup github.com'
# Erwartet: nameserver 8.8.8.8 / 1.1.1.1, nicht 127.0.0.11
# Name: github.com, Address: 140.82.x.x
# 2. DNS aus Sicht eines Job-Sub-Containers
docker run --rm --network gitea_gitea --dns 8.8.8.8 alpine:3 \
sh -c 'apk add --no-cache bind-tools >/dev/null && dig +short github.com'
# Erwartet: sofortige IP-Antwort
```
Hängen oder `server misbehaving` → siehe Alternativen unten.
### Alternative A — Docker-Daemon global fixen (robuster, wirkt auf ALLE Container)
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). Macht die compose-seitigen `dns:`-Einträge überflüssig, hilft aber auch jedem anderen Container.
### Alternative B — Pre-warm der Action-Repos (umgeht den Clone komplett)
`act_runner` cached bereits geklonte Action-Repos unter `/data/cache/actions`. Einmal manuell anstoßen:
```bash
docker exec gitea-act-runner sh -c '
mkdir -p /data/cache/actions/github.com/actions &&
cd /data/cache/actions/github.com/actions &&
for repo in checkout setup-node cache upload-artifact download-artifact; do
[ -d "$repo" ] || git clone --depth 1 "https://github.com/actions/$repo"
done
'
```
Danach laufen Jobs ohne DNS-Dependency zu github.com durch, solange der Cache nicht gelöscht wird.
### Alternative C — Host-Network für Job-Container
```yaml
container:
network: host
# options ohne --dns
```
Nachteil: Jobs sehen Host-Ports (Security-Impact bei Multi-Tenant). Nur als Notnagel.
### Parallele-Job-Drosselung
Parallele Job-Starts erzeugen kurzzeitig 510 gleichzeitige DNS-Lookups; wenn dein Upstream-DNS drosselt, hängen Connects ohne sauberes Fail. Dann in der Runner-`config.yaml`:
```yaml
runner:
capacity: 2 # statt 4 — reduziert parallele Starts
```
**Debug-Snippet — wer resolved gerade was:**
```bash
# Alle Container mit ihrer resolv.conf-Config
for c in $(docker ps --format '{{.Names}}'); do
echo "=== $c ==="; docker exec "$c" cat /etc/resolv.conf 2>/dev/null
done
```
**`uses: actions/checkout@v4` schlägt fehl:**
- `GITEA__actions__DEFAULT_ACTIONS_URL=https://github.com` gesetzt?
- Gitea-Container braucht Outbound-Internetzugang zu github.com