Da un po’ di tempo avevo il PC disseminato di container Docker installati qua e là, senza un vero criterio. Ho deciso che era il momento di mettere ordine e, allo stesso tempo, di costruire qualcosa di nuovo e di concreto: un piccolo ecosistema locale in cui n8n, Python e Ollama (che avevo già installato) potessero parlarsi.
Questo post riassume la mia prima esperienza, con particolare attenzione a un dettaglio che inizialmente ho dovuto assimilare bene: come fanno davvero a comunicare un container Docker e un processo che gira sul PC host.
Sommario
Il progetto
L’idea è semplice: usare n8n come motore di automazione (workflow low-code), farlo girare in un container Docker, e collegarlo a Ollama, che invece gira nativamente sul mio PC, non in un container.
Ho organizzato tutto in una cartella dedicata:
~/docker/n8n/
├── docker-compose.yml
├── n8n_data/
└── python-service/
├── Dockerfile
├── requirements.txt
└── main.py
Un file docker-compose.yml per orchestrare i servizi, con n8n esposto sulla porta 5678 e un piccolo servizio Python (FastAPI) pronto per essere richiamato in futuro quando servirà logica più complessa.
Il primo ostacolo: i permessi
Al primo avvio, n8n si è rifiutato di partire con questo errore:
Error: EACCES: permission denied, open '/home/node/.n8n/config'
Il problema era la cartella n8n_data, creata automaticamente da Docker con proprietario sbagliato. La soluzione:
sudo chown -R 1000:1000 n8n_data
Sull’utente:gruppo1000:1000 è il caso di fare una piccola digressione. Qui c’è un dettaglio che vale la pena capire bene, perché tocca un concetto centrale di come funzionano i permessi con Docker: i permessi Linux si basano sul numero UID, non sul nome utente.
Il mio utente sul PC host (marcob) ha UID 1000 — è la convenzione standard su Ubuntu: il primo utente creato su un sistema riceve quasi sempre questo numero. Per pura coincidenza numerica, anche l’immagine ufficiale di n8n crea al suo interno un utente chiamato node, anch’esso con UID 1000.
Quando lancio chown -R 1000:1000 n8n_data, sto quindi rendendo proprietario della cartella il mio stesso utente host — non un utente “del container”. Solo che il kernel Linux dell’host non sa nulla del nome node, e il processo dentro il container non sa nulla del nome marcob: entrambi vedono soltanto un numero, 1000, e siccome coincide da entrambe le parti, il container riesce a scrivere nella cartella senza problemi.
Se il mio utente host avesse avuto un UID diverso (es. 1001, come capita se non sei il primo utente creato sul sistema), avrei dovuto usare chown -R 1001:1001 — non perché serva “indovinare” il numero giusto per il container, ma perché l’importante è far coincidere l’UID host con quello che il processo usa dentro al container.
Il secondo ostacolo: Ollama non rispondeva
Una volta partito n8n, ho provato a costruire il primo workflow: un trigger manuale che chiama Ollama per generare del testo con il modello phi3:mini. Ma la chiamata falliva.
Il motivo: avevo avviato Ollama con ollama serve, che di default ascolta solo su 127.0.0.1 — cioè accetta connessioni solo dal PC stesso, non da un container Docker. La soluzione è stata avviarlo così:
OLLAMA_HOST=0.0.0.0 ollama serve
Con questa variabile d’ambiente, Ollama si mette in ascolto su tutte le interfacce di rete del PC, non solo su quella “interna”.
Altro punto da chiarire bene: come si parlano host e container
Qui sta il cuore tecnico di questa esperienza, ed è la parte che vale la pena spiegare bene, perché all’inizio l’ho capita al contrario. È un chiarimento sul funzionamento di Docker più che di n8n.
Un container Docker, di norma, non vede il PC che lo ospita come “localhost”. Dal punto di vista del container, l’host è un sistema esterno, raggiungibile solo attraverso un indirizzo IP dedicato.
Su Linux, Docker crea un’interfaccia di rete virtuale sul PC chiamata docker0, con un proprio indirizzo IP (nel mio caso 172.17.0.1). Questa interfaccia è, a tutti gli effetti, un secondo “connettore di rete” del mio stesso computer, distinta da quello WiFi che uso per navigare su internet (wlo1, con IP 192.168.1.9).
Ecco lo schema che riassume la comunicazione:

Come si legge:
- Il PC host ha due interfacce di rete distinte:
wlo1verso internet/casa, edocker0verso i container. - Ollama è un processo dell’host, non è dentro nessun container e non è “parte” di
docker0. Semplicemente, avviato conOLLAMA_HOST=0.0.0.0, ascolta su entrambe le interfacce. - Il container n8n non conosce
localhost, non conosce il WiFi: vede solodocker0come sua unica via d’uscita verso l’host. - Per questo, nel nodo HTTP Request di n8n, l’URL corretto non è
http://localhost:11434, ma:
http://host.docker.internal:11434/api/generate
host.docker.internal è un nome speciale che Docker, su Linux, non risolve automaticamente: bisogna dichiararlo esplicitamente nel docker-compose.yml:
extra_hosts: - "host.docker.internal:host-gateway"
Con questa riga, Docker scrive nel file /etc/hosts del container una riga che fa puntare host.docker.internal proprio all’IP di docker0 — quindi, alla fine, alla mia macchina.
Una distinzione importante: due direzioni, due meccanismi diversi
Un altro punto che mi ha tratto in inganno all’inizio: il fatto che io, dal browser, possa raggiungere n8n con http://localhost:5678 non significa che valga anche il contrario.
| Direzione | Meccanismo | Esempio |
|---|---|---|
| Dall’host verso il container | Port mapping (ports: nel compose) | Il browser chiama localhost:5678 per arrivare a n8n |
| Dal container verso l’host | docker0 / host.docker.internal | n8n chiama host.docker.internal:11434 per arrivare a Ollama |
Sono due strade completamente diverse. Il port mapping ("5678:5678" nel compose) dice a Docker di inoltrare le richieste che arrivano sulla porta 5678 dell’host verso la stessa porta dentro il container. Ma questo meccanismo non funziona al contrario: se dentro n8n avessi provato a chiamare http://localhost:11434, avrei ottenuto un errore, perché “localhost” per un container si riferisce solo a se stesso.
Per approfondire l’argomento del networking Docker ho predisposto un articolo dedicato che trovate qui.
Il primo workflow funzionante
Con questi due problemi risolti, ho costruito il mio primo workflow n8n:
- Manual Trigger — per lanciare il workflow a comando
- HTTP Request verso
http://host.docker.internal:11434/api/generate, con body JSON:
{
"model": "phi3:mini",
"prompt": "Ciao, chi sei?",
"stream": false
}
Al primo test, Ollama ha risposto correttamente con il testo generato da phi3:mini — segno che tutta la catena di comunicazione, dal container fino al processo sull’host, funzionava come previsto.
Cosa mi porto a casa da questa prima esperienza
- Organizzare i container per progetto, ognuno nella sua cartella con un proprio
docker-compose.yml, rende tutto molto più gestibile rispetto a lanciare container sparsi condocker run. - I permessi dei volumi montati vanno sempre controllati al primo avvio, specialmente su Linux.
- Capire la differenza tra “chiamare un container dall’host” e “chiamare l’host da un container” è probabilmente il concetto di rete più importante da avere chiaro quando si comincia a costruire ecosistemi Docker più complessi.
Prossimo passo: collegare anche il servizio Python al workflow, e magari sostituire il trigger manuale con un webhook per rendere il tutto richiamabile dall’esterno.
Link utili
- Cosa è (e cosa non è) Docker
- Un post dedicato all’approfondimento del networking con Docker
- Il sito di n8n: An AI workflow automation platform

