C'è una varietà di tecniche coinvolte, senza un'unica soluzione. Probabilmente vorrai eseguire alcune delle seguenti operazioni:
Innanzitutto, ottimizza i livelli dell'immagine per il riutilizzo. Metti i passaggi che cambiano frequentemente più tardi nel Dockerfile per aumentare le probabilità che i primi livelli vengano memorizzati nella cache dalle build precedenti. Un layer riutilizzato verrà visualizzato come più spazio su disco in a docker image ls
, ma se si esamina il filesystem sottostante, sul disco verrà mai memorizzata solo una copia di ogni layer. Ciò significa che 3 immagini da 2 GB ciascuna, ma che hanno solo 50 MB diversi negli ultimi livelli della build, occuperanno solo 2,1 GB di spazio su disco, anche se l'elenco mostra che stanno usando 6 GB poiché tu sei doppio conteggio di ciascuno dei livelli riutilizzati.
Il riutilizzo dei livelli è il motivo per cui vedi le immagini con dipendenze di build che cambiano di rado installarle prima di copiare nel codice. Vedi qualsiasi esempio di Python che ha un modello come:
FROM python
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
# note how the code is copied only after the pip install
# since code changes but requirements.txt doesn't
COPY . .
CMD ["gunicorn", "app:app"]
Scegli un'immagine di base minima. Questo è il motivo per cui vedi le persone andare da ubuntu
a debian:slim
(le varianti slim sono più piccole, spediscono con meno strumenti) o addirittura alpine
. Ciò riduce le dimensioni del punto di partenza ed è molto utile se si estraggono costantemente nuove versioni dell'immagine di base. Tuttavia, se l'immagine di base cambia raramente, il riutilizzo dei livelli rimuove gran parte del vantaggio di un'immagine di base minima.
L'immagine di base più piccola che puoi scegliere è scratch
, che è niente, nessuna shell o librerie, ed è utile solo con i binari compilati staticamente. Altrimenti, scegli un'immagine di base che includa gli strumenti di cui hai bisogno senza molti strumenti di cui non hai bisogno.
Successivamente, qualsiasi passaggio che modifica o elimina un file deve essere combinato con i passaggi precedenti che creano quel file. In caso contrario, il filesystem a più livelli, che utilizza la funzione di copia su scrittura anche su cose come una modifica delle autorizzazioni dei file, avrà il file originale in un livello precedente e la dimensione dell'immagine non si ridurrà quando si rimuovono i file. Questo è il motivo per cui i tuoi rm
comandi non hanno alcun effetto sullo spazio su disco risultante. Invece, puoi concatenare i comandi, come:
RUN apt-get update \
&& apt-get install -y \
a-package \
wget \
&& ... \
&& apt-get purge -y wget \
&& rm -r a-build-dir \
&& apt-get purge -y a-package
Si noti che un uso eccessivo del concatenamento dei comandi può rallentare le build poiché è necessario reinstallare lo stesso set di strumenti ogni volta che un prerequisito cambia (ad esempio il codice che viene estratto con wget). Vedi più fasi di seguito per una migliore alternativa.
Qualsiasi file creato non necessario nell'immagine risultante deve essere eliminato, nel passaggio che lo crea. Ciò include cache dei pacchetti, registri, pagine man, ecc. Per scoprire quali file vengono creati in ogni livello, è possibile utilizzare uno strumento come wagoodman / dive (che non ho verificato personalmente e esprimerebbe cautela poiché funziona con accesso root completo sul tuo host) oppure puoi creare le tue immagini docker senza potare i contenitori intermedi e quindi visualizzare il diff con:
# first create and leave containers from any RUN step using options on build
docker image build --rm=false --no-cache -t image_name .
# review which layers use an unexpectedly large amount of space
docker image history image_name
# list all containers, particularly the exited ones from above
docker container ps -a
# examine any of those containers
docker container diff ${container_id}
# ... repeat the diff for other build steps
# then cleanup exited containers
docker container prune
Con ciascuno di quei contenitori intermedi, il diff mostrerà ciò si aggiungono file, modificate o eliminate dal fatto che la fase (questi sono indicati con A
, C
o D
prima di ogni nome di file). Ciò che sta mostrando diff è il filesystem di lettura / scrittura specifico del contenitore, che è qualsiasi file modificato dal contenitore dallo stato dell'immagine usando copy-on-write.
Il modo migliore per ridurre le dimensioni dell'immagine è eliminare eventuali componenti non necessari, come i compilatori, dall'immagine spedita. Per questo, le build multi-stage ti consentono di compilare in una fase e quindi copiare solo gli artefatti risultanti dalla fase di creazione in un'immagine di runtime che ha solo il minimo necessario per eseguire l'applicazione. Ciò evita la necessità di ottimizzare i passaggi di compilazione poiché non vengono forniti con l'immagine risultante.
FROM debian:9 as build
# still chain update with install to prevent stale cache issues
RUN apt-get update \
&& apt-get install -y \
a-package \
wget \
RUN ... # perform any download/compile steps
FROM debian:9-slim as release
COPY --from=build /usr/local/bin/app /usr/local/bin/app
CMD [ "/usr/local/bin/app" ]
Il multistadio è ideale con file binari compilati staticamente che è possibile eseguire con zero come immagine di base o passare da un ambiente di compilazione come JDK a un runtime come JRE. Questo è il modo più semplice per ridurre drasticamente le dimensioni dell'immagine pur avendo build veloci. È ancora possibile eseguire il concatenamento dei passaggi nella fase di rilascio se si dispone di passaggi che modificano o eliminano i file creati nei passaggi precedenti, ma per la maggior parte, COPY
da un altro livello, si isola la fase di rilascio da qualsiasi livello di livello sperimentato nelle fasi di generazione precedenti.
Nota, non consiglio di schiacciare le immagini poiché ciò riduce le dimensioni di un'immagine a spese dell'eliminazione del riutilizzo dei livelli. Ciò significa che le future build della stessa immagine richiederanno più traffico su disco e di rete per inviare gli aggiornamenti. Per tornare al primo esempio, la compressione può ridurre l'immagine da 2 GB a 1 GB, ma non 3 immagini potrebbero occupare 3 GB invece di 2,1 GB.
2.37
vs.1.47 GB