
Cas réel : compiler la même application pour N clients × M versions, avec un paramétrage différent à chaque combinaison, sans dupliquer la pipeline et sans empiler des shell scripts. Ansible permet de modéliser ça proprement : 1 conteneur de build par combinaison client/version, exécutés en parallèle, avec les artefacts récupérés dans un volume partagé. Le contrôle de la concurrence se fait par serial: au play et forks: côté ansible.cfg.
À la fin de cette page vous saurez écrire ce playbook avec les modules 2026 (community.docker.docker_image_build et community.docker.docker_container, qui ont remplacé l’ancien docker_image: source: build plus monolithique), comment dimensionner le parallélisme sans saturer la machine de build, et l’alternative containers.podman pour un build rootless quand Docker n’est pas une option (RHEL durci, CI rootless, conformité ANSSI).
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Modéliser un build N×M (N clients × M versions) via un inventaire Ansible groupé.
- Utiliser
community.docker.docker_image_build(module 2026) plutôt que l’anciendocker_image: source: build. - Attendre la fin d’un conteneur de build avec
detach: false. - Récupérer les artefacts via un volume partagé (
/ext). - Dimensionner le parallélisme avec
serial:au play etforks:dansansible.cfg. - Alterner vers
containers.podmanpour un build rootless (RHEL durci, CI sécurisée).
Prérequis
Section intitulée « Prérequis »- Avoir installé la collection :
ansible-galaxy collection install community.docker. - Docker Engine (24+ ou Docker Desktop) ou Podman 4+ installé sur le control node.
- La lib Python
requestsdans le venv Ansible (sinondocker_containerplante avecFailed to import the required Python library). - Connaître les inventaires statiques YAML — la structure repose sur des groupes par client.
Le besoin concret
Section intitulée « Le besoin concret »Vous avez une seule application mais elle se déploie chez plusieurs clients, chaque client ayant sa version validée et ses paramètres de compilation (locale, branding, intégrations). Sans Ansible, vous faites :
- N pipelines GitLab dupliqués (un par client),
- ou un script shell qui boucle et serialise tout (lent),
- ou un fichier
docker-compose.ymlpar client (verbeux, pas dynamique).
Avec Ansible : un seul playbook, un inventaire qui décrit les combinaisons, et le parallélisme natif d’Ansible (forks: + serial:) gère le reste. Ajouter un client = ajouter 3 lignes dans l’inventaire.
Le Dockerfile paramétré
Section intitulée « Le Dockerfile paramétré »L’image de build accepte les paramètres en ARG et expose un répertoire /artefact pour déposer le résultat compilé.
FROM alpine:3.20ARG VERSION=unknownARG CLIENT=unknownENV VERSION=${VERSION} CLIENT=${CLIENT}
RUN apk add --no-cache git python3 tar \ && mkdir -p /artefact
# Compilation : exemple bidon — adapter à la vraie commande de buildCMD ["sh", "-c", "echo 'building ${CLIENT} ${VERSION}' > /artefact/info.txt \ && tar -czf /ext/${CLIENT}-${VERSION}.tar.gz -C /artefact ."]Notes importantes :
- Pas de
CMDinvariant dans le Dockerfile si vous voulez pouvoir le surcharger côté Ansible — laissezCMDpointer vers le script de compilation par défaut, et surchargez aveccommand:côtédocker_containersi besoin. /extest le point de montage où Ansible montera le volume hôte pour récupérer l’archive.- Les secrets ne vont PAS en
ARG. UnARG GIT_TOKENreste lisible dansdocker history image:tag --no-trunc. Pour un token Git, voir la section Secrets.
L’inventaire Ansible
Section intitulée « L’inventaire Ansible »[builds:children]client_aclient_b
[client_a]build-a ansible_connection=local
[client_b]build-b ansible_connection=local
[client_a:vars]client_name=alphatarget_version=1.2.0target_locale=fr_FR
[client_b:vars]client_name=betatarget_version=2.0.0target_locale=en_US1 host = 1 combinaison à builder. Pas besoin que build-a et build-b soient des vraies machines : ansible_connection=local rend tout local au control node, et delegate_to: 127.0.0.1 côté tâche garantit que le build s’exécute sur le control node, pas sur la cible théorique.
Le playbook complet
Section intitulée « Le playbook complet »---- name: Build images en parallèle hosts: builds gather_facts: false serial: 4
tasks: - name: Build de l'image avec ARG client/version community.docker.docker_image_build: name: "build-{{ client_name }}" tag: "{{ target_version }}" path: "{{ playbook_dir }}" args: VERSION: "{{ target_version }}" CLIENT: "{{ client_name }}" nocache: true delegate_to: 127.0.0.1
- name: Lancer le conteneur de compilation community.docker.docker_container: name: "build-{{ client_name }}-{{ target_version }}" image: "build-{{ client_name }}:{{ target_version }}" recreate: true state: started detach: false volumes: - "{{ playbook_dir }}/ext:/ext" env: VERSION: "{{ target_version }}" CLIENT: "{{ client_name }}" delegate_to: 127.0.0.1 register: build_result
- name: Vérifier que l'archive a bien été produite ansible.builtin.stat: path: "{{ playbook_dir }}/ext/{{ client_name }}-{{ target_version }}.tar.gz" register: archive delegate_to: 127.0.0.1 failed_when: not archive.stat.existsLancement :
mkdir -p extansible-playbook -i inventory.ini playbook.ymlAvec serial: 4 et 2 hosts, les 2 builds tournent en parallèle. Sur 50 clients × 5 versions, vous lancez 250 builds par lots de 4 — prévoir une machine de build correcte (16 GB RAM, 8 cœurs, SSD).
docker_image vs docker_image_build (module 2026)
Section intitulée « docker_image vs docker_image_build (module 2026) »L’ancien module community.docker.docker_image faisait tout (build, pull, push, tag) avec un argument source: pour basculer entre les modes. Depuis community.docker 4.0, il a été éclaté en modules dédiés, plus lisibles et plus robustes :
| Action | Module 2026 | Ancien (déprécié) |
|---|---|---|
| Construire une image | docker_image_build | docker_image: source: build |
| Pull depuis un registry | docker_image_pull | docker_image: source: pull |
| Push vers un registry | docker_image_push | docker_image: source: pull + push: true |
| Tag d’une image | docker_image_tag | docker_image: source: local + repository: |
Migrer maintenant : les anciens modules sont en bugfix-only, plus de nouveaux features. Les playbooks qui utilisent docker_image: source: build continueront de marcher mais figeront leur capacité à exploiter les nouvelles options (BuildKit secrets, multi-stage selectif, etc.).
Alternative rootless avec containers.podman
Section intitulée « Alternative rootless avec containers.podman »Si Docker n’est pas autorisé (RHEL durci, conformité ANSSI, CI sans daemon), la collection containers.podman offre les mêmes capacités en mode rootless :
ansible-galaxy collection install containers.podman- name: Build de l'image avec Podman (rootless) containers.podman.podman_image: name: "build-{{ client_name }}" tag: "{{ target_version }}" path: "{{ playbook_dir }}" build: args: VERSION: "{{ target_version }}" CLIENT: "{{ client_name }}"
- name: Lancer le conteneur de compilation containers.podman.podman_container: name: "build-{{ client_name }}-{{ target_version }}" image: "build-{{ client_name }}:{{ target_version }}" recreate: true state: started detach: false # idem Docker — attendre la fin volumes: - "{{ playbook_dir }}/ext:/ext:Z" # :Z pour SELinux env: VERSION: "{{ target_version }}" CLIENT: "{{ client_name }}"Différence clé : sur RHEL/AlmaLinux avec SELinux en enforcing, ajouter :Z au volume — sinon le conteneur reçoit Permission denied opaque. Voir Module SELinux.
Dimensionner le parallélisme
Section intitulée « Dimensionner le parallélisme »Deux leviers contrôlent la concurrence :
| Levier | Où | Effet |
|---|---|---|
forks: | ansible.cfg ou --forks N | Nombre max de hosts traités simultanément par le moteur Ansible |
serial: | au niveau du play | Nombre de hosts traités par vague dans ce play précis |
Pour le pattern build-parallèle :
# ansible.cfg[defaults]forks = 8- hosts: builds serial: 4 # 4 builds simultanés max, indépendamment de forksserial: 4 prime sur forks: 8 — c’est la limite effective. Choisir serial: selon la RAM disponible, pas selon le nombre de cœurs : un build Docker peut consommer plusieurs GB pendant la phase compilation.
Secrets au build (pas d’ARG)
Section intitulée « Secrets au build (pas d’ARG) »Si la compilation a besoin d’un token Git ou d’une clé d’API, ne pas le passer en ARG Dockerfile — il finit dans les couches de l’image et dans docker history.
BuildKit secrets (méthode propre) :
FROM alpine:3.20RUN --mount=type=secret,id=git_token \ GIT_TOKEN=$(cat /run/secrets/git_token) \ && git clone https://oauth2:${GIT_TOKEN}@gitlab.com/org/repo.git /srcCôté Ansible, le module docker_image_build ne supporte pas encore directement --secret BuildKit ; en attendant, deux options pragmatiques :
- Volume tmpfs monté pour le build, contenant le token, démonté immédiatement après.
- Conteneur intermédiaire qui fait le clone, puis
COPY --from=clone /src /srcdans l’image finale (le token reste dans le conteneur intermédiaire jeté).
À éviter absolument : args: { GIT_TOKEN: "{{ secret_token }}" }, même si le secret vient d’un Vault Ansible — la valeur finit en clair dans docker history.
Pièges courants
Section intitulée « Pièges courants »| Symptôme | Cause | Fix |
|---|---|---|
Tâches passent mais aucun artefact dans /ext | detach: true — Ansible n’a pas attendu | detach: false mandatory |
Failed to import the required Python library (requests) | venv Ansible sans requests/docker | pipx inject ansible requests docker |
| Conteneur précédent bloque le suivant | Pas de recreate: true | recreate: true ou nom unique par run |
| OOM killer pendant un build parallèle | serial: trop haut pour la RAM dispo | Réduire serial:, surveiller htop |
Permission denied sur le volume /ext | SELinux + Podman | :Z sur le volume |
Secret en clair dans docker history | args: { TOKEN: ... } | BuildKit secrets ou conteneur intermédiaire |
À retenir
Section intitulée « À retenir »community.docker.docker_image_build+community.docker.docker_container: modules 2026, ne pas utiliser le legacydocker_image: source: build.detach: falseest la clé du pattern — Ansible attend la fin du conteneur.recreate: true+ nom unique par client/version = pas de pollution entre runs.- Inventaire groupé par client + variables → 1 host = 1 combinaison à builder.
serial:au play dimensionne le parallélisme RAM-conscient ;forks:côté config est le plafond global.- Alternative rootless :
containers.podmanavec:Zsur les volumes en SELinux enforcing. - Secrets : jamais en
ARG. BuildKit secrets ou conteneur intermédiaire.