🌐 Esposizione sicura
Questa guida mostra come raggiungere LibreFolio fuori casa senza aprire alcuna porta sul tuo router, usando Tailscale, una VPN mesh sicura e gratuita per uso domestico. Gli stessi passaggi valgono per qualsiasi altro servizio sulla tua rete locale.
Ti servono il Passo 0 più il livello che fa per te. I livelli 3 e 4 richiedono anche la configurazione una tantum di Funnel sulla console.
| Livello | Ideale per | Tailscale sul tuo telefono/PC | Indirizzo HTTPS pubblico |
|---|---|---|---|
| 🏃 1. VPN privata | Solo tu, con la configurazione più rapida | Necessario | No |
| 🥉 2. Subnet router | Ogni dispositivo della tua LAN domestica, non solo LibreFolio | Necessario | No |
| 🥈 3. Funnel | Un indirizzo pubblico per LibreFolio e installarlo come app | Non necessario | Sì |
| 🥇 4. Multi-Funnel con Docker | Un indirizzo pubblico per servizio, ciascuno nel proprio container | Non necessario | Sì |
⭐ La nostra raccomandazione: Livello 4
Il Livello 4 richiede solo poco più lavoro del Livello 3, mantiene ogni servizio isolato nel proprio container e assegna a ciascuno il proprio indirizzo pubblico. I Livelli 1–3 sono alternative più semplici e mostrano il percorso che porta fin lì.
Ovunque sotto, 6040 è la porta predefinita di LibreFolio: se hai impostato un PORT diverso nel tuo .env, usa quel numero. LibreFolio serve l'app web, la sua API e la documentazione integrata su questa unica porta, quindi è l'unica porta da esporre.
🔒 Perché non il semplice port forwarding?
Il modo classico consiste nell'aprire una porta sul router di casa e puntare un nome DNS dinamico (come DuckDNS) al tuo IP pubblico. Funziona, ma:
- L'intera Internet può vederlo: chiunque può scansionare il tuo IP pubblico e attaccare la porta aperta.
- L'HTTPS è compito tuo: devi gestire un reverse proxy (Nginx, Caddy…) e mantenerne rinnovati i certificati SSL.
- Senza HTTPS, i dati viaggiano in chiaro: la tua password e i tuoi dati finanziari possono essere intercettati lungo il tragitto.
graph LR
User["👤 Utente esterno<br>(Fuori casa)"] --- Cloud["☁️ Internet / Router (IP pubblico / DDNS?)"]
Cloud --- Server["🖥️ Server locale<br>(Porta 6040)"]
Tailscale evita tutti e tre i problemi: nessuna porta del router viene aperta, il traffico tra i tuoi dispositivi è cifrato e Funnel (Livelli 3 e 4) aggiunge HTTPS con certificati gestiti da Tailscale per te.
🏁 Passo 0: installa Tailscale sui tuoi dispositivi
Tailscale è una VPN mesh basata sul protocollo WireGuard: i tuoi dispositivi entrano a far parte di una rete privata (la tua tailnet) e comunicano fra loro tramite tunnel cifrati. Funziona su Linux, macOS, Windows, iOS e Android, su un NAS o dentro Docker, e il suo piano Personal gratuito basta per uso domestico (vedi i prezzi di Tailscale per i limiti attuali).
Installalo e accedi su almeno due dispositivi: il server che esegue LibreFolio e un client, come il tuo telefono o portatile.
Esegui il comando di installazione ufficiale sul server:
Per maggiori dettagli, vedi la Guida di installazione generica.
Installa l'app ufficiale dal Mac App Store o usa Homebrew:
Per maggiori dettagli, vedi la Guida di installazione generica.
Scarica il programma di installazione ufficiale dal portale Tailscale e segui la procedura guidata di accesso.
Per i dettagli, vedi la Guida di installazione per Windows.
Installa l'applicazione ufficiale dal Google Play Store.
Installa l'applicazione ufficiale dall'Apple App Store.
🔑 Mantieni il server connesso — disabilita la scadenza della chiave
Per impostazione predefinita, Tailscale chiede a ogni dispositivo di effettuare di nuovo l'accesso dopo 180 giorni. Un server non dovrebbe sparire dalla tua tailnet per questo motivo, quindi disattivalo per il server:
- Nella pagina Machines della console di amministrazione, individua il tuo server.
- Clicca l'icona con i tre punti (...) a destra della riga del dispositivo.
- Seleziona l'opzione Disable Key Expiry.
🏃 Livello 1: VPN privata point-to-point
Ideale per te solo, con la configurazione più rapida. Il tuo telefono o portatile raggiunge LibreFolio attraverso la tua tailnet privata e nulla è esposto a internet.
graph LR
Client["👤 Client (VPN attiva)<br>(100.x.y.z)"] -->|Connessione VPN diretta| Server["🖥️ Server (VPN attiva)<br>(100.a.b.c:6040)"]
subgraph LAN ["Rete LAN locale"]
Server -->|Accesso locale| LibreFolio["📊 LibreFolio (locale)"]
end
style LibreFolio fill:#d4edda,stroke:#28a745,stroke-width:2px;
▶️ Passo 1: verifica che LibreFolio risponda sulla sua porta
Nulla da condividere: con Tailscale attivo sul server, la porta 6040 di LibreFolio è già
raggiungibile dalla tua tailnet all'IP Tailscale del server.
Preferisci un indirizzo HTTPS dentro la tua tailnet?
Esegui questo sul server, poi apri https://<server-name>.your-tailnet.ts.net invece dell'IP:
--bg lo mantiene attivo dopo che chiudi il terminale; tailscale serve reset lo rimuove. La
prima volta, Tailscale potrebbe chiederti di abilitare i certificati HTTPS per la tua tailnet. L'indirizzo
resta comunque privato: solo i dispositivi della tua tailnet possono aprirlo.
📱 Passo 2: apri LibreFolio dal tuo dispositivo
Con Tailscale attivo sul telefono o sul PC, digita l'IP Tailscale del server (o il suo nome MagicDNS) seguito dalla porta nel browser, ad esempio http://100.a.b.c:6040.
Per controllare il risultato, vedi Verificare la sicurezza della connessione.
⚠️ Limiti: ogni dispositivo da cui ti connetti deve avere Tailscale attivo, e raggiungi solo il server, non il resto della tua LAN domestica (il Livello 2 aggiunge questo).
🥉 Livello 2: subnet router per l'intera LAN domestica
Ideale per raggiungere ogni dispositivo di casa, non solo LibreFolio. Il server diventa un subnet router: con Tailscale attivo, il tuo client apre qualsiasi IP locale come se fosse a casa, ad esempio http://192.168.1.2:6040 per LibreFolio.
graph LR
Client["👤 Client (VPN attiva)<br>(100.x.y.z)"] -->|Tunneling WireGuard| Server["🖥️ Server (Subnet Router)<br>(100.a.b.c)"]
subgraph LAN ["Rete LAN locale (192.168.1.0/24)"]
Server -->|Inoltro locale| LibreFolio["📊 LibreFolio<br>(es. 192.168.1.2:6040)"]
Server -->|Inoltro locale| OtherDevice["🖨️ Altri dispositivi/servizi<br>(es. 192.168.1.100)"]
end
style LibreFolio fill:#d4edda,stroke:#28a745,stroke-width:2px;
🔀 Passo 1: abilita il subnet routing sul server
Abilita l'IP forwarding a livello di kernel:
echo 'net.ipv4.ip_forward = 1' | sudo tee -a /etc/sysctl.d/99-tailscale.conf
echo 'net.ipv6.conf.all.forwarding = 1' | sudo tee -a /etc/sysctl.d/99-tailscale.conf
sudo sysctl -p /etc/sysctl.d/99-tailscale.conf
Inizia ad annunciare la subnet (sostituisci l'intervallo IP con la tua rete locale, ad es. 192.168.1.0/24):
Usa il percorso dell'eseguibile Tailscale per annunciare la subnet locale:
✅ Passo 2: approva la route nella console di amministrazione
- Vai alla Console di amministrazione Tailscale.
- Clicca i tre punti accanto al tuo server -> Edit route settings.
- Abilita la subnet annunciata.
Un subnet router fa parte dell'infrastruttura della tua rete: se l'hai saltato, disabilita ora la scadenza della chiave per il server (suggerimento alla fine del Passo 0).
⚠️ Limiti: il client ha comunque bisogno di Tailscale attivo, devi conoscere gli IP locali dei tuoi dispositivi e, all'interno della tua LAN, il traffico viaggia come HTTP in chiaro.
🔑 Abilitare Funnel e le ACL sulla console
Configurazione una tantum, necessaria per i Livelli 3 e 4. Consente Funnel nelle regole di controllo degli accessi (ACL) dell'intera tua tailnet.
🔐 Prima di rendere pubblico il servizio
Con Funnel, chiunque su internet può aprire la tua pagina di accesso a LibreFolio. Crea prima il tuo account: il primo account registrato diventa l'amministratore. Dopodiché, le registrazioni restano aperte (Abilita Registrazione, enable_registration, è attivo per impostazione predefinita): disattivalo nelle Impostazioni globali se non vuoi che estranei si registrino (vedi Impostazioni globali).
- Visita la pagina Access Controls nella console di amministrazione Tailscale.
- Clicca il pulsante Add node attribute.
- Compila il modulo:
- Targets: i nodi autorizzati a usare Funnel. Suggeriamo
tag:external_access(assegnerai questo tag ai container Docker del Livello 4) oppureautogroup:member(tutti i dispositivi registrati sotto il tuo account personale). - Attributes: inserisci
funnel. - Note: qualche parola sul perché esiste la regola.
- IP Pools, App, Capability, ecc.: non servono qui; lasciali vuoti o ai valori predefiniti.
- Targets: i nodi autorizzati a usare Funnel. Suggeriamo

Questa regola non è una auth key: le auth key (Livello 4) registrano soltanto un nuovo dispositivo o container nella tua tailnet.
📄 Visualizza la configurazione JSON completa delle ACL per abilitare Funnel
Se preferisci modificare direttamente il file delle policy, questo esempio funzionante abilita Funnel per i tuoi dispositivi e per i container taggati tag:external_access:
{
// Dichiarazione dei tag autorizzati
"tagOwners": {
"tag:external_access": ["autogroup:admin"]
},
// Regole di accesso standard
"acls": [
// Consente a tutti i nodi della tua rete privata di comunicare
{"action": "accept", "src": ["*"], "dst": ["*:*"]}
],
"ssh": [
{
"action": "check",
"src": ["autogroup:member"],
"dst": ["autogroup:self"],
"users": ["autogroup:nonroot", "root"]
}
],
// Abilitazione di Funnel su nodi o tag specifici
"nodeAttrs": [
{
"target": ["autogroup:member"],
"attr": ["funnel"]
},
{
"target": ["tag:external_access"],
"attr": ["funnel"]
}
]
}
🥈 Livello 3: indirizzo HTTPS pubblico con Tailscale Funnel
Ideale per un indirizzo pubblico, senza VPN sul client. Funnel pubblica LibreFolio a un indirizzo sicuro https://<server-name>.your-tailnet.ts.net che chiunque può aprire senza installare Tailscale. L'HTTPS è anche ciò che ti permette di installare LibreFolio come app (PWA) sul tuo telefono.
Prima di iniziare: completa la configurazione una tantum di Funnel e delle ACL sulla console.
graph LR
User["👤 Utente (nessuna VPN)"] -->|Richiesta HTTPS| Funnel["☁️ Ingress di Tailscale Funnel<br>(Server pubblico Tailscale)"]
Funnel -->|Tunneling WireGuard| Server["🖥️ Server locale (tailscaled)<br>(100.a.b.c)"]
subgraph LAN ["Rete LAN locale"]
Server -->|Inoltro locale| LibreFolio["📊 LibreFolio (Porta 6040)"]
Server -.->|"<font color='red'><b>Impossibile esporre</b></font>"| Other["🔌 Altri servizi locali (porte diverse)"]
end
style LibreFolio fill:#d4edda,stroke:#28a745,stroke-width:2px;
style Other fill:#f8d7da,stroke:#dc3545,stroke-width:2px;
linkStyle 3 stroke:#dc3545,stroke-width:2px;
▶️ Passo 1: avvia il Funnel sul server
Sul server, pubblica la porta locale di LibreFolio:
Funnel lo serve all'indirizzo https://<server-name>.your-tailnet.ts.net sulla porta 443; --bg lo mantiene attivo
dopo che chiudi il terminale, e tailscale funnel reset lo arresta. Qui non serve alcuna auth key: il
server è già entrato nella tua tailnet nel Passo 0.
Niente da configurare nemmeno in LibreFolio: Funnel invia X-Forwarded-Proto: https, quindi con il
valore predefinito SESSION_COOKIE_SECURE=auto LibreFolio contrassegna il suo
cookie di sessione come Secure. never è solo per un proxy che
dichiara HTTPS verso un browser su HTTP in chiaro.
✅ Passo 2: approva e attendi la propagazione
La prima volta, il terminale avverte che Funnel non è ancora consentito per questo nodo e mostra un link come questo:
Funnel is enabled, but the list of allowed nodes in the tailnet policy file does not include the one you are using.
To give access to this node you can edit the tailnet policy file, or visit:
https://login.tailscale.com/f/funnel?node=xxxxxx
- Apri il link nel browser, accedi a Tailscale e approva Funnel per questo nodo.
- Il terminale mostra quindi il tuo URL pubblico.
- Attendi qualche minuto perché i record MagicDNS si propaghino prima di aprirlo da una rete esterna.
Per controllare il risultato, vedi Verificare la sicurezza della connessione.
⚠️ Limiti: una macchina ha un solo nome pubblico, che ogni servizio pubblicato da essa deve condividere. Il Livello 4 assegna a ciascun servizio il proprio.
🥇 Livello 4: Multi-Funnel con sidecar Docker
Ideale per utenti Docker che desiderano un indirizzo pubblico per servizio. Ogni servizio riceve un piccolo container Tailscale (un sidecar) che entra nella tua tailnet come nodo autonomo, con il proprio indirizzo https://<name>.your-tailnet.ts.net. Uno script di avvio installa socat nel sidecar, e socat inoltra il traffico di Funnel all'IP statico LAN del servizio.
Prima di iniziare: completa la configurazione una tantum di Funnel e delle ACL sulla console.
🧰 Cos'è socat?
socat (SOcket CAT) è un piccolo strumento da riga di comando che inoltra dati fra due connessioni. Qui funziona come un mini forwarder: ascolta su una porta dentro il container Tailscale e passa tutto ciò che riceve alla porta reale del servizio sulla tua LAN.
Aggiungi un sidecar per ogni servizio che vuoi pubblicare, su un host o su più host; l'unico limite è il numero di dispositivi taggati consentiti dal tuo piano Tailscale. In questo esempio, due host eseguono due sidecar ciascuno:
graph LR
User["👤 Utente esterno"] -->|HTTPS| Funnel1["☁️ Funnel 1<br>(librefolio.yourtailnet.ts.net)"]
User -->|HTTPS| Funnel2["☁️ Funnel 2<br>(service1.yourtailnet.ts.net)"]
User -->|HTTPS| Funnel3["☁️ Funnel 3<br>(service3.yourtailnet.ts.net)"]
User -->|HTTPS| Funnel4["☁️ Funnel 4<br>(service4.yourtailnet.ts.net)"]
Funnel1 -->|WireGuard| TSC1["🐳 Container Tailscale 1<br>(100.101.x.x)"]
Funnel2 -->|WireGuard| TSC2["🐳 Container Tailscale 2<br>(100.102.x.x)"]
Funnel3 -->|WireGuard| TSC3["🐳 Container Tailscale 3<br>(100.103.x.x)"]
Funnel4 -->|WireGuard| TSC4["🐳 Container Tailscale 4<br>(100.104.x.x)"]
subgraph LAN ["Rete LAN locale (192.168.1.0/24)"]
subgraph Host2 ["Server 2 (es. Mini PC - 192.168.1.10)"]
TSC3 -->|socat: TCP/8080| Service3["🔌 Servizio 3<br>(192.168.1.10:8080)"]
TSC4 -->|socat: TCP/9000| Service4["🔌 Servizio 4<br>(192.168.1.10:9000)"]
end
subgraph Host1 ["Server 1 (es. NAS - 192.168.1.20)"]
TSC1 -->|socat: TCP/6040| LibreFolio["📊 LibreFolio<br>(192.168.1.20:6040)"]
TSC2 -->|socat: TCP/80| Service1["🔌 Servizio 1<br>(192.168.1.20:80)"]
end
end
style LibreFolio fill:#d4edda,stroke:#28a745,stroke-width:2px;
📁 Passo 1: prepara la cartella e lo script
Crea una cartella sul server, ad esempio dove tieni i tuoi volumi persistenti di Docker:
# Crea una cartella per i nodi Tailscale ed entraci
mkdir -p <path_chosen>/tailscale-nodes
cd <path_chosen>/tailscale-nodes
Poi scarica al suo interno lo script di avvio custom_startup.sh:
# Scarica lo script dal repository ufficiale
wget https://raw.githubusercontent.com/Librefolio/LibreFolio/main/mkdocs_src/docs/static/tailscale-guide/custom_startup.sh
# Rendi eseguibile lo script
chmod +x custom_startup.sh
🔄 Hai già configurato il sidecar? Aggiorna la tua copia dello script
Lo script attuale funziona anche come watchdog ed è dotato di un health check Docker (vedi Passo 2). Se il tuo sidecar esegue una copia vecchia, aggiornala:
-
Riscarica lo script nella stessa cartella. L'opzione
-O custom_startup.shsovrascrive il file vecchio (senza di essa,wgetsalva il download comecustom_startup.sh.1). Poi assicurati che lo script sia eseguibile: -
Aggiorna il tuo file compose: aggiungi
TS_ENABLE_HEALTH_CHECK,TS_LOCAL_ADDR_PORTe, facoltativamente,STARTUP_TIMEOUTal servizio Tailscale, insieme al bloccohealthcheck, esattamente come nel Passo 2. -
Ricrea il container: un semplice riavvio non applica le modifiche al compose. Esegui
docker compose up -dnella cartella del tuodocker-compose.yml(ricrea i servizi la cui configurazione è cambiata), oppure usa l'azione Recreate / re-deploy di Portainer o CasaOS. -
Controlla il log con
docker logs -f tailscale-librefolio: dovresti vedereTailscale is running., poiStarting the funnel on port 6040...eAvailable on the internet:con il tuo URL pubblico. Entro i 2 minuti distart_perioddell'health check, Docker mostra il container come healthy (docker ps, Portainer, CasaOS). Se invece continua a riavviarsi, vedi il pannello di troubleshooting nel Passo 3.
🐳 Passo 2: configura Docker Compose
Aggiungi il servizio Tailscale nello stesso docker-compose.yml del servizio che espone (ad esempio LibreFolio), così i due restano insieme:
services:
tailscale-librefolio:
image: tailscale/tailscale:latest
container_name: tailscale-librefolio
hostname: tailscale-librefolio
restart: unless-stopped
privileged: false
network_mode: bridge
cap_add:
- NET_ADMIN
- NET_RAW
devices:
- /dev/net/tun:/dev/net/tun
command:
- /custom_startup.sh
environment:
- HOST_IP=192.168.1.20 # IP locale del servizio da esporre (es. Server 1)
- HOST_PORT=6040 # Porta reale del servizio da esporre
- TAILSCALE_FUNNEL_PORT=6040 # Porta interna di Funnel
- TS_HOSTNAME=librefolio # Hostname pubblico personalizzato (es. librefolio)
- TS_AUTHKEY=tskey-auth-... # Chiave di autenticazione generata da Tailscale
- TS_ACCEPT_DNS=true
- TS_STATE_DIR=/var/lib/tailscale
- TS_USERSPACE=false
- TS_ENABLE_HEALTH_CHECK=true # Espone /healthz per l'healthcheck sottostante (Tailscale ≥ 1.78)
- TS_LOCAL_ADDR_PORT=127.0.0.1:9002 # Dove ascolta /healthz: solo dentro il container
- STARTUP_TIMEOUT=180 # Facoltativo: secondi per raggiungere lo stato Running (predefinito 180)
volumes:
- <path_chosen>/tailscale-nodes/tailscale-librefolio/state:/var/lib/tailscale
- <path_chosen>/tailscale-nodes/custom_startup.sh:/custom_startup.sh
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro
# Mostra healthy/unhealthy in Docker, Portainer o CasaOS; il riavvio stesso deriva dall'uscita di custom_startup.sh
healthcheck:
test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1:9002/healthz"]
interval: 30s
timeout: 5s
retries: 3
start_period: 120s
Poi imposta questi valori per la tua rete:
| Valore | Cosa inserire |
|---|---|
<path_chosen> |
Il percorso assoluto scelto nel Passo 1, dove risiedono lo script e i dati di stato (es. /home/user/docker). |
HOST_IP |
L'IP statico LAN della macchina che esegue il servizio. |
HOST_PORT |
La porta reale del servizio su quella macchina: per LibreFolio, la PORT del tuo .env (6040 per impostazione predefinita). |
TAILSCALE_FUNNEL_PORT |
La porta su cui il container ascolta e che pubblica tramite Funnel. Impostala allo stesso valore di HOST_PORT. |
TS_HOSTNAME |
Il nome del nodo: l'indirizzo pubblico diventa https://TS_HOSTNAME.your-tailnet.ts.net. |
TS_AUTHKEY |
La auth key che registra il container nella tua tailnet (vedi sotto). |
Per ottenere la auth key per TS_AUTHKEY:
- Vai alla pagina Tailscale Admin Settings Keys.
- Nella sezione Auth keys (non nella sezione dei token di accesso API), clicca il pulsante Generate auth key....
- Attiva l'interruttore Tags e seleziona il tuo tag (es.
tag:external_access). Aggiungi una descrizione che riconoscerai, comedocker-librefolio-funnel. - Clicca Generate e copia la chiave (
tskey-auth-...).
Una volta avviato il container, la chiave una tantum è consumata: scompare dall'elenco Keys, e il nuovo dispositivo appare in Machines.
🩺 Watchdog e health check — cosa fanno le impostazioni extra
Lo script di avvio funziona anche come watchdog, mentre il blocco healthcheck rende visibile lo stato del container:
- Watchdog (riavvio automatico): se Tailscale non riesce ad avviarsi (ad esempio, nessuna connessione internet al boot o un flag errato), non raggiunge lo stato Running entro
STARTUP_TIMEOUTsecondi, oppure se Tailscale, socat o il Funnel si fermano in seguito, lo script esce con un errore e Docker riavvia il container (restart: unless-stopped). Il container non resta mai "running" senza nulla dietro, e undocker stoparresta comunque tutto in modo pulito. - Health check (solo stato):
TS_ENABLE_HEALTH_CHECK=trueattiva l'endpoint/healthzdi Tailscale (Tailscale 1.78 o successivo), che risponde200mentre il nodo ha un indirizzo IP Tailscale e503altrimenti. Docker lo interroga regolarmente e contrassegna il container come healthy o unhealthy; Portainer e CasaOS mostrano lo stesso stato.
Il semplice Docker (fuori dalla modalità Swarm) non riavvia un container contrassegnato come unhealthy: il riavvio deriva dall'uscita dello script, quindi non ti serve alcun container "autoheal" aggiuntivo (tali helper richiedono anche il socket Docker, ovvero il pieno controllo dell'host).
| Impostazione facoltativa | Cosa fa |
|---|---|
TS_LOCAL_ADDR_PORT |
Dove ascolta /healthz. L'endpoint non richiede autenticazione, e il valore predefinito di Tailscale, [::]:9002, ascolta su tutte le interfacce: 127.0.0.1:9002 lo mantiene dentro il container. Se lo cambi, aggiorna anche l'URL del test healthcheck. |
STARTUP_TIMEOUT |
Secondi che lo script attende per lo stato Running (predefinito 180) prima di uscire e far riavviare il container a Docker. Aumentalo solo su un server molto lento. |
DEBUG |
Non presente nell'esempio sopra. DEBUG=1 stampa nel log del container ogni comando eseguito dallo script, per il troubleshooting. Disattivato per impostazione predefinita per mantenere il log leggibile. |
📄 Visualizza il file Docker Compose di produzione completo (LibreFolio + Tailscale)
Un docker-compose.yml completo: il servizio librefolio del docker-compose.prod.yml ufficiale, con accanto il sidecar Tailscale:
# =============================================================================
# LibreFolio — Docker Compose di produzione
# =============================================================================
# Ottimizzato per utenti finali che eseguono l'immagine precompilata ufficiale da GHCR.
# =============================================================================
services:
librefolio:
image: ${LIBREFOLIO_IMAGE:-ghcr.io/librefolio/librefolio:latest}
container_name: librefolio
restart: unless-stopped
ports:
- "${PORT:-6040}:6040"
volumes:
- ./LibreFolio-data:/app/backend/data/prod-docker
env_file: .env
environment:
- LIBREFOLIO_DATA_DIR=/app/backend/data/prod-docker
- HOST=0.0.0.0
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:6040/api/v1/system/health')"]
interval: 30s
timeout: 10s
start_period: 15s
retries: 3
tailscale-librefolio:
image: tailscale/tailscale:latest
container_name: tailscale-librefolio
hostname: tailscale-librefolio
restart: unless-stopped
privileged: false
network_mode: bridge
cap_add:
- NET_ADMIN
- NET_RAW
devices:
- /dev/net/tun:/dev/net/tun
command:
- /custom_startup.sh
environment:
- HOST_IP=192.168.1.20 # IP locale del servizio da esporre (es. Server 1)
- HOST_PORT=6040 # Porta reale del servizio da esporre
- TAILSCALE_FUNNEL_PORT=6040 # Porta interna di Funnel
- TS_HOSTNAME=librefolio # Hostname pubblico personalizzato (es. librefolio)
- TS_AUTHKEY=tskey-auth-... # Sostituisci con la tua chiave generata
- TS_ACCEPT_DNS=true
- TS_STATE_DIR=/var/lib/tailscale
- TS_USERSPACE=false
- TS_ENABLE_HEALTH_CHECK=true # Espone /healthz per l'healthcheck sottostante (Tailscale ≥ 1.78)
- TS_LOCAL_ADDR_PORT=127.0.0.1:9002 # Dove ascolta /healthz: solo dentro il container
- STARTUP_TIMEOUT=180 # Facoltativo: secondi per raggiungere lo stato Running (predefinito 180)
volumes:
- /DATA/AppData/tailscale-nodes/tailscale-librefolio/state:/var/lib/tailscale
- /DATA/AppData/tailscale-nodes/custom_startup.sh:/custom_startup.sh
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro
# Mostra healthy/unhealthy in Docker, Portainer o CasaOS; il riavvio stesso deriva dall'uscita di custom_startup.sh
healthcheck:
test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1:9002/healthz"]
interval: 30s
timeout: 5s
retries: 3
start_period: 120s
🚀 Passo 3: avvia e approva il Funnel
Avvia lo stack, il servizio e il suo sidecar Tailscale insieme:
Poi segui il log del container Tailscale:
Al primo avvio, il log mostra il link di approvazione per il nuovo nodo:
Funnel is enabled, but the list of allowed nodes in the tailnet policy file does not include the one you are using.
To give access to this node you can edit the tailnet policy file, or visit:
https://login.tailscale.com/f/funnel?node=nsKGo6k9ZF11CNTRL
- Apri il link nel browser, accedi a Tailscale e approva l'attivazione di Funnel.
- Subito dopo l'approvazione, il log conferma l'URL pubblico e il proxy locale:
Available on the internet:
https://librefolio.yourtailnet.ts.net/
|-- proxy http://127.0.0.1:6040
Press Ctrl+C to exit.
Il servizio è ora online: attendi qualche minuto perché i record MagicDNS si propaghino, poi apri l'URL.
Niente da configurare in LibreFolio: Funnel invia X-Forwarded-Proto: https e socat lo inoltra,
quindi con il valore predefinito SESSION_COOKIE_SECURE=auto LibreFolio contrassegna il suo
cookie di sessione come Secure. never è solo per un proxy che
dichiara HTTPS verso un browser su HTTP in chiaro.
Per controllare il risultato, vedi Verificare la sicurezza della connessione.
🛠️ Il container si riavvia in un loop o è contrassegnato come unhealthy
Un problema persistente si manifesta come un container che continua a riavviarsi (Docker, Portainer o CasaOS possono anche contrassegnarlo come unhealthy), non come uno che sembra "running" ma non funziona. Leggi il log con docker logs tailscale-librefolio: prima di ogni riavvio, lo script stampa cosa è andato storto, di solito seguito da Exiting so Docker restarts the container. Le cause più comuni:
- Un flag in
TS_EXTRA_ARGSnon ha un valore. Questa variabile facoltativa (non presente nel compose sopra) passa flag extra atailscale up, separati da spazi. Un flag senza il suo valore fa falliretailscale up: il motivo è sulla riga subito dopoRunning 'tailscale up', sopra il testo di aiuto che inizia conUSAGE(ad esempioflag needs an argument: -advertise-tags), seguito dafailed to auth tailscale: … tailscale up failed: exit status 2econtainerboot exited before Tailscale was running.Scrivi ogni valore come--flag=valueo--flag value: sia--advertise-tags=tag:containerche--advertise-tags tag:containerfunzionano. - Un pannello di gestione ha troncato il valore. L'editor delle variabili d'ambiente di CasaOS (e di pannelli simili) tronca un valore al suo secondo
=:TS_EXTRA_ARGS=--advertise-tags=tag:containerdiventa--advertise-tags, che fallisce come descritto sopra. In questi pannelli, scrivi i valori dei flag con uno spazio:--advertise-tags tag:container. - Manca una variabile obbligatoria: lo script si arresta subito con
HOST_IP is not set(o lo stesso messaggio perHOST_PORToTAILSCALE_FUNNEL_PORT). - Nessuna connessione internet al boot: il container continua a riavviarsi finché Tailscale non riesce ad avviarsi, poi funziona normalmente. Questo è previsto.
- Avvio molto lento: il log mostra
Tailscale is not running after 180s.; aumentaSTARTUP_TIMEOUT. - Unhealthy, ma senza riavvii: l'health check non ottiene alcuna risposta positiva da
/healthz. Se l'hai appena aggiunto, assicurati cheTS_ENABLE_HEALTH_CHECK=truesia impostato e che l'URL del testhealthcheckcorrisponda aTS_LOCAL_ADDR_PORT; altrimenti, il nodo in quel momento non ha un indirizzo IP Tailscale.
Per tracciare ogni comando dello script, aggiungi DEBUG=1 alla sezione environment, ricrea il container e rileggi il log.
Poiché la sua auth key porta un tag, Tailscale disabilita la scadenza della chiave per il container per impostazione predefinita. Con una chiave senza tag, disabilitala come per il server (suggerimento alla fine del Passo 0).
⚠️ Limiti: richiede un terminale e un po' di editing dei file Docker Compose.
🔮 MagicDNS e domini personalizzati
MagicDNS assegna un nome a ogni dispositivo della tua tailnet: invece di un IP come 100.110.x.x, puoi digitare http://your-server nel browser. Gli indirizzi Funnel pubblici terminano in .ts.net (ad esempio, https://librefolio.your-tailnet.ts.net, dove librefolio è il TS_HOSTNAME del Livello 4).
Preferisci il tuo dominio, come librefolio.mydomain.com? Due metodi funzionano per l'accesso privato, attraverso la VPN:
🌍 Metodo 1 — Un record DNS pubblico che punta all'IP Tailscale (il più semplice)
- Accedi alla console del tuo registrar di domini (ad es. Cloudflare, GoDaddy, Namecheap).
- Crea un record DNS di tipo A (o AAAA per IPv6) per il sottodominio scelto (ad es.
librefolio.mydomain.com). - Punta il record direttamente all'IP Tailscale privato del tuo server (ad es.
100.77.x.x).
Gli indirizzi nella rete 100.64.0.0/10 non sono instradabili su internet, quindi il nome funziona solo mentre sei connesso alla tua tailnet: nessun estraneo può raggiungere o scansionare il servizio. Per i dettagli, vedi la documentazione ufficiale sulle impostazioni DNS.
🧭 Metodo 2 — Split DNS con il tuo server DNS
Per record interni che gestisci tu stesso e non pubblichi mai su internet:
- Configura un server DNS privato nella tua LAN (come Pi-hole, AdGuard Home o CoreDNS).
- Aggiungi record locali del tuo dominio puntandoli ai tuoi IP Tailscale.
- Nella console di amministrazione Tailscale, vai su DNS -> Nameservers -> Add Nameserver e aggiungi l'IP Tailscale del tuo DNS privato come nameserver globale o limitato al tuo dominio. Per i dettagli, vedi la documentazione ufficiale sullo Split DNS.
Per l'accesso pubblico, mantieni l'indirizzo *.ts.net: Funnel lo serve con un certificato firmato per quel nome, quindi puntare il tuo dominio ad esso (CNAME) provoca errori SSL/TLS nei browser, a meno che tu non aggiunga un tuo reverse proxy (come Caddy o Nginx) con certificati per il tuo dominio.