0Pricing
Linux Command Line & Bash Scripting Mastery · Lezione

Scrivere Dockerfile essenziali ed entrypoint shell

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

Scrivere Dockerfile essenziali ed entrypoint shell è una lezione Linux Command Line & Bash Scripting Mastery 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 Linux Command Line & Bash Scripting Mastery, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso Linux Command Line & Bash Scripting Mastery 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.

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 Linux Command Line & Bash Scripting Mastery, passa a CoddyKit PRO. Il corso Linux Command Line & Bash Scripting Mastery 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 Linux Command Line & Bash Scripting Mastery 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 Linux Command Line & Bash Scripting Mastery?

Non è richiesta alcuna esperienza precedente. Linux Command Line & Bash Scripting Mastery 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 Linux Command Line & Bash Scripting Mastery?

Sì. Ogni lezione Linux Command Line & Bash Scripting Mastery 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 Linux Command Line & Bash Scripting Mastery