DevOps Bootcamp · Lezione

Scrivere Dockerfile essenziali ed entrypoint shell

Scriva script di build multistadio e shim entrypoint robusti, con gestione dei segnali e templating della configurazione.

Lezione 1 di 413 passaggi

Scrivere Dockerfile essenziali ed entrypoint shell è una lezione DevOps Bootcamp gratuita su CoddyKit. Questa è la lezione 1 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento DevOps Bootcamp, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso DevOps Bootcamp include 4 lezioni in totale.

Perché i Dockerfile snelli sono importanti per il DevOps

Negli ambienti di produzione, ogni megabyte contenuto in un'immagine Docker ha un costo: download più lenti, superfici di attacco più ampie e spazio sprecato nel registry. Dockerfile snelli, combinati con entrypoint shell robusti, sono un segno distintivo di una pratica DevOps matura.

  • Le build multi-stage separano gli strumenti necessari durante la build dall'immagine runtime finale, riducendone drasticamente le dimensioni.
  • Gli shim di entrypoint sono piccoli script shell che inizializzano i container: generano i file di configurazione dai template, convalidano le variabili d'ambiente, gestiscono i segnali e infine eseguono il processo principale con exec.
  • Insieme costituiscono la base per workload containerizzati affidabili e portabili in Kubernetes, ECS e negli ambienti bare metal.

Questa lezione tratta entrambe le discipline dall'inizio alla fine, con pattern adatti alla produzione che può inserire direttamente nelle pipeline.

Anatomia di un Dockerfile multi-stage

Un Dockerfile multi-stage utilizza più istruzioni FROM. Ogni stage è un insieme isolato di layer; nel passaggio allo stage successivo vengono copiati solo gli artifact necessari.

  • Stage 0 (builder): installa compilatori, test runner e dipendenze di build.
  • Stage 1 (runtime): parte da una base minimale (ad es. alpine, distroless) e copia solo i binari compilati o i bundle dell'applicazione.
  • L'immagine finale non contiene mai gcc, make o il codice sorgente, a meno che non vengano copiati esplicitamente.

Utilizzi --from=<stage> in COPY per trasferire file oltre i confini tra gli stage. Assegni un nome agli stage con AS <name> per migliorare la leggibilità e selezionare uno stage specifico con 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"]

Ridurre i layer e invalidare la cache

Ogni istruzione RUN, COPY e ADD crea un nuovo layer. Un ordine inadeguato delle istruzioni invalida inutilmente la cache di build, rallentando le pipeline CI.

  • Copi i manifest delle dipendenze (package.json, go.mod, requirements.txt) prima di copiare il codice sorgente, così l'installazione delle dipendenze viene memorizzata nella cache in modo indipendente.
  • Concateni i comandi correlati con && ed esegua la pulizia nello stesso layer RUN per evitare di lasciare la cache dei pacchetti nei layer intermedi.
  • Utilizzi --no-cache nei package manager e rimuova i file degli indici dopo l'installazione.
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"]

Secret BuildKit e forwarding SSH

Registry privati, chiavi SSH e token API non devono mai comparire nei layer dell'immagine. Docker BuildKit offre due meccanismi sicuri:

  • --secret: monta un file contenente un secret all'interno di un singolo passaggio RUN, senza incorporarlo in un layer. È possibile accedervi tramite /run/secrets/<id>.
  • --ssh: inoltra il socket dell'agent SSH dell'host nella build, consentendo a git clone di autenticarsi senza incorporare chiavi private.

Abiliti BuildKit con DOCKER_BUILDKIT=1 oppure tramite docker buildx build. La direttiva # syntax=docker/dockerfile:1 abilita queste funzionalità.

# 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 .

Scrivere uno shim di entrypoint robusto

Lo shim di entrypoint è uno script shell impostato come ENTRYPOINT nel Dockerfile. Il suo compito è preparare l'ambiente runtime prima di cedere il controllo al processo principale.

Uno shim ben strutturato segue questo ordine:

  • Passaggio 1: imposti set -euo pipefail in modo che qualsiasi errore interrompa subito l'esecuzione.
  • Passaggio 2: convalidi le variabili d'ambiente obbligatorie e interrompa subito l'esecuzione con un messaggio utile in caso di errore.
  • Passaggio 3: generi i file di configurazione a partire dalle variabili d'ambiente.
  • Passaggio 4: registri gli handler dei segnali per un arresto ordinato.
  • Passaggio 5: exec "$@" — sostituisca la shell con il processo principale, così il PID 1 è l'applicazione e non lo shim.

Il comando exec finale è fondamentale: senza di esso, i segnali inviati da Kubernetes o dal runtime Docker non vengono inoltrati al processo figlio.

#!/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 "$@"

Generare configurazioni dai template con envsubst

envsubst (fornito dal pacchetto GNU gettext, presente in Alpine come gettext) sostituisce i segnaposto ${VAR} in un file template con i valori correnti delle variabili d'ambiente.

  • Inserisca nell'immagine un template di configurazione *.tmpl; l'entrypoint lo renderizza all'avvio.
  • Passi esplicitamente a envsubst l'elenco delle variabili, così non espanderà accidentalmente altri simboli del dollaro (ad es. nelle regex di nginx).
  • Scriva il file renderizzato in un percorso scrivibile come /tmp o in un volume di configurazione dedicato.
#!/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 "$@"

Gestione dei segnali e arresto ordinato

I container ricevono SIGTERM quando vengono arrestati da Kubernetes, ECS o docker stop. Se lo shim di entrypoint è il PID 1 e non inoltra i segnali, il processo principale viene terminato con SIGKILL al termine del periodo di tolleranza, causando richieste perse o corruzione dei dati.

  • Utilizzi trap per intercettare SIGTERM e SIGINT nello shim.
  • Inoltri il segnale al PID figlio utilizzando kill -TERM "$child".
  • Utilizzi wait "$child" per attendere la terminazione del processo figlio, quindi propaghi il relativo codice di uscita.
  • In alternativa, utilizzi exec per sostituire completamente la shell: in questo modo il sistema operativo consegna i segnali direttamente al processo figlio e non serve alcun trap. Questo è il pattern preferito nei casi semplici.
#!/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 $?

Utilizzare tini come processo init minimale

Quando il container genera processi figli (ad es. una shell che crea processi worker), è necessario un vero init che raccolga i processi zombie. tini è un binario init minimale progettato appositamente per i container.

  • Aggiunga tini all'immagine e lo imposti come wrapper dell'entrypoint.
  • Raccoglie i processi figli zombie, inoltra correttamente i segnali e termina con il codice di uscita del processo figlio.
  • Docker include un tini integrato, attivabile con docker run --init, ma incorporarlo nell'immagine garantisce un comportamento coerente tra i diversi runtime (Kubernetes, ECS ecc.).
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"]

Eseguire il processo come utente non root

I container eseguiti come root (UID 0) rappresentano un grave rischio per la sicurezza: una fuga dal container concede l'accesso completo all'host. Passi sempre a un utente senza privilegi prima di eseguire il processo principale.

  • Crei un utente e un gruppo di sistema dedicati nel Dockerfile con addgroup / adduser (Alpine) oppure groupadd / useradd (Debian).
  • Modifichi il proprietario dei file dell'applicazione con COPY --chown=appuser:appgroup, più efficiente rispetto a un layer RUN chown separato.
  • Passi all'utente con l'istruzione USER. L'entrypoint e CMD ereditano questo utente.
  • securityContext.runAsNonRoot: true di Kubernetes rifiuterà di avviare un'immagine che continua a essere eseguita come 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"]

Health check e probe di readiness nell'immagine

Le probe di liveness e readiness di Kubernetes sono definite nei manifest, ma è anche possibile includere un HEALTHCHECK nel Dockerfile per gli ambienti standalone con docker run e Docker Compose.

  • HEALTHCHECK --interval=10s --timeout=3s --start-period=15s --retries=3 CMD ...
  • Utilizzi curl --fail o wget -qO- per i servizi HTTP; per i demoni non HTTP, esegua il probe su un socket con /dev/tcp/localhost/PORT.
  • Installi solo ciò che serve: nelle immagini distroless eviti di aggiungere curl solo per i health check; utilizzi invece un binario di probe dedicato o il binario di health check dell'applicazione.
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 80

Riunire tutti gli elementi: un entrypoint per la produzione

Di seguito è riportato uno shim di entrypoint completo e adatto alla produzione, che combina tutti i pattern di questa lezione: convalida delle variabili d'ambiente, generazione della configurazione dai template, inoltro dei segnali e passaggio del controllo con exec. Questo pattern viene utilizzato in microservizi Node.js, Python e Go reali distribuiti su Kubernetes.

  • Ogni sezione è commentata per fungere da template autoesplicativo.
  • Lo shim è mantenuto al di sotto delle 50 righe: gli entrypoint dovrebbero essere semplici e facilmente verificabili.
  • Noti il comando exec "$@" finale: completata tutta la configurazione, la shell viene sostituita dal processo dell'applicazione, che diventa il PID 1 e riceve direttamente tutti i segnali del sistema operativo.
#!/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 "$@"

Verifica delle conoscenze: gestione dei segnali negli entrypoint

Verifichi la propria comprensione della gestione dei segnali negli script di entrypoint dei container.

Riepilogo: Dockerfile snelli ed entrypoint shell

Ha esaminato tutti gli aspetti della creazione di container per la produzione:

  • Le build multi-stage utilizzano più istruzioni FROM per tenere compilatori e strumenti di build fuori dall'immagine finale, producendo layer runtime snelli e minimali.
  • L'ordine dei layer — copiare i manifest delle dipendenze prima del codice sorgente — massimizza i riscontri nella cache e accelera le pipeline CI.
  • I secret e i mount SSH di BuildKit mantengono le credenziali fuori dalla cronologia dell'immagine senza compromettere le build autenticate.
  • Gli shim di entrypoint convalidano le variabili d'ambiente, generano i file di configurazione con envsubst e cedono il controllo all'applicazione tramite exec "$@".
  • La gestione dei segnali richiede exec (in modo che l'applicazione sia il PID 1) oppure un pattern esplicito trap + kill + wait quando vengono utilizzati job in background.
  • tini aggiunge la raccolta dei processi zombie e il corretto inoltro dei segnali quando il container genera più processi.
  • Gli utenti non root e le istruzioni HEALTHCHECK completano un'immagine sicura e osservabile, pronta per i workload di produzione su Kubernetes.

Combinando questi pattern in modo coerente, le immagini saranno più piccole, più rapide da distribuire e molto più robuste in condizioni di produzione.

Gratis per iniziare

Impara DevOps Bootcamp con un tutor IA — gratis

Scrivi ed esegui vero codice nel tuo browser, ricevi aiuto istantaneo da un tutor IA disponibile 24/7, e riprendi da dove hai lasciato sul web o nell'app.

Corsi
142
Lezioni
568

Domande Frequenti

La lezione «Scrivere Dockerfile essenziali ed entrypoint shell» è gratuita?

Sì — il testo completo di «Scrivere Dockerfile essenziali ed entrypoint shell» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso DevOps Bootcamp, passa a CoddyKit PRO. Il corso DevOps Bootcamp include 4 lezioni in totale.

Cosa imparerò in «Scrivere Dockerfile essenziali ed entrypoint shell»?

Scriva script di build multistadio e shim entrypoint robusti, con gestione dei segnali e templating della configurazione. Eserciti DevOps Bootcamp con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare DevOps Bootcamp?

Non è richiesta alcuna esperienza precedente. DevOps Bootcamp su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 1 di 4.

Quanto tempo richiede la lezione «Scrivere Dockerfile essenziali ed entrypoint shell»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione DevOps Bootcamp?

Sì. Ogni lezione DevOps Bootcamp include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. Scrivere Dockerfile essenziali ed entrypoint shell
  2. Creare template di configurazione con envsubst e heredoc
  3. Gestire risorse cloud tramite CLI e jq
  4. Probe di salute, controlli di readiness e cicli di attesa
← Torna a DevOps Bootcamp