Container image security: vulnerabilità nelle immagini Docker che usi ogni giorno

Container image security: vulnerabilità nelle immagini Docker che usi ogni giorno

Prendi un'immagine php:8.0-fpm costruita un paio d'anni fa, pinnata a quel tag e mai più ricostruita, e scansionala oggi: ti ritrovi decine di CVE, molte delle quali HIGH o CRITICAL, quasi tutte in librerie di sistema che il tuo codice applicativo non chiama mai direttamente. Non è un caso limite, è la norma dei sistemi di produzione "quasi legacy" che incontro negli audit di container security: l'applicazione viene rilasciata di continuo, ma l'immagine di base sotto di essa, con il suo runtime e il suo sistema operativo, resta congelata al giorno in cui qualcuno ha scritto FROM nel Dockerfile la prima volta. E più quel giorno è lontano, peggio è: un'immagine ferma da quattro o cinque anni non ha "qualche" vulnerabilità in più di una aggiornata, ne ha un ordine di grandezza in più, perché ogni mese che passa la community pubblica nuove CVE per le librerie che quell'immagine si porta dentro congelate.

Il punto che quasi nessun team mette a fuoco è dove vivono quelle vulnerabilità: in OpenSSL, glibc, libxml2, zlib, librerie di sistema, non nel codice PHP. La percezione tipica è "ma noi OpenSSL non lo usiamo direttamente". La realtà è che se l'immagine contiene un OpenSSL vulnerabile e l'applicazione ha un qualunque code path che lo tocca indirettamente, una connessione HTTPS in uscita, l'elaborazione di un certificato, qualsiasi chiamata che passa da libcurl, la vulnerabilità è raggiungibile e quindi potenzialmente sfruttabile. Lo scanner che uso come riferimento per misurare tutto questo è Trivy, lo scanner open source mantenuto da Aqua Security, ma lo scanning è solo il termometro: il problema vero, e il filo conduttore di questo articolo, è la catena di fiducia che lega l'immagine in produzione alla sua origine.

Qui descrivo il processo completo che applico per riportare sotto controllo le immagini Docker di un progetto: la scelta di base image manutenute, il multi-stage build per ridurre la superficie d'attacco, l'esecuzione come utente non-root, lo scanning automatico in CI, il refresh sistematico delle immagini base e, soprattutto, come costruire una chain of trust verificabile dal FROM del Dockerfile fino al deploy. Niente di tutto questo richiede attenzione continua del team una volta impostato: è disciplina che si automatizza.

Il problema strutturale: immagini che accumulano debito nel tempo

Il pattern che osservo nei progetti è prevedibile e riproducibile. Il team sceglie un'immagine base (tipicamente php:8.2-fpm o php:8.3-fpm-alpine, ma sui progetti più datati capita di trovare ancora php:7.4-fpm o vecchie alpine), scrive il Dockerfile una volta, lo deploya in produzione, e si ferma lì. L'immagine gira in produzione per mesi o anni, senza essere mai ricostruita a partire da una base aggiornata. Nel frattempo, la community Debian/Alpine rilascia patch di sicurezza per le librerie di sistema inclusi nella base - patch che non arrivano mai nel container in produzione perché il container non viene mai rebuildato.

Il problema si aggrava perché la maggior parte dei team non distingue fra immagine applicativa (il codice della loro app, che rilasciano frequentemente) e immagine base (il runtime PHP + OS, che tende a essere trattato come "cosa che c'è"). Quando rilasciano una nuova versione della loro app, fanno docker build che parte da un Dockerfile che dice FROM php:8.2-fpm. Se il tag php:8.2-fpm è cambiato da ultimo pull (perché la community ha rilasciato aggiornamenti sul registry), il nuovo build prende la versione aggiornata. Se il tag non è cambiato (o se c'è Docker cache locale), il nuovo build usa la vecchia versione. In produzione, senza disciplina esplicita di refresh base image, si finisce con immagini vecchie di mesi anche se il codice applicativo è stato rilasciato ieri.

La documentazione Docker sulle best practice per Dockerfile copre la dimensione tecnica ma tende a trascurare la dimensione organizzativa - la disciplina di trattare le immagini base come dipendenze che invecchiano e richiedono update sistematico. Il pattern operativo che applico sui clienti colma esattamente questo gap.

La scelta della base image: alpine vs debian-slim vs distroless

La prima decisione architetturale è quale base image usare. Le tre opzioni principali per applicazioni PHP sono Alpine Linux (php:8.3-fpm-alpine), Debian slim (php:8.3-fpm-bookworm-slim), e immagini distroless.

Alpine è la scelta storicamente popolare per container "minimal". L'immagine base è circa 5 MB vs i 30+ MB di Debian slim. Usa musl libc invece di glibc. La superficie di attacco è minore perché contiene meno pacchetti di sistema. Il trade-off: alcune librerie PHP hanno comportamento leggermente diverso su musl rispetto a glibc (estensioni specifiche come intl richiedono build più attento), e il parco pacchetti APK di Alpine è meno ricco del parco APT di Debian. In produzione reale ho riscontrato 2-3 edge case di compatibilità in 18 mesi su quattro progetti Alpine - gestibili ma reali.

Debian slim è la scelta più conservativa. L'immagine è più grande ma il comportamento è identico a un server Debian standard, senza sorprese di compatibilità. Gli aggiornamenti di sicurezza arrivano tipicamente più rapidamente sul parco Debian rispetto al parco Alpine per librerie meno comuni. Il trade-off: immagine più pesante, più superficie di attacco, più CVE potenziali da gestire.

Le immagini distroless (mantenute da Google Container Tools) sono la terza opzione, meno comune ma interessante per contesti ad alta sicurezza. L'immagine include solo il runtime dell'applicazione e nessun shell, nessun package manager, nessun utility di debugging. Superficie di attacco minima in assoluto - un attaccante che ottiene esecuzione codice nel container non ha nemmeno bash o sh per muoversi. Il trade-off: debugging in produzione è significativamente più difficile (non puoi docker exec -it e navigare), e non tutte le runtime PHP hanno immagini distroless ufficiali, richiedendo build custom.

La scelta che applico di default sui clienti PMI italiani è Debian slim pinned a specific sha256 digest, con refresh settimanale automatico. Il ragionamento è: la differenza di 25 MB rispetto ad Alpine è irrilevante in contesti di PMI (non si gestiscono decine di migliaia di container), la compatibilità maggiore di Debian riduce il rischio di bug sottili di runtime, e la disciplina di refresh settimanale (invece del pinning a singola versione) mantiene le CVE sotto controllo.

Multi-stage build: la tecnica che riduce la superficie di attacco

Il pattern di multi-stage build è uno dei singoli miglioramenti più impattanti sulla sicurezza delle immagini Docker. L'idea è separare l'immagine di build (che ha tutti i compiler, package manager, dev dependencies necessari per compilare l'applicazione) dall'immagine di runtime (che ha solo il minimo necessario per eseguirla). L'immagine finale in produzione non contiene strumenti di build - riduce sia dimensione che superficie di attacco.

Un Dockerfile multi-stage tipico per Laravel:

# Dockerfile - multi-stage build per Laravel
# syntax=docker/dockerfile:1.4

# STAGE 1: build - ha tutti i tool necessari
FROM php:8.3-fpm-bookworm AS builder

RUN apt-get update && apt-get install -y \
    git \
    unzip \
    libzip-dev \
    libicu-dev \
    libpng-dev \
    && rm -rf /var/lib/apt/lists/*

RUN docker-php-ext-install \
    pdo_mysql \
    opcache \
    zip \
    intl \
    gd \
    bcmath

COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer

WORKDIR /app
COPY composer.json composer.lock ./
RUN composer install --no-dev --no-scripts --no-autoloader --prefer-dist

COPY . .
RUN composer dump-autoload --optimize --no-dev

# Opzionale: pre-compila view, config, route cache
RUN php artisan config:cache \
    && php artisan route:cache \
    && php artisan view:cache

# STAGE 2: runtime - solo il minimo necessario
FROM php:8.3-fpm-bookworm-slim AS runtime

# Installa solo runtime packages (no -dev)
RUN apt-get update && apt-get install -y \
    libzip4 \
    libicu72 \
    libpng16-16 \
    && rm -rf /var/lib/apt/lists/* \
    && docker-php-ext-install pdo_mysql opcache zip intl gd bcmath

# Crea utente non-root
RUN groupadd -g 1000 app && useradd -u 1000 -g app -s /bin/bash -m app

WORKDIR /var/www/html
COPY --from=builder --chown=app:app /app .

USER app

HEALTHCHECK --interval=15s --timeout=5s --start-period=30s --retries=3 \
    CMD php /var/www/html/docker/healthcheck.php || exit 1

EXPOSE 9000
CMD ["php-fpm", "-F"]

Tre dettagli meritano attenzione. Primo: l'immagine di builder usa php:8.3-fpm-bookworm (non slim) perché ha più tool disponibili per la build. L'immagine finale usa -bookworm-slim per ridurre superficie. Secondo: gli apt-get install di runtime includono solo i pacchetti lib runtime, senza i corrispondenti -dev. Questo è importante: un attaccante che ottiene esecuzione codice non ha gcc, make, libtool per compilare exploit custom. Terzo: l'utente non-root app con UID/GID 1000 esegue l'applicazione. Se il container viene compromesso, l'attaccante è limitato nei permessi - non può modificare file di sistema, non può bindare porte privilegiate.

Esecuzione come non-root: il principio del minimo privilegio

L'esecuzione come utente non-root è probabilmente la misura di hardening con il miglior rapporto beneficio/effort su container PHP. Il default delle immagini php:*-fpm ufficiali è di eseguire PHP-FPM come root (master) che poi spawna worker come www-data. Questa architettura è storicamente sensata su server bare-metal ma è subottimale in container dove la separazione dei privilegi del kernel è diversa.

Il pattern corretto è far girare sia il master che i worker di PHP-FPM come utente non-root. Il Dockerfile sopra crea l'utente app (UID 1000) e lo usa con USER app. PHP-FPM deve essere configurato per non richiedere capability elevate:

; php-fpm.d/www.conf - configurazione per non-root
[www]
listen = /run/php/php-fpm.sock
listen.owner = app
listen.group = app
listen.mode = 0660

user = app
group = app

pm = dynamic
pm.max_children = 20
pm.start_servers = 4
pm.min_spare_servers = 2
pm.max_spare_servers = 8
pm.max_requests = 500

Il file PID /run/php/php-fpm.pid deve essere in una directory scrivibile dall'utente app. Il socket deve essere configurato con owner corretto. Queste sono modifiche piccole ma essenziali - senza di esse, PHP-FPM fallirà silenziosamente al boot come non-root.

Un controllo di verifica da integrare in CI per assicurarsi che l'immagine sia effettivamente non-root:

# Verifica che il container giri come non-root
USER_IN_IMAGE=$(docker run --rm myapp:latest id -u)
if [ "$USER_IN_IMAGE" = "0" ]; then
    echo "ERRORE: container gira come root"
    exit 1
fi

Stai cercando un Consulente Informatico esperto per ristrutturare la pipeline Docker del tuo team con security hardening sistematico, multi-stage build ottimizzati e processo di refresh automatico delle base image? Nel mio profilo professionale trovi l'esperienza concreta su container security, Kubernetes, orchestrazione Docker e pipeline DevSecOps per PMI italiane.

Lo scanning automatico in CI: Trivy come baseline

Il secondo layer di difesa è la scansione automatica delle immagini prima del deploy. Trivy è il tool che uso come baseline per tutti i clienti - open source, maintained attivamente, supporta ampia varietà di formati (OS packages, language dependencies, IaC, secrets hardcoded).

L'integrazione in GitHub Actions:

# .github/workflows/container-security.yml
name: Container Security
on:
    pull_request:
        paths:
            - "Dockerfile"
            - "docker-compose.yml"
            - "composer.lock"
            - "package-lock.json"
    push:
        branches: [main]
    schedule:
        - cron: "0 2 * * 1" # Lunedi notte rescan

jobs:
    build-and-scan:
        runs-on: ubuntu-latest
        timeout-minutes: 15
        permissions:
            contents: read
            security-events: write
        steps:
            - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683  # v4.2.2
            - name: Build image
              run: docker build -t myapp:${{ github.sha }} .
            - name: Scan with Trivy
              uses: aquasecurity/trivy-action@master
              with:
                  image-ref: myapp:${{ github.sha }}
                  format: sarif
                  output: trivy-results.sarif
                  severity: CRITICAL,HIGH
                  ignore-unfixed: true
                  exit-code: 1
            - name: Upload results
              if: always()
              uses: github/codeql-action/upload-sarif@v3
              with:
                  sarif_file: trivy-results.sarif

Due dettagli operativi. Primo: severity: CRITICAL,HIGH filtra solo vulnerabilità di severity alta. Senza questo filtro, Trivy riporterebbe centinaia di CVE (inclusa la lunga coda di LOW e MEDIUM su librerie di sistema) che sommergono il team di rumore. Secondo: ignore-unfixed: true esclude le CVE per le quali non esiste ancora fix disponibile - report-arle sarebbe actionable zero, e il team perde fiducia nel sistema se riceve alert che non sono risolvibili.

Il schedule settimanale a Lunedì notte è importante. Le CVE emergono nel tempo: un'immagine buildata oggi può essere pulita, ma dopo tre settimane può aver accumulato CVE HIGH in librerie di sistema. Il rescan settimanale rileva queste situazioni prima che diventino critiche e prima che un deploy sia necessario per applicazioni fix. Gli alert del rescan scheduled generano PR automatiche per rebuild dell'immagine con base image aggiornata.

Il refresh automatico delle base image: il pattern che risolve il problema strutturale

L'elemento che separa una pipeline Docker "OK" da una "resiliente nel tempo" è il refresh automatico delle base image. Il pattern è questo: settimanalmente, un job CI re-esegue il build dell'immagine da zero (senza cache), pullando la versione più recente del base image (php:8.3-fpm-bookworm-slim al suo tag, che potrebbe essere avanzato dalla community). Se il rebuild produce un'immagine con meno CVE (o senza nuove CVE emerse), viene taggata e preparata per il deploy nella successiva release. Se produce un'immagine con comportamento cambiato (test di smoke fallisce), invece, viene alert al team per review.

L'implementazione:

# .github/workflows/weekly-refresh.yml
name: Weekly Base Image Refresh
on:
    schedule:
        - cron: "0 3 * * 1"
    workflow_dispatch:

jobs:
    refresh:
        runs-on: ubuntu-latest
        steps:
            - uses: actions/checkout@...
            - name: Pull latest base image
              run: docker pull php:8.3-fpm-bookworm-slim
            - name: Rebuild without cache
              run: docker build --no-cache -t myapp:refresh-${{ github.run_number }} .
            - name: Smoke test nuova immagine
              run: |
                  docker run --rm myapp:refresh-${{ github.run_number }} \
                      php artisan --version
            - name: Scan with Trivy
              run: trivy image --exit-code 1 --severity CRITICAL,HIGH myapp:refresh-${{ github.run_number }}
            - name: Open PR with new base digest if changed
              run: |
                  NEW_DIGEST=$(docker inspect php:8.3-fpm-bookworm-slim --format='{{index .RepoDigests 0}}')
                  if ! grep -q "$NEW_DIGEST" Dockerfile; then
                      # aggiorna Dockerfile con nuovo digest
                      sed -i "s|FROM php:8.3-fpm-bookworm-slim.*|FROM $NEW_DIGEST|" Dockerfile
                      git checkout -b refresh/base-image-$(date +%Y%m%d)
                      git add Dockerfile
                      git commit -m "Refresh base image to latest"
                      git push
                      gh pr create --title "Refresh base image (weekly)" --body "..."
                  fi

Questo pattern trasforma le immagini base da "cose statiche che invecchiano silenziosamente" a "dipendenze gestite come codice". Il team riceve settimanalmente (al massimo) una PR di update, la rivede, mergeia, triggera un deploy. Le CVE vengono applicate entro una settimana dalla loro disponibilità upstream - SLA di security molto più stretto di quello tipico senza questa automation. Lo stesso principio di pinning esplicito e aggiornamento controllato che descrivo per l'aggiornamento automatico delle dipendenze PHP con Dependabot e Renovate si applica, un livello più sotto, alle immagini di base.

La catena di fiducia: dal FROM del Dockerfile al deploy

Ridurre le CVE è metà del lavoro. L'altra metà è poter rispondere a una domanda che pochi team si pongono: come faccio a sapere che l'immagine che gira in produzione è davvero quella che ho costruito io, dalla sorgente che credo, senza manomissioni? È la differenza tra fidarsi per assunzione e fidarsi per verifica, ed è il cuore della container supply chain security. La catena ha diversi anelli, e ognuno ha un punto debole reale.

Il tag mutabile. FROM php:8.3-fpm-bookworm-slim non identifica un'immagine: identifica un'etichetta che la community può ripuntare in qualsiasi momento. Due build dallo stesso Dockerfile a una settimana di distanza possono produrre immagini diverse. Il primo anello di fiducia è pinnare al digest sha256 (FROM php:8.3-fpm-bookworm-slim@sha256:...), così che l'immagine di partenza sia immutabile e verificabile, e gestire l'avanzamento del digest come codice, con il refresh automatico visto sopra.

La firma dell'immagine. Storicamente Docker offriva Docker Content Trust (DCT), basato su Notary v1. È in dismissione: il servizio Notary v1 su notary.docker.io chiude l'8 dicembre 2026, dopo che da agosto 2025 i certificati DCT delle Docker Official Images hanno iniziato a scadere, con un utilizzo ormai sotto lo 0,05% dei pull (guida ufficiale Docker alla migrazione). Lo standard di fatto oggi è la firma OCI-native con Sigstore e Cosign (firma keyless via identità OIDC, CA Fulcio, transparency log Rekor) o con Notation del Notary Project. La firma risponde alla domanda chi ha costruito l'immagine.

La provenienza. La firma dice chi, l'attestazione dice come. Le attestazioni in formato in-toto e la provenance descritta dal framework SLSA documentano da quale sorgente e con quale builder l'immagine è stata costruita. Il livello pratico da puntare è SLSA Build L3, che richiede una provenance firmata da una piattaforma di build isolata con chiavi effimere inaccessibili agli step di build: è ciò che evita la trappola classica, ovvero la provenance generata dallo stesso script di build, che un attaccante con accesso al repository può semplicemente falsificare. Su GitHub, actions/attest-build-provenance porta a SLSA L2 in pochi minuti.

L'SBOM. Una distinta dei materiali (Software Bill of Materials) elenca cosa c'è dentro l'immagine. È ciò che, quando esce la prossima CVE critica in una libreria diffusa, ti permette di sapere in minuti quali immagini sono affette, invece di scoprirlo a incidente avvenuto.

L'enforcement al deploy. Tutto questo è inutile se poi in produzione si schiera qualunque immagine. L'ultimo anello è la policy di ammissione: su Kubernetes, controller come Kyverno (che verifica le firme Cosign) o Gatekeeper rifiutano il deploy di immagini non firmate o prive di provenance valida. Il controllo minimo ma decisivo è che il digest dell'artefatto schierato coincida con quello attestato.

Ogni anello di questa catena trasforma un "mi fido che sia così" in un "verifico che sia così". La firma da sola non basta, lo scanning da solo non basta: è la combinazione, dal digest pinnato alla policy di ammissione, a chiudere il cerchio.

Chi parte da zero non deve costruire tutto a mano, esistono base image che arrivano già con firma, attestazioni di provenance e SBOM. Ma il principio resta lo stesso a qualunque scala, ed è lo stesso ragionamento che applico alla sicurezza della pipeline CI/CD contro injection e supply chain attack e al pinning e audit dei modelli nella supply chain dell'AI: la pipeline e il registry sono superfici d'attacco esattamente quanto il codice applicativo, e vanno trattate con la stessa serietà.

L'audit pattern: cosa cerco quando arrivo in un progetto Docker-based

Quando arrivo su un progetto Docker maturo per fare audit di sicurezza, il processo è strutturato. Il checklist è in otto punti.

Primo: scan Trivy di tutte le immagini in produzione. Produce la baseline numerica. Secondo: review dei Dockerfile per pattern subottimali (utente root, tool di build nell'immagine finale, secrets hardcoded, cache non ottimizzata). Terzo: review dei tag utilizzati - pinned a digest SHA o tag mutabili? L'uso di latest o 8.3 (senza minor) è un rischio. Quarto: analisi della pipeline CI/CD per verificare se c'è scanning automatico e dove. Quinto: verifica della frequenza di rebuild - quando è stata buildata l'ultima volta l'immagine in produzione? Sesto: review dei privilegi runtime - il container gira come root? Ha capability privilegiate? Monta volumi sensitive? Settimo: network policy - il container può comunicare con qualunque altro container, o c'è segmentazione? Ottavo: secrets management - le credenziali di database sono in environment variables o in un vault dedicato?

La combinazione di questi otto check produce tipicamente un report con raccomandazioni per priorità. Il pattern più ricorrente è che i punti 4, 5 e 7 sono quasi sempre carenti: scanning assente, frequenza di rebuild "mai", e network flat dove ogni container può contattare ogni altro, la stessa superficie piatta che rende un container escape verso l'host molto più dannoso di quanto dovrebbe essere.

Le metriche post-intervento: come misurare il successo

Dopo 3-6 mesi dall'intervento, le metriche che monitoro sono quattro. Primo: numero medio di CVE HIGH+CRITICAL per immagine in produzione (target: <10 per immagine, con zero critiche senza fix). Secondo: età della base image più vecchia in produzione (target: <14 giorni). Terzo: percentuale di immagini che girano come non-root (target: 100%). Quarto: percentuale di build che passano lo scan Trivy al primo tentativo (indicatore di quanto la pipeline è sostenibile - target >80%).

Quando questo processo entra a regime, l'impatto è netto e misurabile su tutte e quattro le metriche: la media di CVE per immagine crolla, l'età della base image più vecchia scende da "mai aggiornata" a pochi giorni, la quota di container non-root arriva al 100% e la pipeline diventa sostenibile, con un effort di manutenzione dell'ordine di poche ore a settimana. Il punto non sono i numeri assoluti, che dipendono dal punto di partenza, ma la direzione: da un debito che cresce silenziosamente a un debito che resta sotto una soglia controllata.

Il pattern più interessante emerso è che l'adozione di un refresh settimanale automatico delle base image riduce radicalmente la pressione sul team. Prima del refresh automatico, il team doveva rispondere reattivamente alle CVE man mano che emergevano - pattern stressante e scomposto. Dopo, il ciclo settimanale assorbe la maggioranza delle CVE in modo invisibile (una PR che si mergeia dopo review rapida), e solo le CVE veramente critiche che emergono fra i refresh settimanali richiedono intervento ad hoc. Lo stesso pattern è applicabile all'aggiornamento automatico dei container Docker in produzione senza downtime che descrivo in un articolo dedicato - scanning e refresh delle immagini lavorano insieme al deploy zero-downtime per chiudere il cerchio.

php:8.3-fpm-bookworm-slim è una base image sicura?

Sì, php:8.3-fpm-bookworm-slim è oggi una delle scelte più solide per PHP in produzione, ma a due condizioni precise: che la pinni a un digest sha256 specifico invece di affidarti al tag mobile, e che la ricostruisca con cadenza settimanale per assorbire le patch di sicurezza Debian. Il punto che sfugge a molti team è che un tag non è "sicuro" o "insicuro" in assoluto: lo stesso bookworm-slim ricostruito ieri ha pochissime CVE, mentre fermo da otto mesi ne accumula a decine nelle librerie di sistema, esattamente come accadeva sulle immagini php:8.2-fpm che ho trovato negli audit.

La sicurezza di una base image non è una proprietà del tag, ma una funzione della sua età. La domanda giusta non è "quale immagine è sicura", ma "ogni quanto la ricostruisco".

Meglio alpine, debian-slim o distroless per un'app PHP?

Dipende dal contesto, ma la mia scelta di default per le PMI è Debian slim (php:8.3-fpm-bookworm-slim): compatibilità identica a un server Debian standard, niente sorprese di runtime con musl, patch di sicurezza tempestive. php:8.3-fpm-alpine resta valida quando la dimensione dell'immagine conta davvero, accettando qualche edge case di compatibilità su estensioni come intl. Le immagini distroless (Google Container Tools) danno la superficie d'attacco minima in assoluto, al prezzo di un debugging in produzione molto più scomodo, perché non hanno nemmeno una shell.

Come riduco le CVE in un'immagine Docker php-fpm?

Quattro mosse, in ordine di impatto: parti da una base slim manutenuta e pinnala a digest; usa un multi-stage build per tenere fuori dall'immagine finale i tool di compilazione; fai girare il container come utente non-root; aggiungi uno scan Trivy in CI che blocca il merge sulle CVE critiche. Negli interventi che ho descritto qui, questa combinazione ha portato la media da decine di CVE per immagine a poche unità, con zero critiche in produzione.

Ogni quanto va ricostruita un'immagine Docker in produzione?

Al massimo ogni sette giorni, in modo automatico. La metrica operativa che monitoro è l'età della base image più vecchia in produzione, con un target sotto i quattordici giorni: il refresh settimanale automatico la tiene comodamente entro quella soglia e assorbe la maggioranza delle CVE prima ancora che qualcuno se ne accorga.

Se gestisci applicazioni containerizzate in produzione ma non hai un processo sistematico di container security - scanning, base image refresh, runtime privilege management - oppure hai subito un alert di CVE critica che hai dovuto gestire manualmente con il cuore in gola, contattami per un audit: in una settimana di lavoro analizzo il tuo stack Docker attuale, identifico i gap prioritari di sicurezza, implemento il processo di scanning in CI, rielaboro i Dockerfile con multi-stage e non-root, imposto il refresh automatico settimanale - con l'obiettivo che dopo il mio intervento la gestione delle CVE nei container sia un processo silenzioso e automatico, non un'emergenza ricorrente che interrompe il lavoro del team.

Ultima modifica: