Hai un'immagine Docker da 1.2 GB, ci metti 12 minuti a buildare e alla fine scopri che dentro ci sono tool di sviluppo, cache di composer e chiavi SSH. Ti suona familiare? Noi lo vediamo continuamente nei progetti che ci arrivano in consulenza: immagini gonfie, build lente, vulnerabilità in produzione.
Un Dockerfile scritto male costa tempo e sicurezza. Un Dockerfile ottimizzato — con multi-stage build, layer caching e attenzione alla sicurezza — riduce le immagini del 70%, accelera le build del 40% e tiene fuori vulnerabilità evitabili.
Noi, di Meteora Web, non scriviamo Dockerfile solo per far funzionare le cose. Lo scriviamo perché quando deployi in produzione, ogni secondo di build e ogni MB in più sono un costo. Veniamo dalla contabilità: lo sappiamo bene.
Questa guida è pratica. Leggila, applicala, e il tuo prossimo Dockerfile sarà più veloce, più piccolo e più sicuro.
Perché il Multi-Stage Build è un cambio di paradigma
Il problema classico: per compilare un'applicazione PHP o Node ti servono tool di build (Composer, npm, compilatori C). Ma in produzione quei tool non servono a nulla. Pesano, introducono superficie d'attacco e allungano le build.
Il multi-stage build ti permette di avere più fasi (stage) in un unico Dockerfile. Ogni stage parte da una base diversa, ma solo l'ultimo stage finisce nell'immagine finale. Gli stage intermedi — dove installi tool, compili, scarichi dipendenze — vengono scartati.
Risultato: immagini snelle, build veloci, zero tool superflui in produzione.
Esempio concreto: un'applicazione PHP con Laravel
Vediamo un Dockerfile reale che parte da un'immagine enorme e la riduce con multi-stage.
# Stage 1: builder
FROM php:8.2-cli AS builder
WORKDIR /app
# Installiamo solo le estensioni necessarie per composer
RUN apt-get update && apt-get install -y --no-install-recommends \
libzip-dev \
unzip \
git \
&& docker-php-ext-install zip pdo_mysql \
&& rm -rf /var/lib/apt/lists/*
# Copiamo composer installato globalmente (o installiamo)
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
# Copia codice e installa dipendenze di produzione
COPY . .
RUN composer install --no-dev --optimize-autoloader
# Stage 2: produzione
FROM php:8.2-fpm-alpine AS production
WORKDIR /var/www/html
# Copiamo solo vendor e codice dallo stage builder
COPY --from=builder /app/vendor ./vendor
COPY --from=builder /app/app ./app
COPY --from=builder /app/config ./config
COPY --from=builder /app/public ./public
COPY --from=builder /app/routes ./routes
COPY --from=builder /app/resources ./resources
COPY --from=builder /app/storage ./storage
COPY --from=builder /app/.env .env
# Estensioni PHP minime per runtime
RUN docker-php-ext-install pdo_mysql
EXPOSE 9000
CMD ["php-fpm"]
L'immagine finale è almeno 200 MB più piccola rispetto a una singola fase con tool inclusi.
Layer Caching: sfrutta la cache, non combatterla
Docker costruisce le immagini per layer. Ogni istruzione RUN, COPY, ADD crea un layer. Se un layer non cambia, Docker lo riusa dalla cache. È il tuo migliore amico per la velocità.
Errori comuni:
- Copiare tutto il codice prima di installare le dipendenze — ogni cambiamento del codice invalida la cache dei layer successivi, compresi quelli pesanti come
RUN composer install. - Non ordinare le istruzioni per stabilità — metti prima ciò che cambia raramente (package.json, composer.lock, file di configurazione) e poi ciò che cambia spesso (codice).
Dockerfile ottimizzato per il caching
# Otteniamo il massimo dalla cache
FROM node:18-alpine AS builder
WORKDIR /app
# 1. Prima i file che cambiano raramente
COPY package.json package-lock.json ./
RUN npm ci --only=production
# 2. Poi solo i file che cambiano più spesso
COPY . .
RUN npm run build
Con questo approccio, se modifichi solo il codice sorgente (non le dipendenze), il layer RUN npm ci viene preso dalla cache e la build dura secondi, non minuti.
Sicurezza: ridurre la superficie d'attacco nel Dockerfile
Un'immagine Docker non è solo un contenitore: è un sistema operativo minimo. Ogni pacchetto installato è una potenziale vulnerabilità.
1. Usa immagini ufficiali e pinned
FROM php:8.2-fpm è meglio di FROM php:latest. Latest cambia e può romperti la build. Usa sempre tag specifici come php:8.2-fpm-alpine. Alpine Linux riduce la superficie d'attacco e le dimensioni.
2. Non eseguire mai come root
# Crea un utente non privilegiato
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
Se un attaccante compromette il processo PHP, non ha accesso root al container.
3. Pulisci cache e pacchetti temporanei nello stesso RUN
In un singolo RUN, installa, usa e rimuovi. I layer salvano solo lo stato finale.
RUN apt-get update && apt-get install -y --no-install-recommends \
libzip-dev \
&& docker-php-ext-install zip \
&& rm -rf /var/lib/apt/lists/*
4. Non copiare file sensibili (chiavi SSH, .env con credenziali)
Usa variabili d'ambiente, secret mount (Docker BuildKit) o vault esterni. Mai COPY id_rsa ..
5. Scansiona le immagini con tool come Trivy o Docker Scout
Integra la scansione nella pipeline CI/CD. Noi usiamo trivy image --severity HIGH,CRITICAL my-image prima di ogni deploy.
Best practice combinate: multi-stage + caching + sicurezza
Ecco un Dockerfile completo per un'applicazione Node.js con Next.js che unisce tutto.
# Stage builder
FROM node:18-alpine AS builder
WORKDIR /app
# Dipendenze (cambiano raramente)
COPY package.json package-lock.json ./
RUN npm ci --only=production
# Backup delle node_modules per lo stage finale
RUN cp -R node_modules /prod_modules
# Copia codice e build
COPY . .
RUN npm run build
# Stage produzione
FROM node:18-alpine AS production
# Utente non root
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
# Copia solo ciò che serve
COPY --from=builder /app/next.config.js ./
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/package.json ./
# Usa node_modules dal builder (produzione)
COPY --from=builder /prod_modules ./node_modules
# Esponi porta
EXPOSE 3000
USER appuser
CMD ["npm", "start"]
Analisi: immagine finale ~150 MB (contro ~1 GB con devDependencies), build veloce grazie a caching, zero pacchetti di sviluppo, utente non root.
Strumenti per controllare la qualità del Dockerfile
- Hadolint — lint per Dockerfile. Installa e lancia:
hadolint Dockerfile. Ti segnala best practice non rispettate. - Dive — analizza i layer delle immagini. Scopri cosa occupa spazio.
- Docker Scout (integrato in Docker Desktop) — scansiona vulnerabilità.
In sintesi — cosa fare adesso
- Riscrivi il tuo Dockerfile con multi-stage. Separa build e runtime.
- Ordina le istruzioni per massimizzare il caching: file stabili prima, codice dopo.
- Usa immagini ufficiali pinned e preferisci Alpine per ridurre superficie.
- Crea un utente non root e usalo nel CMD.
- Pulisci cache e rimuovi tool di build nello stesso layer.
- Scansiona l'immagine con Trivy o Docker Scout prima del deploy.
Se applichi questi sei punti, il tuo prossimo deploy sarà più veloce, più sicuro e costerà meno in termini di risorse. Come dicevamo: un sito (o un container) si misura in fatturato e tempi, non in complimenti. Buona containerizzazione.
Sponsored Protocol