in Architetture Software, Informatica

Docker – Multi-Stage builds

Reading Time: 3 minutes

Il builder pattern in docker è composto da due DockerFile: uno per la fase sviluppo e uno per la produzione. Solitamente all’interno della versione di sviluppo viene inserito tutto il necessario per la creazione dell’applicazione, mentre la versione di produzione contiene solo gli elementi necessari per l’esecuzione del container.

Automatizzando il processo si può pensare di creare uno script (ad esempio in powershell, oppure bash) che crea prima la versione di sviluppo, copiando i file e tutto il necessario all’esecuzione all’interno di una cartella, e successivamente partendo da questa cartella costruisce la versione di produzione (con i soli file di cui effettivamente abbiamo bisogno).

Solitamente i file che vengono utilizzati prendono il nome di Dockerfile.dev e DockerFile. Il DockerFile accetta, ad esempio, solo gli artefatti statici che sono stati costruiti dal DockerFile.Dev escludendo eventuali librerie e moduli non necessari in ambiente di produzione.

Considerando, ad esempio, un ambiente linux, un possibile script bash potrebbe essere:

#!/bin/bash

echo Building nodewebapp from Dockerfile.dev

docker build -t nodewebapp:v1 -f Dockerfile.dev .

echo Creating Conatiner Out of Image
docker container create --name extract nodewebapp:v1

docker container cp extract:/usr/src/app/WebApp/dist ./dist
docker container rm -f extract

echo Building nodewebapp version 2
docker build --no-cache -t nodewebapp:v2 .

rm ./dist

E’ chiaro come la creazione della versione finale dell’immagine (in questa coso la versione 2 viene effettuata creando prima la versione 1, che ha una dimensione sicuramente maggiore rispetto a quella finale.

Considerazioni sul Builder Pattern

Sicuramente l’utilizzo di uno script per la generazione dell’immagine in combinazione con la creazione di due Dockerfile (Dockerfile e Dockerfile.dev) può essere funzionale e consente di creare l’immagine finale, in base a step successivi. Sebbene funzionale, questa soluzione ha alcuni difetti:

  • mantenimento di due Dockerfile differenti, con il rischio di creare problemi di versionamento
  • per arrivare all’immagine finale viene creata sempre e comunque un’immagine intermedia, che occupa spazio all’interno dell’ambiente di sviluppo

Multi-stage Builds

Docker mette a disposizione i multi-stage builds per la definizione delle fasi di creazione delle immagini. In pratica, si tratta di combinare tra loro piu’ piu’ istruzioni FROM per effettuare la build dell’immagine finale. Ogni nuova istruzione FROM inizia da dove è arrivata la FROM precedente, lasciando di fatto tutto quello che non serve nell’immagine precedente.

Un DockerFile per i multi-stage builds è caratterizzato dalla presenza della parola chiave AS nella definizione del FROM. In questo modo è possibile referenziare all’interno di uno step successivo l’immagine precedentemente creata.

# first stage, that is the Builder
FROM node:8-alpine AS ts-sample-builder
WORKDIR /app
COPY . .
RUN npm install
RUN npm run clean
RUN npm run build

# Second stage, that creates an image for production
FROM node:8-alpine AS ts-sample-prod
WORKDIR /app
COPY --from=ts-sample-builder ./app/dist ./dist
COPY package* ./
RUN npm install --production
CMD npm start

Nell’esempio precedente vengono definire due immagini che possono essere utilizzate come base per gli step successivi. In particolar modo sono state definite le immagini ts-sample-builder e l’immagine ts-sample-prod.

Vantaggi nell’utilizzo di multi-stage builds

Il vantaggio fondamentale dell’utilizzo del multi-stage builds è quello di avere tutte le fasi di build raccolte all’interno di unico Dockerfile. Nella build multi-stage, possiamo usare il flag --from per copiare i file da uno stage a un all’altro, senza dover far riferimento ad immagini intermedie.

Inoltre lo sviluppo è raggruppato all’interno di steps successivi: possiamo utilizzare il flag –target per fermarci ad una specifica fase. Ad esempio:

docker build --target ui-build -t webapp: v1.

la fase di build si fermerà al target con nome ui-build.

Nel builder pattern, infatti, è necessario ricorrere all’utilizzo di un artifatto intermedio per la creazione della build di produzione.

In sintesi:

  • La creazione di immagini Docker in maniera efficiente è molto importante per download più veloci ed un utilizzo ridotto delle risorse
  • Il Pattern Builder aiuta a costruire immagini efficienti in una certa misura, ma comporta complessità
  • Docker ha introdotto build in più fasi per risolvere i problemi di complessitià del Pattern Builder
  • Con le multi-stage builds possiamo avere più fasi e copiare solo i livelli necessari per l’immagine finale dalla fase precedente.