Image de développement pour l'Infrastructure as Code (IaC) du monorepo infrastructure.
Construite sur Rocky Linux 10 (UBI) et publiée sur
ghcr.io/stackopshq/devcontainer:latest.
Les versions sont alignées sur celles de .tasks.d/install/Taskfile.yml et du
groupe ansible de pyproject.toml du repo stackopshq/infrastructure
(GitHub).
Les outils Kubernetes (kubectl, helm, kustomize, flux) ne sont
volontairement pas embarqués : aucun playbook ni pipeline du repo consommateur
ne les utilise. Ils restent installables à la demande avec
task install:helm, task install:kubectl, etc.
| Catégorie | Outil | Version |
|---|---|---|
| Task runner | Task | v3.49.1 |
| Runtime | Node.js | v22.23.2 |
| Python | uv + Python | 0.11.3 / 3.13.9 |
| IaC | OpenTofu | v1.11.5 |
| IaC | Terraform | v1.15.6 |
| IaC | terraform-docs | v0.21.0 |
| Secrets | Vault (OpenBao-compatible) | v1.21.4 |
| Ansible | ansible-core | 2.20.7 |
| Ansible | ansible-lint | 26.4.0 |
| Ansible | molecule (+ plugins) | 26.4.0 |
Outils système : git, curl, jq, gnupg2, unzip/tar/xz, gcc,
libffi/openssl/cairo-devel, vi (via vim-minimal, il n'y a pas de
vim), bind-utils, net-tools, bash-completion.
Chaque binaire téléchargé est vérifié contre une empreinte SHA256 épinglée dans
le Containerfile, à côté de sa version. Ces empreintes proviennent des
fichiers de sommes publiés en amont pour l'artefact linux/amd64 exact.
Un artefact modifié en amont, un miroir compromis ou un téléchargement tronqué font échouer le build au lieu de produire une image altérée. Version et empreinte se mettent donc à jour ensemble : bumper l'une sans l'autre casse le build, et c'est voulu.
L'image de base est elle aussi épinglée par digest (@sha256:...), ce qui rend
le build reproductible dans le temps.
- Par défaut :
root, requis par les jobs CI GitLab qui écrivent dans l'arbre de build partagé (/builds/...). - VS Code Dev Container :
devops(UID/GID1000/1000, sudo sans mot de passe) via"containerUser"dansdevcontainer.json.
.
├── Containerfile # Build multi-stage, versions et empreintes
├── scripts/
│ ├── fetch.sh # Téléchargement + vérification SHA256 + extraction
│ ├── install-base.sh # Paquets système et utilisateur de l'image finale
│ └── finalize.sh # Exposition des outils uv, complétions, vérifications
├── devcontainer.json # Configuration VS Code Dev Containers
├── .github/workflows/ # CI GitHub Actions (build + push GHCR)
└── README.md
Le build est multi-stage. Chaque outil a son propre stage t-<outil> qui le
télécharge, vérifie son empreinte et dépose le binaire dans /out. Le stage
final ne récupère que ces binaires : les archives, les décompresseurs et les
caches de téléchargement restent en amont.
┌─ t-vault ─┐
base (digest) ───┤ t-terraform├─┐
│ └─ t-... ───┘ ├──> image finale (un COPY par outil)
├────────────── t-node ────┤
└────────────── python-tools ─┘
Trois conséquences pratiques :
- Cache granulaire au build : bumper Terraform ne reconstruit que le stage
t-terraform, pas Ansible ni Node. - Delta minimal au pull : chaque outil est copié par un
COPYdistinct, donc il occupe son propre blob. Bumper Terraform ne fait re-télécharger que ses 117 Mo aux consommateurs, au lieu de l'ensemble des binaires. Ne pas regrouper cesCOPY: la couche agrégée annulerait ce bénéfice. - Parallélisme : sous BuildKit (la CI), les stages
t-*se construisent simultanément. Podman en local les enchaîne, le résultat est identique.
Vault et Node sont les deux seuls binaires livrés non strippés en amont : leurs stages retirent les tables de symboles, ce qui économise 149 Mo sans coût au démarrage (contrairement à une compression type UPX).
La toolchain de compilation (gcc, *-devel) est volontairement conservée
dans l'image finale : le dépôt consommateur lance uv sync, qui compile des
extensions Python natives dans le conteneur.
podman build -t ghcr.io/stackopshq/devcontainer:latest -f Containerfile .Le stage final exécute scripts/finalize.sh, qui appelle chaque outil installé.
Un binaire manquant ou un environnement uv cassé fait échouer le build plutôt
que de se manifester au premier usage.
.github/workflows/build-devcontainer.yml construit l'image et la pousse sur
GHCR (ghcr.io/${{ github.repository }}).
Déclencheurs :
- Push sur
main/mastersiContainerfile,scripts/**ou le workflow changent. - Tag git
v*→ image taggée avec le tag (release versionnée). - Déclenchement manuel (
workflow_dispatch).
Tags publiés : :latest, :sha-<short>, et :<git-tag> sur les tags git.
podman pull ghcr.io/stackopshq/devcontainer:latestSi le package GHCR est privé :
podman login ghcr.ioavec un PAT GitHub disposant du scoperead:packages.
- Ajouter
<OUTIL>_VERSIONet<OUTIL>_SHA256dans le bloc d'ARGen tête duContainerfile, avec un commentaire# renovate:. L'empreinte se récupère dans le fichier de sommes publié par le projet amont. - Ajouter un stage
t-<outil>qui appellefetch <url> <sha256> <binaire>. - Ajouter un
COPY --from=t-<outil> /out/ /usr/local/bin/dans le stage final, en le plaçant selon la fréquence de mise à jour attendue (les plus volatils en dernier). - Ajouter une ligne
checkdansscripts/finalize.sh, et sa complétion bash si l'outil en fournit une. - Mettre à jour le tableau des versions ci-dessus.