Écrire des Dockerfiles légers et des points d’entrée Shell
Rédigez des scripts de construction en plusieurs étapes et des relais de point d’entrée robustes avec gestion des signaux et modèles de configuration.
Écrire des Dockerfiles légers et des points d’entrée Shell est une leçon DevOps Bootcamp gratuite sur CoddyKit. Ceci est la leçon 1 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage DevOps Bootcamp, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours DevOps Bootcamp comprend 4 leçons au total.
Pourquoi les fichiers Docker légers sont importants pour le DevOps
Dans les environnements de production, chaque mégaoctet d’une image Docker a un coût : téléchargements plus lents, surface d’attaque plus grande et espace de stockage du registre gaspillé. Des fichiers Docker légers associés à des scripts shell robustes servant de points d’entrée sont la marque d’une pratique DevOps mature.
- Les compilations multi-étapes séparent les outils nécessaires à la compilation de l’image finale d’exécution, ce qui réduit considérablement sa taille.
- Les relais de point d’entrée sont de petits scripts shell qui initialisent les conteneurs : ils génèrent les fichiers de configuration à partir de modèles, valident les variables d’environnement, gèrent les signaux et exécutent finalement le processus principal.
- Ensemble, ils constituent la base de charges de travail conteneurisées fiables et portables dans Kubernetes, ECS et les environnements sur serveurs physiques.
Cette leçon couvre ces deux domaines de bout en bout, avec des modèles prêts pour la production que vous pouvez intégrer directement à vos pipelines.
Anatomie d’un fichier Docker multi-étapes
Un fichier Docker multi-étapes utilise plusieurs instructions FROM. Chaque étape est un ensemble de couches isolé ; vous ne copiez dans l’étape suivante que les artefacts dont vous avez besoin.
- Étape 0 (builder) : installe les compilateurs, les lanceurs de tests et les dépendances de compilation.
- Étape 1 (runtime) : part d’une base minimale (par exemple
alpine,distroless) et ne copie que les binaires compilés ou les ensembles de fichiers de l’application. - L’image finale ne contient jamais
gcc,makeni le code source, sauf si vous les copiez explicitement.
Utilisez --from=<stage> dans COPY pour récupérer des fichiers au-delà des limites entre les étapes. Nommez les étapes avec AS <name> pour améliorer la lisibilité et permettre de cibler sélectivement une étape avec docker build --target.
# ---- Stage 0: builder ----
FROM golang:1.22-alpine AS builder
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags='-s -w' -o /app/server ./cmd/server
# ---- Stage 1: runtime ----
FROM gcr.io/distroless/static-debian12
COPY --from=builder /app/server /server
ENTRYPOINT ["/server"]Réduire le nombre de couches et invalider le cache
Chaque instruction RUN, COPY et ADD crée une nouvelle couche. Des instructions mal ordonnées invalident inutilement le cache de compilation, ce qui ralentit les pipelines d’intégration continue.
- Copiez les manifestes de dépendances (
package.json,go.mod,requirements.txt) avant de copier le code source afin que l’installation des dépendances soit mise en cache indépendamment. - Enchaînez les commandes associées avec
&&et effectuez le nettoyage dans la même coucheRUNafin d’éviter de laisser le cache des paquets dans les couches intermédiaires. - Utilisez
--no-cachedans les gestionnaires de paquets et supprimez les fichiers de listes après l’installation.
FROM python:3.12-slim AS builder
WORKDIR /app
# 1. Install deps first (cached until requirements change)
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
# 2. Copy source (cache busted only when code changes)
COPY src/ ./src/
FROM python:3.12-slim
COPY --from=builder /install /usr/local
COPY --from=builder /app/src /app/src
WORKDIR /app
CMD ["python", "-m", "src.main"]Secrets BuildKit et transfert SSH
Les registres privés, les clés SSH et les jetons d’API ne doivent jamais apparaître dans les couches d’une image. Docker BuildKit fournit deux mécanismes sûrs :
--secret: monte un fichier secret dans une seule étapeRUNsans l’intégrer à une couche. Accédez-y via/run/secrets/<id>.--ssh: transfère le socket de l’agent SSH de l’hôte dans la compilation afin quegit clonepuisse s’authentifier sans intégrer de clés privées.
Activez BuildKit avec DOCKER_BUILDKIT=1 ou via docker buildx build. La directive # syntax=docker/dockerfile:1 active ces fonctionnalités.
# syntax=docker/dockerfile:1
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
# Mount NPM token as a secret — never stored in the image
RUN --mount=type=secret,id=npm_token \
NPM_TOKEN=$(cat /run/secrets/npm_token) \
npm ci --prefer-offline
# Usage at build time:
# DOCKER_BUILDKIT=1 docker build \
# --secret id=npm_token,src=~/.npmrc-token \
# -t myapp:latest .Écrire un relais de point d’entrée robuste
Le relais de point d’entrée est un script shell défini comme ENTRYPOINT dans le fichier Docker. Son rôle est de préparer l’environnement d’exécution avant de céder le contrôle au processus principal.
Un relais bien structuré respecte l’ordre suivant :
- Étape 1 : définissez
set -euo pipefailafin que tout échec provoque un arrêt immédiat. - Étape 2 : validez les variables d’environnement requises et arrêtez rapidement l’exécution avec un message explicite en cas de problème.
- Étape 3 : générez les fichiers de configuration à partir des variables d’environnement.
- Étape 4 : enregistrez des gestionnaires de signaux pour permettre un arrêt correct.
- Étape 5 :
exec "$@"— remplacez le shell par le processus principal afin que le PID 1 soit l’application, et non le relais.
Le dernier exec est essentiel : sans lui, les signaux envoyés par Kubernetes ou le moteur d’exécution Docker ne sont pas transmis au processus enfant.
#!/usr/bin/env bash
set -euo pipefail
# Step 2: validate required env vars
REQUIRED_VARS=(DATABASE_URL APP_SECRET PORT)
for var in "${REQUIRED_VARS[@]}"; do
if [[ -z "${!var:-}" ]]; then
echo "[entrypoint] ERROR: required env var '$var' is not set" >&2
exit 1
fi
done
# Step 3: template config (see next scene)
# Step 4: signal handling (see scene after that)
# Step 5: hand off to CMD
exec "$@"Générer la configuration à partir de modèles avec envsubst
envsubst (issu du paquet GNU gettext, disponible dans Alpine sous la forme de gettext) remplace les marqueurs ${VAR} d’un fichier modèle par les valeurs actuelles de leurs variables d’environnement.
- Incluez dans l’image un modèle de configuration
*.tmpl; le point d’entrée le génère au démarrage. - Transmettez explicitement la liste des variables à
envsubstafin qu’il ne développe pas accidentellement des signes dollar sans rapport (par exemple dans une expression régulière NGINX). - Écrivez le fichier généré dans un chemin accessible en écriture, comme
/tmp, ou dans un volume de configuration dédié.
#!/usr/bin/env bash
# Template: /etc/nginx/conf.d/app.conf.tmpl contains:
# server { listen ${NGINX_PORT}; server_name ${SERVER_NAME}; ... }
export NGINX_PORT=${NGINX_PORT:-8080}
export SERVER_NAME=${SERVER_NAME:-localhost}
envsubst '${NGINX_PORT} ${SERVER_NAME}' \
< /etc/nginx/conf.d/app.conf.tmpl \
> /etc/nginx/conf.d/app.conf
echo "[entrypoint] nginx config rendered:"
grep -E 'listen|server_name' /etc/nginx/conf.d/app.conf
exec "$@"Gestion des signaux et arrêt correct
Les conteneurs reçoivent SIGTERM lorsqu’ils sont arrêtés par Kubernetes, ECS ou docker stop. Si votre relais de point d’entrée est le PID 1 et qu’il ne transmet pas les signaux, le processus principal est tué avec SIGKILL à la fin du délai de grâce, ce qui peut entraîner la perte de requêtes ou la corruption de données.
- Utilisez
trappour intercepterSIGTERMetSIGINTdans le relais. - Transmettez le signal au PID enfant avec
kill -TERM "$child". - Utilisez
wait "$child"pour bloquer jusqu’à la fin du processus enfant, puis retransmettez son code de sortie. - Vous pouvez également utiliser
execpour remplacer entièrement le shell ; le système d’exploitation transmet alors directement les signaux au processus enfant, sans qu’un gestionnaire soit nécessaire. C’est le modèle recommandé dans les cas simples.
#!/usr/bin/env bash
set -euo pipefail
# Start main process in background
"$@" &
child=$!
# Forward SIGTERM and SIGINT to the child
trap 'echo "[entrypoint] caught SIGTERM, forwarding..."; kill -TERM "$child"' TERM
trap 'echo "[entrypoint] caught SIGINT, forwarding..."; kill -INT "$child"' INT
# Wait for child to exit and capture its exit code
wait "$child"
exit $?Utiliser tini comme processus d’initialisation minimal
Lorsque votre conteneur crée des processus enfants (par exemple un shell qui lance des processus de travail), vous avez besoin d’un véritable processus d’initialisation pour récupérer les processus zombies. tini est un petit binaire d’initialisation conçu spécialement pour les conteneurs.
- Ajoutez
tinià votre image et définissez-le comme relais du point d’entrée. - Il récupère les processus enfants zombies, transmet correctement les signaux et se termine avec le code d’état du processus enfant.
- Docker fournit un tini intégré, activé avec
docker run --init, mais l’intégrer à l’image garantit un comportement cohérent entre les moteurs d’exécution (Kubernetes, ECS, etc.).
FROM node:20-alpine
# Install tini for proper signal handling and zombie reaping
RUN apk add --no-cache tini
WORKDIR /app
COPY --chown=node:node . .
RUN npm ci --omit=dev
USER node
# tini wraps CMD; forwards SIGTERM and reaps zombies
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["node", "server.js"]Exécuter le conteneur avec un utilisateur non root
Les conteneurs qui s’exécutent en tant que root (UID 0) représentent un risque de sécurité majeur : une évasion du conteneur donne un accès complet à l’hôte. Passez toujours à un utilisateur non privilégié avant d’exécuter le processus principal.
- Créez un utilisateur et un groupe système dédiés dans le fichier Docker avec
addgroup/adduser(Alpine) ougroupadd/useradd(Debian). - Modifiez le propriétaire des fichiers de l’application avec
COPY --chown=appuser:appgroup— c’est plus efficace qu’une coucheRUN chowndistincte. - Passez à cet utilisateur avec l’instruction
USER. Le point d’entrée et CMD héritent de cet utilisateur. securityContext.runAsNonRoot: truede Kubernetes refuse de démarrer une image qui s’exécute encore en tant que root.
FROM python:3.12-slim
# Create non-root user
RUN groupadd --gid 1001 appgroup && \
useradd --uid 1001 --gid appgroup --shell /bin/bash --create-home appuser
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy app files with correct ownership in a single layer
COPY --chown=appuser:appgroup src/ ./src/
COPY --chown=appuser:appgroup entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
USER appuser
ENTRYPOINT ["/entrypoint.sh"]
CMD ["python", "-m", "src.main"]Contrôles de santé et sondes de disponibilité dans l’image
Les sondes de vivacité et de disponibilité de Kubernetes sont définies dans les manifestes, mais vous pouvez également intégrer un HEALTHCHECK au fichier Docker pour les environnements autonomes utilisant docker run et Docker Compose.
HEALTHCHECK --interval=10s --timeout=3s --start-period=15s --retries=3 CMD ...- Utilisez
curl --failouwget -qO-pour les services HTTP ; pour les démons non HTTP, testez un socket avec/dev/tcp/localhost/PORT. - Installez uniquement ce dont vous avez besoin : dans les images distroless, évitez d’ajouter
curluniquement pour les contrôles de santé ; utilisez plutôt un binaire de sonde dédié ou le binaire de contrôle de santé de l’application.
FROM nginx:1.27-alpine
COPY nginx.conf /etc/nginx/nginx.conf
COPY dist/ /usr/share/nginx/html/
# Lightweight health check using bash TCP pseudo-device
# (no curl needed — works on any image with bash)
HEALTHCHECK --interval=15s --timeout=5s --start-period=10s --retries=3 \
CMD bash -c 'exec 3<>/dev/tcp/localhost/80 && echo -e "GET /health HTTP/1.0\r\n" >&3 && cat <&3 | grep -q "200 OK"' || exit 1
EXPOSE 80Tout assembler : un point d’entrée de production
Voici un relais de point d’entrée complet, prêt pour la production, qui combine tous les modèles de cette leçon : validation de l’environnement, génération de la configuration à partir d’un modèle, transmission des signaux et transfert avec exec. Ce modèle est utilisé dans des microservices Node.js, Python et Go réels déployés sur Kubernetes.
- Chaque section est commentée pour servir de modèle explicite et auto-documenté.
- Le relais fait moins de 50 lignes — les points d’entrée doivent rester simples et faciles à auditer.
- Remarquez le
exec "$@"final : une fois toute la préparation terminée, le shell est remplacé par le processus de l’application, qui devient ainsi le PID 1 et reçoit directement tous les signaux du système d’exploitation.
#!/usr/bin/env bash
# entrypoint.sh — production-grade container entrypoint shim
set -euo pipefail
# ── 1. Validate required environment variables ──────────────────
REQUIRED=(DATABASE_URL APP_SECRET PORT LOG_LEVEL)
for var in "${REQUIRED[@]}"; do
[[ -n "${!var:-}" ]] || { echo "[entrypoint] FATAL: $var is not set" >&2; exit 1; }
done
# ── 2. Set safe defaults for optional variables ─────────────────
export HOST=${HOST:-0.0.0.0}
export WORKERS=${WORKERS:-2}
# ── 3. Render config template ───────────────────────────────────
if [[ -f /etc/app/app.conf.tmpl ]]; then
envsubst '${DATABASE_URL} ${PORT} ${LOG_LEVEL} ${HOST}' \
< /etc/app/app.conf.tmpl \
> /etc/app/app.conf
echo "[entrypoint] config rendered at /etc/app/app.conf"
fi
# ── 4. Wait for dependent services (optional, fast) ─────────────
if [[ -n "${WAIT_FOR_HOST:-}" ]]; then
echo "[entrypoint] waiting for ${WAIT_FOR_HOST}:${WAIT_FOR_PORT:-5432}..."
until bash -c "exec 3<>/dev/tcp/${WAIT_FOR_HOST}/${WAIT_FOR_PORT:-5432}" 2>/dev/null; do
sleep 1
done
echo "[entrypoint] dependency ready"
fi
# ── 5. Hand off to CMD (PID 1 becomes the application) ──────────
echo "[entrypoint] starting: $*"
exec "$@"Vérification des connaissances : gestion des signaux dans les points d’entrée
Vérifiez votre compréhension de la gestion des signaux dans les scripts servant de points d’entrée aux conteneurs.
Récapitulatif : fichiers Docker légers et points d’entrée shell
Vous avez étudié tous les aspects de la création de conteneurs prêts pour la production :
- Les compilations multi-étapes utilisent plusieurs instructions
FROMpour exclure les compilateurs et les outils de compilation de l’image finale, ce qui produit des couches d’exécution légères et minimales. - L’ordre des couches — copier les manifestes de dépendances avant le code source — maximise les utilisations du cache et accélère les pipelines d’intégration continue.
- Les secrets et montages SSH de BuildKit gardent les identifiants hors de l’historique de l’image sans empêcher les compilations authentifiées.
- Les relais de point d’entrée valident les variables d’environnement, génèrent les fichiers de configuration avec
envsubstet transfèrent le contrôle à l’application viaexec "$@". - La gestion des signaux nécessite soit
exec(pour que l’application soit le PID 1), soit un modèle explicitetrap+kill+waitlorsque des tâches en arrière-plan sont utilisées. - tini assure la récupération des processus zombies et la transmission correcte des signaux lorsque le conteneur crée plusieurs processus.
- Les utilisateurs non root et les instructions
HEALTHCHECKcomplètent une image sécurisée et observable, prête pour les charges de travail Kubernetes en production.
Appliquez ces modèles de façon cohérente : vos images seront plus petites, plus rapides à déployer et nettement plus robustes en conditions de production.
Questions Fréquemment Posées
La leçon « Écrire des Dockerfiles légers et des points d’entrée Shell » est-elle gratuite ?
Oui — le texte complet de « Écrire des Dockerfiles légers et des points d’entrée Shell » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours DevOps Bootcamp, passe à CoddyKit PRO. Le cours DevOps Bootcamp comprend 4 leçons au total.
Qu'est-ce que j'apprendrai dans « Écrire des Dockerfiles légers et des points d’entrée Shell » ?
Rédigez des scripts de construction en plusieurs étapes et des relais de point d’entrée robustes avec gestion des signaux et modèles de configuration. Tu pratiques DevOps Bootcamp avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.
Dois-je avoir de l'expérience pour commencer DevOps Bootcamp ?
Aucune expérience préalable n'est requise. DevOps Bootcamp sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 1 sur 4.
Combien de temps prend la leçon « Écrire des Dockerfiles légers et des points d’entrée Shell » ?
La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.
Peux-tu écrire et exécuter du code dans cette leçon DevOps Bootcamp ?
Oui. Chaque leçon DevOps Bootcamp inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.
Toutes les leçons de ce cours
- Écrire des Dockerfiles légers et des points d’entrée Shell
- Créer des modèles de configuration avec envsubst et des heredocs
- Scripter des ressources cloud avec la CLI et jq
- Sondes d’état, barrières de disponibilité et boucles d’attente