In questo articolo, Dockerfile – Personalizzazione di immagini e PUSH su Docker Hub, vediamo come utilizzare il Dockerfile per customizzare un’immagine Docker (aggiungere layer). Vedremo anche alcuni esempi pratici ed, infine, come si fa l’upload (PUSH) della propria immagine customizzata per renderla pubblica
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.
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.
Struttura di base del Dockerfile
Ecco le direttive principali:
Istruzione | Descrizione |
---|---|
FROM | Specifica l’immagine di base da cui partire |
RUN | Esegue comandi all’interno dell’immagine durante la sua costruzione |
COPY / ADD | Copia file o directory nella nuova immagine |
WORKDIR | Imposta la directory di lavoro all’interno del container |
CMD / ENTRYPOINT | Definisce il comando eseguito all’avvio del container |
EXPOSE | Espone una porta per l’applicazione containerizzata |
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
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
1. Struttura del progetto

2. Creazione di file e directories
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.
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
code .

Nella cartella templates creare un file index.html come ad esempio:
<!--definiamo il template index.html di un sito web 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>
Nella cartella /static/css creare un file stile.css come ad esempio:
/* Stile generale per il corpo */
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
background-color: #f5f8fa;
color: #333;
}
/* Stile per l'intestazione */
header {
background-color: #007bff;
color: white;
padding: 1rem 0;
text-align: center;
}
/* Stile per la barra di navigazione */
nav {
background-color: #0056b3;
}
nav ul {
list-style: none;
margin: 0;
padding: 0;
display: flex;
justify-content: center;
background-color: #0056b3;
}
nav ul li {
margin: 0;
}
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;
}
/* Stile per i link */
a {
color: #007bff;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
/* Contenuto principale */
main {
padding: 2rem;
background-color: white;
max-width: 1000px;
margin: 0 auto;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
/* Stile per il footer */
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
3. 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.
Creare quindi il file app.py ed inserire il codice seguente:
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.
N.B.: host="0.0.0.0"
è necessario per rendere il server accessibile dall’esterno del container, mentre debug=True serve a visualizzare automaticamente le eventuali modifiche (senza dover ricreare un nuovo container)
4. 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.
Creiamo il file scrivendo all’interno semplicemente il testo:
flask==2.3.3
Questo blocca la versione e garantisce stabilità
5. Creazione del Dockerfile
Il Dockerfile è un file testuale senza estensione
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"]
- FROM python:3.14-rc-alpine –> costruiamo la nostra immagine sulla base di una esistente ed, attualmente, priva di vulnerabilità
- WORKDIR /app –> creo una directory di lavoro all’interno del container
- COPY app.py . –> copio il file app.py nella nuova immagine
- EXPOSE 5000 –> espongo la porta 5000
- COPY requirements.txt . –> copio il contenuto dei miei 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.
questo comando serve per installare tutte le librerie elencate nel file requirements.txt
, ma con un comportamento particolare legato alla gestione della cache. Vediamo nel dettaglio ogni parte:
Parte del comando | Significato |
---|---|
pip install | Comando per installare pacchetti Python tramite pip , il gestore ufficiale. |
-r requirements.txt | Dice a pip di leggere l’elenco dei pacchetti da un file (-r = requirements ). |
--no-cache-dir | Disattiva l’uso della cache locale di pip durante l’installazione. |
Normalmente, pip
conserva una cache locale dei pacchetti scaricati (in una directory come ~/.cache/pip
) per evitare di riscaricarli in futuro. Questo velocizza le installazioni successive. Ma noi evitiamo l’uso della cache locale per non avere file temporanei nel container (quindi mantenere leggera l’immagine) e per avere un’installazione pulita anche se perdiamo in velocità poichè ogni volta i pacchetti verranno riscaricati
6. 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:
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:

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

Si può verificare che qualsiasi modifica fatta viene riflessa automaticamente
PUSH dell’immagine su Docker Hub
Per eseguire il push della nostra immagine personalizzata dobbiamo fare il login a Docker Hub:
docker login

Ed utilizzare il comando push:
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à
Best Practices nella scrittura di Dockerfile
Abbiamo visto come personalizzare un’immagine con Dockerfile. Vediamo ora alcune best practices:
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. Esempio:
RUN useradd -m appuser
USER appuser
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
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
Lascia un commento