Les environnements air-gapped qui adoptent la culture DevOps se heurtent rapidement à une contrainte fondamentale : comment construire des images Docker sans accès aux registries externes ? La question semble simple. En pratique, elle ne l'est pas.
Un secteur comme la défense, l'industrie critique ou le datacenter totalement isolé a besoin d'outillage. Ces équipes montent en charge sur Docker, les pipelines CI/CD, les pratiques GitOps... mais sur un réseau qui ne touche pas l'internet. Le premier verrou : les images de base.
La réalité des environnements isolés
Un environnement 100% air-gapped ne fait pas de compromis. Pas de proxy sortant, pas d'outillage allégé avec une allowlist DNS, pas de miroir synchronisé automatiquement. Les postes et serveurs opèrent sur un LAN fermé, avec comme seul canal physique vers l'extérieur : la clé USB.
Monter en puissance sur Docker implique de disposer localement de toutes les images nécessaires aux builds : images de base (debian:12, python:3.11-slim, node:20-alpine), images intermédiaires dans les builds multi-stage, images spécifiques aux outils (linters, scanners, runtimes). À chaque docker build, il faut que tout soit présent. Sinon, le build échoue.
Hauler : la référence et ses limites
L'outil le plus connu dans cet espace est Hauler (SUSE/Rancher). L'approche : déclarer une liste d'images, exporter un snapshot complet, le transporter hors ligne, l'importer.
Ça fonctionne. Mais le modèle bulk a des limites dans un contexte d'usage intensif :
- Aucune résolution de Dockerfile : il faut identifier manuellement quelles images sont nécessaires pour chaque build
- Pas de delta : chaque synchronisation exporte les images en entier, même si 90 % des layers sont déjà présents dans le store local
- Pas de chiffrement du canal de transport : la clé USB voyage sans protection cryptographique
- Pas de piste d'audit : aucun log structuré des opérations de synchronisation
Pour un usage ponctuel, ces limites sont gérables. Pour une équipe qui build des dizaines d'images avec des cycles de mise à jour réguliers, le modèle devient pesant.
Buncker : synchronisation chirurgicale
Buncker aborde le problème différemment. Plutôt que d'exporter des snapshots complets, l'outil analyse statiquement les Dockerfiles, identifie les layers manquants dans le store local, et ne transfère que le delta. L'architecture est volontairement minimale : deux composants indépendants, zéro communication réseau entre eux.
Résolution statique de Dockerfile
Le composant offline (buncker daemon) intègre un résolveur de Dockerfile : il parse les instructions FROM, résout les ARG de pré-build (valeurs par défaut et overrides via --build-arg), gère le multi-stage et le multi-arch, et normalise les références Docker Hub (nginx devient docker.io/library/nginx).
En sortie : la liste exacte des blobs manquants dans le store, avec leur taille et leur digest SHA256. Avant de générer quoi que ce soit, l'opérateur sait précisément ce qui va être transféré.
Transfer chiffré via USB
La demande de synchronisation est chiffrée en AES-256-GCM et signée HMAC-SHA256 avant d'être écrite sur la clé USB (request.json.enc). Le secret partagé est dérivé d'une phrase mnémonique BIP-39 (12 mots), communiquée une seule fois via un canal humain. Aucune PKI à gérer, aucun certificat à renouveler.
Du côté connecté, buncker-fetch déchiffre la demande, vérifie l'intégrité HMAC, télécharge uniquement les blobs listés depuis les registries publics (Docker Hub, ghcr.io, quay.io...), vérifie chaque digest SHA256, et produit une réponse chiffrée (response.tar.enc). La réponse revient sur clé USB, puis est importée et vérifiée blob par blob côté offline.
Aucun blob non demandé, aucun blob corrompu : le store reste propre ou l'import échoue avec un message explicite.
Registry OCI permanent sur le LAN isolé
Une fois importés, les blobs sont servis via un daemon HTTP permanent (systemd) qui implémente le sous-ensemble pull de l'OCI Distribution API. Les clients Docker du LAN n'ont besoin que d'une ligne dans hosts.toml : aucun changement de configuration côté build.
Un docker build sur un poste du LAN isolé se comporte exactement comme s'il tirait les images depuis docker.io. Le registre local répond aux requêtes manifest et blob avec les headers OCI requis.
Piste d'audit complète
Toutes les opérations sont journalisées en JSON Lines (append-only) : analyse de Dockerfile, génération de manifest, import, pull de blob, garbage collection, rotation de clés. Les logs ne contiennent jamais de secrets (mnémonique, clés dérivées, tokens d'authentification).
Le GC est manuel uniquement : rapport de candidats inactifs, confirmation de l'opérateur, suppression. Aucune suppression automatique.
Philosophie technique
Le choix technique reflète les contraintes du terrain. Python >=3.11 (natif sur Debian 12/Ubuntu 22.04), zéro pip en production, zéro virtualenv. La seule dépendance externe est python3-cryptography, installée via apt. Les deux composants sont packagés en .deb, avec service systemd et hardening (NoNewPrivileges, ProtectSystem=strict, PrivateTmp).
Toutes les écritures dans le store passent par une séquence atomique (fichier temporaire + vérification SHA256 + rename). Un crash ne corrompt pas le store.
Buncker est open source sur GitHub sous licence Apache 2.0.