# Docker: dallo sviluppo alla produzione > Guida completa a Docker per containerizzare applicazioni. Dockerfile, Docker Compose, build multi-stage e deployment in produzione con esempi pratici. - Published: 2026-01-21 - Updated: 2026-04-10 - Author: SharpSkill - Tags: docker, containerization, devops, docker compose, deployment - Reading time: 18 min --- Docker rivoluziona il modo in cui le applicazioni vengono sviluppate, testate e distribuite. Incapsulando un'applicazione e le sue dipendenze in un container portabile, Docker elimina il famigerato problema "funziona sulla mia macchina" e garantisce coerenza in tutti gli ambienti. Questa guida copre il percorso completo dal primo Dockerfile al deployment in produzione. > **Docker nel 2026** > > Docker Desktop 5.x porta miglioramenti significativi delle prestazioni, incluso il supporto nativo di containerd, la gestione ottimizzata delle risorse e l'integrazione fluida con Kubernetes. Le immagini multi-architettura (ARM/x86) sono ormai pratica standard. ## Fondamenti della containerizzazione Un container è un'unità software leggera che racchiude codice, runtime, librerie di sistema e configurazioni. A differenza delle macchine virtuali che virtualizzano l'hardware, i container condividono il kernel del sistema host, rendendoli più veloci da avviare e meno esigenti in termini di risorse. ```bash # terminal # Docker installation on Ubuntu sudo apt update sudo apt install -y docker.io # Add user to docker group (avoids sudo) sudo usermod -aG docker $USER # Verify installation docker --version # Docker version 26.1.0, build 1234567 # First container: downloads image and runs docker run hello-world ``` Questo comando scarica l'immagine `hello-world` da Docker Hub e avvia un container che mostra un messaggio di conferma. ```bash # terminal # List running containers docker ps # List all containers (including stopped) docker ps -a # List downloaded images docker images # Remove a container docker rm # Remove an image docker rmi ``` Questi comandi di base gestiscono il ciclo di vita di container e immagini. ## Creare il primo Dockerfile Un Dockerfile contiene istruzioni per costruire un'immagine Docker. Ogni istruzione crea un layer nell'immagine finale, abilitando caching e riutilizzo. ```dockerfile # Dockerfile # Base image: Node.js 22 on Alpine Linux (lightweight) FROM node:22-alpine # Set working directory in the container WORKDIR /app # Copy dependency files first (cache optimization) COPY package*.json ./ # Install dependencies RUN npm ci --only=production # Copy source code COPY . . # Expose port (documentation) EXPOSE 3000 # Startup command CMD ["node", "server.js"] ``` L'ordine delle istruzioni è cruciale per l'ottimizzazione della cache. I file che cambiano raramente (package.json) devono essere copiati prima del codice sorgente. ```bash # terminal # Build image with a tag docker build -t my-app:1.0 . # Run the container docker run -d -p 3000:3000 --name my-app-container my-app:1.0 # Check logs docker logs my-app-container # Access container shell docker exec -it my-app-container sh ``` Il flag `-d` esegue il container in background, `-p` mappa la porta 3000 del container alla porta 3000 dell'host. > **Alpine vs Debian** > > Le immagini Alpine sono significativamente più piccole (circa 5 MB contro 120 MB per Debian). Tuttavia, utilizzano musl libc invece di glibc, il che può causare incompatibilità con alcune dipendenze native. In caso di problemi, si consiglia di preferire immagini basate su Debian (node:22-slim). ## Build multi-stage per la produzione I build multi-stage creano immagini di produzione ottimizzate separando l'ambiente di build dall'ambiente di esecuzione. Solo gli artefatti necessari vengono inclusi nell'immagine finale. ```dockerfile # Dockerfile.production # ============================================ # Stage 1: Build # ============================================ FROM node:22-alpine AS builder WORKDIR /app # Copy and install dependencies (including devDependencies) COPY package*.json ./ RUN npm ci # Copy source code COPY . . # Build the application (TypeScript, bundling, etc.) RUN npm run build # ============================================ # Stage 2: Production # ============================================ FROM node:22-alpine AS production # Non-root user for security RUN addgroup -g 1001 -S nodejs && \ adduser -S nodejs -u 1001 WORKDIR /app # Copy only necessary files from builder stage COPY --from=builder /app/dist ./dist COPY --from=builder /app/node_modules ./node_modules COPY --from=builder /app/package.json ./ # Switch to non-root user USER nodejs # Environment variables ENV NODE_ENV=production ENV PORT=3000 EXPOSE 3000 # Startup command CMD ["node", "dist/server.js"] ``` Questo approccio riduce significativamente la dimensione dell'immagine finale escludendo strumenti di build, devDependencies e file sorgente. ```bash # terminal # Build with specific file docker build -f Dockerfile.production -t my-app:production . # Compare image sizes docker images | grep my-app # my-app production abc123 150MB # my-app 1.0 def456 450MB ``` La riduzione delle dimensioni può raggiungere il 60-70% a seconda del progetto, migliorando i tempi di deployment e riducendo la superficie di attacco. ## Docker Compose per l'orchestrazione locale Docker Compose semplifica la gestione di applicazioni multi-container. Un file YAML dichiara tutti i servizi, le loro configurazioni e dipendenze. ```yaml # docker-compose.yml version: "3.9" services: # Main application app: build: context: . dockerfile: Dockerfile ports: - "3000:3000" environment: - NODE_ENV=development - DATABASE_URL=postgresql://postgres:secret@db:5432/myapp - REDIS_URL=redis://cache:6379 volumes: # Mount source code for hot-reload - ./src:/app/src - ./package.json:/app/package.json depends_on: db: condition: service_healthy cache: condition: service_started networks: - app-network # PostgreSQL database db: image: postgres:16-alpine environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: secret POSTGRES_DB: myapp volumes: # Data persistence - postgres_data:/var/lib/postgresql/data # Initialization script - ./init.sql:/docker-entrypoint-initdb.d/init.sql ports: - "5432:5432" healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 5s timeout: 5s retries: 5 networks: - app-network # Redis cache cache: image: redis:7-alpine ports: - "6379:6379" volumes: - redis_data:/data command: redis-server --appendonly yes networks: - app-network # Named volumes for persistence volumes: postgres_data: redis_data: # Dedicated network for isolation networks: app-network: driver: bridge ``` I servizi comunicano usando i loro nomi (db, cache) attraverso la rete interna di Docker. Gli healthcheck assicurano che le dipendenze siano pronte prima dell'avvio dell'applicazione. ```bash # terminal # Start all services docker compose up -d # View logs from all services docker compose logs -f # Logs from a specific service docker compose logs -f app # Stop and remove containers docker compose down # Removal including volumes (caution: data loss) docker compose down -v # Rebuild after Dockerfile changes docker compose up -d --build ``` ## Gestione di secret e variabili d'ambiente La gestione sicura dei secret è cruciale in produzione. Docker offre diversi approcci a seconda del contesto. ```yaml # docker-compose.override.yml (development only) version: "3.9" services: app: env_file: - .env.development environment: - DEBUG=true ``` Per la produzione, Docker secrets offre maggiore sicurezza. ```yaml # docker-compose.production.yml version: "3.9" services: app: build: context: . dockerfile: Dockerfile.production secrets: - db_password - api_key environment: - NODE_ENV=production - DATABASE_PASSWORD_FILE=/run/secrets/db_password - API_KEY_FILE=/run/secrets/api_key secrets: db_password: file: ./secrets/db_password.txt api_key: file: ./secrets/api_key.txt ``` Il codice dell'applicazione legge i secret da file montati. ```javascript // config/secrets.js const fs = require('fs'); const path = require('path'); // Utility function to read Docker secrets function readSecret(secretName) { const secretPath = `/run/secrets/${secretName}`; // Check if secret file exists if (fs.existsSync(secretPath)) { return fs.readFileSync(secretPath, 'utf8').trim(); } // Fallback to classic environment variables const envVar = secretName.toUpperCase(); return process.env[envVar]; } module.exports = { databasePassword: readSecret('db_password'), apiKey: readSecret('api_key'), }; ``` Questo approccio evita di esporre i secret nelle variabili d'ambiente o nelle immagini Docker. ## Ottimizzazione delle immagini Docker Diverse tecniche riducono la dimensione dell'immagine e migliorano le prestazioni. ```dockerfile # Dockerfile.optimized FROM node:22-alpine AS base # Install necessary tools in a single layer RUN apk add --no-cache \ dumb-init \ && rm -rf /var/cache/apk/* # ============================================ # Stage: Dependencies # ============================================ FROM base AS deps WORKDIR /app # Copy only lock files for caching COPY package.json package-lock.json ./ # Install with mounted npm cache (BuildKit) RUN --mount=type=cache,target=/root/.npm \ npm ci --only=production # ============================================ # Stage: Builder # ============================================ FROM base AS builder WORKDIR /app COPY package.json package-lock.json ./ RUN --mount=type=cache,target=/root/.npm \ npm ci COPY . . RUN npm run build # ============================================ # Stage: Production # ============================================ FROM base AS production # Image metadata LABEL maintainer="team@example.com" LABEL version="1.0" LABEL description="Production-ready Node.js application" # Non-root user RUN addgroup -g 1001 -S nodejs && \ adduser -S nodejs -u 1001 WORKDIR /app # Copy production dependencies COPY --from=deps --chown=nodejs:nodejs /app/node_modules ./node_modules # Copy build output COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist COPY --from=builder --chown=nodejs:nodejs /app/package.json ./ USER nodejs ENV NODE_ENV=production # dumb-init as PID 1 for signal handling ENTRYPOINT ["dumb-init", "--"] CMD ["node", "dist/server.js"] ``` L'uso di `dumb-init` garantisce la corretta gestione dei segnali Unix, permettendo lo shutdown graceful del container. ```bash # terminal # Enable BuildKit for advanced features export DOCKER_BUILDKIT=1 # Build with cache and detailed output docker build --progress=plain -t my-app:optimized . # Analyze image layers docker history my-app:optimized # Detailed image inspection docker inspect my-app:optimized ``` > **Sicurezza delle immagini** > > Le immagini devono essere scansionate regolarmente per individuare vulnerabilità usando strumenti come Trivy o Snyk. Le immagini base devono essere aggiornate periodicamente per includere le patch di sicurezza. ## Reti avanzate in Docker Docker offre diversi driver di rete per differenti casi d'uso. ```yaml # docker-compose.networking.yml version: "3.9" services: # Publicly accessible frontend frontend: build: ./frontend ports: - "80:80" networks: - frontend-network - backend-network # API accessible only from frontend api: build: ./api networks: - backend-network - database-network # No external ports exposed # Isolated database database: image: postgres:16-alpine networks: - database-network # Accessible only by API networks: frontend-network: driver: bridge backend-network: driver: bridge internal: true # No internet access database-network: driver: bridge internal: true ``` Questa configurazione isola i servizi seguendo il principio del minimo privilegio. Il database è accessibile solo dall'API. ```bash # terminal # Inspect Docker networks docker network ls # Details of a specific network docker network inspect app-network # Create a custom network docker network create --driver bridge --subnet 172.28.0.0/16 custom-network # Connect a container to an existing network docker network connect custom-network my-container ``` ## Volumi e persistenza dei dati I volumi Docker preservano i dati oltre il ciclo di vita del container. ```yaml # docker-compose.volumes.yml version: "3.9" services: app: image: my-app:latest volumes: # Named volume for persistent data - app_data:/app/data # Bind mount for development - ./uploads:/app/uploads:rw # Read-only mount for configuration - ./config:/app/config:ro backup: image: alpine volumes: # Access same volume for backups - app_data:/data:ro - ./backups:/backups command: | sh -c "tar czf /backups/backup-$$(date +%Y%m%d).tar.gz /data" volumes: app_data: driver: local driver_opts: type: none device: /path/to/host/data o: bind ``` La distinzione tra volumi nominati e bind mount è importante: i volumi sono gestiti da Docker mentre i bind mount usano direttamente il filesystem dell'host. ```bash # terminal # List volumes docker volume ls # Inspect a volume docker volume inspect app_data # Remove orphaned volumes docker volume prune # Backup a volume docker run --rm -v app_data:/data -v $(pwd):/backup alpine \ tar czf /backup/volume-backup.tar.gz /data ``` ## Deployment in produzione Un workflow di deployment robusto include build, test e push verso un registry. ```bash # deploy.sh #!/bin/bash set -e # Variables REGISTRY="registry.example.com" IMAGE_NAME="my-app" VERSION=$(git describe --tags --always) echo "Building version: $VERSION" # Build production image docker build \ -f Dockerfile.production \ -t $REGISTRY/$IMAGE_NAME:$VERSION \ -t $REGISTRY/$IMAGE_NAME:latest \ --build-arg BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ") \ --build-arg VERSION=$VERSION \ . # Security scan echo "Running security scan..." docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \ aquasec/trivy image $REGISTRY/$IMAGE_NAME:$VERSION # Push to registry echo "Pushing to registry..." docker push $REGISTRY/$IMAGE_NAME:$VERSION docker push $REGISTRY/$IMAGE_NAME:latest echo "Deployment complete: $REGISTRY/$IMAGE_NAME:$VERSION" ``` Per i deployment su server, un file compose di produzione separato adatta la configurazione. ```yaml # docker-compose.prod.yml version: "3.9" services: app: image: registry.example.com/my-app:latest restart: always deploy: replicas: 3 resources: limits: cpus: "0.5" memory: 512M reservations: cpus: "0.25" memory: 256M update_config: parallelism: 1 delay: 10s failure_action: rollback healthcheck: test: ["CMD", "curl", "-f", "http://localhost:3000/health"] interval: 30s timeout: 10s retries: 3 start_period: 40s logging: driver: "json-file" options: max-size: "10m" max-file: "3" ``` Questa configurazione definisce l'allocazione delle risorse, la strategia di aggiornamento e gli healthcheck per un deployment affidabile. ```bash # terminal # Production deployment with Docker Compose docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d # Zero-downtime update (rolling update) docker compose -f docker-compose.yml -f docker-compose.prod.yml pull docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --no-deps app # Rollback if issues occur docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --no-deps \ --scale app=0 && \ docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --no-deps app ``` ## Monitoraggio e debug dei container Il monitoraggio dei container è essenziale in produzione. ```bash # terminal # Real-time statistics for all containers docker stats # Statistics for a specific container with custom format docker stats my-app --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}" # Inspect processes in a container docker top my-app # Real-time Docker events docker events --filter container=my-app # Copy files from/to a container docker cp my-app:/app/logs/error.log ./error.log ``` Per il debug avanzato, sono disponibili diverse tecniche. ```bash # terminal # Interactive shell in a running container docker exec -it my-app sh # Execute a single command docker exec my-app cat /app/config/settings.json # Start a container in debug mode docker run -it --rm --entrypoint sh my-app:latest # Inspect environment variables docker exec my-app printenv # Analyze logs with filters docker logs my-app --since 1h --tail 100 | grep ERROR ``` ```yaml # docker-compose.monitoring.yml version: "3.9" services: app: # ... existing configuration labels: - "prometheus.scrape=true" - "prometheus.port=3000" - "prometheus.path=/metrics" prometheus: image: prom/prometheus:latest ports: - "9090:9090" volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml - prometheus_data:/prometheus command: - '--config.file=/etc/prometheus/prometheus.yml' - '--storage.tsdb.retention.time=15d' grafana: image: grafana/grafana:latest ports: - "3001:3000" volumes: - grafana_data:/var/lib/grafana environment: - GF_SECURITY_ADMIN_PASSWORD=secret volumes: prometheus_data: grafana_data: ``` Questo stack di monitoraggio permette di raccogliere e visualizzare le metriche dei container. ## Conclusione Docker trasforma il ciclo di sviluppo garantendo coerenza in tutti gli ambienti. La containerizzazione porta portabilità, isolamento e riproducibilità — qualità essenziali per le applicazioni moderne. ### Checklist Docker per la produzione - ✅ Build multi-stage per immagini ottimizzate - ✅ Utente non-root nei container - ✅ Healthcheck configurati per tutti i servizi - ✅ Secret gestiti tramite Docker secrets o variabili d'ambiente sicure - ✅ Limiti di risorse (CPU, memoria) definiti - ✅ Volumi per la persistenza dei dati critici - ✅ Logging centralizzato con rotazione dei file - ✅ Scansione di sicurezza delle immagini prima del deployment - ✅ Strategia di aggiornamento senza downtime - ✅ Rete isolata tra i servizi Padroneggiare Docker è una competenza fondamentale per ogni sviluppatore moderno. Dall'ambiente locale al deployment in produzione, Docker standardizza i workflow e semplifica le operazioni. I concetti presentati qui costituiscono una base solida per esplorare Kubernetes e l'orchestrazione di container su larga scala. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/it/blog/devops/docker-from-development-to-production