Immagini vs container
Nel mondo Docker ci sono due concetti centrali:
- immagini: i blueprint (template) immutabili che contengono:
- filesystem preconfigurato (runtime, tool, codice)
- metadati (es. comando di avvio predefinito)
- container: le istanze in esecuzione di un’immagine
- usano il filesystem definito dall’immagine
- aggiungono uno strato finale “vivo” (processo, log, eventuali file scritti a runtime)
Flusso tipico:
- scrivi un Dockerfile che descrive come costruire l’immagine
- esegui
docker build→ ottieni un’image - esegui
docker runsu quell’immagine → ottieni uno o più container
Esempio di relazione 1:N:
- 1 immagine
goals:latest - N container (
goals-1,goals-2, …) basati su quella immagine
I container non duplicano il codice dell’immagine: lo condividono in sola lettura, aggiungendo solo lo strato di esecuzione. Questo è uno dei motivi per cui sono leggeri.
Dockerfile → image → container
Il Dockerfile è un file testuale che contiene le istruzioni per creare l’immagine. Le istruzioni più comuni:
FROM: immagine base da cui partire (es.node:18-alpine,python:3.11)WORKDIR: directory di lavoro all’interno dell’immagine/container (es./app)COPY: copia file dal contesto di build (host) nell’immagineRUN: esegue comandi in fase di build (es.npm install)EXPOSE: documenta quali porte saranno usate nel containerCMD: comando da eseguire quando il container parte
Esempio concettuale per una app Node.js:
FROM node:18-alpineWORKDIR /app
COPY package.json .RUN npm install
COPY . .
EXPOSE 3000CMD ["node", "app.mjs"]Costruzione immagine:
docker build -t goals:latest .Esecuzione container:
docker run -p 3000:3000 goals:latestImmagini: immutabilità e rebuild
Una volta che un’immagine è stata buildata, è da considerarsi immutabile:
- il contenuto del filesystem dell’immagine è “fotografato” in quel momento
- se modifichi il codice sull’host (es. cambi
server.js), l’immagine non cambia
Per aggiornare l’immagine dopo modifiche al codice:
- cambi i file nel progetto
- esegui di nuovo
docker build(con lo stesso tag o un tag nuovo)
Esempio:
# prima builddocker build -t goals:latest .
# modifichi server.js
# rebuild (sovrascrive il tag latest con l’immagine aggiornata)docker build -t goals:latest .Solo i container creati dalla nuova immagine vedranno il nuovo codice.
Immagini layer-based e caching
Docker costruisce le immagini a layer, uno per ogni istruzione (più i layer dell’immagine base).
Per ogni RUN, COPY, ecc.:
- Docker esegue l’istruzione
- conserva il risultato in cache come layer
Alle build successive:
- se niente è cambiato per quell’istruzione → Docker riusa il layer da cache
- se qualcosa è cambiato (file copiati, comandi, ecc.) → ricostruisce:
- quel layer
- tutti i layer successivi
Questo ha implicazioni importanti sul Dockerfile. Esempio tipico (Node.js):
FROM node:18-alpineWORKDIR /app
COPY package.json .RUN npm install
COPY . .CMD ["node", "server.js"]Perché in questo ordine?
- se cambi solo il codice applicativo (file diversi da
package.json):COPY package.json .→ invariato → layer da cacheRUN npm install→ invariato → layer da cache (non reinstalli ogni volta)COPY . .→ cambia → solo da qui in poi Docker rifà il lavoro
Se invertissi l’ordine (prima COPY . ., poi RUN npm install), ogni cambio al codice
invaliderebbe anche il layer con npm install, rallentando le build.
Container: run, attach, logs
Per avviare un container:
docker run IMAGELe opzioni più usate:
-p host:container: pubblica una porta del container sull’host- es.
-p 3000:80→ porta 80 del container visibile sulocalhost:3000
- es.
-d: esegue in detached mode (in background)-it: modalità interattiva (stdin aperto + TTY), utile per:- shell interattive (
bash,sh) - app che leggono dallo standard input (es. script Python che chiedono input)
- shell interattive (
--rm: rimuove automaticamente il container quando si arresta--name goalsapp: assegna un nome leggibile al container
Esempi:
# web app, visibile su localhost:3000, rimossa alla chiusuradocker run -d --rm -p 3000:3000 --name goalsapp goals:latest
# sessione interattiva in un'immagine Pythondocker run -it python:3.11Gestione container:
docker ps # container in esecuzionedocker ps -a # anche quelli terminati
docker stop NAME_OR_IDdocker start NAME_OR_ID
docker logs NAME_OR_ID # log storicidocker logs -f NAME_OR_ID # segue i log in tempo reale (follow)
docker attach NAME_OR_ID # si collega all'output/INPUT di un container già in esecuzioneDifferenza attach/detach:
docker rundi default:- attach (il processo occupa il terminale) se non usi
-d - detach se usi
-d
- attach (il processo occupa il terminale) se non usi
docker startdi default:- detach, a meno che non usi
-a/-ai
- detach, a meno che non usi
Gestione di immagini e container
Elencare e rimuovere container
docker ps # solo in esecuzionedocker ps -a # tutti (anche terminati)
docker rm NAME_OR_ID # rimuove container terminatodocker rm -f NAME_OR_ID # forza lo stop e rimozioneSpesso ha senso usare --rm su docker run per evitare accumulo di container terminati:
docker run --rm -p 3000:3000 goals:latestElencare e rimuovere immagini
docker images # elenco immagini locali
docker rmi IMAGE_ID_OR_NAME # rimuove immagine (se non usata da container)
docker image prune # rimuove immagini "dangling"docker image prune -a # rimuove tutte le immagini non usate da containerUn’immagine non può essere rimossa se esiste ancora un container (anche terminato) che la usa.
Naming e tagging delle immagini
Ogni immagine ha:
- un repository (es.
node,python,goals) - un tag (es.
18-alpine,3.11,latest)
Insieme formano il nome completo: repository:tag.
Esempi:
node→ in realtànode:latestnode:18-alpinegoals:latest
Tag tipici:
latest: convenzione per “versione di default” (ma non va intesa come “la più nuova” in assoluto)- versioni numeriche:
1.0.0,2.1.3 - descrittori:
dev,prod,staging
Assegnare un tag in fase di build:
docker build -t goals:latest .Ritagga un’immagine esistente:
docker tag old-image-id myuser/goals:1.0.0Copiare file da/verso un container (docker cp)
Per copiare file dall’host al container:
docker cp ./local-dir CONTAINER_NAME_OR_ID:/path/in/containerPer copiare file dal container all’host:
docker cp CONTAINER_NAME_OR_ID:/path/in/container ./local-dirUso tipico:
- estrarre log, export, artefatti generati dal container
- occasionalmente inserire file di configurazione o script di debug
Non è il meccanismo ideale per aggiornare codice applicativo (vedrai soluzioni migliori con volumi).
Condivisione immagini: Docker Hub
Per condividere immagini con altre persone o con altri ambienti (es. server di produzione) si usa un registry:
- pubblico (es. Docker Hub)
- privato (es. registry self‑hosted o fornito da cloud provider)
Workflow con Docker Hub:
- crei un account su
hub.docker.com - nel tuo account Docker Hub crei un repository (es.
myuser/goals-app) - tagghi la tua immagine locale con quel nome:
docker tag goals:latest myuser/goals-app:1.0.0- fai login da terminale:
docker login- pubblichi l’immagine:
docker push myuser/goals-app:1.0.0Da un’altra macchina (o per qualcun altro), per usare la stessa immagine:
docker pull myuser/goals-app:1.0.0docker run --rm -p 3000:3000 myuser/goals-app:1.0.0Note importanti:
docker run:- se l’immagine non esiste localmente, prova a scaricarla dal registry predefinito (Docker Hub)
- se l’immagine esiste già localmente, non controlla se su Hub è presente una versione più recente
- per assicurarti di avere l’ultima versione dal registry:
docker pull myuser/goals-app:1.0.0Riepilogo operativo
- Immagine:
- definita da un Dockerfile
- costruita con
docker build - immutabile e layer-based
- Container:
- istanza in esecuzione di un’immagine
- creato con
docker run, gestito condocker ps/stop/start/logs - può essere eseguito in attached o detached mode
- Naming e tagging:
- immagini:
repository:tag(myuser/app:1.0.0) - container:
--nameper nomi leggibili (--name goalsapp)
- immagini:
- Sharing:
docker push/docker pullverso/da Docker Hub o registry privati
Questi concetti sono la base di tutto il lavoro con Docker. Nel prossimo capitolo li userai per affrontare i temi successivi: dati, volumi e gestione della persistenza.