Programma
Quando lavori con i container Docker, uno degli aspetti più importanti che ho incontrato è abilitare la comunicazione di rete tra i container e il mondo esterno. Senza una corretta configurazione delle porte, le applicazioni containerizzate restano isolate e inaccessibili. In questo articolo ti guiderò nel processo di esposizione delle porte Docker, una competenza fondamentale per chiunque lavori con applicazioni containerizzate.
Questa guida è pensata in particolare per sviluppatori software, ingegneri DevOps e professionisti IT che devono rendere le proprie applicazioni containerizzate accessibili a utenti, ad altri servizi o a sistemi esterni. Che tu stia distribuendo un server web, una API o un database all'interno di un container, capire come esporre le porte è essenziale per costruire sistemi funzionali basati su Docker.
Al termine di questo articolo, avrai una chiara comprensione di come funziona il networking in Docker, dei diversi metodi per esporre le porte e delle tecniche pratiche per applicare questi concetti nei tuoi progetti.
Se sei alle prime armi con Docker, valuta di seguire uno dei nostri corsi, come Introduction to Docker, Containerization and Virtualization with Docker and Kubernetes, Intermediate Docker o Containerization and Virtualization Concepts.
Cosa significa esporre una porta in Docker?
Prima di entrare nei dettagli tecnici, è importante capire cosa significa esposizione delle porte nell'ecosistema Docker.
Principi di isolamento della rete dei container
I container Docker sono progettati con l'isolamento come principio fondamentale. Questo isolamento è ottenuto tramite funzionalità del kernel Linux come i namespaces e i cgroups. I namespaces forniscono isolamento dei processi, garantendo che i processi all'interno di un container non possano vedere o interagire con processi di altri container o del sistema host. I cgroups, invece, gestiscono l'allocazione delle risorse, limitando quanto CPU, memoria e altre risorse un container può consumare.
Dal punto di vista della rete, ogni container riceve il proprio namespace di rete, completo di un'interfaccia Ethernet virtuale (coppia veth). Questa interfaccia si collega di default a una rete bridge di Docker, permettendo la comunicazione tra container pur mantenendo l'isolamento dalla rete dell'host. È come se ogni container avesse il proprio indirizzo di rete privato, invisibile al mondo esterno.
Nella configurazione predefinita, un container Docker è completamente isolato. I servizi in esecuzione al suo interno possono comunicare tra loro, ma sono irraggiungibili dall'esterno del container, persino dalla macchina host. Ed è qui che l'esposizione delle porte diventa necessaria.
Expose vs publish: distinzione semantica
Quando configuri le porte in Docker, incontrerai due concetti correlati ma distinti: esporre e pubblicare le porte.
Esporre le porte è principalmente una funzionalità di documentazione. Quando esponi una porta in Docker, stai essenzialmente aggiungendo metadati all'immagine del container, indicando che l'applicazione containerizzata ascolta su porte specifiche.
Tuttavia, esporre una porta non la rende effettivamente accessibile dall'esterno del container. Serve da documentazione per gli utenti della tua immagine.
Pubblicare le porte è ciò che rende davvero i servizi containerizzati disponibili al mondo esterno. La pubblicazione crea una mappatura tra una porta sulla macchina host e una porta all'interno del container. Quando pubblichi una porta, Docker configura la rete dell'host per inoltrare il traffico dalla porta host specificata alla corrispondente porta del container.
Ecco quando usare ciascun approccio:
- Usa expose quando crei immagini destinate ad altri utenti Docker, per documentare quali porte usa la tua applicazione.
- Usa publish quando hai bisogno che sistemi esterni (incluso l'host) accedano ai servizi in esecuzione nel tuo container.
- Usali entrambi insieme per una documentazione completa e funzionalità effettiva, soprattutto in ambienti di produzione.
Come esporre le porte Docker
Ora che abbiamo capito i concetti, vediamo gli aspetti pratici dell'esposizione delle porte Docker in diversi contesti.
Strategie di dichiarazione nel Dockerfile
Il modo più basilare per esporre una porta è usare l'istruzione EXPOSE nel tuo Dockerfile. Questa dichiara le porte che il container è progettato per utilizzare.
FROM my-image:latest
EXPOSE 80
EXPOSE 443
In questo esempio, ho specificato che il container utilizzerà le porte 80 e 443, che sono standard per il traffico HTTP (Hypertext Transfer Protocol) e HTTPS (Hypertext Transfer Protocol Secure). Puoi anche combinarle in un'unica istruzione:
EXPOSE 80 443
Quando specifichi le porte, è buona pratica includere il protocollo, TCP (Transmission Control Protocol) o UDP (User Datagram Protocol) se la tua applicazione ne usa uno specifico:
EXPOSE 53/udp
EXPOSE 80/tcp
Se non viene specificato alcun protocollo, per impostazione predefinita si assume TCP. Questo è appropriato per la maggior parte delle applicazioni web, ma servizi come il DNS (Domain Name System) o i server di gaming spesso richiedono UDP.
Dal punto di vista della sicurezza, raccomando di esporre solo le porte di cui la tua applicazione ha effettivamente bisogno. Ogni porta esposta rappresenta un potenziale vettore di attacco, quindi ridurre al minimo il numero di porte esposte segue il principio del privilegio minimo.
Gestione delle porte in fase di esecuzione
Sebbene l'istruzione EXPOSE documenti quali porte usa un container, per rendere queste porte accessibili dall'host o da altre macchine, devi pubblicarle quando avvii il container.
Ecco come associare esplicitamente una porta del container a una specifica porta dell'host:
docker run -p 8081:80 my-image
Il flag -p (o --publish) ti permette di associare una singola porta o un intervallo di porte del container all'host.
Questo comando mappa la porta 80 all'interno del container alla porta 8081 sull'host. Dopo aver eseguito questo comando, puoi accedere al server web navigando su http://localhost:8081 nel browser.
Per comodità durante lo sviluppo, Docker fornisce il flag -P, P maiuscola o --publish-all, che pubblica automaticamente tutte le porte esposte su porte casuali con numerazione alta sull'host:
docker run -P my-image
Per scoprire quali porte host sono state assegnate, posso usare il comando docker ps:
docker ps
Mostrerà un output simile a:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a7c53d9413bf image "/docker-.…" 10 s ago Up 9 s 0.0.0.0:49153->80/tcp, :::49153->80/tcp wizardl
Qui, la porta 80 all'interno del container è mappata alla porta 49153 sull'host.
Quando lavori con servizi che usano sia TCP che UDP sulla stessa porta (come i server DNS), devi specificare il protocollo durante la pubblicazione:
docker run -p 53:53/tcp -p 53:53/udp dns-server
Orchestrazione con Docker Compose
Per applicazioni multi-container, Docker Compose offre un modo più gestibile per configurare rete e porte.
In un file YAML di Docker Compose, puoi specificare le mappature delle porte sotto la chiave ports per ciascun servizio:
services:
web:
image: my-image
ports:
- "8081:80"
- "443:443"
api:
build: ./api
ports:
- "3000:3000"
Docker Compose fa un'importante distinzione tra ports ed expose. La sezione ports crea mappature pubblicate delle porte accessibili dall'esterno, mentre expose rende disponibili le porte solo ai servizi collegati all'interno della stessa rete di Compose:
services:
web:
image: my-image
ports:
- "8081:80"
database:
image: postgres
expose:
- "5432"
In questo esempio, il servizio web è accessibile dall'host alla porta 8081, ma il database PostgreSQL è accessibile solo agli altri servizi all'interno del file Compose, non direttamente dall'host.
Per configurazioni flessibili, soprattutto tra ambienti diversi, posso usare variabili d'ambiente nelle mappature delle porte:
services:
web:
image: my-image
ports:
- "${WEB_PORT:-8081}:80"
Questa sintassi mi consente di specificare la porta host tramite una variabile d'ambiente (WEB_PORT), con default a 8081 se non impostata.
Esposizione delle porte Docker - Esempio pratico
Vediamo un esempio completo di esposizione delle porte Docker per un'applicazione web con un database backend.
Immagina di costruire una semplice applicazione web composta da un servizio API Python Flask e un database PostgreSQL. Il servizio API ascolta sulla porta 5000 e PostgreSQL usa la sua porta predefinita 5432.
Ecco come potrebbe apparire la nostra semplice applicazione Flask (app.py). Questa applicazione creerà un database myapp e recupererà i dati dalla tabella items, una volta che questa tabella sarà creata all'interno del database.
from flask import Flask, jsonify
import os
import psycopg2
app = Flask(__name__)
# Database connection parameters from environment variables
DB_HOST = os.environ.get('DB_HOST', 'db')
DB_NAME = os.environ.get('DB_NAME', 'myapp')
DB_USER = os.environ.get('DB_USER', 'postgres')
DB_PASS = os.environ.get('DB_PASSWORD', 'postgres')
@app.route('/')
def index():
return jsonify({'message': 'API is running'})
@app.route('/items')
def get_items():
# Connect to the PostgreSQL database
conn = psycopg2.connect(
host=DB_HOST,
database=DB_NAME,
user=DB_USER,
password=DB_PASS
)
# Create a cursor and execute a query
cur = conn.cursor()
cur.execute('SELECT id, name FROM items')
# Fetch results and format as list of dictionaries
items = [{'id': row[0], 'name': row[1]} for row in cur.fetchall()]
# Close connections
cur.close()
conn.close()
return jsonify(items)
if __name__ == '__main__':
# Run the Flask app, binding to all interfaces (important for Docker)
app.run(host='0.0.0.0', port=5000)
Successivamente, creerò il nostro file requirements.txt:
flask
psycopg2-binary
Ora devo creare un Dockerfile per il nostro servizio API Python:
FROM python:3.9-slim
WORKDIR /app
# Install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy application code
COPY . .
# Expose the port Flask runs on
EXPOSE 5000
# Command to run the application
CMD ["python", "app.py"]
Infine, creerò un file Docker Compose per orchestrare entrambi i servizi:
services:
api:
build:
context: .
dockerfile: Dockerfile
tags:
- "my-custom-image:latest"
container_name: my-custom-api
ports:
- "5000:5000"
environment:
- DB_HOST=db
- DB_NAME=myapp
- DB_USER=postgres
- DB_PASSWORD=postgres
depends_on:
- db
db:
container_name: my-custom-db
image: postgres:13
expose:
- "5432"
environment:
- POSTGRES_DB=myapp
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
In questa configurazione:
- Il servizio API viene buildato dal nostro Dockerfile e pubblica la porta
5000, rendendola accessibile dalla macchina host. - Il servizio PostgreSQL espone la porta
5432ma non la pubblica, rendendola accessibile solo agli altri servizi all'interno della rete di Compose. - Il servizio API può accedere al database usando l'hostname db (che è il nome del servizio) e la porta
5432.
Per eseguire questa applicazione, usa:
docker-compose up
Ora posso accedere all'API su http://localhost:5000, mentre l'istanza di PostgreSQL non è direttamente accessibile dall'esterno della rete Docker, il che è una buona pratica di sicurezza per i servizi di database. Ma puoi accedere a PostgreSQL dal container in esecuzione:
docker compose exec db psql -U postgres -d myapp
Questo comando accederà alla CLI di PostgreSQL, dove posso visualizzare il database e creare una tabella items. Una volta creata, posso visualizzare i dati su http://localhost:5000/items
Come esporre più porte Docker
Se la mia applicazione Python deve esporre più porte, posso specificarle tutte nel Dockerfile:
FROM python:3.9-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 5000 8081 9090
CMD ["python", "app.py"]
Questo Dockerfile espone:
- Porta
5000per l'applicazione Flask principale (attualmente attiva) - Porta
8081per un potenziale dashboard di monitoraggio - Porta
9090per una futura interfaccia di debugging
È importante notare che, sebbene io abbia esposto le porte 8081 e 9090 nel Dockerfile come documentazione, la mia attuale applicazione Flask è configurata per ascoltare solo sulla porta 5000. Le ulteriori porte esposte indicano dove potrebbero essere implementate funzionalità future.
Come pubblicare le porte esposte
Per pubblicare le porte esposte in un Dockerfile, uso il flag -p o -P quando eseguo il container:
docker run -p 8081:5000 my-custom-image
Questo mappa la porta 5000 nel container (che è esposta nel Dockerfile e dove Flask è in ascolto) alla porta 8081 sull'host. Dopo aver eseguito questo comando, posso accedere alla mia applicazione Flask navigando su http://localhost:8081 nel browser.
Posso anche usare il flag -P per pubblicare automaticamente tutte le porte esposte su porte casuali dell'host:
docker run -P my-custom-image
Per verificare quali porte sono state pubblicate, posso usare:
docker ps
Per informazioni più dettagliate, posso usare:
docker port [container_id]
5000/tcp -> 0.0.0.0:32768
8081/tcp -> 0.0.0.0:32769
9090/tcp -> 0.0.0.0:32770
Questo comando mostra tutte le mappature delle porte per un container specifico. Ad esempio, se la mia applicazione Flask ascolta solo sulla porta 5000 ma ho esposto più porte nel Dockerfile, docker port mi mostrerà esattamente quali porte host sono mappate a quali porte del container. Quindi, se la porta 5000 è stata mappata a 32768, posso accedere alla mia applicazione Flask su http://localhost:32768
Tecniche di diagnostica e troubleshooting
Anche con una configurazione accurata, possono sorgere problemi con il mapping delle porte in Docker. Ecco alcune tecniche che uso per diagnosticare e risolvere i problemi più comuni.
Ispezione delle mappature delle porte
Il primo passo nella diagnosi di problemi legati alle porte è verificare che le mappature siano configurate correttamente. Uso questi comandi:
Elenca tutti i container in esecuzione con le mappature delle porte:
docker ps
Ottieni informazioni dettagliate su un container specifico:
docker inspect [container_id]
Controlla le mappature delle porte per un container specifico:
docker port [container_id]
Il comando docker inspect fornisce informazioni dettagliate, incluse le impostazioni di rete. Per concentrarti sui dettagli relativi alla rete, usa:
docker inspect --format='{{json .NetworkSettings.Ports}}' [container_id] | jq
Se hai bisogno di un'analisi più approfondita del traffico, strumenti come tcpdump e netstat possono essere molto utili. Innanzitutto, assicurati che siano installati all'interno del container. Se il container non include già questi strumenti, puoi installarli eseguendo il seguente comando:
docker exec -u root -it f5c6cac71492 bash -c "apt-get update && apt-get install -y net-tools tcpdump"
Per vedere su quali porte l'applicazione è in ascolto all'interno del container:
docker exec -it [container_id] netstat -tuln
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 0.0.0.0:5000 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.11:42265 0.0.0.0:* LISTEN
udp 0 0 127.0.0.11:34267 0.0.0.0:*
Questo mostra, per esempio, che l'app Flask è in ascolto su tutte le interfacce sulla porta 5000.
Per monitorare il traffico in ingresso e in uscita sull'interfaccia di rete principale del container (solitamente eth0):
docker exec -it [container_id] tcpdump -i eth0
listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
Questo comando ti permette di tracciare il traffico e identificare se le richieste raggiungono il container o se i pacchetti vengono scartati o instradati in modo errato.
Analisi delle regole iptables
Docker utilizza iptables per gestire la Network Address Translation (NAT) e controllare il flusso di traffico tra host e container. Inserisce automaticamente regole per inoltrare le porte dell'host agli IP e alle porte dei container, abilitando un accesso senza soluzione di continuità ai servizi containerizzati.
Su host Linux nativi, puoi ispezionare queste regole utilizzando comandi come:
sudo iptables -t nat -L DOCKER -n -v
Questo mostra come Docker mappa il traffico in ingresso su porte specifiche dell'host verso gli endpoint del container. In base al file Docker Compose visto prima, vedrai le porte inoltrate:
Chain DOCKER (2 references)
pkts bytes target prot opt in out source destination
50 3000 RETURN all -- docker0 * 0.0.0.0/0 0.0.0.0/0
100 6000 RETURN all -- br-xxxxxxx * 0.0.0.0/0 0.0.0.0/0
500 30000 DNAT tcp -- !br-xxxxxxx * 0.0.0.0/0 0.0.0.0/0 tcp dpt:5000 to:172.18.0.2:5000
400 24000 DNAT tcp -- !br-xxxxxxx * 0.0.0.0/0 0.0.0.0/0 tcp dpt:8081 to:172.18.0.2:5000
300 18000 DNAT tcp -- !br-xxxxxxx * 0.0.0.0/0 0.0.0.0/0 tcp dpt:9090 to:172.18.0.2:5000
Qui:
DNATreindirizza il traffico dall'host al container.RETURNgarantisce il corretto instradamento del traffico interno alla rete Docker.
Tuttavia, a seconda dell'ambiente (ad esempio, Docker Desktop su Windows o WSL2), Docker può gestire il port forwarding in modo diverso e le regole iptables potrebbero non essere visibili o modificabili dall'interno del sistema. In questo caso, puoi controllare il port forwarding con il seguente comando:
sudo ss -tulnp
Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port
tcp LISTEN 0 4096 *:5000 *:*
tcp LISTEN 0 4096 *:8081 *:*
tcp LISTEN 0 4096 *:9090 *:*
Suggerimenti per il troubleshooting:
- Conferma le mappature delle porte con
docker psedocker port [container]. - Verifica se il firewall del sistema host o software di sicurezza stanno bloccando le porte.
- Usa i log del container e strumenti di rete come
netstatetcpdumpper verificare il comportamento di rete della tua applicazione.
Conclusione
Esporre le porte Docker è una competenza fondamentale che colma il divario tra l'isolamento dei container e l'usabilità pratica. In questo articolo ho spiegato come funziona il modello di networking di Docker, la differenza tra esporre e pubblicare le porte e ho fornito esempi pratici per vari scenari.
Tieni a mente questi punti chiave:
- Usa
EXPOSEnei Dockerfile per documentare quali porte usa la tua applicazione - Usa
-po-Pquando esegui i container per rendere queste porte accessibili dall'esterno - Sfrutta Docker Compose per gestire applicazioni complesse multi-container
- Usa strumenti di diagnostica per risolvere i problemi quando si presentano
Padroneggiare questi concetti e tecniche ti permetterà di progettare applicazioni containerizzate più robuste che comunicano efficacemente sia internamente sia con l'esterno. Che tu stia sviluppando una semplice applicazione web o un'architettura a microservizi complessa, una corretta gestione delle porte è fondamentale per il successo.
Poiché i container continuano a dominare le strategie moderne di deployment delle applicazioni, la capacità di gestire con sicurezza il networking e le porte di Docker rimarrà una competenza essenziale nel toolkit di ogni sviluppatore.
Per continuare a imparare, dai un'occhiata alle seguenti risorse:
Exposing a Docker Port FAQs
Cosa significa esporre una porta in Docker?
Esporre una porta in Docker è un modo per documentare su quali porte la tua applicazione containerizzata è in ascolto, ma non rende la porta accessibile al di fuori del container.
Come rendo accessibile la porta di un container Docker dalla mia macchina host?
Pubblichi una porta usando il flag -p con docker run o la sezione ports in Docker Compose, che mappa una porta dell'host alla porta del container.
Qual è la differenza tra `EXPOSE` e `-p` in Docker?
EXPOSE si usa nel Dockerfile per dichiarare le porte a scopo di documentazione, mentre -p pubblica e mappa quelle porte per l'accesso esterno.
Posso esporre più porte per un singolo container Docker?
Sì, puoi esporre più porte usando più istruzioni EXPOSE nel Dockerfile o specificando più porte nella sezione ports di Docker Compose.
Come risolvo i problemi di mapping delle porte in Docker?
Usa comandi come docker ps, docker port, docker inspect e strumenti di rete all'interno del container (ad es. netstat, tcpdump) per verificare le mappature delle porte e il flusso di traffico.
Come fondatore di Martin Data Solutions e Data Scientist/ML & AI Engineer freelance, porto un portfolio vario che include Regression, Classification, NLP, LLM, RAG, reti neurali, metodi ensemble e computer vision.
- Ho sviluppato con successo diversi progetti ML end-to-end, includendo data cleaning, analytics, modellazione e deployment su AWS e GCP, offrendo soluzioni efficaci e scalabili.
- Ho realizzato applicazioni web interattive e scalabili con Streamlit e Gradio per casi d’uso in settori diversi.
- Ho insegnato e fatto mentoring a studenti di data science e analytics, supportandone la crescita professionale con approcci di apprendimento personalizzati.
- Ho progettato contenuti didattici per applicazioni di retrieval-augmented generation (RAG) su misura per le esigenze enterprise.
- Ho scritto articoli tecnici di grande impatto su AI & ML, trattando temi come MLOps, database vettoriali e LLM, ottenendo un notevole coinvolgimento.
In ogni progetto che affronto, applico pratiche aggiornate di ingegneria del software e DevOps, come CI/CD, linting del codice, formattazione, monitoraggio dei modelli, tracciamento degli esperimenti e gestione robusta degli errori. Mi impegno a fornire soluzioni complete, trasformando gli insight dai dati in strategie pratiche che aiutano le aziende a crescere e a sfruttare al meglio data science, machine learning e AI.


