guest@6admin.io:~$

Une image Ansible pour GitLab CI, construite propre depuis un sous-sol parisien

Chaque runner CI qui fait tourner Ansible a le même problème: le job a besoin d’ansible-core, des collections pour vos plateformes, git, ssh et il lui faut tout ça maintenant, sur une machine qui sera jetée après le job. Les images officielles existent, mais elles dérivent: latest change sous vous, la version que vous avez pinée se fait rebaser et installer huit collections Galaxy par job brûle des minutes à chaque pipeline.

Alors je me suis écrit la mienne, alpine/ansible, une image toolkit Ansible pour mes runners GitLab. Et elle n’est pas née hier: je l’ai montée pour moi vers 2016 et en 2018 elle a remplacé, chez un employeur parisien, une CI qui réinstallait bêtement Ansible à chaque run. En 2022 je la rends enfin publique et j’en profite pour relire son histoire, péchés de jeunesse compris.

La seule décision réussie dès le jour un: le stage venv

Le Dockerfile s’ouvre sur le motif qui a survécu à toutes les réécritures:

FROM alpine:3.15.0 as builder
RUN apk add --virtual .build-deps gcc musl-dev libffi-dev ...
RUN python3 -m venv /opt/venv && \
    pip install --no-compile ansible-core==$VERSION PyMySQL docker pyvmomi ...
FROM alpine:3.15.0 as default
COPY --from=builder /opt/venv /opt/venv

Le builder compile ce qui a besoin de compilation (cffi, lxml et copains); le runtime ne copie que le venv fini. Pas de gcc dans l’image livrée, pas de headers de build, une surface d’attaque plus petite. Des années plus tard, cette forme de stage est toujours le squelette: seuls les pins sont devenus plus stricts.

ENTRYPOINT ["ansible"] et CMD ["--version"] pour qu’un job CI soit une ligne image: et que docker run sans argument vous dise ce qu’il embarque.

Les péchés livrés quand même

Relire ce premier Dockerfile aujourd’hui, c’est de l’archéologie avec grimaces:

  • Collections installées non pinées, un ansible-galaxy collection install par ligne. Chaque build tirait community.general au HEAD de ce matin-là. Deux builds du même commit pouvaient produire des images différentes. Je le savais; j’ai laissé glisser parce que “ça marchait”.
  • Image de base pinée par tag, alpine:3.15.0, pas par digest. Un tag est une promesse que quelqu’un peut briser.
  • Instruction MAINTAINER (dépréciée des années avant que je l’écrive), un label org.opencontainers.image.licenses qui dit GPL3, ce qui n’est pas un identifiant de licence.
  • Le chemin du registry dans les labels dit 6admin/docker/... alors que le vrai registry est 6admin.io/.... Cette typo a survécu jusqu’à ce que quelqu’un la grep enfin en 2026.
  • set xe au lieu de set -xe, silencieusement: la ligne RUN fonctionnait quand même parce que rien ne criait.
  • Deux images séparées, une pour ansible, une pour ansible-lint, parce que je n’arrivais pas à décider ce qui appartenait à “l’image”. Chaque pipeline qui avait besoin des deux tirait les deux.

Ce dont la CI a vraiment besoin

La partie intéressante d’une image dédiée CI, c’est que ce n’est pas un desktop: les runs sont éphémères, les hôtes changent d’identité en permanence et personne ne regarde une barre de progression. C’est ce qui a dicté les défauts arrivés avec le temps: HOST_KEY_CHECKING=False pour des runners neufs sans known_hosts, SSH_PIPELINING=True parce que les jobs CI sont petits et fréquents, retry files off, gathering smart. Une poignée de variables d’environnement ANSIBLE_* cuites dans l’image pour qu’aucun YAML de pipeline ne les répète jamais.

La leçon ennuyeuse, apprise tôt pour une fois

Pin l’interpréteur, pin les collections, pin la base. Cette image n’en pine exactement qu’une sur trois et je le sais déjà en l’écrivant. Finir l’échelle, chaque couche à un digest ou une version exacte avec un lock file pour le prouver, c’est le chantier que je me promets et que je ne tiendrai pas avant un bon moment.

Cette image livre une fraction de la taille des officielles, se reconstruit en quelques minutes et fait tourner mes pipelines en prod depuis 2018 pendant que je remarque lentement qu’elle me ment sur la reproductibilité. Ce qui, je soupçonne, est la façon dont tout projet infra grandit vraiment: ça marche d’abord, ça devient honnête plus tard.

[edit: 2026] Cette image a aujourd’hui dix ans et tourne en prod depuis au moins huit ans. La CVE qui a transformé le pinning en discipline, la fusion des deux images et la chaîne de confiance signée, je les ai racontées dans l’épisode deux, l’Ansible CI Toolkit v1 .