Files
gilles 0fbca06d3d docs: roadmap tâches 1.9-8 (briefs, gates de validation, designs tâche 2) + plans d'implémentation
Cartographie complète (liste_taches/coherence_taches), briefs tacheN + gates
validation_tacheN, design tâche 2 (docs/design/tache2/), specs/plans jalon 1-2
et tâche 1.9/2 (Phase 1, Phase 2, SJ-0→3). Validations consignées (1.9 , 2-8 🟡).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 19:50:25 +02:00

12 KiB

20 — Docker Compose : inventaire, flux et pseudo-shell

Axe D + livrables §4.1/§4.2. Conçu pour cocher chaque case de validation_tache2.md §6 (« Focus Docker Compose »). Gestion par SSH sur la machine cible via server/ssh/client.ts et templates versionnés ; pas de moteur parallèle.


1. Méthode retenue (MVP)

  • Gestion par SSH sur la machine cible, réutilisant runScriptSudo / runPlain et la table executions, le WebSocket terminal, rawLogPath, reportPath, statut ok|warning|error. Pas de second système de jobs Docker.
  • Variante docker context over SSH : citée comme alternative opérateur, pas le moteur MVP.
  • Découverte des stacks depuis des racines déclarées par machine (composeRoots, ex. /opt/stacks, /srv/docker), scan limité en profondeur (composeScanDepth, défaut 4), puis validation UI.
  • Détection par labels Compose (com.docker.compose.project, com.docker.compose.service, et si présent com.docker.compose.project.working_dir) = complément pour retrouver les stacks actifs, pas l'unique source de vérité.
  • Cycle de vie d'un stack : candidate (juste détecté) → enabled (validé par l'utilisateur) → actions autorisées. pull, up, down, prune uniquement sur un stack enabled/validé.

Sources citées : docker compose pull https://docs.docker.com/reference/cli/docker/compose/pull/ · up https://docs.docker.com/reference/cli/docker/compose/up/ · config https://docs.docker.com/reference/cli/docker/compose/config/ · ps https://docs.docker.com/reference/cli/docker/compose/ps/ · images https://docs.docker.com/reference/cli/docker/compose/images/ · down https://docs.docker.com/reference/cli/docker/compose/down/ · image inspect https://docs.docker.com/reference/cli/docker/image/inspect/ · image prune https://docs.docker.com/reference/cli/docker/image/prune/


2. Inventaire des templates Docker

Template Action (ActionType) Rôle Effet disque Destructif ? Validation UI
docker/scan-compose.sh.tpl docker_scan Scanne les racines déclarées, trouve les fichiers compose, valide chaque candidat. Passif. non non non
docker/inspect-compose.sh.tpl docker_inspect_current État actuel sans changement : config images, ps, images, inspect. Passif. non non non
docker/pull-check.sh.tpl docker_pull_check docker compose pull (télécharge sans démarrer), compare ID/digest/labels avant-après. Écrit sur le disque Docker (pas un scan pur). oui (cache images) non (pas applicatif) non (mais non passif)
docker/apply-compose.sh.tpl docker_compose_apply docker compose up -d --remove-orphans après validation. Recapture ps/images/inspect. oui oui (recrée conteneurs) oui, explicite
docker/prune-images.sh.tpl docker_prune_images docker image prune -f (safe) ; mode agressif -a -f --filter "until=168h". oui oui (agressif) oui pour agressif
docker/down-compose.sh.tpl docker_compose_down docker compose down. Action séparée et destructive, hors chemin de mise à jour normal. oui oui oui, forte

--volumes et --rmi sur down : interdits au MVP (ou protégés par une validation forte distincte). Le chemin de mise à jour normal n'utilise jamais down : up -d recrée les conteneurs quand l'image ou la config change, en préservant les volumes montés.

Tous les templates : LC_ALL=C, marqueurs ===SU:DOCKER_*===, sortie parsable, log brut archivé.


3. Flux de mise à jour Docker (formalisé)

  1. docker_scan — découverte des stacks candidats (racines déclarées + labels actifs).
  2. docker_inspect_current — état actuel des services, conteneurs, images.
  3. docker_pull_check — téléchargement des images candidates sans démarrage de conteneurs.
  4. Comparaison déterministe — image ref, image ID, repo digest, labels OCI (org.opencontainers.image.version, revision, source, created) si présents.
  5. Proposition UI/Hermes — liste des stacks/services avec update dispo, erreurs de pull, inconnues.
  6. docker_compose_apply après validation utilisateur — docker compose up -d --remove-orphans.
  7. Vérification après application — conteneurs recréés, état running/exited, health si dispo, erreurs.
  8. docker_prune_images après succès ou action séparée — images supprimées, espace récupéré, erreurs.

Points clés validés :

  • docker compose pull télécharge mais ne démarre pas les conteneurs → bon pré-check applicatif, pas un scan sans effet.
  • docker compose up -d recrée les conteneurs quand l'image/config a changé, en préservant les volumesdown inutile pour une MAJ normale.
  • docker image prune -f = images dangling (sûr) ; docker image prune -a = toutes les images non référencées par un conteneur (destructif).

4. Pseudo-shell des templates

4.1 docker/scan-compose.sh.tpl

#!/bin/sh
export LC_ALL=C
echo "===SU:DOCKER_SCAN==="
# {{composeRoots}} rendu en liste shell-safe par le backend (une racine par ligne).
ROOTS="{{composeRoots}}"
DEPTH="{{composeScanDepth}}"
for root in $ROOTS; do
  [ -d "$root" ] || continue
  find "$root" -maxdepth "$DEPTH" -type f \
    \( -name 'compose.yaml' -o -name 'compose.yml' \
       -o -name 'docker-compose.yaml' -o -name 'docker-compose.yml' \) \
    -not -path '*/.git/*' -not -path '*/node_modules/*' \
    -not -path '*/backup/*' -not -path '*/old/*' -not -path '*/archive/*' \
    2>/dev/null | while IFS= read -r f; do
      dir=$(dirname "$f")
      # Valide le candidat ; n'applique rien.
      if docker compose -f "$f" config --quiet >/dev/null 2>&1; then
        echo "STACK_OK\tdir=$dir\tfile=$f"
      else
        echo "STACK_INVALID\tdir=$dir\tfile=$f"
      fi
  done
done
echo "===SU:DOCKER_LABELS==="
# Complément : stacks actifs détectés par labels.
docker ps --format '{{ "{{.ID}}" }}' 2>/dev/null | while read -r id; do
  proj=$(docker inspect --format '{{ "{{index .Config.Labels \"com.docker.compose.project\"}}" }}' "$id" 2>/dev/null)
  wd=$(docker inspect --format '{{ "{{index .Config.Labels \"com.docker.compose.project.working_dir\"}}" }}' "$id" 2>/dev/null)
  [ -n "$proj" ] && echo "ACTIVE\tproject=$proj\tworking_dir=$wd"
done
echo "===SU:EXIT=0==="

Note de rendu : les {{ }} Docker Go-template sont échappés ici pour ne pas être interprétés par Mustache (le moteur de rendu réel utilisera des délimiteurs Mustache personnalisés ou un échappement, à fixer en implémentation). Seules composeRoots/composeScanDepth sont des variables Mustache.

4.2 docker/inspect-compose.sh.tpl

#!/bin/sh
export LC_ALL=C
cd "{{stackDir}}" || { echo "===SU:DOCKER_ERR===\ncompose_not_found"; echo "===SU:EXIT=2==="; exit 2; }
echo "===SU:DOCKER_CONFIG_IMAGES==="
docker compose config --images 2>&1
echo "===SU:DOCKER_PS==="
docker compose ps --format json 2>&1
echo "===SU:DOCKER_IMAGES==="
docker compose images --format json 2>&1
echo "===SU:DOCKER_INSPECT==="
# Pour chaque image utilisée : Id, RepoDigests, labels OCI.
docker compose config --images 2>/dev/null | while IFS= read -r img; do
  docker image inspect "$img" \
    --format '{{ "IMG\t{{.Id}}\t{{join .RepoDigests \",\"}}\t{{index .Config.Labels \"org.opencontainers.image.version\"}}\t{{index .Config.Labels \"org.opencontainers.image.source\"}}" }}' 2>/dev/null \
    || echo "IMG_MISSING\t$img"
done
echo "===SU:EXIT=0==="

4.3 docker/pull-check.sh.tpl

#!/bin/sh
export LC_ALL=C
cd "{{stackDir}}" || { echo "compose_not_found"; echo "===SU:EXIT=2==="; exit 2; }
echo "===SU:DOCKER_INSPECT_BEFORE==="
docker compose config --images 2>/dev/null | while IFS= read -r img; do
  id=$(docker image inspect "$img" --format '{{ "{{.Id}}" }}' 2>/dev/null || echo "")
  dg=$(docker image inspect "$img" --format '{{ "{{join .RepoDigests \",\"}}" }}' 2>/dev/null || echo "")
  echo "BEFORE\t$img\t$id\t$dg"
done
echo "===SU:DOCKER_PULL==="
# Télécharge les images candidates SANS démarrer de conteneurs.
docker compose pull --policy always --ignore-buildable 2>&1
CODE=$?
echo "===SU:DOCKER_INSPECT_AFTER==="
docker compose config --images 2>/dev/null | while IFS= read -r img; do
  id=$(docker image inspect "$img" --format '{{ "{{.Id}}" }}' 2>/dev/null || echo "")
  dg=$(docker image inspect "$img" --format '{{ "{{join .RepoDigests \",\"}}" }}' 2>/dev/null || echo "")
  ver=$(docker image inspect "$img" --format '{{ "{{index .Config.Labels \"org.opencontainers.image.version\"}}" }}' 2>/dev/null || echo "")
  echo "AFTER\t$img\t$id\t$dg\t$ver"
done
echo "===SU:EXIT=${CODE}==="

Backend : compare BEFORE/AFTER par image ref. Si id/digest change → updates_available. Erreurs pull_failed/registry_auth_failed nettoyées (jamais d'URL/token sensible vers UI/MCP).

4.4 docker/apply-compose.sh.tpl

#!/bin/sh
export LC_ALL=C
cd "{{stackDir}}" || { echo "compose_not_found"; echo "===SU:EXIT=2==="; exit 2; }
echo "===SU:DOCKER_APPLY==="
docker compose up -d --remove-orphans 2>&1
CODE=$?
echo "===SU:DOCKER_PS_AFTER==="
docker compose ps --format json 2>&1
echo "===SU:DOCKER_INSPECT_AFTER==="
docker compose config --images 2>/dev/null | while IFS= read -r img; do
  docker image inspect "$img" --format '{{ "IMG\t{{.Id}}\t{{join .RepoDigests \",\"}}" }}' 2>/dev/null || echo "IMG_MISSING\t$img"
done
echo "===SU:EXIT=${CODE}==="

4.5 docker/prune-images.sh.tpl

#!/bin/sh
export LC_ALL=C
echo "===SU:DOCKER_PRUNE==="
{{#aggressive}}
# Mode agressif : nécessite validation UI explicite distincte.
docker image prune -a -f --filter "until=168h" 2>&1
{{/aggressive}}
{{^aggressive}}
# Mode sûr par défaut : dangling images uniquement.
docker image prune -f 2>&1
{{/aggressive}}
CODE=$?
echo "===SU:EXIT=${CODE}==="

Le backend parse Total reclaimed space et deleted pour bytesReclaimed et la liste d'images supprimées.

4.6 docker/down-compose.sh.tpl

#!/bin/sh
export LC_ALL=C
cd "{{stackDir}}" || { echo "compose_not_found"; echo "===SU:EXIT=2==="; exit 2; }
echo "===SU:DOCKER_DOWN==="
# --volumes et --rmi INTERDITS au MVP. down simple uniquement.
docker compose down 2>&1
CODE=$?
echo "===SU:EXIT=${CODE}==="

5. Réduction Hermes (Docker)

Seules ces lignes (+ le JSON canonique) sont transmises : Pulling, Digest, Status, Downloaded newer image, Recreating, Started, Error, deleted, Total reclaimed space. Le log brut complet reste archivé (raw_artifacts / rawLogPath).


6. Insertion dans la webapp existante

  • Config machine : nouveaux champs dockerEnabled, composeRoots[], composeScanDepth, composeStacks[]optionnels ou dans un endpoint dédié ; MachineView n'est pas cassé (les champs sont ajoutés en option). Stockés dans docker_settings / docker_compose_roots / docker_compose_stacks (tache1.9.md).
  • Refresh/snapshot : le refresh machine peut produire un snapshot combiné apt + docker, ou un refresh Docker séparé pour éviter de lancer docker_pull_check automatiquement (recommandé : pull-check séparé car non-passif).
  • Actions : extension progressive de ActionType (docker_scan, docker_pull_check, docker_compose_apply, docker_prune_images, docker_compose_down) avec filtrage d'autorisation conservé sur POST /api/machines/:id/actions.
  • Executions/rapports : réutilisation de la table executions, du WebSocket terminal, de rawLogPath/reportPath/statut. Pas de second système.
  • UI machine : compteur Docker séparé du compteur APT (ex. stacks avec updates) ; vue détail par stack/service ; boutons d'action validés (Pull/check, Appliquer, Prune, Down).
  • Validation utilisateur : docker_compose_apply, docker_prune_images agressif et docker_compose_down passent par une confirmation UI explicite (via action_requests). Hermes propose, ne déclenche jamais.
  • Secrets : credentials registry (~/.docker/config.json, helpers, tokens) jamais lus ni renvoyés ; erreurs nettoyées si elles exposent une URL sensible (voir 70-securite.md).