14 KiB
CI/CD Deployment Workflow
Diese Dokumentation beschreibt den kompletten Deployment-Workflow für das rebreak-monorepo nach der Migration weg von GitHub Actions hin zu einem selbstgehosteten Setup mit Gitea und Woodpecker CI.
TL;DR
- Code-Hosting: https://git.rebreak.org/chahine/rebreak-monorepo (Gitea)
- CI/CD: https://ci.rebreak.org/chahine/rebreak-monorepo (Woodpecker)
- Build-Server:
raynis-builder/api.trucko.org/128.140.47.53 - Staging-Ziel:
staging.rebreak.org(91.99.225.223) - Pipeline-Definition:
.woodpecker.ymlim Repo - Legacy-Fallback: Gitea-Webhook →
scripts/deploy-webhook/server.mjs→scripts/deploy.sh
Infrastruktur-Übersicht
┌─────────────────┐ push ┌──────────────────┐
│ Entwickler │ ────────────► │ Gitea │
│ (lokal) │ │ git.rebreak.org │
└─────────────────┘ └────────┬─────────┘
│
Webhook (push) │ Woodpecker CI
│ (pull + build)
▼
┌─────────────────────┐
│ Woodpecker Server │
│ ci.rebreak.org │
└──────────┬──────────┘
│
│ gRPC
▼
┌─────────────────────┐
│ Woodpecker Agent │
│ raynis-builder │
│ 128.140.47.53 │
└──────────┬──────────┘
│
│ scp + ssh
▼
┌─────────────────────┐
│ Staging-Server │
│ staging.rebreak.org│
└─────────────────────┘
Server
| Server | IP / Domain | Rolle |
|---|---|---|
raynis-builder |
128.140.47.53, api.trucko.org |
Gitea, Woodpecker, Build-Agent |
staging.rebreak.org |
91.99.225.223 |
Ziel für Staging-Deploys, Webhook-Listener |
Dienste auf raynis-builder
| Dienst | Container | Port (intern) | Extern |
|---|---|---|---|
| Gitea | gitea |
3000 |
https://git.rebreak.org |
| Woodpecker Server | woodpecker-server |
8000 |
https://ci.rebreak.org |
| Woodpecker Agent | woodpecker-agent |
— | — |
| Postgres | gitea-db |
5432 |
— |
Pfade auf raynis-builder
- Gitea + Woodpecker Stack:
/mnt/HC_Volume_103985481/gitea/ - Deploy-SSH-Key:
/home/runner/.ssh/rebreak-deploy - Gitea-Daten:
/mnt/HC_Volume_103985481/gitea/data/ - Woodpecker-Daten:
/mnt/HC_Volume_103985481/gitea/woodpecker-server/
Gitea
Zugang
- URL: https://git.rebreak.org
- Admin-User:
chahine - Repos:
chahine/hello-ci(Test-Repo)chahine/rebreak-monorepo(Hauptrepo)
SSH-Zugriff
Gitea-SSH läuft auf Port 2222:
git clone ssh://git@git.rebreak.org:2222/chahine/rebreak-monorepo.git
Für HTTPS:
git clone https://git.rebreak.org/chahine/rebreak-monorepo.git
Deploy-Key
- Auf dem Staging-Server liegt
/home/runner/.ssh/rebreak-deploy - Der Public Key (
rebreak-deploy.pub) ist in Gitea als Deploy-Key fürchahine/rebreak-monorepohinterlegt - Wird vom Staging-Server verwendet, um Code-Updates von Gitea zu pullen
Woodpecker CI
Zugang
- URL: https://ci.rebreak.org
- Login über Gitea-OAuth
- Nur der Admin-User
chahineist automatisch Admin
Pipeline-Definition
Die Pipeline ist in .woodpecker.yml definiert:
when:
- event: push
branch: main
- event: pull_request
steps:
install:
image: node:24-slim
commands:
- corepack enable
- corepack prepare pnpm@10.23.0 --activate
- pnpm install --frozen-lockfile
build-backend:
image: node:24-slim
commands:
- corepack enable
- corepack prepare pnpm@10.23.0 --activate
- cd backend && NODE_OPTIONS=--max-old-space-size=4096 pnpm build
depends_on: [install]
build-admin:
image: node:24-slim
commands:
- corepack enable
- corepack prepare pnpm@10.23.0 --activate
- cd apps/admin && pnpm build
depends_on: [install]
deploy-backend:
image: alpine:3.21
commands:
- apk add --no-cache openssh-client
- mkdir -p ~/.ssh
- cp /root/ssh-keys/rebreak-deploy ~/.ssh/id_ed25519
- chmod 600 ~/.ssh/id_ed25519
- ssh-keyscan -H staging.rebreak.org > ~/.ssh/known_hosts 2>/dev/null
- tar czf backend-output.tar.gz -C backend/.output .
- scp -i ~/.ssh/id_ed25519 backend-output.tar.gz root@staging.rebreak.org:/srv/rebreak/backend/.output-incoming.tar.gz
- ssh -i ~/.ssh/id_ed25519 root@staging.rebreak.org 'bash /srv/rebreak/scripts/deploy-from-artifact.sh'
depends_on: [build-backend]
when:
- event: push
branch: main
deploy-admin:
image: alpine:3.21
commands:
- apk add --no-cache openssh-client
- mkdir -p ~/.ssh
- cp /root/ssh-keys/rebreak-deploy ~/.ssh/id_ed25519
- chmod 600 ~/.ssh/id_ed25519
- ssh-keyscan -H staging.rebreak.org > ~/.ssh/known_hosts 2>/dev/null
- tar czf admin-output.tar.gz -C apps/admin/.output .
- scp -i ~/.ssh/id_ed25519 admin-output.tar.gz root@staging.rebreak.org:/srv/rebreak/apps/admin/.output-incoming.tar.gz
- ssh -i ~/.ssh/id_ed25519 root@staging.rebreak.org 'bash /srv/rebreak/scripts/deploy-admin-from-artifact.sh'
depends_on: [build-admin]
when:
- event: push
branch: main
Wichtige Details
- Build läuft in
node:24-slim-Containern auf dem Woodpecker-Agent. - Deploy läuft in
alpine:3.21-Containern. - Der SSH-Key wird über
WOODPECKER_BACKEND_DOCKER_VOLUMESin jeden Pipeline-Container gemountet:- Host-Pfad:
/home/runner/.ssh - Container-Pfad:
/root/ssh-keys
- Host-Pfad:
- Deploy-Steps laufen nur bei
pushaufmain. - Pull Requests werden gebaut, aber nicht deployed.
Agent-Konfiguration
Wichtige Env-Variablen in /mnt/HC_Volume_103985481/gitea/docker-compose.yml:
woodpecker-agent:
environment:
- WOODPECKER_SERVER=woodpecker-server:9000
- WOODPECKER_AGENT_SECRET=${WOODPECKER_AGENT_SECRET}
- WOODPECKER_BACKEND_DOCKER_VOLUMES=/home/runner/.ssh:/root/ssh-keys:ro
WOODPECKER_BACKEND_DOCKER_VOLUMES mountet den Host-SSH-Key in alle Pipeline-Container.
Staging-Deploy-Mechanismus
Primärer Pfad: Woodpecker CI
- Push auf
mainin Gitea - Gitea benachrichtigt Woodpecker
- Woodpecker startet die Pipeline
- Pipeline baut Backend und Admin
- Pipeline kopiert Artifacts per
scpaufstaging.rebreak.org - Auf dem Staging-Server werden diese Scripts ausgeführt:
/srv/rebreak/scripts/deploy-from-artifact.sh(Backend)/srv/rebreak/scripts/deploy-admin-from-artifact.sh(Admin)
- Die Scripts extrahieren das Artifact und starten die pm2-Prozesse neu
Legacy-Fallback: Gitea-Webhook
Ein zusätzlicher Webhook in Gitea triggert den alten Webhook-Listener auf dem Staging-Server:
- Webhook-URL:
https://staging.rebreak.org/webhook - Listener:
scripts/deploy-webhook/server.mjs(Port 9000, reverse-proxied via Nginx) - Deploy-Script:
scripts/deploy.sh - Funktion:
- Validiert HMAC-SHA256-Signatur
- Prüft, ob Branch
mainist - Startet
scripts/deploy.sh - Führt
git pull,pnpm install,prisma migrate deploy(falls nötig), Build und pm2-Restart aus
Warum zwei Pfade?
- Woodpecker ist der bevorzugte Pfad: Build auf leistungsfähigem Server, Artifact-Deploy.
- Webhook ist ein Legacy-Fallback, der z.B. Migrationen direkt auf dem Staging-Server ausführt.
- Der
.deploy-ga.lockverhindert, dass beide gleichzeitig laufen.
Signatur-Validierung
Der Listener unterstützt beide Formate:
- GitHub:
x-hub-signature-256: sha256=... - Gitea:
x-gitea-signature: ...(nur Hex, keinsha256=Präfix)
Secret: GITHUB_WEBHOOK_SECRET aus /etc/environment auf dem Staging-Server.
SSH-Key / Secrets
rebreak-deploy Key
- Privater Key:
/home/runner/.ssh/rebreak-deployauf raynis-builder - Public Key:
/home/runner/.ssh/rebreak-deploy.pub - Fingerprint:
SHA256:Wkw1O4YGEM9q++dbCd3+CjAfILtjhPasE71wG5wnH4Q - Verwendung:
- Woodpecker-Deploy-Steps verwenden ihn für
scp/sshzum Staging-Server - Staging-Server pullt damit Code von Gitea (Deploy-Key im Repo)
- Woodpecker-Deploy-Steps verwenden ihn für
- Auf Staging-Server authorisiert: Ja, in
~root/.ssh/authorized_keys
Gitea-OAuth-App für Woodpecker
- Name:
Woodpecker CI - Type:
confidential_client - Redirect URI:
https://ci.rebreak.org/authorize - Scopes: Standard (keine expliziten nötig)
Woodpecker Secrets
staging_deploy_keywurde ursprünglich in Woodpecker hinterlegt, wird aber nicht mehr verwendet.- Der Key wird stattdessen über
WOODPECKER_BACKEND_DOCKER_VOLUMESgemountet.
Wichtige Dateien
| Datei | Zweck |
|---|---|
.woodpecker.yml |
Pipeline-Definition für Woodpecker |
scripts/deploy-webhook/server.mjs |
Webhook-Listener auf Staging-Server |
scripts/deploy.sh |
Legacy Deploy-Script (inkl. Prisma-Migrationen) |
scripts/deploy-from-artifact.sh |
Artifact-Deploy für Backend |
scripts/deploy-admin-from-artifact.sh |
Artifact-Deploy für Admin |
ecosystem.config.js |
pm2-Konfiguration auf Staging-Server |
/mnt/HC_Volume_103985481/gitea/docker-compose.yml |
Gitea + Woodpecker Stack |
Prisma-Migrationen
Migrationen werden an zwei Stellen geprüft:
-
In
scripts/deploy-from-artifact.sh(Woodpecker-Deploy):- Vergleicht
.last-deployed-shamit HEAD - Führt
prisma migrate deployaus, wenn sich etwas unterbackend/prisma/migrations/oderbackend/prisma/schema.prismageändert hat
- Vergleicht
-
In
scripts/deploy.sh(Webhook-Deploy):- Gleicher Mechanismus
- Wird als Fallback ausgeführt
⚠️ Wichtig: Migrationen werden nur ausgeführt, wenn sich die Prisma-Dateien seit dem letzten Deploy geändert haben. Bei manuellem Eingriff muss ggf.
.last-deployed-shagelöscht werden.
Betrieb: Pipelines ansehen
Woodpecker-Logs
ssh root@128.140.47.53
docker logs woodpecker-server --tail 100
docker logs woodpecker-agent --tail 100
Pipeline-Status in DB
sqlite3 /mnt/HC_Volume_103985481/gitea/woodpecker-server/woodpecker.sqlite \
"SELECT id, repo_id, number, status, message FROM pipelines WHERE repo_id=2 ORDER BY id DESC LIMIT 5;"
Webhook-Logs auf Staging
ssh root@staging.rebreak.org
pm2 logs rebreak-webhook --lines 50
Troubleshooting
Pipeline-Clone schlägt fehl mit Submodule-Fehler
Ursache: Verwaister Submodule-Eintrag ohne .gitmodules.
Lösung:
git rm --cached <pfad-zum-submodule>
echo '<pfad-zum-submodule>/' >> .gitignore
git commit -m "Remove broken submodule entry"
Deploy-Step: "Load key ... error in libcrypto"
Ursache: Alpine-OpenSSH konnte das Key-Format nicht lesen.
Lösung: image: alpine:3.21 statt image: alpine verwenden.
"Permission denied (publickey)" beim Deploy
Ursache: SSH-Key nicht im Container verfügbar oder nicht auf Staging authorisiert. Prüfung:
# Ist der Key auf Staging authorisiert?
ssh -i /home/runner/.ssh/rebreak-deploy root@staging.rebreak.org 'echo OK'
# Ist WOODPECKER_BACKEND_DOCKER_VOLUMES gesetzt?
docker inspect woodpecker-agent --format '{{json .Config.Env}}'
Webhook gibt 401
Ursache: Signatur stimmt nicht. Prüfung:
- Ist
GITHUB_WEBHOOK_SECRETin/etc/environmentauf Staging gesetzt? - Stimmt das Secret im Gitea-Webhook?
- Verwendet Gitea den richtigen Signatur-Header? Der Listener akzeptiert sowohl
x-hub-signature-256(GitHub) als auchx-gitea-signature(Gitea).
Woodpecker zeigt keine Repos
Ursache: OAuth-App falsch konfiguriert oder User nicht angemeldet. Lösung:
- Auf https://ci.rebreak.org ausloggen und wieder einloggen
- In Gitea unter Settings → Applications prüfen, ob die OAuth-App existiert
- OAuth-App muss
confidential_client: truesein
Zwei Deploys kollidieren
Der .deploy-ga.lock verhindert parallele Deploys. Wenn ein Deploy hängt:
ssh root@staging.rebreak.org
rm -f /srv/rebreak/.deploy-ga.lock
Migration von GitHub
Was wurde gemacht?
- Repo auf Gitea erstellt
- Code von GitHub nach Gitea gepusht
- Woodpecker mit Gitea verbunden
.woodpecker.ymlerstellt- Staging-Server remote auf Gitea umgestellt
- Gitea-Webhook für Legacy-Deploy eingerichtet
- Webhook-Listener für Gitea-Signaturen angepasst
Was noch zu tun ist?
- Lokale Entwickler-Repos sollten den
origin-Remote auf Gitea umstellen:
git remote set-url origin https://git.rebreak.org/chahine/rebreak-monorepo.git
# oder SSH:
git remote set-url origin ssh://git@git.rebreak.org:2222/chahine/rebreak-monorepo.git
- Falls GitHub komplett abgeschaltet werden soll, den GitHub-Remote entfernen und keinen
git push origin mainmehr ausführen. - Der
staging_deploy_key-Secret in Woodpecker kann gelöscht werden (wird nicht mehr verwendet).
Kontakte / Verantwortlichkeiten
- Infrastruktur:
raynis-builder(Hetzner VPS) - Gitea / Woodpecker Admin:
chahine - Staging-Server:
staging.rebreak.org
Letzte Aktualisierung: 2026-06-18