Immagini e container: concetti fondamentali

13 marzo 2026
7 min di lettura

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:

  1. scrivi un Dockerfile che descrive come costruire l’immagine
  2. esegui docker build → ottieni un’image
  3. esegui docker run su 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’immagine
  • RUN: esegue comandi in fase di build (es. npm install)
  • EXPOSE: documenta quali porte saranno usate nel container
  • CMD: comando da eseguire quando il container parte

Esempio concettuale per una app Node.js:

FROM node:18-alpine
WORKDIR /app
COPY package.json .
RUN npm install
COPY . .
EXPOSE 3000
CMD ["node", "app.mjs"]

Costruzione immagine:

Terminal window
docker build -t goals:latest .

Esecuzione container:

Terminal window
docker run -p 3000:3000 goals:latest

Immagini: 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:

  1. cambi i file nel progetto
  2. esegui di nuovo docker build (con lo stesso tag o un tag nuovo)

Esempio:

Terminal window
# prima build
docker 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-alpine
WORKDIR /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 cache
    • RUN 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:

Terminal window
docker run IMAGE

Le opzioni più usate:

  • -p host:container: pubblica una porta del container sull’host
    • es. -p 3000:80 → porta 80 del container visibile su localhost:3000
  • -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)
  • --rm: rimuove automaticamente il container quando si arresta
  • --name goalsapp: assegna un nome leggibile al container

Esempi:

Terminal window
# web app, visibile su localhost:3000, rimossa alla chiusura
docker run -d --rm -p 3000:3000 --name goalsapp goals:latest
# sessione interattiva in un'immagine Python
docker run -it python:3.11

Gestione container:

Terminal window
docker ps # container in esecuzione
docker ps -a # anche quelli terminati
docker stop NAME_OR_ID
docker start NAME_OR_ID
docker logs NAME_OR_ID # log storici
docker 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 esecuzione

Differenza attach/detach:

  • docker run di default:
    • attach (il processo occupa il terminale) se non usi -d
    • detach se usi -d
  • docker start di default:
    • detach, a meno che non usi -a / -ai

Gestione di immagini e container

Elencare e rimuovere container

Terminal window
docker ps # solo in esecuzione
docker ps -a # tutti (anche terminati)
docker rm NAME_OR_ID # rimuove container terminato
docker rm -f NAME_OR_ID # forza lo stop e rimozione

Spesso ha senso usare --rm su docker run per evitare accumulo di container terminati:

Terminal window
docker run --rm -p 3000:3000 goals:latest

Elencare e rimuovere immagini

Terminal window
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 container

Un’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:latest
  • node:18-alpine
  • goals: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:

Terminal window
docker build -t goals:latest .

Ritagga un’immagine esistente:

Terminal window
docker tag old-image-id myuser/goals:1.0.0

Copiare file da/verso un container (docker cp)

Per copiare file dall’host al container:

Terminal window
docker cp ./local-dir CONTAINER_NAME_OR_ID:/path/in/container

Per copiare file dal container all’host:

Terminal window
docker cp CONTAINER_NAME_OR_ID:/path/in/container ./local-dir

Uso 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:

  1. crei un account su hub.docker.com
  2. nel tuo account Docker Hub crei un repository (es. myuser/goals-app)
  3. tagghi la tua immagine locale con quel nome:
Terminal window
docker tag goals:latest myuser/goals-app:1.0.0
  1. fai login da terminale:
Terminal window
docker login
  1. pubblichi l’immagine:
Terminal window
docker push myuser/goals-app:1.0.0

Da un’altra macchina (o per qualcun altro), per usare la stessa immagine:

Terminal window
docker pull myuser/goals-app:1.0.0
docker run --rm -p 3000:3000 myuser/goals-app:1.0.0

Note 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:
Terminal window
docker pull myuser/goals-app:1.0.0

Riepilogo 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 con docker ps/stop/start/logs
    • può essere eseguito in attached o detached mode
  • Naming e tagging:
    • immagini: repository:tag (myuser/app:1.0.0)
    • container: --name per nomi leggibili (--name goalsapp)
  • Sharing:
    • docker push / docker pull verso/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.

Continua la lettura

Hai completato tutti i 4 capitoli di questa serie.

Torna all'indice