Esegui n8n e SearXNG Localmente con Docker: Costruisci il Tuo Playground AI No-Code

Esegui n8n e SearXNG Localmente con Docker: Costruisci il Tuo Playground AI No-Code

Scritto da Francesco Di Donato 20 aprile 2025 14 minuti di lettura

Viviamo in un mondo in cui chiunque abbia un computer e una connessione a Internet può accedere ad agenti di intelligenza artificiale, spesso senza costi e in esecuzione su macchine personali. In questa guida, imparerai come configurare un ambiente locale no-code utilizzando due strumenti open-source:

Tutto viene eseguito localmente con Docker Compose, quindi non sono necessarie iscrizioni, pagamenti o condivisione di dati.

Questo articolo fa parte di una serie basata su approfondimenti dalle mie sperimentazioni notturne 🦉. Trovo questa configurazione potente e facile da implementare, rendendola accessibile a chiunque sia interessato a esplorare queste tecnologie.


Cosa Sarai in Grado di Fare 🌟

Alla fine di questa configurazione, avrai un playground no-code in cui potrai:

  • Attivare flussi di lavoro utilizzando l’editor visivo di n8n.
  • Condurre ricerche automatizzate sul web con SearXNG.
  • Costruire flussi di lavoro basati sull’intelligenza artificiale senza sforzo, come un bot per il fact-checking, un riassuntore di notizie o un assistente di ricerca.

La cosa migliore è che è 100% locale e privato, ad eccezione dell’elaborazione AI effettiva, che rimane gratuita poiché utilizzeremo Gemini 2.0 Flash. Nei futuri post del blog, esploreremo come integrare un modello come DeepSeek V3 in esecuzione localmente con ollama in n8n, completando la configurazione locale.

Prima di iniziare, assicurati di avere Docker 🔗 installato. Questa guida funzionerà indipendentemente dal tuo sistema operativo.

Se non hai un background di programmazione, la prossima sezione potrebbe sembrare scoraggiante, ma fidati, è più facile di quanto potresti aspettarti. Scoprirai presto quanto possa essere potente questa abilità!

Passaggio 1: Crea il Tuo File Docker Compose 🏗️

Prima che emergessero soluzioni come Docker 🔗, l’esecuzione di software sul tuo computer o su un server remoto richiedeva il download e la configurazione di più componenti.

Docker semplifica questo processo. Gli sviluppatori possono creare servizi (siti web, server, database, ecc.) e definire un file chiamato Dockerfile. Sebbene non sia necessario comprendere i dettagli della sua creazione, è importante sapere che contiene istruzioni che qualsiasi computer con Docker può seguire per assemblare il software al volo. Questo pre-packaging include tutto il necessario per il funzionamento del software, migliorando la replicabilità.

Per prima cosa, crea una cartella sul tuo computer (chiamiamola workspace) e aggiungi un file vuoto al suo interno chiamato docker-compose.yml.

workspace
└── docker-compose.yml

I creatori di n8n hanno rilasciato un repository GitHub pubblico chiamato Self-hosted AI Starter Kit 🔗. Questo repository contiene un file docker-compose.yml ben strutturato. Lo apriremo 🔗 per identificare i componenti di cui abbiamo bisogno. Se hai fretta, puoi usare direttamente la loro versione, che include altri servizi utili che potrei trattare nei futuri post del blog. Tuttavia, scomporre le cose può essere un valido esercizio mentale, specialmente durante l’apprendimento.

Nel loro docker-compose.yml, ci concentreremo su tutto ciò che riguarda n8n e la sua dipendenza, Postgres. Filtreremo i componenti relativi a ollama (poiché useremo invece il modello gratuito Gemini 2.0 Flash) e qdrant (un database vettoriale che preferisco sostituire con pgvector—restate sintonizzati per saperne di più).

n8n:
  hostname: n8n
  container_name: n8n
  image: n8nio/n8n:latest
  networks: ['workspace']
  environment:
    - DB_TYPE=postgresdb
    - DB_POSTGRESDB_HOST=postgres
    - DB_POSTGRESDB_USER=root
    - DB_POSTGRESDB_PASSWORD=password
    - N8N_DIAGNOSTICS_ENABLED=false
    - N8N_PERSONALIZATION_ENABLED=false
    - N8N_ENCRYPTION_KEY
    - N8N_USER_MANAGEMENT_JWT_SECRET
  restart: unless-stopped
  ports:
    - 5678:5678
  volumes:
    - n8n_storage:/home/node/.n8n
    - ./n8n/backup:/backup
    - ./shared:/data/shared
  depends_on:
    postgres:
      condition: service_healthy
    n8n-import:
      condition: service_completed_successfully

Dipende da Postgres, quindi dobbiamo includere il database nella nostra configurazione. Si basa anche su n8n-import. Sebbene opzionale, ci permette di sincronizzare i flussi di lavoro e le credenziali di n8n. Lo manterremo nella nostra configurazione.

Dopo averlo eseguito la prima volta, noterai una nuova cartella n8n.

workspace
├── docker-compose.yml
└── n8n
    └── backup
        ├── credentials
        └── workflows

Esposizione

  • Istruiamo il container a consentire la comunicazione dal nostro computer. n8n espone le sue funzionalità sulla porta 5678. Questo ci permette di accedere a n8n localmente nel nostro browser all’indirizzo http://localhost:5678.
ports:
  - 5678:5678
# Se cambi in "3000:5678",
# sarà "http://localhost:3000"

Copia il seguente file all’interno del tuo docker-compose.yml

volumes:
  n8n_storage:
  postgres_storage:

networks:
  workspace:

# Gli attributi elencati in x-n8n sono necessari sia per il servizio n8n che per n8n-import.
# Lo definiamo solo una volta, evitando stupidi errori di battitura e avendo meno righe.
# È unibile nei servizi con la sintassi <<: *service-n8n (vedi sotto).
x-n8n: &service-n8n
  image: n8nio/n8n:latest
  networks:
    - workspace
  environment:
    - DB_TYPE=postgresdb
    - DB_POSTGRESDB_HOST=postgres
    - DB_POSTGRESDB_USER=${POSTGRES_USER}
    - DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
    - N8N_DIAGNOSTICS_ENABLED=false
    - N8N_PERSONALIZATION_ENABLED=false
    - N8N_ENCRYPTION_KEY
    - N8N_USER_MANAGEMENT_JWT_SECRET

services:
  postgres:
    image: postgres:16-alpine
    hostname: postgres
    networks:
      - workspace
    restart: unless-stopped
    environment:
      - POSTGRES_USER
      - POSTGRES_PASSWORD
      - POSTGRES_DB
    volumes:
      - postgres_storage:/var/lib/postgresql/data
    healthcheck:
      test: ['CMD-SHELL', 'pg_isready -h localhost -U ${POSTGRES_USER} -d ${POSTGRES_DB}']
      interval: 5s
      timeout: 5s
      retries: 10

  # n8n-import ha l'unico scopo e ciclo di vita di leggere le credenziali e i flussi di lavoro memorizzati
  # sul nostro computer nella cartella n8n/backup e iniettarli in n8n.
  n8n-import:
    <<: *service-n8n
    hostname: n8n-import
    container_name: n8n-import
    entrypoint: /bin/sh
    command:
      - '-c'
      - 'n8n import:credentials --separate --input=/backup/credentials && n8n import:workflow --separate --input=/backup/workflows'
    volumes:
      - ./n8n/backup:/backup
    depends_on:
      postgres:
        condition: service_healthy

  n8n:
    <<: *service-n8n
    hostname: n8n
    container_name: n8n
    restart: unless-stopped
    ports:
      - 5678:5678
    volumes:
      - n8n_storage:/home/node/.n8n
      - ./n8n/backup:/backup
      - ./shared:/data/shared
    depends_on:
      postgres:
        condition: service_healthy
      n8n-import:
        condition: service_completed_successfully

È ora di istruire Docker a scaricare ed eseguire questi servizi. La prima volta che lo fai, potrebbe richiedere alcuni minuti poiché deve scaricare i componenti necessari. Tuttavia, Docker memorizza queste immagini per impostazione predefinita, quindi i riavvii successivi saranno molto più veloci.

Apri il tuo terminale ed esegui il seguente comando:

docker compose up

Questo comando funge da accensione per il tuo motore. Quando vuoi spegnere i servizi, esegui semplicemente docker compose down. Se questa è la prima volta che esegui una configurazione del genere e funziona correttamente, prenditi un momento per fare una pausa e festeggiare!

Passaggio 2: Crea un Agente AI n8n con Gemini 2.0 Flash 🧠

Il comando precedente ha riempito il tuo terminale di numerosi log. Alla fine di questi log, dovresti vedere il container n8n che indica che è in ascolto su http://localhost:5678. Inserisci questo URL nel tuo browser e dovresti vedere la schermata di autenticazione di n8n. Per la prima volta, dovrai creare un account. Stai tranquillo, l’email e la password che fornisci non verranno inviate a internet; rimarranno sulla tua macchina, memorizzate nel database Postgres che abbiamo configurato in precedenza.

Prima di creare il nostro agente AI, dobbiamo trovare un “cervello” per esso. Visita aistudio.google.com 🔗 e crea una chiave API gratuita per Gemini 2.0 Flash (è gratuita al momento della scrittura).

Assicurati di copiare la chiave API, poiché non verrà visualizzata di nuovo. Dovrai fornirla durante la creazione delle credenziali di n8n.

Crea chiave API Gemini 2.0 Flash

Successivamente, aggiungeremo i seguenti nodi:

  • Trigger chat
  • Agente AI
  • Modello: Gemini 2.0 Flash
  • (opzionale) Memoria Semplice

Agente AI

Ora, testiamo la chat chiedendogli qualcosa.

Ottimo! A questo punto, hai essenzialmente ricreato un’interfaccia per quel modello.

Ma sai cosa c’è di ancora più bello di un cervello altamente intelligente intrappolato in una scatola? Un cervello altamente intelligente che può recuperare informazioni fresche dal web.


Passaggio 3: Configura localmente SearXNG

Esiste un utile repository GitHub pubblico chiamato searxng/searxng-docker 🔗 che contiene un file docker-compose.yml. Similmente a quanto fatto per n8n, estrarremo ciò di cui abbiamo bisogno da questo repository. In questo caso, useremo tutto, che dovrà essere correttamente unito con il docker-compose.yml che abbiamo assemblato in precedenza.

Crea un Caddyfile nella cartella.

workspace
├── docker-compose.yml
├── Caddyfile
└── n8n
    └── backup
        ├── credentials
        └── workflows

E aggiungi quanto segue all’interno.

{
	admin off

	log {
		output stderr
		format filter {
			# Conserva i primi 8 bit da IPv4 e 32 bit da IPv6
			request>remote_ip ip_mask 8 32
			request>client_ip ip_mask 8 32

			# Rimuovi informazioni identificabili
			request>remote_port delete
			request>headers delete
			request>uri query {
				delete url
				delete h
				delete q
			}
		}
	}
}

{$SEARXNG_HOSTNAME}

tls {$SEARXNG_TLS}

encode zstd gzip

@api {
	path /config
	path /healthz
	path /stats/errors
	path /stats/checker
}

@search {
	path /search
}

@imageproxy {
	path /image_proxy
}

@static {
	path /static/*
}

header {
	# CSP (https://content-security-policy.com)
	Content-Security-Policy "upgrade-insecure-requests; default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; form-action 'self' https://github.com/searxng/searxng/issues/new; font-src 'self'; frame-ancestors 'self'; base-uri 'self'; connect-src 'self' https://overpass-api.de; img-src * data:; frame-src https://www.youtube-nocookie.com https://player.vimeo.com https://www.dailymotion.com https://www.deezer.com https://www.mixcloud.com https://w.soundcloud.com https://embed.spotify.com;"

	# Disabilita alcune funzionalità del browser
	Permissions-Policy "accelerometer=(),camera=(),geolocation=(),gyroscope=(),magnetometer=(),microphone=(),payment=(),usb=()"

	# Imposta la politica del referrer
	Referrer-Policy "no-referrer"

	# Forza i client a usare HTTPS
	Strict-Transport-Security "max-age=31536000"

	# Impedisce lo sniffing del tipo MIME dal Content-Type dichiarato
	X-Content-Type-Options "nosniff"

	# X-Robots-Tag (commenta per consentire l'indicizzazione del sito)
	X-Robots-Tag "noindex, noarchive, nofollow"

	# Rimuovi l'header "Server"
	-Server
}

header @api {
	Access-Control-Allow-Methods "GET, OPTIONS"
	Access-Control-Allow-Origin "*"
}

route {
	# Politica di cache
	header Cache-Control "max-age=0, no-store"
	header @search Cache-Control "max-age=5, private"
	header @imageproxy Cache-Control "max-age=604800, public"
	header @static Cache-Control "max-age=31536000, public, immutable"
}

# SearXNG (uWSGI)
reverse_proxy localhost:8080 {
	header_up X-Forwarded-Port {http.request.port}
	header_up X-Real-IP {http.request.remote.host}

	# https://github.com/searx/searx-docker/issues/24
	header_up Connection "close"
}

Per brevità, ecco il docker-compose.yml finale. Aggiunge caddy e redis per supportare searxng.

volumes:
  n8n_storage:
  postgres_storage:
  caddy-data:
  caddy-config:
  valkey-data2:

networks:
  workspace:

# Gli attributi elencati in x-n8n sono necessari sia per il servizio n8n che per n8n-import.
# Lo definiamo solo una volta, evitando stupidi errori di battitura e avendo meno righe.
# È unibile nei servizi con la sintassi <<: *service-n8n (vedi sotto).
x-n8n: &service-n8n
  image: n8nio/n8n:latest
  networks:
    - workspace
  environment:
    - DB_TYPE=postgresdb
    - DB_POSTGRESDB_HOST=postgres
    - DB_POSTGRESDB_USER=${POSTGRES_USER}
    - DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
    - N8N_DIAGNOSTICS_ENABLED=false
    - N8N_PERSONALIZATION_ENABLED=false
    - N8N_ENCRYPTION_KEY
    - N8N_USER_MANAGEMENT_JWT_SECRET

services:
  postgres:
    image: postgres:16-alpine
    hostname: postgres
    networks:
      - workspace
    restart: unless-stopped
    environment:
      - POSTGRES_USER
      - POSTGRES_PASSWORD
      - POSTGRES_DB
    volumes:
      - postgres_storage:/var/lib/postgresql/data
    healthcheck:
      test: ['CMD-SHELL', 'pg_isready -h localhost -U ${POSTGRES_USER} -d ${POSTGRES_DB}']
      interval: 5s
      timeout: 5s
      retries: 10

  # n8n-import ha l'unico scopo e ciclo di vita di leggere le credenziali e i flussi di lavoro memorizzati
  # sul nostro computer nella cartella n8n/backup e iniettarli in n8n.
  n8n-import:
    <<: *service-n8n
    hostname: n8n-import
    container_name: n8n-import
    entrypoint: /bin/sh
    command:
      - '-c'
      - 'n8n import:credentials --separate --input=/backup/credentials && n8n import:workflow --separate --input=/backup/workflows'
    volumes:
      - ./n8n/backup:/backup
    depends_on:
      postgres:
        condition: service_healthy

  n8n:
    <<: *service-n8n
    hostname: n8n
    container_name: n8n
    restart: unless-stopped
    ports:
      - 5678:5678
    volumes:
      - n8n_storage:/home/node/.n8n
      - ./n8n/backup:/backup
      - ./shared:/data/shared
    depends_on:
      postgres:
        condition: service_healthy
      n8n-import:
        condition: service_completed_successfully
  
  caddy:
    container_name: caddy
    image: docker.io/library/caddy:2-alpine
    networks:
      - workspace
    restart: unless-stopped
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy-data:/data:rw
      - caddy-config:/config:rw
    environment:
      - SEARXNG_HOSTNAME=${SEARXNG_HOSTNAME:-http://localhost:8080}
      - SEARXNG_TLS=${LETSENCRYPT_EMAIL:-internal}
    cap_drop:
      - ALL
    cap_add:
      - NET_BIND_SERVICE
    logging:
      driver: 'json-file'
      options:
        max-size: '1m'
        max-file: '1'

  redis:
    container_name: redis
    image: docker.io/valkey/valkey:8-alpine
    command: valkey-server --save 30 1 --loglevel warning
    restart: unless-stopped
    networks:
      - workspace
    volumes:
      - valkey-data2:/data
    cap_drop:
      - ALL
    cap_add:
      - SETGID
      - SETUID
      - DAC_OVERRIDE
    logging:
      driver: 'json-file'
      options:
        max-size: '1m'
        max-file: '1'

  searxng:
    container_name: searxng
    image: docker.io/searxng/searxng:latest
    restart: unless-stopped
    networks:
      - workspace
    ports:
      - '8080:8080'
    volumes:
      - ./searxng:/etc/searxng:rw
    environment:
      - SEARXNG_BASE_URL=https://${SEARXNG_HOSTNAME:-localhost:8080}/
      - UWSGI_WORKERS=${SEARXNG_UWSGI_WORKERS:-4}
      - UWSGI_THREADS=${SEARXNG_UWSGI_THREADS:-4}
    cap_drop:
      - ALL
    cap_add:
      - CHOWN
      - SETGID
      - SETUID
    logging:
      driver: 'json-file'
      options:
        max-size: '1m'
        max-file: '1'

Per prima cosa, dovresti spegnere la macchina:

docker compose down

Ora, aggiorna i contenuti del tuo docker-compose.yml. Infine, riavvia tutto.

docker compose up

Questo scaricherà i componenti necessari e renderà il servizio disponibile su http://localhost:8080. Quando accedi a questo URL nel tuo browser, troverai un’interfaccia semplice per utilizzare il servizio.

Dopo aver eseguito una query iniziale, vedrai che espone un endpoint GET /search. Questo endpoint richiede un parametro di query chiamato q, che dovrebbe contenere la query di ricerca. Dai pannelli laterali, puoi anche aggiungere parametri di query opzionali per controllare aspetti come time_range (con opzioni come day, month o year). Possiamo anche specificare la language e safesearch. Per maggiori informazioni sulla personalizzazione, consulta la documentazione ufficiale di SearXNG 🔗.

Tuttavia, c’è una cosa che impedisce all’Agente AI di n8n di utilizzare questo servizio: SearXNG deve comunicare in formato JSON. Attualmente, restituisce solo HTML (che è adatto per la visualizzazione in un browser).

Configurarlo per restituire risposte in formato JSON è semplice. Potresti aver notato che, dopo aver eseguito searxng, è apparsa una nuova cartella chiamata searxng.

workspace
├── docker-compose.yml
├── n8n
   └── backup
       ├── credentials
       └── workflows
└── searxng
    ├── limiter.toml
    └── settings.yml

È sufficiente modificare searxng/settings.yml in modo che appaia così:

# formati: [html, csv, json, rss]
formats:
  - html
  - json # <-- Aggiungi questo

Ricordati di docker restart searxng.

Ora possiamo aggiungere un altro parametro di query chiamato format e impostarlo su json. Ciò consentirà al servizio SearXNG di restituire risposte in formato JSON, che è essenziale per il nostro Agente AI per elaborare i dati in modo efficace.

Ora, è il momento di integrare questa configurazione nell’Agente AI.


Passaggio 4: Integra l’Agente AI con SearXNG

Agente AI completo

Questa integrazione consentirà al nostro agente di recuperare dati in tempo reale dal web, rendendolo più intelligente e affidabile.

Passiamo al sodo e configuriamo uno Strumento di Richiesta HTTP in n8n. Ecco come fare:

  1. Descrizione: Qui aiuti l’Agente AI a capire a cosa serve lo strumento. Una buona descrizione potrebbe essere: “Usa questo strumento per recuperare dati freschi dal web.”

  2. URL: Puntalo al container SearXNG con l’URL: http://searxng:8080/search.

  3. Parametro di query q: Lascia che l’Agente AI decida cosa cercare, questo mantiene le cose flessibili.

  4. Parametro di query json: Impostalo su json per assicurarti di ottenere la risposta nel formato giusto.

Con lo Strumento di Richiesta HTTP configurato, ora puoi dare al tuo agente alcune istruzioni chiare:

  • Rendilo un Agente Sfatamiti.
  • Usa sempre lo strumento di ricerca web per basare le sue risposte sui dati più recenti.
  • Assicurati che fornisca le fonti for ogni affermazione che fa.

Ecco un semplice prompt di sistema per iniziare, ma sentiti libero di modificarlo a tuo piacimento!

Sei un Agente Sfatamiti. La tua funzione principale è identificare e sfatare miti e idee sbagliate comuni fornendo informazioni accurate basate sui dati recuperati con lo strumento di ricerca web.

  1. Utilizzo Obbligatorio della Ricerca Web: Devi sempre invocare lo strumento di ricerca web per raccogliere informazioni. Le tue risposte devono basarsi esclusivamente sui dati recuperati da fonti online affidabili.

  2. Attribuzione delle Fonti: Devi sempre restituire le fonti con ogni affermazione che fai. Fornisci un link alla fonte delle tue informazioni, assicurandoti che le fonti siano credibili e pertinenti all’argomento in questione.

  3. Chiarezza e Concisinone: Presenta le tue scoperte in modo chiaro e conciso, rendendo facile per gli utenti comprendere la verità dietro il mito.

  4. Coinvolgimento: Incoraggia gli utenti a porre domande di approfondimento o a chiedere chiarimenti su qualsiasi punto tu faccia.

  5. Tono Rispettoso: Mantieni un tono rispettoso e informativo, riconoscendo che le idee sbagliate possono derivare da varie fonti.

Questo è solo un semplice esempio, ma credo che ponga le basi per iniziare a esplorare questo strumento. C’è così tanto potenziale qui, e sono entusiasta di vedere cosa puoi creare! Nei prossimi mesi, non vedo l’ora di condividere ulteriori approfondimenti e imparare insieme mentre ci addentriamo più a fondo nelle possibilità.

Buona automazione!


Perché Questa Configurazione Spacca ✅

  • Gratuita e Locale – Nessun abbonamento, chiavi API gratuite e nessun limite nascosto (almeno per l’uso da parte di un singolo utente).
  • Modulare ed Estensibile – Integra facilmente altri strumenti come LLM locali o database vettoriali.
  • Adatta a Chi Non Sa Programmare – Hai una nuova idea? Assemblare qualcosa è molto più facile quando non devi programmare (specialmente dopo aver programmato tutto il giorno per qualcun altro). Invece, colleghi semplicemente dei blocchi. Sebbene ci siano delle complessità, lo trovo utile per la prototipazione e la sperimentazione. Inoltre, ti permette di creare chat, moduli a più passaggi o persino esporre flussi di lavoro come endpoint, consentendoti di creare la tua interfaccia utente.
  • (Non) Privata per Progettazione – Stiamo usando Gemini 2.0 Flash, il che significa che i nostri dati vengono elaborati sui loro server per definizione. Questo potrebbe non essere necessariamente un problema, a seconda del tuo caso d’uso. Se hai bisogno di mantenere i tuoi dati al 100% privati, puoi tornare alle istruzioni di Ollama che abbiamo filtrato durante il Passaggio 1 e usare quello invece.

Perché non usare i nodi della community di n8n?

Se decidi di sostituire n8n con un’implementazione ottimizzata personalizzata in futuro, non dovrai fare alcun lavoro aggiuntivo per gli altri componenti come SearXNG o Postgres. Puoi semplicemente sostituire il servizio n8n con il tuo server Python (o assumere qualcun altro per farlo per te), e tutto il resto continuerà a funzionare come prima.

Questo approccio rende molto più facile la transizione al cloud se crei un flusso di lavoro di valore che desideri rendere disponibile online. Ad esempio, puoi copiare e incollare quel docker-compose.yml in Coolify 🔗 per replicare ciò che abbiamo fatto localmente su un server remoto.


Hai Domande? 💬

Questa configurazione è solo il punto di partenza, c’è molto che puoi costruire da qui. Se incontri problemi o vuoi aiuto per esplorare idee, sentiti libero di lasciare un commento o di connetterti con me su Twitter/LinkedIn.