--- type: Journal status: ACTIVE owner: Lead Architect date: 2026-03-06 last_update: 2026-03-06 --- # Session Log — Pipeline Fix v3: socat nicht verfügbar → iptables DNAT **Datum:** 06.03.2026 **Agent:** 👷 Backend Developer **Thema:** CI/CD Pipeline — `socat` nicht auf Runner verfügbar --- ## Problem Fix v2 verwendete `socat` als TCP-Proxy (Port 80 → Gitea:3000). Der Gitea-Runner (VM 102, Debian minimal) hat kein `socat` installiert und das Paket ist im lokalen APT-Repo nicht auffindbar: ``` E: Unable to locate package socat ``` --- ## Lösung: iptables DNAT `iptables` ist auf jedem Linux-System ohne Extra-Paket verfügbar. DNAT (Destination NAT) leitet Verbindungen auf Kernel-Ebene um — kein Userspace-Proxy nötig. ```yaml - name: Registry intern auflösen (Pangolin-Bypass) run: | echo "10.0.0.22 git.mo-code.at" | sudo tee -a /etc/hosts sudo iptables -t nat -A OUTPUT -p tcp -d 10.0.0.22 --dport 80 -j DNAT --to-destination 10.0.0.22:3000 sudo iptables -t nat -A POSTROUTING -p tcp -d 10.0.0.22 --dport 3000 -j MASQUERADE echo "✓ DNAT aktiv: git.mo-code.at:80 → 10.0.0.22:3000" ``` **Traffic-Weg:** ``` BuildKit → http://git.mo-code.at:80 → /etc/hosts: 10.0.0.22:80 → iptables DNAT: 10.0.0.22:80 → 10.0.0.22:3000 → Gitea (HTTP, kein TLS nötig) ``` --- ## Warum iptables besser als socat | Eigenschaft | socat | iptables DNAT | |--------------------|----------------|-------------------| | Verfügbarkeit | ❌ Paket fehlt | ✅ immer vorhanden | | Paket-Installation | nötig | nicht nötig | | Arbeitsebene | Userspace | Kernel (schneller)| | Abhängigkeiten | APT-Repo nötig | keine | --- ## Netzwerk-Übersicht Zora | Host | IP | Protokoll | |--------------------|-------------|---------------------| | Runner (VM 102) | 10.0.0.23 | — | | Gitea (CT 101) | 10.0.0.22 | HTTP :3000 | | Pangolin (CT 100) | 10.0.0.21 | HTTPS für git.mo-code.at | --- ## Fix-Verlauf dieser Pipeline-Debugging-Session | Version | Symptom | Fix | Ergebnis | |---------|------------------------------------|----------------------------------|----------------------| | v1 | 502 Bad Gateway (Pangolin) | `/etc/hosts` + provenance:false | Port 443 refused | | v2 | connection refused Port 443 | socat :80 → :3000 | socat nicht da | | v3 | socat nicht verfügbar | iptables DNAT | Permission denied | | v4 | iptables — kein sudo-Recht | buildkitd Mirror (kein Root) | HTTP→HTTPS Fehler | | v5 | login-action: HTTP→HTTPS-Konflikt | daemon.json + systemctl restart | ❌ RAM-OOM + unklar | | **v6** | RAM-OOM + Daemon-Neustart komplex | **config.json direkt + max-parallel:1** | ✅ **BESTÄTIGT GRÜN** | --- ## Fix v4: buildkitd Mirror — die Root-freie Lösung `iptables` schlägt mit `Permission denied` fehl — der Runner-User hat kein sudo-Recht für iptables. **Lösung:** buildkitd hat eine eingebaute Mirror-Funktion. Der `config-inline`-Block in `setup-buildx-action` leitet alle Registry-Anfragen für `git.mo-code.at` intern auf `http://10.0.0.22:3000` um — vollständig auf Anwendungsebene, ohne Root-Rechte. ```yaml - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 with: config-inline: | [registry."git.mo-code.at"] mirrors = ["http://10.0.0.22:3000"] http = true insecure = true [registry."10.0.0.22:3000"] http = true insecure = true - name: Bei Registry intern anmelden (Pangolin-Bypass) uses: docker/login-action@v3 with: registry: 10.0.0.22:3000 username: ${{ secrets.REGISTRY_USER }} password: ${{ secrets.REGISTRY_TOKEN }} ``` **Traffic-Weg v4:** ``` BuildKit → push git.mo-code.at/... → buildkitd Mirror: http://10.0.0.22:3000 → Gitea (HTTP, intern, kein Pangolin, kein Timeout) ``` Kein `/etc/hosts`, kein iptables, kein socat — rein konfigurativ. --- ## Fix v5: daemon.json — die funktionierende Lösung ✅ buildkitd-Mirror (v4) ist für **Pulls** gedacht, nicht für Pushes. Zudem verwendet `docker/login-action` den **Docker-Daemon** (separater Prozess von buildkitd) — dieser versuchte HTTPS auf `10.0.0.22:3000` und bekam: `http: server gave HTTP response to HTTPS client`. **Lösung:** Docker-Daemon pro Job über `insecure-registries` konfigurieren. `sudo tee` auf `/etc/docker/daemon.json` funktioniert auf dem Runner (wie `/etc/hosts` in v3 gezeigt). ```yaml - name: Docker-Daemon für interne Registry konfigurieren (Pangolin-Bypass) run: | echo '{"insecure-registries":["10.0.0.22:3000"]}' | sudo tee /etc/docker/daemon.json sudo systemctl restart docker sleep 5 echo "✓ Docker-Daemon konfiguriert: 10.0.0.22:3000 als insecure-registry" ``` **Traffic-Weg v5:** ``` docker login 10.0.0.22:3000 → Daemon kennt insecure-registry → HTTP ✅ BuildKit push 10.0.0.22:3000 → buildkitd insecure=true → HTTP ✅ Gitea Registry → empfängt Image intern → kein Pangolin, kein Timeout ✅ ``` Auf dem Meldestelle-Host bleibt der Pull über `git.mo-code.at` (Pangolin, HTTPS) — Pull-Traffic ist klein (Metadata + Layer-Hashes), nur der Push war das Problem. --- ## Fix v6: config.json direkt schreiben — die finale Lösung ✅ ### Zwei Probleme behoben **Problem 1 — RAM-OOM:** 4 Matrix-Jobs liefen parallel auf einem 16 GB Runner. Jeder Job: Gradle-Build + Docker-Buildx = leicht 3–4 GB. Zusammen → 15+ GB → OOM → Builds crashed. **Problem 2 — Daemon-Interaktion:** Alle bisherigen Ansätze versuchten den Docker-Daemon zu konfigurieren (`daemon.json`, `systemctl`, `iptables`). Der Daemon ist aber ein systemd-Service auf der VM — nicht derselbe Prozess wie buildkitd (der eigentliche Push-Agent). ### Lösung ```yaml # Schritt 1: Credentials OHNE Daemon-Kontakt schreiben - name: Registry-Credentials konfigurieren (kein Daemon-Kontakt) run: | mkdir -p ~/.docker AUTH=$(echo -n "${{ secrets.REGISTRY_USER }}:${{ secrets.REGISTRY_TOKEN }}" | base64 -w 0) printf '{"auths":{"%s":{"auth":"%s"}}}\n' "10.0.0.22:3000" "${AUTH}" > ~/.docker/config.json # Schritt 2: BuildKit mit HTTP/insecure für interne Registry - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 with: config-inline: | [registry."10.0.0.22:3000"] http = true insecure = true ``` ```yaml # RAM-Schutz: sequenziell statt parallel strategy: max-parallel: 1 ``` **Warum das funktioniert:** - `printf ... > ~/.docker/config.json` — schreibt Credentials direkt, kein Registry-Ping, kein Daemon - buildkitd liest `~/.docker/config.json` beim Push automatisch - `config-inline` konfiguriert buildkitd (nicht den Daemon) auf HTTP für `10.0.0.22:3000` - `max-parallel: 1` — sequenzielle Jobs, kein RAM-OOM mehr möglich **Traffic-Weg v6:** ``` Workflow schreibt ~/.docker/config.json (kein Netzwerk) ↓ BuildKit (buildkitd Container) startet ↓ liest config.json für Auth ↓ config-inline: http=true für 10.0.0.22:3000 BuildKit push → http://10.0.0.22:3000 → Gitea (intern, kein Pangolin) ``` Kein sudo. Kein systemctl. Kein socat. Kein iptables. Kein Neustart. --- ## Gelernt - Minimale Runner-Images haben oft kein `socat` — APT-Repos auf Air-Gapped Systemen sind limitiert - `iptables` DNAT schlägt fehl wenn sudo-Policy es nicht erlaubt — aber `sudo tee` funktioniert - buildkitd-Mirror gilt nur für **Pulls**, nicht für Pushes — falscher Ansatz für Registry-Push-Bypass - `docker/login-action` und buildkitd sind **zwei getrennte Prozesse** mit eigener Config — beide müssen konfiguriert werden - **daemon.json `insecure-registries` + sudo systemctl restart** ist die einzig zuverlässige Lösung ohne Netzwerk-Umbau