Dockerfile - Personalizzazione Immagini e Flask - Lezione 4
Tecnologia - Anno 5 - Modulo 4 - Lezione 4
✍ Autore: profgiagnotti

Dockerfile: come creare un'immagine personalizzata con Flask

Dalla teoria alla pratica: costruiamo una vera Web App Python, scriviamo il nostro primo Dockerfile, creiamo l'immagine e facciamo il push su Docker Hub.

Anno 5 Modulo 4 Lezione 4 Dockerfile Aggiornata 2025/26

1. Introduzione

Uno dei punti cardine di Docker è il Dockerfile, un semplice file di testo che contiene le istruzioni necessarie per costruire un'immagine personalizzata.

Personalizzare un'immagine tramite un Dockerfile permette di automatizzare la configurazione dell'ambiente di esecuzione della propria applicazione, rendendolo riproducibile su qualsiasi sistema compatibile con Docker.

💬 Nota del docente - Perché è fondamentale

Ragazzi, fino ad ora abbiamo usato immagini già pronte da Docker Hub. Ma in azienda non troverete mai l'immagine perfetta per la vostra app. Dovete costruirvela. Il Dockerfile è il vostro "libretto di istruzioni" che dice a Docker come assemblare il vostro ambiente. Imparare a scriverlo bene è quello che distingue chi usa Docker da chi lo capisce davvero. E all'esame è una delle domande più gettonate.

2. Cos’è un Dockerfile?

Un Dockerfile è un file di testo che contiene una serie di comandi, in un linguaggio dichiarativo, che Docker interpreta per costruire un'immagine. È senza estensione, si chiama proprio Dockerfile con la D maiuscola.

3. Struttura di base del Dockerfile

Ecco le direttive principali:

IstruzioneDescrizione
FROMSpecifica l’immagine di base da cui partire
RUNEsegue comandi all’interno dell’immagine durante la sua costruzione
COPY / ADDCopia file o directory nella nuova immagine
WORKDIRImposta la directory di lavoro all'interno del container
CMD / ENTRYPOINTDefinisce il comando eseguito all'avvio del container
EXPOSEEspone una porta per l’applicazione containerizzata
🎯 Trucco per ricordarle all'orale

Io dico sempre ai miei alunni: FROM è "da dove parto", COPY è "cosa ci metto dentro", RUN è "cosa ci installo", WORKDIR è "dove lavoro", EXPOSE è "che porta apro", CMD è "cosa faccio partire". Se dite questa filastrocca all'orale, non vi dimenticate nulla.

4. Creazione di un’immagine personalizzata Docker + Flask

Introduzione a Flask

Flask è un micro framework web per Python, leggero ma estremamente potente, ideale per creare API REST e applicazioni web in modo rapido e modulare.

Caratteristiche principali

  • Architettura minimalista e flessibile
  • Routing semplice tramite decoratori (@app.route)
  • Supporto per template HTML (Jinja2)
  • Facilità di integrazione con database e librerie esterne
🔧 Perché Flask e non Django?

Per i nostri scopi didattici Flask è perfetto: è minimalista, in 10 righe avete un server web funzionante. Django è potentissimo ma ha già database, admin, ORM... troppo per capire Docker. Con Flask vedete subito il risultato. In molte startup e per i microservizi si usa proprio Flask per questo motivo.

5. Obiettivo

Creare un’immagine Docker personalizzata che:

  • Contenga una semplice web app Flask
  • Risponda sulla porta 5000 con visualizzazione della home page
  • Sia pubblicabile su Docker Hub

6. Struttura del progetto e creazione file

Creiamo la nostra struttura. Fare riferimento alla guida precedente all'interno della quale avevamo già creato una directory per i nostri progetti "Applicazioni con Docker". In questa Directory creiamo la sottodirectory flask-docker-app al cui interno creiamo le directory static e templates che conterranno, rispettivamente, un file css per lo stile della nostra Web application e la home page.

STRUTTURA CARTELLE
Applicazioni con Docker/ └── flask-docker-app/ ├── static/ │ └── css/ │ └── stile.css ├── templates/ │ └── index.html ├── app.py ├── requirements.txt └── Dockerfile

Dopo aver creato la struttura, aprire VSC ed aprire la directory "flask-docker-app" oppure posizionarsi nella directory, aprire il terminale ed eseguire il comando:

BASH
code .

File templates/index.html

Nella cartella templates creare un file index.html come ad esempio:

HTML - INDEX.HTML
<!-- template index.html sito personale --> <!DOCTYPE html> <html lang="it"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Il mio sito personale</title> <link rel="stylesheet" href="/static/css/stile.css"> </head> <body> <header> <h1>Benvenuto nel mio sito personale</h1> <nav> <ul> <li><a href="https://profgiagnotti.it/docker/" target="_blank">Docker</a></li> <li><a href="https://profgiagnotti.it/docker-hub-pull-di-immagini-ufficiali-e-creazione-di-container/" target="_blank">Docker Hub</a></li> </ul> </nav> </header> <main> <section> <p>Questa pagina funziona all'interno di un Docker Container</p> </section> </main> <footer> <p>© 2025 Mario Giagnotti. Tutti i diritti riservati.</p> </footer> </body> </html>

File static/css/stile.css

Nella cartella /static/css creare un file stile.css come ad esempio:

CSS - STILE.CSS
/* Stile generale per il corpo */ body { font-family: Arial, sans-serif; margin: 0; padding: 0; background-color: #f5f8fa; color: #333; } header { background-color: #007bff; color: white; padding: 1rem 0; text-align: center; } nav { background-color: #0056b3; } nav ul { list-style: none; margin: 0; padding: 0; display: flex; justify-content: center; background-color: #0056b3; } nav ul li a { display: block; padding: 1rem 1.5rem; color: white; text-decoration: none; font-weight: bold; transition: background-color 0.3s ease; } nav ul li a:hover { background-color: #003f7f; } main { padding: 2rem; background-color: white; max-width: 1000px; margin: 0 auto; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); } footer { background-color: #333; color: white; text-align: center; padding: 1rem 0; margin-top: 2rem; }
💡 Nota

Tutti i file di questo progetto sono disponibili nella sezione DOWNLOAD del corso. Se volete, potete partire da quelli.

7. Creazione del file app.py

Il file app.py gestisce il routing delle pagine della Web application (URL che gestiscono le richieste HTTP), contiene il codice per creare l'istanza dell'applicazione Flask, definisce le view (funzioni che gestiscono il contenuto da restituire) e altri elementi essenziali per il funzionamento dell'applicazione.

PYTHON - APP.PY
from flask import Flask, render_template app = Flask(__name__) @app.route("/") def index(): return render_template("index.html") if __name__ == "__main__": app.run(debug=True, host="0.0.0.0", port=5000)

Questa è la struttura base di un file app.py. La prima riga importa il framework flask e la funzione render_template (serve per le view dei template html), poi viene definita l'app e poi c'è il decorator @app.route. Queste righe di codice consentono di visualizzare la pagina index.html ogni volta che viene fatta una richiesta HTTP su localhost:5000.

⚠️ Attenzione - host 0.0.0.0

host="0.0.0.0" è fondamentale e obbligatorio in Docker! Se scrivete host="127.0.0.1" o non lo mettete, il server Flask ascolta solo dentro il container e non risponde all'esterno. Con 0.0.0.0 dite "ascolta da tutte le interfacce". È l'errore che blocca il 90% degli studenti al primo tentativo. E debug=True serve a vedere subito le modifiche senza ricreare il container.

8. Creazione del file requirements.txt

Il file requirements.txt è un file di testo utilizzato nei progetti Python per elencare tutte le dipendenze (librerie e pacchetti) necessarie al funzionamento dell'applicazione. È uno standard molto diffuso in ambito Python, specialmente in ambienti virtuali, container Docker e deploy su server.

TXT - REQUIREMENTS
flask==2.3.3

Questo blocca la versione e garantisce stabilità. Se scrivessimo solo flask, oggi scaricherebbe la 2.3.3, tra 6 mesi la 3.0 che potrebbe rompere tutto.

9. Creazione del Dockerfile

Il Dockerfile è un file testuale senza estensione

DOCKERFILE
FROM python:3.14-rc-alpine # Crea una directory di lavoro WORKDIR /app # Copia requirements e installa dipendenze COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # Copia il codice sorgente COPY app.py . # Espone la porta 5000 EXPOSE 5000 # Comando di esecuzione CMD ["python", "app.py"]

Spiegazione riga per riga:

  • FROM python:3.14-rc-alpine --> costruiamo la nostra immagine sulla base di una esistente ed, attualmente, priva di vulnerabilità. Alpine è super leggera (circa 5MB contro 900MB di una Debian completa)
  • WORKDIR /app --> creo una directory di lavoro all'interno del container
  • COPY requirements.txt . --> copio i requisiti
  • RUN pip install --no-cache-dir -r requirements.txt --> questa è la parte più importante: RUN crea in effetti un nuovo layer sull'immagine di base. In questo caso sull'immagine Python di base monto un layer con il framework Flask.
  • COPY app.py . --> copio il file app.py nella nuova immagine
  • EXPOSE 5000 --> espongo la porta 5000
  • CMD ["python", "app.py"] --> comando di esecuzione all'avvio
Parte del comandoSignificato
pip installComando per installare pacchetti Python tramite pip
-r requirements.txtLegge l’elenco dei pacchetti da un file (-r = requirements)
--no-cache-dirDisattiva l'uso della cache locale di pip per mantenere leggera l'immagine ed avere installazione pulita
💬 Nota del docente - Perché --no-cache-dir

Normalmente pip conserva una cache locale dei pacchetti scaricati per velocizzare le installazioni future. Ma in Docker non ci serve: il container è effimero, ogni build parte da zero. Se teniamo la cache, l'immagine diventa più pesante di 50-100MB inutili. Con --no-cache-dir teniamo l'immagine pulita e leggera. In produzione questa attenzione fa risparmiare gigabyte quando avete centinaia di immagini.

10. Creazione dell'immagine personalizzata

Come di consueto l'help di Docker ci dice che per la creazione di immagini dobbiamo usare il comando build. Per sapere come usarlo facciamo l'help di questo comando. Ci sono diverse opzioni possibili ma quella di cui necessitiamo è la possibilità di taggare la nostra immagine. Questo è possibile farlo con -t

Scegliamo quindi il nome della nostra immagine in questo modo: nome_utente/nome_immagine:tag

  • nome utente = nome utente di Docker Hub (nel mio caso m76g)
  • nome immagine = nome cartella di progetto (nel mio caso flask-docker-app)
  • tag = versione (nel mio caso v1.0)

Quindi il comando per la creazione diventa:

BASH
docker build -t m76g/flask-docker-app:v1.0 .

Il punto finale rappresenta la directory corrente per cui occorre lanciare il comando nella directory che contiene il Dockerfile. Possiamo farlo da VSC. Docker costruisce l'immagine la cui presenza possiamo verificarla in Docker Desktop. Da Docker Desktop, se clicchiamo sull'immagine appena creata, possiamo vedere effettivamente i layer aggiunti all'immagine base.

🔧 Tip da laboratorio

Quando fate docker build, Docker scarica l'immagine base python:alpine solo la prima volta. Le volte successive usa la cache e ci mette 2 secondi. Se modificate solo app.py, ricostruisce solo l'ultimo layer. È velocissimo. Se invece modificate requirements.txt, ricostruisce da lì in poi. Per questo motivo si copia prima requirements.txt e si fa pip install, e solo dopo si copia app.py: così cambiate spesso app.py senza reinstallare Flask ogni volta.

11. Creazione di un container basato sull'immagine personalizzata

Non ci resta che testare la nostra immagine creando un'istanza dell'immagine stessa cioè un container:

BASH
docker run -d --name flask-app -v .:/app -p 5000:5000 m76g/flask-docker-app:v1.0

Controlliamo che il container sia stato creato e che funzioni da Docker Desktop. Clicchiamo sul link 5000:5000 che punta a index.html e vediamo la nostra pagina. Si può verificare che qualsiasi modifica fatta viene riflessa automaticamente grazie al volume -v.

⚠️ Attenzione - Volume in sviluppo

Qui usiamo -v .:/app per sviluppo, così modificate il codice in VS Code e vedete subito le modifiche nel browser senza rifare build. Ma in produzione NON si usa il volume: l'immagine deve contenere già tutto il codice. Altrimenti a cosa serve aver fatto COPY? Ricordate: volume per sviluppare, COPY per rilasciare.

12. PUSH dell'immagine su Docker Hub

Per eseguire il push della nostra immagine personalizzata dobbiamo fare il login a Docker Hub:

BASH
docker login

Ed utilizzare il comando push:

BASH
docker push m76g/flask-docker-app:v1.0

Verifichiamo che il PUSH sia andato a buon fine su Docker Hub. Di default l'immagine è pubblica per cui è visibile da chiunque acceda a Docker Hub. E' comunque possibile renderla privata e gestire le sue proprietà.

🌍 Cosa succede dopo il push

Ora la vostra immagine è su Docker Hub! Chiunque nel mondo può fare docker pull m76g/flask-docker-app:v1.0 e far partire la vostra app. È come aver pubblicato la vostra app sullo store. Potete mettere il link nel CV: è un ottimo portfolio per i colloqui.

13. Best Practices nella scrittura di Dockerfile

Scrivere un Dockerfile pulito, efficiente e manutenibile è essenziale per la sicurezza e le prestazioni dei container. Di seguito, alcune linee guida consigliate:

  • Utilizzare immagini ufficiali e leggere: Preferire immagini ufficiali e versioni "slim" o "alpine" per ridurre le dimensioni e i rischi di sicurezza.
  • Minimizzare il numero di layer: Ogni istruzione RUN, COPY e ADD genera un layer. Unire più comandi in una singola istruzione aiuta a mantenere l'immagine leggera.
  • Utilizzare .dockerignore: Creare un file .dockerignore per escludere file inutili (es. .git, __pycache__, file temporanei), migliorando tempi di build e sicurezza.
  • Specificare un WORKDIR: Evitare l'uso di cd nei comandi RUN. Usa WORKDIR, più chiaro e riutilizzabile.
  • Evitare privilegi elevati: Evitare l'esecuzione come root se non strettamente necessario. Usare un utente dedicato.
  • Specificare CMD in formato array: Evitare l’uso di shell per eseguire comandi: l’approccio array (exec form) è più sicuro e gestisce meglio i segnali (es. shutdown)
  • Versionare le dipendenze: Bloccare le versioni nel requirements.txt evita comportamenti imprevedibili
DOCKERFILE - USER NON ROOT
RUN useradd -m appuser USER appuser
💬 Nota del docente - Sicurezza

Far girare tutto come root dentro il container è come navigare sempre come amministratore su Windows: se entra un malware ha tutti i permessi. In produzione si crea sempre un utente non privilegiato appuser. È una best practice che fa la differenza tra un Dockerfile da studente e uno da professionista. Segnatevela per il colloquio DevOps.

14. Conclusioni

Ora che abbiamo a disposizione gli strumenti per costruire le nostre immagini personalizzate potremmo, ad esempio, costruire un ambiente per lo sviluppo di Web Application con Apache-PHP, MySQL, PHPmyadmin

In questo caso, però, è sconsigliato l'uso di Dockerfile per costruire un'immagine complessa perché:

  • phpMyAdmin è un’app separata che si connette al servizio MySQL
  • PHP + Apache possono stare in un container (immagine php:apache)
  • MySQL richiede un container dedicato per la gestione dei dati e volumi

Quindi, sebbene si possa tentare di creare un ambiente complesso con Dockerfile non è una best practice per vari motivi:

  • Violazione del principio "1 container = 1 processo"
  • Modularità e manutenibilità ridotta
  • Problemi di gestione dei processi multipli
  • Complicazioni con il networking interno

Soluzione corretta: Docker Compose che sarà l'argomento della prossima lezione.

🐙 Laboratori Pratici

Vuoi consultare il video relativo a questa lezione? Accedi cliccando il pulsante qui sotto.

🎬 Video Dockerfile

Vuoi passare all'azione? Accedi al laboratorio cliccando il pulsante qui sotto.

🧪 Laboratorio Pratico - App Python

Vuoi consultare tutti i laboratori di questa sezione? Accedi cliccando il pulsante qui sotto.

🧪 Laboratori Pratici - Docker

📚 Compito per casa

Provate a modificare stile.css cambiando il colore dell'header, rifate docker build -t m76g/flask-docker-app:v1.1 . e rilanciate il container. Poi fate docker push della v1.1. Avrete così due versioni della vostra app su Docker Hub. Nella prossima lezione vedremo come orchestrare Apache, PHP, MySQL e phpMyAdmin con un unico file docker-compose.yml.