Introduzione
I Docker multistage build rappresentano una delle tecniche più efficaci per ottimizzare le dimensioni delle immagini container e migliorare la sicurezza delle tue applicazioni. Se hai già familiarità con la creazione di Dockerfile (qui trovi la guida base per iniziare con Docker), questa guida avanzata ti mostrerà come portare le tue competenze al livello successivo.
Per comprendere meglio il concetto, immagina di dover costruire un’applicazione: hai bisogno di strumenti per la compilazione (compiler, build tools, dipendenze di sviluppo), ma una volta che l’applicazione è pronta, questi strumenti non servono più nell’ambiente di produzione. I multistage build ti permettono di utilizzare un’immagine “pesante” per compilare il codice, e poi copiare solo il risultato finale in un’immagine “leggera” per l’esecuzione.
Questo approccio è come avere due ambienti separati: uno per la costruzione (con tutti gli strumenti necessari) e uno per l’esecuzione (con solo quello che serve per far girare l’applicazione).
Cos’è un Multistage Build Docker
Un multistage build è una tecnica che permette di utilizzare multiple istruzioni FROM
all’interno dello stesso Dockerfile, creando essenzialmente più fasi di build separate. Ogni fase può utilizzare una base image diversa e può copiare artefatti dalle fasi precedenti.
Vantaggi Principali
-
Riduzione dimensioni immagine: Eliminazione di dipendenze di build non necessarie in produzione
-
Miglioramento sicurezza: Esclusione di strumenti di sviluppo dall’immagine finale
-
Ottimizzazione performance: Immagini più leggere si scaricano e avviano più velocemente
-
Separazione delle responsabilità: Build e runtime environment separati
Sintassi e Struttura Base
Esempio Fondamentale
# Fase 1: Build stage - Immagine con strumenti di compilazione
FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
# Compila l'applicazione in un binario statico
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .
# Fase 2: Production stage - Immagine minima per esecuzione
FROM scratch AS production
# Copia SOLO il binario compilato, non tutto il resto
COPY --from=builder /app/main /
EXPOSE 8080
ENTRYPOINT ["/main"]
Risultato: Da un’immagine di build di ~300MB ottieni un’immagine finale di soli ~10MB!
Componenti Chiave
-
AS keyword: Assegna un nome alla fase per riferimenti futuri
-
COPY --from: Copia file/directory da una fase specifica
-
Multiple FROM: Ogni FROM inizia una nuova fase
Casi d’Uso Pratici Avanzati
Esempio Completo: API Go con Database
# Build stage - Ambiente di compilazione completo
FROM golang:1.21-alpine AS builder
# Installa dipendenze di sistema necessarie per la compilazione
RUN apk add --no-cache git ca-certificates tzdata
WORKDIR /app
# Copia e scarica dipendenze Go
COPY go.mod go.sum ./
RUN go mod download
# Copia tutto il codice sorgente
COPY . .
# Compila l'applicazione
# -ldflags="-w -s" riduce ulteriormente la dimensione
RUN CGO_ENABLED=0 GOOS=linux go build \
-ldflags="-w -s" \
-a -installsuffix cgo \
-o api-server ./cmd/server
# Production stage - Solo quello che serve per eseguire
FROM scratch
# Copia certificati per HTTPS (se necessario)
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
# Copia SOLO il binario compilato
COPY --from=builder /app/api-server /api-server
# Copia eventuali file di configurazione statici
COPY --from=builder /app/config/ /config/
EXPOSE 8080
ENTRYPOINT ["/api-server"]
Confronto dimensioni:
-
Immagine con tutto (golang:1.21-alpine + codice): ~350MB
-
Immagine multistage (solo binario): ~15MB
-
Riduzione del 95%!
Ottimizzazioni Avanzate
Layer Caching Strategico
# Ottimizzazione: copiare prima i file che cambiano meno
FROM node:18-alpine AS builder
WORKDIR /app
# 1. Copiare solo package files per cache layer
COPY package*.json ./
RUN npm ci
# 2. Copiare source code dopo
COPY src/ ./src/
COPY public/ ./public/
RUN npm run build
Build Arguments nei Multistage
ARG NODE_ENV=production
ARG BUILD_VERSION
FROM node:18-alpine AS base
ARG NODE_ENV
ENV NODE_ENV=${NODE_ENV}
FROM base AS development
RUN if [ "$NODE_ENV" = "development" ]; then npm install; fi
FROM base AS production
ARG BUILD_VERSION
LABEL version=${BUILD_VERSION}
COPY --from=builder /app/dist ./dist
Target Specifici e Build Condizionali
Build Target Selettivi
# Build solo fino allo stage di testing
docker build --target testing -t myapp:test .
# Build completo per produzione
docker build --target production -t myapp:prod .
# Build per sviluppo
docker build --target development -t myapp:dev .
Dockerfile con Branch Condizionali
FROM alpine:latest AS base
ARG ENVIRONMENT=production
FROM base AS development
RUN apk add --no-cache curl vim git
FROM base AS production
RUN apk add --no-cache ca-certificates
FROM ${ENVIRONMENT} AS final
WORKDIR /app
COPY app .
CMD ["./app"]
Debugging e Troubleshooting
Ispezione delle Fasi Intermedie
# Visualizzare tutte le fasi
docker build --target builder -t debug:builder .
docker run -it debug:builder sh
# Analisi dimensioni layer
docker history myimage:latest --human
Problemi Comuni e Soluzioni
-
COPY --from fallisce: Verificare che il path sorgente esista nella fase specificata
-
Stage non trovato: Controllare naming delle fasi con AS
-
Dimensioni non ottimizzate: Rivedere ordine delle operazioni e cache layer
Metriche e Monitoraggio
Confronto Dimensioni
# Prima del multistage
docker images myapp:single-stage
# REPOSITORY TAG SIZE
# myapp single-stage 1.2GB
# Dopo multistage
docker images myapp:multistage
# REPOSITORY TAG SIZE
# myapp multistage 45MB
Conclusioni
I Docker multistage build sono essenziali per:
-
Produzione di immagini ottimizzate e sicure
-
Separazione efficace tra ambiente di build e runtime
-
Miglioramento delle performance di deployment
-
Riduzione dei costi di storage e bandwidth
Implementando queste tecniche avanzate, potrai creare pipeline di container più efficienti e mantenibili, riducendo significativamente le dimensioni delle tue immagini Docker senza sacrificare funzionalità.