Récap complet de l'infra CI sonic/openus : Gitea, Woodpecker server/agent, Dependency-Track, tips Woodpecker next, checklist nouvelle app. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
7.2 KiB
Woodpecker next — Bugs connus et règles de la stack
Tous les problèmes rencontrés en production et leurs fixes.
Source : docs-sonic/retrospective-problemes-2026-03-17.md
Règles à appliquer systématiquement
| Règle | Raison |
|---|---|
Jamais ${VAR} dans commands: — toujours $VAR |
Woodpecker next substitue ${VAR} au parse YAML, avant exécution. Vaut pour les secrets, vars CI, et vars shell locales. |
from_secret et volumes: dans des steps séparés |
Bug Woodpecker next : les secrets sont vides si le step a aussi des volumes:. |
Chemins absolus après un cd |
Un cd dans commands: persiste pour toutes les lignes suivantes du même step. |
ACME_EXIT=0; cmd || ACME_EXIT=$? pour capturer les codes non-zéro |
set -e est actif dans les blocs - |. cmd; VAR=$? tue le script si cmd retourne != 0. |
1. ${VAR} substitué au parse YAML
Symptôme : valeurs vides dans les commandes, ou docker exec "-db" (nom coupé).
# ❌
echo "${PS_DOMAIN}"
docker exec "${PROJECT}-db"
# ✅
env | grep '^PS_DOMAIN=' | cut -d= -f2
docker exec "$PROJECT-db"
2. from_secret + volumes: incompatibles dans le même step
Symptôme : secrets vides (DTRACK_TOKEN vide, etc.) quand le step a aussi un volumes:.
Fix : séparer en deux steps :
- step avec
volumes:: lit depuis des fichiers copiés dans le workspace ou dans un chemin monté - step avec
from_secret: pas devolumes:
Exemple de découpage :
# ✅ Step secrets uniquement (pas de volumes)
- name: write-env
environment:
MON_SECRET:
from_secret: mon_secret
commands:
- echo "MON_SECRET=$MON_SECRET" > .env.deploy
# ✅ Step deploy avec volumes (lit depuis .env.deploy)
- name: deploy
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /opt/monapp:/opt/monapp
commands:
- cp .env.deploy /opt/monapp/.env
3. set -e et capture de codes de retour non-zéro
Symptôme : pipeline échoue sur une commande qui retourne un code non-zéro attendu (ex : acme.sh exit 2 = cert déjà valide).
# ❌ fragile : set -e tue le script si exit != 0
cmd ; EXIT=$?
# ✅ correct
EXIT=0
cmd || EXIT=$?
if [ "$EXIT" -ne 0 ] && [ "$EXIT" -ne 2 ]; then # 2 = cas acceptable
exit 1
fi
4. YAML cassé par variables shell multi-lignes
Symptôme : yaml: line XX: could not find expected ':'.
Cause : une variable assignée sur plusieurs lignes dans un - | est interprétée comme YAML.
# ❌ casse le YAML
ROUTES="route add ... \
route add ..."
# ✅
ROUTES=$(printf 'route add %s %s/* http://%s:80/\nroute add %s %s:443/* http://%s:80/' \
"$SERVICE" "$DOMAIN" "$IP" "$SERVICE" "$DOMAIN" "$IP")
5. CWD modifié par cd dans les commandes précédentes
Symptôme : cat: can't open '.env.deploy': No such file or directory alors que le fichier existe.
Cause : un cd /opt/monapp dans une ligne précédente change le CWD pour tout le step.
# ❌
commands:
- cd /opt/monapp && docker compose up -d
- PASS=$(grep '^DB_PASSWORD=' .env.deploy | cut -d= -f2) # cherche dans /opt/monapp/
# ✅ copier avant le cd, puis chemin absolu
commands:
- cp .env.deploy /opt/monapp/.env
- cd /opt/monapp && docker compose up -d
- PASS=$(grep '^DB_PASSWORD=' /opt/monapp/.env | cut -d= -f2)
6. SERVICE_80_CHECK_TCP : valeur "true" obligatoire
Registrator ignore les valeurs vides. Avec PS_SSL_ENABLED_EVERYWHERE=1,
le service redirige tout en 302 → un check HTTP voit "failing". Utiliser TCP.
# ❌ ignoré par Registrator
- SERVICE_80_CHECK_TCP=
# ✅
- SERVICE_80_CHECK_TCP=true
7. Routes Fabio : toujours /*
Fabio utilise le glob matcher. / matche uniquement le chemin exact.
Sans /*, tous les sous-chemins tombent sur le catch-all nginx → 404.
# ❌ ne route que /
route add monservice mondomaine.fr/ http://172.22.0.x:80/
# ✅ route tout
route add monservice mondomaine.fr/* http://172.22.0.x:80/
route add monservice mondomaine.fr:443/* http://172.22.0.x:80/
8. Consul catalog seul insuffisant pour Fabio
Testé en production : Fabio ne détecte pas les services via le catalog Consul même
quand ils sont healthy avec les bons tags urlprefix-*.
Le KV fabio/config/<projet> est obligatoire. Mettre à jour l'IP à chaque déploiement :
# Dans le step deploy, après docker compose up :
IP=$(docker inspect "$PROJECT-app" \
--format '{{(index .NetworkSettings.Networks "sonic").IPAddress}}')
ROUTES=$(printf 'route add %s %s/* http://%s:80/\nroute add %s %s:443/* http://%s:80/' \
"$PROJECT" "$DOMAIN" "$IP" "$PROJECT" "$DOMAIN" "$IP")
docker exec sonic-consul env CONSUL_HTTP_TOKEN="$CTOK" consul kv put "fabio/config/$PROJECT" "$ROUTES"
Utiliser une sous-clé fabio/config/$PROJECT (pas fabio/config) pour ne pas écraser
les routes des autres projets.
9. acme.sh : toujours --home /etc/acme.sh
Sans ce flag, acme.sh écrit dans /root/.acme.sh dans le container → non persistant.
# ✅ pattern complet (idempotent)
ACME_EXIT=0
docker exec sonic-acme-1 /app/acme.sh \
--home /etc/acme.sh \
--issue -d "$DOMAIN" \
--webroot /usr/share/nginx/html \
--server letsencrypt \
--accountemail support+acme@asycn.io || ACME_EXIT=$?
[ "$ACME_EXIT" -ne 0 ] && [ "$ACME_EXIT" -ne 2 ] && exit 1
docker exec sonic-acme-1 cp /etc/acme.sh/$DOMAIN/fullchain.cer /host/certs/$DOMAIN-cert.pem
docker exec sonic-acme-1 cp /etc/acme.sh/$DOMAIN/$DOMAIN.key /host/certs/$DOMAIN-key.pem
acme-companion (LETSENCRYPT_HOST) ne couvre que les containers sur le réseau nginx-proxy.
Pour les services Fabio, acme.sh via docker exec est la seule méthode qui fonctionne.
10. Instances Consul stale après renommage de container
Changer COMPOSE_PROJECT_NAME → nouveaux containers, nouvelle IP, mais les anciennes
registrations Consul restent actives → Fabio peut router vers des IPs mortes.
Fix :
docker stop <ancien-container> && docker rm <ancien-container>- Registrator désenregistre automatiquement à l'arrêt
- Supprimer les entrées manuelles orphelines si besoin :
curl -X PUT -H "X-Consul-Token: $CTOK" \ http://localhost:8500/v1/agent/service/deregister/<service-id>
11. Syft : image distroless incompatible avec Woodpecker
anchore/syft:latest est distroless → exec: "/bin/sh": no such file or directory.
# ❌
image: anchore/syft:latest
# ✅
image: alpine:3.20
commands:
- apk add --no-cache curl
- curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin latest
- syft mon-image:tag -o cyclonedx-json --file .reports/sbom.cyclonedx.json
12. Dependency-Track : token sans permissions → HTTP 403
Le token de l'équipe Automation doit avoir les permissions :
BOM_UPLOADPROJECT_CREATION
Administration > Access Management > Teams > Automation > Permissions.
Pour debugger une réponse HTTP :
# ❌ -f masque le body de l'erreur
curl -sf -X POST ...
# ✅ capture HTTP code + body
HTTP=$(curl -s -o /tmp/resp.txt -w "%{http_code}" -X POST ...)
echo "HTTP $HTTP : $(cat /tmp/resp.txt)"
[ "$HTTP" -ge 200 ] && [ "$HTTP" -lt 300 ] || exit 1