Déploiements Edge sans root : Architecturer des pipelines CI/CD sans daemon avec Podman et Buildah

Quick answer
Bypassing docker.sock: Daemonless Pipeline Orchestration: webhook testing answer
For local webhook testing, run your app locally, expose it with a public HTTPS tunnel, and paste the stable callback URL into the provider dashboard.
How do I test webhooks on localhost?
Start your local server, open a public HTTPS tunnel to that port, configure the provider webhook URL, and inspect events in your local logs.
Why does a stable webhook URL matter?
Stable URLs prevent provider dashboards from needing manual callback updates every time you restart a tunnel.
Exposer un socket Docker au niveau root dans un pipeline ayant un accès direct à votre matériel physique local constitue une bombe à retardement pour la chaîne d’approvisionnement. Il est temps de supprimer complètement le daemon. Voici comment architecturer des pipelines de déploiement entièrement sans root, sans daemon, en utilisant Podman et Buildah — et ce qui a réellement changé dans l’écosystème des containers sans root jusqu’à mi-2026.
1. La vulnérabilité du socket partagé
Dans les environnements CI/CD traditionnels, la dépendance à Docker a instauré un antipattern architectural répandu : monter le socket Docker au niveau root (/var/run/docker.sock) directement dans les runners de pipeline. Souvent appelé Docker-in-Docker (DinD) ou montage de socket, ce pattern donne au processus du runner un accès administratif complet au moteur hôte. C’est pratique pour construire, taguer et pousser des images OCI, mais c’est une voie d’attaque sérieuse dans les pipelines chargés de provisionner du matériel local, des passerelles edge ou des proxies locaux.
[ Composant du runner CI/CD compromis ]
│
▼ (Exécute une charge malveillante)
[ Appel API /var/run/docker.sock ]
│
▼ (Escalade instantanée vers le root de l’hôte)
[ Matériel Edge physique / ressources du système hôte compromis ]
Lorsqu’un pipeline a un accès explicite à du matériel physique — interfaces IIoT, réseaux SCADA, systèmes médicaux ou nœuds AI en périphérie — une fuite de container n’est pas une simple erreur logicielle localisée. C’est un vecteur pour des dommages cyber-physiques. Parce que dockerd fonctionne nativement en tant que root, tout processus pouvant atteindre son socket UNIX non chiffré peut émettre des appels API arbitraires : docker run --privileged -v /:/host depuis une dépendance compromise suffit à compromettre le système hôte.
Ce n’est pas une hypothèse. CVE-2024-21626 (« Vaisseaux fuyants »), divulguée en janvier 2024, illustre précisément cette classe de faille : une fuite de descripteur de fichier interne dans runc (versions jusqu’à 1.1.11) permet à une image malveillante ou une charge runc exec de définir le répertoire de travail d’un container sur un descripteur de fichier fuitant pointant vers le système de fichiers hôte — plaçant le processus du container hors de son propre root, avec une voie documentée pour écraser des binaires host. La correction est arrivée dans runc 1.1.12, mais cela montre bien que le rayon d’impact d’une fuite d’exécution en runtime est aussi critique que sa probabilité. Un daemon tournant en root transforme toute telle faille en compromission totale de l’hôte ; un pipeline sans root correctement configuré limite cette faille à un compte de service avec peu de privilèges, sans accès aux binaires système, modules noyau ou dispositifs physiques.
Pour protéger l’infrastructure industrielle, l’architecture moderne doit s’éloigner du daemon monolithique vers des mécanismes d’isolation où la création de containers et la construction d’images s’exécutent nativement dans des espaces utilisateurs non privilégiés — c’est l’approche sur laquelle Podman et Buildah ont été conçus.
2. Analyse technique : architecture de Podman et Buildah
Architecture Docker traditionnelle :
[ CLI client ] ──(socket UNIX/TCP)──► [ Daemon monolithique (dockerd) ] ──► [ Runtime OCI (runc) ]
Architecture sans daemon :
[ CLI Podman / Buildah ] ──(fork/exec Linux direct via namespaces)──► [ Runtime OCI (crun / runc) ]
Le modèle client-serveur de Docker traduit l’entrée CLI en appels REST envoyés via un socket à dockerd, un processus root en fonctionnement continu qui gère chaque container en tant qu’enfant. Si le daemon échoue, toute orchestration de containers sur l’hôte est compromise.
Podman et Buildah suivent plutôt un modèle fork-exec proche du cycle de vie traditionnel d’un processus Linux. Le binaire est une utilité éphémère : il communique directement avec le noyau via un runtime conforme à OCI (crun ou runc), suit l’état via la bibliothèque décentralisée containers/storage, et se termine une fois la tâche achevée. Il n’y a pas de service en arrière-plan permanent ni de socket API exposée.
Composants principaux
- Podman (Gestionnaire de pods) — gère, exécute et débogue des pods et containers OCI (
run,stop,ps,exec) sans daemon. - Buildah — utilitaire spécialisé pour construire des images OCI. Podman utilise ses bibliothèques pour
podman build; Buildah en mode autonome offre des commandes de build granulaires, scriptables, couche par couche. - Skopeo — inspecte, signe et copie des images entre registres et stockage local sans télécharger ni décompresser toutes les couches.
Mécanisme des namespaces utilisateur : la clé du sans root
Le confinement sans root repose sur les namespaces utilisateur Linux (user_namespaces(7)), qui mappent une plage d’UID/GID du système hôte vers un ensemble différent à l’intérieur du container. Lorsqu’un compte non privilégié démarre une instance Podman sans root, le noyau associe l’UID réel de l’utilisateur (par exemple 1001) à l’UID 0 dans l’espace de noms du container. Toute tentative de cette procédure de sortir du container est traduite en UID 1001 sur l’hôte.
Si une attaque dans la chaîne d’approvisionnement injecte une charge malveillante dans un container lancé via un pipeline sans root, et que cette charge exploite une vulnérabilité en runtime pour s’échapper, l’attaquant atterrit sur l’hôte en tant que ce même compte de service à faibles privilèges — pas en root. Il ne peut pas modifier les binaires système, charger des modules noyau ou toucher directement des dispositifs physiques.
3. Configuration des namespaces utilisateur pour la provision matérielle
Le mappage des namespaces sans root dépend des fichiers /etc/subuid et /etc/subgid du système hôte, qui allouent des blocs d’UID/GID subordonnés à des comptes non privilégiés.
gitlab-runner:100000:65536
Trois champs séparés par deux points :
- Identifiant — le compte de service hôte (gitlab-runner)
- UID subordonné initial — début du bloc alloué (100000)
- Nombre d’UID — taille du bloc (65536)
| UID interne du container | Mappage UID hôte | Description |
|---|---|---|
| 0 (root interne) | 1001 | UID réel du compte de service sur l’hôte |
| 1 | 100000 | Premier UID subordonné du bloc |
| 2 | 100001 | Deuxième UID subordonné |
| 65535 | 165535 | Dernier UID du bloc |
Les gestionnaires de paquets modernes configurent cela automatiquement — sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 <utilisateur> — mais les comptes de service de pipeline créés par l’infrastructure-as-code doivent encore être configurés explicitement, et il est conseillé de vérifier avec grep <utilisateur> /etc/subuid /etc/subgid plutôt que de supposer que c’est déjà fait.
Pilotes de stockage : overlay natif a largement remplacé fuse-overlayfs
La contrainte initiale était réelle mais est désormais dépassée, il est donc important de préciser ce qui a changé. Les containers avec root utilisent directement le système de fichiers overlay du noyau. Pendant des années, les utilisateurs non privilégiés ne pouvaient pas monter overlay du tout, ce qui faisait que Podman sans root utilisait fuse-overlayfs, une implémentation FUSE en espace utilisateur qui gère le copy-on-write dans l’espace utilisateur sans toucher au système de montage privilégié du noyau.
Ce contournement n’est plus strictement nécessaire avec les kernels actuels. Linux 5.11 a ajouté le montage overlay sans privilège, mais un bug de balisage SELinux empêchait de s’y fier jusqu’à kernel 5.13, où la correction a été intégrée. Depuis, Podman privilégie nativement l’overlay sans root dès que le noyau le supporte, car fuse-overlayfs est un système de fichiers en espace utilisateur qui doit interpréter chaque lecture/écriture avant de la transmettre au noyau — une surcharge mesurable sous charges lourdes de build ou d’écriture. fuse-overlayfs reste pertinent en fallback pour les kernels plus anciens, pour les répertoires NFS, ou dans certains scénarios de containers imbriqués où /dev/fuse est plus simple à gérer qu’un overlay natif.
Implication pratique pour la conception des pipelines : vérifier podman info | grep -i "native overlay" (ou Native Overlay Diff: true dans la sortie info) sur vos hôtes de runner avant de supposer que vous avez besoin de fuse-overlayfs. Sur un kernel 5.13+, la configuration recommandée dans ~/.config/containers/storage.conf n’a plus besoin de la ligne mount_program :
[storage]
driver = "overlay"
[storage.options.overlay]
# L’overlay natif sans root est utilisé automatiquement sur kernel 5.13+.
# Décommentez uniquement si vous utilisez un kernel plus ancien,
# des répertoires NFS, ou des scénarios de containers imbriqués.
# mount_program = "/usr/bin/fuse-overlayfs"
mountopt = "nodev,metacopy=on"
metacopy=on s’applique toujours — cela évite de copier les données de couches inférieures jusqu’à modification, accélérant significativement les builds avec de grandes images de base.
4. Modèle pour la construction d’images sans daemon avec Buildah
Buildah est idéal pour des builds programmatiques et scriptés plutôt que pour analyser des Dockerfile statiques, ce qui est utile lorsqu’un pipeline doit tirer des matrices de configuration locales, compiler des firmwares, et assembler une image renforcée sans couches intermédiaires superflues.
#!/usr/bin/env bash
set -o errexit
set -o pipefail
set -o nounset
# Étape 1 : Initialiser un environnement de travail propre à partir d’une image de base minimale
echo "=== Initialisation du container OCI de base ==="
container=$(buildah from alpine:3.19)
# Étape 2 : Monter le système de fichiers racine du container dans l’espace utilisateur non privilégié
echo "=== Exposition de la couche du système de fichiers du container ==="
container_mount=$(buildah mount "${container}")
# Étape 3 : Provisionner la couche applicative
echo "=== Provisionnement de la couche applicative et des artefacts edge ==="
mkdir -p "${container_mount}/opt/edge/proxy"
mkdir -p "${container_mount}/etc/edge"
echo "Initialisation de la boucle de communication renforcée" "${container_mount}/opt/edge/proxy/core.bin"
chmod 755 "${container_mount}/opt/edge/proxy/core.bin"
# Étape 4 : Configurer le container
echo "=== Application de la configuration du container ==="
buildah config --workingdir "/opt/edge/proxy" "${container}"
buildah config --entrypoint '["/opt/edge/proxy/core.bin"]' "${container}"
buildah config --user "1001:1001" "${container}"
buildah config --label architecture="edge-hardened" "${container}"
buildah config --env PROXY_PORT="8080" "${container}"
# Étape 5 : Commit dans un stockage immuable local
echo "=== Validation de l’image OCI scellée ==="
buildah commit --rm "${container}" "localhost/edge-proxy:v1.0.0"
echo "=== Build terminé : localhost/edge-proxy:v1.0.0 ==="
Pourquoi cela a une importance pratique :
- Aucun socket exposé — tout le build se déroule dans le processus du pipeline lui-même, sans daemon en arrière-plan.
- Contrôle granulaire des fichiers — buildah mount donne un accès direct au système de fichiers, évitant la surcharge de couches avec des instructions répétées RUN cp / ADD.
- Définition non-root — buildah config --user "1001:1001" garantit que l’image déployée s’exécute sans privilèges sur le matériel cible.
Depuis mi-2026, Buildah est à la version v1.44.0 (sortie le 27 mai 2026) et la branche stable de Podman est à v5.8.x (v5.8.2, avril 2026) — il est conseillé de la fixer explicitement dans les images CI plutôt que de suivre :latest, car les deux projets publient fréquemment des correctifs de sécurité (plus d’informations dans la checklist ci-dessous).
5. Guide de mise en œuvre : pipeline sécurisé de provision edge
La configuration suivante utilise la syntaxe GitLab CI/CD ; la structure est directement transposable à GitHub Actions, Tekton ou Jenkins.
stages:
- lint
- build
- provision
variables:
# Isoler les couches de stockage dans l’espace de travail du runner local
CONTAINERS_STORAGE_CONF: "$CI_PROJECT_DIR/.containers/storage.conf"
REGISTRY_AUTH_FILE: "$CI_PROJECT_DIR/.containers/auth.json"
STORAGE_DRIVER: "overlay"
default:
before_script:
- mkdir -p $(dirname "$CONTAINERS_STORAGE_CONF")
- |
echo '[storage]' "$CONTAINERS_STORAGE_CONF"
echo 'driver = "overlay"' "$CONTAINERS_STORAGE_CONF"
# L’overlay natif sans root est utilisé automatiquement sur kernel 5.13+ ;
# décommentez uniquement si votre kernel de runner est antérieur.
# echo '[storage.options.overlay]' "$CONTAINERS_STORAGE_CONF"
# echo 'mount_program = "/usr/bin/fuse-overlayfs"' "$CONTAINERS_STORAGE_CONF"
- podman login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
build_edge_image:
stage: build
tags:
- rootless-edge-runner
script:
- echo "Construction de l’artefact edge (sans root)..."
- podman build --storage-driver=$STORAGE_DRIVER --layers -t "$CI_REGISTRY_IMAGE/edge-node:$CI_COMMIT_SHA" .
- echo "Push de l’image dans le registre..."
- podman push "$CI_REGISTRY_IMAGE/edge-node:$CI_COMMIT_SHA"
provision_local_hardware:
stage: provision
tags:
- rootless-edge-runner
script:
- echo "Provisionnement de l’appareil edge via SSH..."
- |
ssh -i "$EDGE_PROXY_DEPLOY_KEY" -o StrictHostKeyChecking=require "$EDGE_PROXY_USER@$EDGE_PROXY_IP" EOF
set -e
echo "Connecté au noeud de déploiement physique."
podman login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
podman pull "$CI_REGISTRY_IMAGE/edge-node:$CI_COMMIT_SHA"
podman stop telemetry-collector || true
podman rm telemetry-collector || true
# Pas de --privileged, pas de montage hôte. Les dispositifs sont mappés explicitement.
podman run -d \
--name telemetry-collector \
--restart=always \
--user=1001:1001 \
--security-opt label=disable \
--device /dev/ttyUSB0:/dev/ttyUSB0:rw \
-p 8443:8443 \
"$CI_REGISTRY_IMAGE/edge-node:$CI_COMMIT_SHA"
echo "Interface matérielle initialisée et en streaming."
EOF
only:
- main
Mesures de sécurité intégrées dans ce pipeline :
- Contexte de stockage isolé — réécrire CONTAINERS_STORAGE_CONF dans $CI_PROJECT_DIR évite la contamination entre locataires sur runners partagés.
- Mapping explicite --device — plutôt que --privileged, qui expose tous les dispositifs hôte, --device /dev/ttyUSB0:/dev/ttyUSB0:rw ne mappe que l’interface spécifique nécessaire.
- Réseautage en espace utilisateur — les containers sans root utilisent un helper réseau en espace utilisateur plutôt que des ponts et règles iptables root (détails ci-dessous — c’est l’un des domaines où le changement est le plus marqué depuis 2024).
6. Ce qui a réellement changé : réseau, stockage et versions en 2026
C’est la section la plus susceptible de devenir obsolète dans un article sur les containers sans root, il est donc utile d’être précis sur l’état actuel mi-2026.
Pasta a remplacé slirp4netns comme backend réseau par défaut pour les containers sans root. Pendant des années, Podman sans root s’appuyait sur slirp4netns, qui crée une interface virtuelle et traduit chaque paquet via une NAT en espace utilisateur — fonctionnel, mais avec un surcoût réel par paquet et un goulot d’étranglement en thread unique sous charge. À partir de Podman 5.0 (et par défaut depuis 5.3), pasta (aussi appelé passt) a pris le relais. Pasta est une réécriture complète qui reflète la configuration réseau réelle de l’hôte (adresses, routes, MTU) directement dans le container au lieu d’utiliser NAT, ce qui supprime une part significative de la « taxe sans root ». Ce n’est pas une victoire absolue — des benchmarks indépendants montrent que pasta dépasse slirp4netns jusqu’à un certain niveau de parallélisme, mais que slirp4netns reste compétitif en très haute concurrence, et chaque processus pasta consomme un peu plus de mémoire (environ 29 MB contre 3-4 MB pour slirp4netns par container). Pour la majorité des workloads edge-gateway et CI, la suppression du saut NAT par pasta est la plus importante. slirp4netns sera complètement supprimé dans la prochaine version Podman 6.0 (en développement actif dans une proposition Fedora Change mi-2026), avec la suppression de cgroups v1 et de l’ancien backend de stockage BoltDB, donc ceux qui utilisent encore network=slirp4netns doivent prévoir une migration.
La restriction sur la liaison de ports faibles est inchangée. net.ipv4.ip_unprivileged_port_start reste à la valeur par défaut 1024 sur les kernels standards, bloquant les liaisons non privilégiées sur les ports en dessous. La recommandation initiale — la réduire via un fichier /etc/sysctl.d/ dédié plutôt que modifier /etc/sysctl.conf directement — reste valable :
# /etc/sysctl.d/99-rootless-ports.conf
net.ipv4.ip_unprivileged_port_start=443
La mettre à 443 plutôt qu’à 0 maintient la restriction, tout en permettant l’accès HTTPS standard pour les proxies edge sans root.
Les mismatches de permissions de volume entre UID hôte et UID mappé dans le container restent résolus de la même façon : flags SELinux :Z/:z, ou --userns=keep-id pour mapper directement le UID réel de l’utilisateur dans le container.
Empreinte mémoire inactive : c’est réel, mais l’affirmer comme « 0 MB » est un peu exagéré. La déclaration principale — qu’un runtime sans daemon n’a pas de processus en arrière-plan permanent, contrairement à dockerd — est correcte et importante sur du matériel edge à mémoire limitée. Des benchmarks indépendants de 2026 indiquent qu’un dockerd + containerd en veille consomme environ 55 à 200 MB de RSS selon la version et la configuration, avec une moyenne autour de 140-150 MB. La consommation idle de Podman est beaucoup plus proche de zéro, puisqu’il n’y a pas de daemon persistant — mais « zéro » n’est pas littéral : un container en cours d’exécution maintient un processus léger conmon par container, et un processus pasta (~29 MB) si ce container a son propre namespace réseau. La conclusion essentielle pour dimensionner du hardware edge reste que Docker a une surcharge même quand rien ne tourne, alors que Podman n’en a pas.
7. Liste de vérification architecturale : renforcer la pile de containers
┌────────────────────────────────────────┐
│ AUDIT DE SÉCURITÉ SANS DAEMON TERMINÉ │
└────────────────────────────────────────┘
│
┌────────────────────────────────┼────────────────────────────────┐
▼ ▼ ▼
[ NAMESPACES UTILISATEUR ] [ ZÉRO DAEMONS ACTIFS ] [ DISPOSITIFS GRAPHIQUES GRANDS LARGES ]
Vérifié via Aucun dockerd actif Chemins explicites
/etc/subuid mappings en fonctionnement sur l’hôte mappés via --device
- [ ] Vérifier les limites des namespaces utilisateur — chaque compte runner doit avoir une entrée dans
/etc/subuidet/etc/subgidavec au moins 65 536 IDs alloués. - [ ] Appliquer le zéro daemon — supprimer ou désactiver
dockerd(systemctl disable --now docker) sur toute la machine. - [ ] Valider la provenance des images avant exécution — utiliser Skopeo avec
cosignpour vérifier les signatures. Ce n’est pas une étape optionnelle : en 2026, des vulnérabilités réelles ont été découvertes dans ce domaine, notamment une faille de chemin dans la construction d’image (CVE-2026-44517) affectantADD/COPYdepuis un dépôt Git malveillant ou une archive tar, et une fuite d’environnement malicieuse (CVE-2026-57231) pouvant exfiltrer des variables d’environnement hôte dans un container en utilisant desEnvglobaux. Ces failles sont précisément ce que la vérification de signature et la liste blanche de registres doivent prévenir avant que l’image n’atteigne le hardware edge. - [ ] Appliquer le principe du moindre privilège sur les dispositifs — remplacer chaque
--privilegedpar des mappages--deviceexplicites et des--cap-addciblés (par ex.CAP_NET_ADMIN,CAP_SYS_RAWIO) uniquement si une interface physique le nécessite. - [ ] Configurer l’exécution non-root — construire et exécuter tous les payloads sous un
USERnon root (1001ou similaire). - [ ] Maintenir le runtime à jour —
runc, utilisé par Podman et Buildah, a connu plusieurs CVEs de classe escape (CVE-2024-21626, et plus récemment CVE-2025-31133, CVE-2025-52565, CVE-2025-52881), corrigés via une mise à jour enrunc1.3.4 intégrée dans Buildah 1.42. Il est crucial de fixer les versions de Buildah/Podman dans la CI pour ne pas hériter des CVEs du runtime.
Conclusion
Exposer un socket Docker au niveau root dans un pipeline avec accès direct à une infrastructure physique est une vulnérabilité critique, bien documentée — pas une hypothèse : CVE-2024-21626 et ses suites le prouvent. Passer à un modèle décentralisé, sans daemon, avec Podman et Buildah, permet de la mitiger structurellement : une compromission en chaîne d’approvisionnement se traduit par un utilisateur non privilégié, pas root. La nouveauté depuis l’adoption du sans daemon est surtout positive — support native de l’overlay sur kernels modernes élimine la majorité de la surcharge FUSE, et pasta supprime la surcharge NAT du réseau sans root. Les principes fondamentaux — isolation par namespace utilisateur, mappage explicite des dispositifs, vérification des signatures d’image, et un rythme de patchs régulier pour le runtime — restent la clé pour durcir un pipeline de déploiement edge.
Changelog
Métadonnées supprimées : Les métadonnées et artefacts de formatage initiaux ont été retirés pour publication.
Vérifications et corrections :
- Correction de « Docker-in-Docker (DinD) ou socket-mouting » → « socket-mounting. »
- Correction de la cohérence dans la variable LOCAL_PROXEY_USER et les tags des runners dans l’exemple de pipeline.
- Vérification de la valeur par défaut de net.ipv4.ip_unprivileged_port_start (1024) et de l’approche sysctl.
- Vérification de CONTAINERS_STORAGE_CONF et REGISTRY_AUTH_FILE comme variables d’environnement utilisées par les bibliothèques containers/storage et containers/image.
Ajouts pour 2026 :
- Exemple concret (CVE-2024-21626 / « Vaisseaux fuyants ») illustrant la classe de faille évoquée.
- Mise à jour sur le pilote de stockage : l’overlay natif (kernel 5.13+) a remplacé fuse-overlayfs par défaut ; fuse-overlayfs est désormais une solution de fallback.
- Ajout d’une section sur pasta qui a remplacé slirp4netns comme backend réseau par défaut depuis Podman 5.0/5.3, avec ses compromis de performance.
- Remplacement de la déclaration « mémoire idle : 140–180 MB / Podman : 0 MB » par une fourchette basée sur plusieurs benchmarks, précisant que Podman est proche de zéro mais pas littéralement zéro.
- Références aux versions actuelles : Podman 5.8.2 (avril 2026), Buildah 1.44.0 (mai 2026), et la proposition Fedora pour Podman 6.0.
- Ajout de deux CVEs liés à la chaîne d’approvisionnement (CVE-2026-44517, CVE-2026-57231) dans la checklist, ainsi qu’une note sur les CVEs de runc (CVE-2025-31133/52565/52881) corrigés dans Buildah 1.42.
Formatage : Conversion des diagrammes et blocs de code en blocs Markdown avec syntaxe appropriée ; restructuration de la liste de vérification en liste de tâches Markdown ; ajout d’un tableau comparatif pour le mappage UID.
Related InstaTunnel pages
Continue from this article into the most relevant product guides and workflows.
Related Topics
Keep building with InstaTunnel
Read the docs for implementation details or compare plans before you ship.