🐋

Docker per Svogliati

La guida a Docker che avresti dovuto leggere prima di dire "ma sulla mia macchina funzionava".

Da "cos'è un container" a "perché non usavo Docker prima?"

Alla fine Docker non è poi così terribile, basta che qualcuno te lo spieghi senza il tono da "senior architect che ha fatto 14 certificazioni".

Capitolo 01

Che Cavolo è Docker?

"Funziona sulla mia macchina" — Il problema che Docker risolve.

📦 Analogia per Svogliati

Immagina di mandare una pizza per posta. Senza Docker mandi la ricetta e speri che dall'altra parte abbiano gli stessi ingredienti, lo stesso forno, la stessa acqua. Con Docker mandi la pizza già fatta dentro una scatola standard. Funziona ovunque: sul tuo PC, su un server, nel cloud, sulla luna. La scatola è il container.

📦 Container

Un ambiente isolato e leggero che contiene la tua app con TUTTO quello che le serve: codice, runtime, librerie, configurazione. È come una VM ma senza il peso di un intero sistema operativo.

💡 Un container usa il kernel del sistema host. Non virtualizza l'hardware. Per questo parte in millisecondi e pesa megabyte, non gigabyte.

🐋 Docker

La piattaforma che crea, distribuisce e esegue i container. Non è l'unica (c'è Podman, containerd) ma è lo standard de facto. Quello che tutti usano e tutti conoscono.

⚠️ Docker NON è: una VM, un hosting, un linguaggio. È un tool per impacchettare ed eseguire app in modo riproducibile.

💔 Senza Docker

"Installa Python 3.9, poi pip install, poi configura Postgres, poi... ah aspetta, tu hai Python 3.11 e rompe tutto. E su Linux serve una lib diversa. E il collega usa Windows e..."

VS

💚 Con Docker

docker compose up e tutto funziona. Su Mac, Linux, Windows, CI/CD, produzione. Stesso ambiente. Sempre. Fine della discussione.

⚖️ VM vs Container
Virtual Machine Pesante, lenta ad avviarsi Hardware / Infrastruttura Hypervisor (VMware, KVM...) VM 1 Guest OS Libs/Deps App A VM 2 Guest OS Libs/Deps App B VM 3 Guest OS Libs/Deps App C Container (Docker) Leggero, parte in millisecondi Hardware / Infrastruttura Host OS + Docker Engine Container 1 Libs/Deps App A Container 2 Libs/Deps App B Container 3 Libs/Deps App C 3x Guest OS = spreco di risorse Nessun Guest OS = risorse condivise
Capitolo 02

Come Funziona Docker

Immagine, Container, Layer: le 3 parole che devi sapere

📂 Da Dockerfile a Container in Esecuzione
Dockerfile Le istruzioni "la ricetta" build Image Template read-only "la foto" run Container Istanza in esecuzione "la pizza pronta" push Registry Docker Hub "lo scaffale"
🍲 In parole povere

Dockerfile = la ricetta della pizza.
Image = la pizza surgelata (pronta, immutabile, distribuibile).
Container = la pizza nel forno (in esecuzione, viva).
Registry = il supermercato dove prendi le pizze surgelate (Docker Hub).

Da una image puoi creare N container. Come stampare copie di un libro.

📜

Dockerfile

File di testo con le istruzioni per creare un'immagine. FROM, COPY, RUN, CMD.

📷

Image

Template read-only a layer. Ogni istruzione crea un layer. I layer sono cachati.

📦

Container

Un'istanza di un'image in esecuzione. Ha il suo filesystem, rete, processi.

📚

Layer

Ogni istruzione Dockerfile = 1 layer. I layer sono condivisi tra immagini. Risparmi spazio.

📂

Volume

Storage persistente. Senza volume, i dati muoiono col container.

🌐

Network

Rete virtuale tra container. Si parlano per nome, come magia.

Capitolo 03

Installazione

3 minuti e hai Docker

💻 Docker Desktop 💤 Più Facile

bash
# macOS
brew install --cask docker
# Apri Docker Desktop dall'applicazione, aspetta che la balena sia ferma

# Linux (Docker Engine - senza GUI)
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker $USER   # così non serve sudo ogni volta
newgrp docker                     # applica subito

# Verifica
docker --version
Docker version 27.x.x, build abc123

# Il test definitivo
docker run hello-world
Hello from Docker!
This message shows that your installation appears to be working correctly.
💡 Docker Desktop vs Docker Engine: Docker Desktop = GUI + Engine (Mac/Win/Linux). Docker Engine = solo CLI (Linux server). Su un server di produzione, installi solo Engine. Sul tuo portatile, Desktop è più comodo.
Capitolo 04

Primi Passi

Da zero a "ho un container che gira" in 5 minuti

1

Esegui il tuo primo container

bash
# Scarica e avvia nginx
docker run -d --name mio-web -p 8080:80 nginx
#    -d        = detached (in background)
#    --name    = dagli un nome (sennò Docker inventa nomi assurdi)
#    -p 8080:80 = porta 8080 del tuo PC → porta 80 del container

# Apri http://localhost:8080 🎉 Nginx funziona!
2

Controlla cosa gira

bash
# Container attivi
docker ps
CONTAINER ID  IMAGE  COMMAND               STATUS        PORTS                  NAMES
a1b2c3d4e5f6  nginx  "/docker-entrypoint"  Up 2 min      0.0.0.0:8080->80/tcp   mio-web

# Tutti i container (anche quelli stoppati)
docker ps -a

# Log del container
docker logs mio-web
docker logs -f mio-web   # -f = follow (live)

# Entra dentro il container
docker exec -it mio-web /bin/bash
3

Ferma, riavvia, cancella

bash
# Ferma
docker stop mio-web

# Riavvia
docker start mio-web

# Cancella (deve essere stoppato)
docker rm mio-web

# Ferma E cancella in un colpo
docker rm -f mio-web

# Cancella TUTTI i container stoppati
docker container prune

# Vedi le immagini scaricate
docker images

# Cancella un'immagine
docker rmi nginx

# Pulizia totale (container, immagini, network, cache)
docker system prune -a
# ⚠️ Cancella TUTTO quello che non è in uso. Usalo con giudizio.
Capitolo 05

Dockerfile

La ricetta per creare le tue immagini personalizzate

📜 Anatomia di un Dockerfile

Dockerfile — app Node.js commentata per svogliati
# Parti da un'immagine base (il "sistema operativo")
FROM node:20-alpine          # Alpine = versione leggera (~50MB vs ~1GB)

# Cartella di lavoro dentro il container
WORKDIR /app

# Copia PRIMA solo package.json (per sfruttare la cache dei layer)
COPY package*.json ./

# Installa le dipendenze (questo layer viene cachato!)
RUN npm ci --only=production

# ORA copia il resto del codice
COPY . .

# Esponi la porta (documentazione, non apre davvero)
EXPOSE 3000

# Utente non-root (sicurezza!)
USER node

# Comando di avvio
CMD ["node", "server.js"]

🛠️ Build & Run

bash
# Costruisci l'immagine (il . è il contesto, la cartella con il Dockerfile)
docker build -t mia-app:1.0 .
#    -t = tag (nome:versione)

# Eseguila
docker run -d --name mia-app -p 3000:3000 mia-app:1.0

# Vedi i layer e la dimensione
docker history mia-app:1.0

🔥 Multi-stage Build (Pro Move)

Compila in un container grosso, copia solo il risultato in uno piccolo. Immagine finale minuscola.

Dockerfile — Multi-stage per app Go
# Stage 1: Compila
FROM golang:1.22 AS builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 go build -o server .

# Stage 2: Immagine finale (solo il binario!)
FROM alpine:3.19
COPY --from=builder /app/server /server
EXPOSE 8080
CMD ["/server"]

# Risultato: immagine da ~15MB invece di ~1GB 🚀
💡 Regole d'oro Dockerfile:
1. Usa Alpine come base quando puoi (immagini 10x più piccole)
2. COPY package.json prima del codice per sfruttare la cache
3. Un processo per container (non mettere app + db nello stesso container)
4. MAI girare come root (usa USER)
5. Usa .dockerignore per escludere node_modules, .git, .env

🚫 .dockerignore

.dockerignore
node_modules
.git
.env
.env.*
*.md
.vscode
.idea
docker-compose*.yml
Dockerfile*
.DS_Store
Capitolo 06

Docker Compose

Il vero game changer. Un file per domarli tutti.

🎵 Analogia per Svogliati

Se Docker è uno strumento musicale, Docker Compose è lo spartito dell'orchestra. Un file YAML che dice: "fammi partire l'app, il database, Redis e Nginx tutti insieme, collegati tra loro, con i volumi giusti". Un comando: docker compose up. Fatto.

📜 Esempio Completo: App + Postgres + Redis

docker-compose.yml — L'esempio che copierai sempre
# docker-compose.yml (o compose.yml, entrambi funzionano)

services:

  # La tua app
  app:
    build: .                       # Usa il Dockerfile nella cartella corrente
    ports:
      - "3000:3000"               # host:container
    environment:
      - DATABASE_URL=postgres://user:pass@db:5432/mydb
      - REDIS_URL=redis://cache:6379
    depends_on:                   # Parte DOPO db e cache
      - db
      - cache
    volumes:
      - ./src:/app/src             # Hot reload! Modifichi il codice e si aggiorna
    restart: unless-stopped       # Si riavvia da solo se crasha

  # PostgreSQL
  db:
    image: postgres:16-alpine
    environment:
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=pass
      - POSTGRES_DB=mydb
    volumes:
      - db-data:/var/lib/postgresql/data   # Dati persistenti!
    ports:
      - "5432:5432"               # Opzionale: per connetterti da fuori

  # Redis (cache)
  cache:
    image: redis:7-alpine
    ports:
      - "6379:6379"

# Volumi con nome (persistono tra i restart)
volumes:
  db-data:

💻 Comandi Docker Compose

bash — I comandi che userai ogni giorno
# Avvia tutto (build se necessario)
docker compose up -d           # -d = background

# Avvia e forza rebuild delle immagini
docker compose up -d --build

# Ferma tutto
docker compose down

# Ferma e CANCELLA i volumi (⚠️ cancella i dati del DB!)
docker compose down -v

# Vedi i log di tutti i servizi
docker compose logs -f

# Log di un servizio specifico
docker compose logs -f app

# Stato dei servizi
docker compose ps

# Esegui un comando in un servizio
docker compose exec app /bin/sh
docker compose exec db psql -U user mydb

# Riavvia un singolo servizio
docker compose restart app

# Scala un servizio (più istanze)
docker compose up -d --scale app=3

🔒 Variabili d'Ambiente con .env

Non mettere le password nel compose file. Usa un .env.

.env — Il file che NON committi MAI
POSTGRES_USER=admin
POSTGRES_PASSWORD=super_segreta_123
POSTGRES_DB=produzione
APP_PORT=3000
docker-compose.yml — Usa le variabili
services:
  db:
    image: postgres:16-alpine
    environment:
      - POSTGRES_USER=${POSTGRES_USER}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - POSTGRES_DB=${POSTGRES_DB}
  app:
    ports:
      - "${APP_PORT}:3000"
🔥 Hot take: Docker Compose è il 90% di Docker nella vita reale. Se impari solo una cosa di questa guida, impara Compose. Un docker compose up e hai l'intero stack locale. Un docker compose down e sparisce tutto senza lasciare traccia. Il sogno di ogni svogliato.
Capitolo 07

Networking

Come i container si parlano tra loro

🌐 Network Docker
Docker Network (bridge) app 172.18.0.2 Raggiunge "db" e "cache" per NOME, non per IP curl http://db:5432 db 172.18.0.3 Postgres Porta 5432 cache 172.18.0.4 Redis Porta 6379 Docker Compose crea automaticamente una rete per tutti i servizi

🌐 Comandi Network

bash
# Vedi le reti
docker network ls

# Crea una rete custom
docker network create mia-rete

# Collega un container a una rete
docker run -d --name app --network mia-rete nginx

# Ispeziona una rete (chi c'è connesso)
docker network inspect mia-rete

# In Docker Compose non serve fare niente:
# i container si vedono per NOME del servizio automaticamente
# app raggiunge db con "db:5432", cache con "cache:6379"
💡 Regola d'oro: In Docker Compose i container si raggiungono per nome del servizio. Se il servizio si chiama "db", l'host è "db". Non serve sapere l'IP. Non usare "localhost" tra container. Usa il nome del servizio.
Capitolo 08

Volumi & Storage

Perché senza volume i dati spariscono come i tuoi weekend

💥 Senza Volume

Il container ha un filesystem effimero. Quando lo cancelli, tutto sparisce. Database, file uploadati, log. Tutto. Puff.

💚 Con Volume

I dati vivono fuori dal container, in un volume Docker o in una cartella dell'host. Cancelli il container? I dati restano.

📂 3 Tipi di Mount

bash
# 1. VOLUME (gestito da Docker - CONSIGLIATO per dati)
docker run -d -v db-data:/var/lib/postgresql/data postgres
# Docker gestisce dove salvare. Performante, portabile.

# 2. BIND MOUNT (cartella dell'host - CONSIGLIATO per sviluppo)
docker run -d -v $(pwd)/src:/app/src mia-app
# Monta la cartella src del tuo PC dentro il container.
# Modifichi un file sul PC → si aggiorna nel container. Hot reload!

# 3. TMPFS (solo in memoria - per dati temporanei)
docker run -d --tmpfs /tmp mia-app

# Comandi utili
docker volume ls              # lista volumi
docker volume inspect db-data  # dettagli
docker volume rm db-data       # cancella
docker volume prune            # cancella tutti quelli non in uso
💥 Errore classico: docker compose down -v cancella i volumi. Se il database è li dentro, hai appena fatto DROP DATABASE senza volerlo. Usa docker compose down (senza -v) per fermare senza perdere dati.
Capitolo 09

Registry & Push

Come condividere le tue immagini col mondo (o col server di produzione)

📦 Push su Docker Hub

bash
# 1. Login
docker login

# 2. Tagga l'immagine con il tuo username
docker tag mia-app:1.0 tuoutente/mia-app:1.0

# 3. Pusha
docker push tuoutente/mia-app:1.0

# 4. Da un altro PC, chiunque può scaricarla
docker pull tuoutente/mia-app:1.0

🔒 Registry Privati

bash
# GitHub Container Registry
docker login ghcr.io
docker tag mia-app:1.0 ghcr.io/tuoutente/mia-app:1.0
docker push ghcr.io/tuoutente/mia-app:1.0

# GitLab Container Registry
docker login registry.gitlab.com
docker tag mia-app:1.0 registry.gitlab.com/tuogruppo/tuorepo:1.0
docker push registry.gitlab.com/tuogruppo/tuorepo:1.0

# AWS ECR / Google GCR / Azure ACR: stessa logica, login diverso
💡 MAI pushare :latest in produzione. Usa sempre tag specifici (mia-app:1.2.3 o mia-app:abc123 con l'hash del commit). Così sai esattamente cosa gira e puoi fare rollback.
Capitolo 10

Docker in Produzione

Le regole per non farsi male quando fa sul serio

🔒 Sicurezza

  • MAI root — usa USER nel Dockerfile
  • Immagini ufficiali — non usare roba random da Docker Hub
  • Scansiona le immaginidocker scout cves mia-app
  • Niente segreti nel Dockerfile — usa env vars o Docker secrets
  • Read-only filesystem--read-only dove possibile

🚀 Performance

  • Multi-stage build — immagini piccole = deploy veloci
  • Alpine — base images leggere
  • Layer cache — ordina le istruzioni dalla meno alla più variabile
  • Healthcheck — Docker sa se l'app è viva
  • Limita risorse--memory=512m --cpus=1

💚 Healthcheck

Dockerfile
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
  CMD curl -f http://localhost:3000/health || exit 1
docker-compose.yml
services:
  app:
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 3s
      retries: 3
      start_period: 10s

📚 Logging

bash — Log come si deve
# La tua app deve loggare su STDOUT/STDERR, non su file
# Docker li cattura automaticamente

# Vedi i log
docker logs mia-app
docker logs --since 1h mia-app     # ultima ora
docker logs --tail 100 mia-app     # ultime 100 righe

# Limita la dimensione dei log (in compose)
docker-compose.yml
services:
  app:
    logging:
      driver: json-file
      options:
        max-size: "10m"      # Max 10MB per file di log
        max-file: "3"        # Max 3 file (rotazione)
Capitolo 11

🚨 SOS Docker

Quando il container non parte e vuoi tornare a fare il pastore

PANICO Il container esce subito (exit code 1)

Il processo principale crasha. Il container muore con lui.

bash
# Guarda i log
docker logs nome-container

# Avvia in modo interattivo per debug
docker run -it mia-app /bin/sh
# Ora sei dentro e puoi capire cosa non va

# Controlla l'exit code
docker inspect nome-container --format='{{.State.ExitCode}}'
# 137 = OOM Killed (poca RAM)
# 1 = errore generico dell'app
# 126 = permesso negato sull'entrypoint

COMUNE "port is already allocated"

Qualcos'altro sta già usando quella porta.

bash
# Chi usa la porta 3000?
lsof -i :3000          # macOS/Linux
netstat -tlnp | grep 3000  # Linux

# Opzione 1: uccidi il processo
kill <PID>

# Opzione 2: usa una porta diversa
docker run -p 3001:3000 mia-app

COMUNE "no space left on device"

Docker mangia disco come un adolescente al buffet. Immagini, container stoppati, volumi orfani, cache di build.

bash
# Quanto spazio usa Docker?
docker system df

# Pulizia conservativa (solo roba non in uso)
docker system prune

# Pulizia nucleare (TUTTO quello non in uso, incluse immagini)
docker system prune -a --volumes

COMUNE Container non raggiunge un altro container

bash
# Sono sulla stessa rete?
docker network inspect <nome-rete>

# Stai usando "localhost"? SBAGLIATO!
# Tra container, usa il NOME del servizio come host
# ❌ http://localhost:5432
# ✅ http://db:5432

# Test di connessione dall'interno
docker exec app ping db
docker exec app wget -qO- http://db:5432

AVANZATO Build lentissima, non usa la cache

L'ordine delle istruzioni nel Dockerfile conta. Se cambi qualcosa nei primi layer, TUTTO quello dopo viene ricostruito.

Dockerfile — Ordine giusto
# ❌ SBAGLIATO: copia tutto prima, poi installa
COPY . .
RUN npm install    # Riesegue OGNI volta che cambi un file

# ✅ GIUSTO: copia prima le dipendenze, poi il codice
COPY package*.json ./
RUN npm ci          # Riesegue SOLO se cambiano le dipendenze
COPY . .             # Il codice cambia spesso ma npm ci è cachato!

AVANZATO Permessi file sbagliati (Linux)

I file creati dal container hanno UID 0 (root) sull'host. Classico problema con bind mount su Linux.

bash / Dockerfile
# Nel Dockerfile, crea un utente con lo stesso UID
RUN addgroup -g 1000 app && adduser -u 1000 -G app -s /bin/sh -D app
USER app

# Oppure nel docker-compose.yml
services:
  app:
    user: "1000:1000"
Capitolo 12

Cheat Sheet Definitivo

Il foglietto che salva la giornata

📦 Container

CosaComando
Avvia containerdocker run -d --name X -p 8080:80 image
Lista container attividocker ps
Lista tuttidocker ps -a
Logdocker logs -f X
Shell dentrodocker exec -it X /bin/sh
Fermadocker stop X
Riavviadocker restart X
Cancelladocker rm -f X
Statistiche livedocker stats

📷 Immagini

CosaComando
Builddocker build -t nome:tag .
Lista immaginidocker images
Scaricadocker pull image:tag
Pushadocker push user/image:tag
Taggadocker tag image:old user/image:new
Cancelladocker rmi image
Ispeziona layerdocker history image
Scansiona CVEdocker scout cves image

📜 Docker Compose

CosaComando
Avvia tuttodocker compose up -d
Avvia + rebuilddocker compose up -d --build
Ferma tuttodocker compose down
Ferma + cancella volumidocker compose down -v
Logdocker compose logs -f
Statodocker compose ps
Shell in un serviziodocker compose exec app sh
Scaladocker compose up -d --scale app=3

🔧 Pulizia & Manutenzione

CosaComando
Spazio usatodocker system df
Pulizia softdocker system prune
Pulizia totaledocker system prune -a --volumes
Cancella container stoppatidocker container prune
Cancella immagini danglingdocker image prune
Cancella volumi orfanidocker volume prune
~/.bashrc o ~/.zshrc — Alias per svogliati
alias d=docker
alias dc='docker compose'
alias dcu='docker compose up -d'
alias dcd='docker compose down'
alias dcl='docker compose logs -f'
alias dps='docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"'
alias dlog='docker logs -f'
alias dex='docker exec -it'
alias dnuke='docker system prune -a --volumes'  # il pulsante rosso
🏆 Le Regole d'Oro dello Svogliato Docker

Un processo per container. Non fare il tuttofare.
Docker Compose per tutto. Anche per un solo container.
Tag specifici, mai :latest. In produzione vuoi sapere cosa gira.
MAI root nei container. Neanche "per fare una prova veloce".
Volumi per i dati. Senza volume, i dati non esistono.
docker system prune una volta a settimana. Il tuo disco ti ringrazierà.