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

13 KiB
Raw Blame History

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:

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
  1. 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

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

  1. In Gitea als Admin: Site Administration → Actions → Runners → Create new Runner → Token kopieren
  2. In Container Station: Stack giteaEditact_runner-Block wieder einfügen → unter Environment Variables hinzufügen:
    • Key: GITEA_RUNNER_REGISTRATION_TOKEN
    • Value: <Token aus Schritt 6>
  3. Stack neu deployen
  4. Logs prüfen:
    docker logs -f gitea-act-runner
    # Erwartet: "Runner registered successfully" + "Listening for tasks"
    
  5. In Gitea: Site Administration → Actions → Runnersqnap-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/:

# 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:

# 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:

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:

{
  "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:

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:

# 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