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

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:

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

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:

Copy
<!--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 comandoSignificato
pip installComando per installare pacchetti Python tramite pip, il gestore ufficiale.
-r requirements.txtDice a pip di leggere l’elenco dei pacchetti da un file (-r = requirements).
--no-cache-dirDisattiva 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

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *