Ollama in Podman rootless su un VPS: guida
Configura Ollama in Podman rootless su un VPS con utente dedicato, lingering e Quadlet, riavvio dopo reboot, SELinux e API chiusa sulla porta 11434.
Esegui Ollama in Podman rootless su un VPS
Per eseguire Ollama in Podman rootless su un server, devono essere soddisfatte cinque condizioni che una guida per desktop può tralasciare. Un utente non privilegiato dedicato deve essere proprietario del container. Per questo utente deve essere abilitato il lingering, così il container continua a funzionare dopo la disconnessione. Un file Quadlet deve affidare il container a systemd, così il servizio viene riavviato dopo un reboot. La directory dei modelli deve avere un'etichetta SELinux nelle distribuzioni che applicano SELinux. L'API deve essere in ascolto solo sull'interfaccia loopback e deve essere raggiunta tramite un tunnel SSH (secure shell).
Ollama è un server per modelli linguistici di grandi dimensioni (LLM). Memorizza i pesi dei modelli su disco, li carica in memoria e risponde alle richieste HTTP sulla porta 11434. Non dispone di login, API key o account utente, quindi l'unico controllo degli accessi disponibile è quello di rete. Podman esegue i container senza daemon e senza root, quindi qualsiasi processo che esca dal container viene inizialmente eseguito come utente ordinario non privilegiato. Se vuoi prima un confronto tra i runtime, leggi come differiscono Podman e Docker su un VPS. Se preferisci evitare del tutto i container, installare Ollama direttamente su un VPS è un percorso più breve.
SSD Nodes include Fedora tra le immagini disponibili e Fedora fornisce Podman e SELinux (security-enhanced Linux) per impostazione predefinita. Tutti i comandi riportati di seguito funzionano su qualsiasi distribuzione che includa Podman 5 o una versione successiva.
Perché la versione per laptop richiede modifiche su un server
Fedora Magazine ha pubblicato il 5 August 2026 una guida chiara a questo stack: Esecuzione locale di Ollama con Podman su Fedora Linux, di Yazan Monshed. È un buon punto di partenza per familiarizzare con gli strumenti. La guida è pensata per un laptop e quattro delle sue scelte si comportano in modo diverso su una macchina con un indirizzo IP pubblico.
- Avvia il container con un semplice
podman run -d. Un container avviato manualmente non viene riavviato dopo un reboot, perché non è mai stato configurato per l'avvio automatico. - Usa il tag variabile
ollama/ollama. Su un laptop ci si accorge del cambiamento il giorno in cui il comportamento cambia. Su un server, il primo segnale può essere uno script che ha smesso di funzionare durante la notte. - Pubblica il servizio con
-p 11434:11434, che esegue il binding su tutte le interfacce. Dietro un router domestico, il servizio non è raggiungibile da Internet. Su un VPS, diventa un'API di inferenza pubblica senza password. - Esegue il servizio con il proprio utente di login. Su un server, l'account proprietario del container non dovrebbe essere proprietario di nient'altro, così un'eventuale evasione dal container porta a una directory home vuota.
Nessuna di queste scelte è errata per la macchina a cui era destinata la guida. Sono semplicemente decisioni da rivalutare quando il server è raggiungibile da Internet e non c'è nessuno davanti alla macchina.
Crea l'utente non privilegiato e controlla subuid
Rootless Podman mappa gli ID utente (UID) interni del container su un blocco di ID non utilizzati nell'host. Questo blocco è dichiarato in /etc/subuid e /etc/subgid. Senza questa configurazione, i container rootless non possono avviarsi.
sudo dnf install -y podman # or: sudo apt install -y podman
sudo useradd --create-home --shell /bin/bash --comment "Ollama container owner" ollama
sudo passwd --lock ollama
grep ollama /etc/subuid /etc/subgidgrep dovrebbe restituire due righe, una per ciascun file. Ogni riga deve indicare un intervallo di 65536 ID:
/etc/subuid:ollama:100000:65536
/etc/subgid:ollama:100000:65536Il numero iniziale sarà diverso, ed è normale. Se grep non restituisce nulla, useradd non ha assegnato alcun intervallo e il primo comando podman eseguito da quell'utente fallisce con questo errore:
Error: cannot find UID/GID for user ollama: no subuid ranges found for user "ollama" in /etc/subuidAssegna un intervallo non utilizzato da altri utenti, quindi comunica a Podman che la vecchia mappatura non è più valida:
sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 ollama
sudo -iu ollama podman system migrateBloccare la password impedisce a chiunque di accedere direttamente come ollama. Per raggiungere l'account usa sudo -iu ollama dall'utente amministrativo.
Abilitare il lingering per mantenere il servizio attivo dopo il logout
L'istanza systemd di un utente normalmente viene avviata al login e arrestata al logout; insieme a essa viene rimosso anche /run/user/<uid>. Ogni container rootless appartenente a quell'utente si arresta nello stesso momento. Il lingering mantiene in esecuzione l'istanza dell'utente anche senza una sessione associata.
sudo loginctl enable-linger ollama
loginctl show-user ollama --property=LingerIl comando dovrebbe stampare Linger=yes. Abilitatelo prima di creare l'unità, perché la directory richiesta dall'unità, /run/user/<uid>, esiste solo dopo l'attivazione del lingering.
È necessario un altro passaggio che spesso viene trascurato. sudo -iu ollama fornisce una shell, ma non un bus di sessione; di conseguenza systemctl --user fallisce immediatamente:
Failed to connect to bus: $DBUS_SESSION_BUS_ADDRESS and $XDG_RUNTIME_DIR not definedsystemd cerca il bus dell'utente in $XDG_RUNTIME_DIR/bus, ma sudo -i non imposta questa variabile. Impostatela manualmente in ogni shell amministrativa da cui gestite questo servizio:
sudo -iu ollama
export XDG_RUNTIME_DIR=/run/user/$(id -u)
systemctl --user statusDove vengono salvati i blob dei modelli e quanto spazio su disco prevedere
Ollama scrive i pesi in /root/.ollama/models all'interno del container. Collega una directory della home dell'utente a quel percorso: i file verranno salvati in una posizione che puoi misurare, /home/ollama/ollama-data/models. I blob vengono memorizzati in models/blobs come file indicizzati per contenuto, mentre models/manifests contiene il piccolo indice che li identifica. Se invece usi un volume denominato, come nell'articolo di Fedora Magazine, la stessa struttura si trova in /home/ollama/.local/share/containers/storage/volumes/<volume>/_data.
Dimensiona il disco prima di scaricare qualsiasi elemento. Le dimensioni pubblicate dei download indicano il valore minimo da prevedere.
The data behind this chart
[
{
"label": "gemma3:4b",
"download_gb": 3.3
},
{
"label": "mistral:7b",
"download_gb": 4.4
},
{
"label": "qwen3:8b",
"download_gb": 5.2
},
{
"label": "gemma3:12b",
"download_gb": 8.1
},
{
"label": "qwen3:14b",
"download_gb": 9.3
},
{
"label": "gemma3:27b",
"download_gb": 17
},
{
"label": "qwen3:30b",
"download_gb": 19
}
]Tutte le 7 righe riportano valori pubblicati su ollama.com/library, non dimensioni misurate su un disco. Il tag più piccolo, gemma3:4b, richiede il download di 3.3 GB. Quello più grande, qwen3:30b, richiede il download di 19 GB. L'immagine del container occupa inoltre spazio nello storage interno di Podman, quindi verifica entrambi i valori con podman system df e df -h /home. Durante il caricamento, un modello richiede inoltre una quantità di RAM approssimativamente pari alla dimensione del file, oltre allo spazio per la finestra di contesto. Per questo un modello da 19 GB non funzionerà su una VPS con 16 GB di RAM.
Blocca il tag dell'immagine e usa il nome completo del registry
sudo -iu ollama
export XDG_RUNTIME_DIR=/run/user/$(id -u)
mkdir -p ~/ollama-data ~/.config/containers/systemd
podman pull docker.io/ollama/ollama:0.32.9Usa un tag di versione rilasciata, 0.32.9 ad agosto 2026, e non latest. Un tag bloccato garantisce che un riavvio alle 04:00 utilizzi lo stesso binario che hai testato. Ogni variazione del comportamento dipende quindi da una modifica effettuata da te. Docker Hub pubblica anche i tag -rc e -rocm per le stesse versioni; usa quello semplice, a meno che tu non disponga di una GPU AMD.
Specifica anche l'host del registry. Su Fedora, un nome breve in un'unità systemd non dispone di un terminale in cui richiedere conferma e l'unità termina con:
Error: short-name "ollama/ollama" did not resolve to an alias and no unqualified-search registries are definedEseguire prima il pull manualmente è facoltativo, ma utile. In questo modo il download di diversi gigabyte non rientra nel timeout di avvio dell'unità.
L'unità Quadlet che sopravvive a un riavvio
Quadlet è il generatore systemd di Podman. Si scrive un file .container, systemd lo trasforma in un servizio all'avvio e podman generate systemd non è più necessario. Salvare il file come /home/ollama/.config/containers/systemd/ollama.container, con proprietario l'utente ollama.
[Unit]
Description=Ollama API (rootless)
After=network-online.target
Wants=network-online.target
[Container]
Image=docker.io/ollama/ollama:0.32.9
ContainerName=ollama
PublishPort=127.0.0.1:11434:11434
Volume=/home/ollama/ollama-data:/root/.ollama:Z
Environment=OLLAMA_KEEP_ALIVE=30m
Environment=OLLAMA_MAX_LOADED_MODELS=1
[Service]
Restart=always
TimeoutStartSec=900
[Install]
WantedBy=default.targetIl nome del file determina il nome del servizio, quindi ollama.container diventa ollama.service.
systemctl --user daemon-reload
systemctl --user start ollama.service
systemctl --user status ollama.servicestatus dovrebbe mostrare active (running). Non eseguire systemctl --user enable ollama.service. L'unità non esiste come file sul disco, quindi systemd restituisce un errore:
Failed to enable unit: Unit file /run/user/1001/systemd/generator/ollama.service is transient or generated.La sezione [Install] svolge già questa funzione. Quadlet crea autonomamente il collegamento per l'avvio automatico durante daemon-reload, per questo il comando non è facoltativo. TimeoutStartSec=900 gestisce un primo avvio che deve ancora scaricare l'immagine, perché i 90 secondi predefiniti non sono sufficienti per un download di due gigabyte e systemd interromperebbe l'avvio come non riuscito. OLLAMA_KEEP_ALIVE=30m mantiene un modello in memoria tra una richiesta e l'altra invece di scaricarlo dalla memoria dopo cinque minuti; i compromessi sono descritti in mantenere un modello Ollama caricato in memoria. Se alcuni termini di systemd non sono familiari, come funzionano i servizi e i timer systemd su un VPS descrive le unità stesse.
Perché la directory dei modelli restituisce «permission denied» con SELinux
Su Fedora, RHEL, Rocky e AlmaLinux, SELinux è in modalità enforcing per impostazione predefinita. Un processo del container viene eseguito nel dominio container_t, mentre una directory nella home di un utente ha l'etichetta user_home_t. La policy non consente a questi due contesti di interagire. Ollama non può quindi creare la struttura dei modelli e il container termina. Su questi sistemi, getenforce restituisce Enforcing e il diniego viene registrato:
sudo ausearch -m avc -ts recentVerrà visualizzata una riga che indica il dominio e l'etichetta della destinazione:
avc: denied { write } for pid=1842 comm="ollama" name="models" dev="vda1" ino=131077 scontext=system_u:system_r:container_t:s0:c214,c827 tcontext=unconfined_u:object_r:user_home_t:s0 tclass=dir permlisted=0:Z alla fine della riga Volume= risolve il problema. Rietichetta la directory dell'host come container_file_t e le assegna una categoria MCS (multi-category security) privata, presente solo in questo container. La variante minuscola :z usa invece un'etichetta condivisa. È la scelta corretta quando due container devono leggere la stessa directory.
Una precauzione riguarda :Z, perché l'operazione è distruttiva e non produce output. La rietichettatura è ricorsiva. Se si indica /home/ollama, ogni file contenuto nella directory home viene rietichettato. Questo impedisce all'utente di accedere alle proprie chiavi SSH. Assegnare sempre a :Z una sottodirectory dedicata, che non contenga altri file. I volumi denominati non richiedono questa operazione, perché Podman li etichetta correttamente durante la creazione. Per una spiegazione più ampia, Nozioni di base su SELinux per un server illustra contesti e booleani. Ubuntu e Debian usano invece AppArmor. In questi sistemi :Z non esegue alcuna operazione e lasciarlo nell'unità non causa problemi.
Chiudere la porta 11434 e raggiungere l'API tramite SSH
PublishPort=127.0.0.1:11434:11434 associa il lato host all'interfaccia di loopback. Verificalo:
ss -ltnp | grep 11434
curl http://127.0.0.1:11434L'output di ss deve mostrare 127.0.0.1:11434. 0.0.0.0:11434 o *:11434 indicano che la porta è aperta a Internet e curl deve rispondere a Ollama is running.
Specifica con precisione quale lato stai associando. L'indirizzo in PublishPort è l'indirizzo dell'host. All'interno del container, Ollama deve continuare ad ascoltare su tutte le interfacce, che è il comportamento predefinito dell'immagine. Impostare Environment=OLLAMA_HOST=127.0.0.1 associa Ollama alla loopback del container, mentre Podman inoltra il traffico pubblicato all'indirizzo di rete del container. Di conseguenza, ogni richiesta viene rifiutata, anche se proviene dall'host.
Lasciare aperta la porta 11434 comporta due rischi. Ollama non dispone di autenticazione, quindi chiunque raggiunga la porta può elencare i modelli tramite /api/tags, eseguire inferenza sulla CPU e sul traffico disponibile tramite /api/generate, scaricare nuovi modelli sul disco ed eliminare quelli presenti. Inoltre, le richieste HTTP non cifrate verso una porta remota inviano prompt e completamenti in chiaro. Ogni macchina lungo il percorso può quindi leggerli. Entrambi i problemi scompaiono se la porta non lascia mai il server.
Dalla workstation, inoltra la porta tramite SSH:
ssh -N -L 11434:127.0.0.1:11434 you@vps.example.comA questo punto http://127.0.0.1:11434 sul laptop è l'istanza Ollama del server, all'interno della cifratura della sessione SSH. Se il laptop esegue già Ollama, l'associazione locale non riesce con bind [127.0.0.1]:11434: Address already in use. Usa -L 11435:127.0.0.1:11434 e configura il client per usare la porta 11435.
Quando serve un client browser, anteponi invece un reverse proxy con password. Un blocco di sito Caddy richiede quattro righe e caddy hash-password stampa l'hash bcrypt richiesto:
ollama.example.com {
basic_auth {
you $2a$14$replace_with_the_generated_hash
}
reverse_proxy 127.0.0.1:11434
}Caddy ottiene autonomamente un certificato tramite TLS (transport layer security), quindi il traffico è cifrato. Testa prima il client: molti strumenti che comunicano con Ollama non dispongono di un campo per un'intestazione Authorization e non funzioneranno con l'autenticazione di base se ricevono un semplice 401 Unauthorized. Il tunnel SSH non presenta questo problema, motivo per cui è la raccomandazione predefinita in questo caso.
Scarica un modello e verifica l'intero percorso
podman exec -it ollama ollama pull gemma3:4b
curl -s http://127.0.0.1:11434/api/tags
curl -s http://127.0.0.1:11434/api/generate -d '{"model":"gemma3:4b","prompt":"Reply with the single word: ready","stream":false}'
du -sh ~/ollama-data/models/api/tags restituisce un JSON che elenca gemma3:4b. /api/generate restituisce un oggetto JSON con un campo response, dopo una pausa necessaria per caricare i pesi dal disco. du dovrebbe riportare un numero vicino alla dimensione di download pubblicata. Verifica quindi la parte a cui è dedicata l'intera guida:
sudo reboot
# reconnect, then:
sudo -iu ollama
export XDG_RUNTIME_DIR=/run/user/$(id -u)
systemctl --user is-active ollama.serviceactive indica che il processo è rimasto in esecuzione, che la sezione [Install] e daemon-reload hanno funzionato correttamente. inactive indica che manca uno dei tre elementi.
Modalità di errore e stringhe visualizzate
Il container scompare dopo un riavvio. Controlla prima loginctl show-user ollama --property=Linger, perché senza Linger=yes l'istanza systemd dell'utente non viene mai avviata al boot. Se il lingering è attivo, nella sezione [Install] manca il file .container oppure hai modificato il file senza eseguire systemctl --user daemon-reload.
Error: statfs /home/ollama/ollama-data: no such file or directory. La sorgente del bind mount deve esistere prima dell'avvio del container. Podman non crea automaticamente le directory sull'host. Esegui mkdir -p ~/ollama-data come utente ollama.
L'avvio si interrompe dopo 90 secondi. journalctl --user -u ollama.service mostra Start operation timed out. Terminating. perché il download dell'immagine era ancora in corso. Esegui il pull manualmente oppure mantieni TimeoutStartSec=900.
Il container si avvia e termina. podman logs ollama e sudo ausearch -m avc -ts recent indicano insieme se il problema riguarda l'etichetta SELinux. Un AVC che nomina container_t e user_home_t significa che manca :Z.
Le richieste provenienti dall'host vengono rifiutate. curl: (7) Failed to connect to 127.0.0.1 port 11434: Connection refused con il servizio active indica generalmente che OLLAMA_HOST è stato impostato su un indirizzo di loopback all'interno del container. Rimuovi quella riga.
La generazione è molto lenta oppure il container viene terminato. Senza GPU, l'inferenza viene eseguita sulla CPU e un modello di grandi dimensioni è naturalmente lento. Se un container termina durante una richiesta e nei log compare signal: killed, si tratta dell'out-of-memory killer del kernel. Scegli quindi un tag più piccolo dalla tabella precedente.
Aggiornamento di un'immagine bloccata
Il pinning fa sì che gli aggiornamenti siano un'operazione da eseguire, non un evento automatico. Modifica Image= in ollama.container, quindi ricarica la configurazione e riavvia:
systemctl --user daemon-reload
systemctl --user restart ollama.service
podman exec ollama ollama --versionI modelli si trovano nel bind mount, quindi restano invariati quando cambia l'immagine. AutoUpdate=registry nella sezione [Container] serve a chi usa un tag mobile e non ha alcuna utilità accanto a un tag di versione fisso, perché il contenuto di quel tag non cambia. Esegui il backup di /home/ollama/ollama-data/models/manifests e del file .container e non includere i blob: sono voluminosi e ollama pull li scarica nuovamente su un nuovo server.
FAQ
Perché il mio container Podman rootless si arresta quando eseguo il logout?
L'istanza systemd di un utente e la relativa directory /run/user/<uid> vengono eliminate al termine dell'ultima sessione di quell'utente, e con esse viene eliminato ogni container rootless. Esegui sudo loginctl enable-linger ollama e verifica che loginctl show-user ollama --property=Linger restituisca Linger=yes. Abilita il lingering prima di creare l'unità Quadlet, perché la directory di runtime richiesta dall'unità esiste solo quando il lingering è attivo.
Servono le etichette SELinux nella directory dei modelli Ollama?
Su Fedora, RHEL, Rocky e AlmaLinux sì, se monti una directory dell'host tramite bind mount. Il container viene eseguito nel dominio container_t, mentre una directory nella home ha l'etichetta user_home_t; di conseguenza la scrittura viene negata e Ollama termina. Aggiungi :Z alla riga Volume= e assegnagli una sottodirectory dedicata, perché la rietichettatura è ricorsiva e indicare :Z come intera directory home impedisce l'accesso alle chiavi SSH per quell'utente. I volumi denominati ricevono automaticamente le etichette corrette da Podman e non richiedono altro.
Quanto spazio su disco richiede un modello Ollama?
Parti dalla dimensione di download pubblicata su ollama.com/library, che varia da 3.3 GB per gemma3:4b fino a 19 GB per qwen3:30b. Aggiungi anche l'immagine Podman e lascia spazio libero, perché un secondo modello non sostituisce il primo sul disco. Controlla df -h /home prima del download e du -sh ~/ollama-data/models dopo. Pianifica la RAM nello stesso modo: mentre è caricato, un modello richiede all'incirca una quantità di memoria pari alla dimensione del file, oltre allo spazio per la finestra di contesto.
È sicuro esporre la porta 11434 su un VPS?
No. Ollama viene distribuito senza alcun tipo di autenticazione, quindi chiunque raggiunga la porta può elencare i tuoi modelli, eliminarli, scaricarne di nuovi sul disco ed eseguire l'inferenza usando la CPU e la quota di banda disponibile. Inoltre, il traffico HTTP in chiaro su Internet trasmette ogni prompt e ogni completamento senza cifratura. Associa il lato host a 127.0.0.1 con PublishPort=127.0.0.1:11434:11434, verifica con ss -ltnp | grep 11434 e raggiungilo tramite un tunnel SSH o un reverse proxy che richieda una password.