Come eseguire sandboxd in proprio su un VPS
Installa sandboxd sul tuo VPS con versione bloccata, chiavi dei modelli e anteprime HTTPS. Requisiti: 2 vCPU, RAM e disco, più pulizia dei sandbox obsoleti.
Che cos'è sandboxd e cosa si ottiene eseguendolo in proprio
Per eseguire sandboxd in proprio servono un server Linux con Docker e un nome di dominio. Si invia un prompt, un agente di coding crea un'applicazione reale all'interno di un container isolato e l'applicazione diventa disponibile al relativo URL di anteprima. I builder che trasformano un prompt in un'applicazione sono la categoria hosted più visibile del 2026; sandboxd è la soluzione eseguita sul proprio VPS, con licenza MIT e codice generato archiviato sul proprio disco.
Il design è volutamente essenziale. Un control plane scritto in Go gestisce Docker, Traefik v3 instrada ogni hostname di anteprima, SQLite conserva lo stato e ogni app viene eseguita all'interno di un container. Non sono presenti Kubernetes né un server di database separato. Per questo può funzionare anche su un server con 2 vCPU.
L'intero modello si basa su quattro oggetti. Una app è il progetto persistente e contiene il nome, i metadati git e i secret. Un sandbox è il container Docker in cui viene eseguita l'app; un'app punta a un solo sandbox alla volta. Un workspace contiene i file dell'app, archiviati sull'host e conservati anche dopo la rimozione del container. Un task è un singolo prompt inviato all'agente all'interno del sandbox. Arrestare un sandbox libera memoria e conserva i file. Eliminarlo rimuove il container e l'app può avviarne uno nuovo.
In che modo sandboxd differisce da Dify e OpenHands?
Questi tre strumenti vengono spesso confusi perché eseguono tutti un LLM (large language model) sul server, ma producono risultati diversi. Dify crea applicazioni LLM: interfacce di chat, pipeline di retrieval e workflow che chiamano un modello ogni volta che qualcuno li utilizza. Il modello fa parte del prodotto finito. OpenHands lavora su un repository già esistente: lo si indirizza al codice, quindi legge i file, esegue comandi e propone modifiche. sandboxd parte da zero. Crea la struttura di un progetto a partire da un preset, lo compila in un container vuoto e fornisce un URL da consultare. Il risultato è una normale applicazione React o FastAPI che non richiede alcun modello per funzionare.
La scelta dipende quindi dal risultato finale desiderato. sandboxd serve per partire da una frase e conservare il codice prodotto. Gli altri due strumenti servono quando il repository o il prodotto basato sul modello esistono già.
L'altra differenza riguarda l'età del progetto. È l'aspetto da valutare prima di basarci sopra qualcosa di realmente operativo.
The data behind this chart
[
{
"tool": "sandboxd",
"github_stars": "875",
"forks": "50"
},
{
"tool": "OpenHands",
"github_stars": "83,091",
"forks": "10,711"
},
{
"tool": "Dify",
"github_stars": "151,320",
"forks": "23,886"
}
]sandboxd ha 875 stelle, rispetto alle 83,091 di OpenHands e alle 151,320 di Dify. Il repository è stato creato il 3 giugno 2026, quindi ad agosto 2026 ha due mesi, mentre OpenHands risale a marzo 2024 e Dify ad aprile 2023. La release v0.1.0 è stata pubblicata il 6 giugno 2026 e la v0.3.6 il 1 agosto 2026. Il progetto si definisce beta e specifica che le release 0.x possono introdurre modifiche incompatibili. Questi numeri indicano il rischio legato alle dipendenze, non esprimono un giudizio sulla qualità: un progetto vecchio di due mesi ha avuto soltanto due mesi perché altre persone ne individuassero i bug.
Requisiti del server e problemi causati da risorse insufficienti
Il progetto indica 2 vCPU e 4 GB di RAM come requisiti sufficienti per iniziare. Questa configurazione è adeguata per il control plane e una sandbox di piccole dimensioni, ma non per due persone che eseguono build contemporaneamente. Suddividi la memoria tra i vari componenti. Traefik e il control plane Go richiedono poche risorse. Ogni sandbox in esecuzione contiene una toolchain completa per Node o Python e il picco si verifica durante un npm install seguito da una build di produzione. Prevedi 8 GB per un server che deve mantenere attive alcune applicazioni. Considera lo swap una rete di sicurezza, non una riserva di capacità: una build che usa lo swap richiede minuti invece di secondi.
Quando la memoria si esaurisce, si verificano due problemi distinti, con effetti molto diversi. All'interno di una sandbox, il container raggiunge il limite rigido di --memory impostato da sandboxd e il kernel termina il processo più grande. La build si interrompe senza un messaggio utile da parte dell'agent. docker ps -a mostra il codice di uscita 137 per quel container e docker inspect eseguito su di esso restituisce "OOMKilled": true. Una build Node che termina in questo modo spesso stampa prima JavaScript heap out of memory.
Il secondo problema si verifica sull'host. sandboxd esegue un pressure reaper che arresta le sandbox quando la memoria dell'host si riduce troppo. Su un server con poche risorse, quindi, una sandbox può scomparire mentre ne osservi l'anteprima. I file restano al sicuro e la richiesta successiva all'URL dell'anteprima la riavvia, ma un'attività in esecuzione quando il container viene arrestato non riprende.
Il disco è il problema meno evidente. Ogni applicazione conserva il proprio workspace sull'host e un progetto JavaScript contiene una struttura node_modules delle dimensioni di centinaia di megabyte. Dieci applicazioni richiedono diversi gigabyte di dipendenze, senza contare le immagini. Inizia con 40 GB e monitoralo:
docker system df
sudo du -sh /var/lib/sandboxed/workspacesLa directory dati predefinita è /var/lib/sandboxed, con la e aggiuntiva. Se digiti /var/lib/sandboxd ottieni una directory vuota e perdi cinque minuti cercando di capire il motivo.
Installare una versione fissata di sandboxd
Prima è necessario installare sul server Docker Engine con il plugin Compose e git. La guida Installare Docker su un VPS descrive questa procedura.
docker compose version
git --versionEntrambi i comandi devono stampare una versione. docker: 'compose' is not a docker command indica che è presente il vecchio binario standalone docker-compose; il programma di installazione richiede il plugin v2.
Il programma di installazione è uno script shell scaricato dalla rete. Leggetelo prima di eseguirlo e fissate la versione.
curl -fsSL https://raw.githubusercontent.com/tastyeffectco/sandboxd/v0.3.6/install.sh -o install-sandboxd.sh
less install-sandboxd.sh
SANDBOXD_REF=v0.3.6 bash install-sandboxd.shSANDBOXD_REF è il riferimento git che il programma di installazione verifica ed estrae in $HOME/.sandboxd/src e, per impostazione predefinita, corrisponde a main. Se non lo impostate, l'installazione utilizzerà qualunque modifica sia stata integrata quella mattina. Questo è rilevante per un progetto che nel solo luglio 2026 ha pubblicato sei release. Fissate la versione e aggiornate solo dopo aver letto il changelog.
Lo script clona il codice sorgente, compila le immagini, avvia lo stack con docker compose up -d e, al termine, stampa l'URL della console e un token API. Salvate il token in un luogo sicuro. È la credenziale per un'API che gestisce Docker con privilegi root.
curl http://127.0.0.1:9090/healthzIl comando stampa ok quando il control plane è attivo. Se non stampa nulla, lo stack non è stato avviato: eseguite docker compose ps da ~/.sandboxd/src per verificare quale servizio non è attivo, quindi eseguite docker compose logs sandboxd per determinarne la causa.
Accesso alla console su un server remoto
La console è pubblicata tramite Traefik su HTTP_PORT, che per impostazione predefinita è 80, usando il nome host http://console.localhost. Traefik instrada le richieste in base al nome host. Se inserisci nel browser l'indirizzo IP del server, nessuna regola corrisponde e viene restituito un errore 404. Fino a quando non configuri un dominio reale, inoltra la porta e mantieni il nome host:
ssh -L 8080:127.0.0.1:80 you@your-vpsQuindi apri http://console.localhost:8080 sul laptop. Su Linux e macOS, qualsiasi nome che termina con .localhost viene risolto in 127.0.0.1. La richiesta passa quindi attraverso il tunnel con l'header Host corretto. Imposta la password della console al primo accesso.
Assegna un modello all’agente
Nell’immagine di base sono inclusi due agenti di codifica: OpenCode e Claude Code. SANDBOXD_DEFAULT_AGENT determina quale agente esegue un’attività che non ne specifica uno e, per impostazione predefinita, usa opencode. Se non colleghi alcuna chiave, le attività vengono eseguite sui modelli gratuiti senza chiave di OpenCode Zen. In questo modo la prima build non ha costi e puoi testare l’intero flusso prima di sostenere una spesa.
Collega una tua chiave quando vuoi usare un modello più potente. Le chiavi vengono inviate al control plane e non entrano mai nella sandbox: sono archiviate in forma crittografata nella directory dei dati e inserite nelle richieste da un credential proxy. Di conseguenza, né l’agente né il codice che scrive possono leggerle.
export API=http://127.0.0.1:9090
export SANDBOXD_TOKEN=sk_... # printed by the installer
export AUTH="Authorization: Bearer $SANDBOXD_TOKEN"
curl -s -XPOST $API/v1/agents/claude-code/api-key -H "$AUTH" \
-H 'content-type: application/json' \
-d '{"api_key":"sk-ant-..."}'La console offre la stessa configurazione in Settings, AI Agents e include un flusso OAuth guidato, se vuoi usare un abbonamento Claude invece di una chiave API. Il modello predefinito per ogni agente si trova nello stesso pannello e una singola attività può sostituirlo.
Crea una piccola applicazione dall’inizio alla fine
Crea l’applicazione, avvia il relativo sandbox, quindi invia un prompt. Gli identificativi vengono restituiti in formato JSON e il quickstart li estrae con sed, quindi non è necessario installare jq.
APP=$(curl -s -XPOST $API/v1/apps -H "$AUTH" \
-H 'content-type: application/json' \
-d '{"name":"todo","runtime_preset":"react-vite"}' \
| sed -E 's/.*"id":"([^"]+)".*/\1/')
SB=$(curl -s -XPOST $API/v1/apps/$APP/sandbox -H "$AUTH" \
-H 'content-type: application/json' -d '{"ports":[3000]}' \
| sed -E 's/.*"id":"([^"]+)".*/\1/')
echo "app=$APP sandbox=$SB"Entrambe le variabili devono contenere un identificativo. Un $SB vuoto indica che il sandbox non è mai stato avviato. Le cause più comuni sono che l’immagine di base è ancora in fase di build oppure che sull’host non è disponibile memoria sufficiente. Un 401 al posto di un identificativo indica che il bearer token non è corretto.
curl -s -XPOST $API/v1/sandboxes/$SB/tasks -H "$AUTH" \
-H 'content-type: application/json' \
-d '{"prompt":"Add a todo list with a text input, an add button, and a delete button on each row. Keep the list in localStorage.","agent":"opencode"}'La risposta contiene un task id. GET /v1/sandboxes/$SB/tasks/<task id> ne restituisce il risultato, mentre il percorso /events relativo allo stesso task è uno stream SSE (server sent events) in tempo reale che mostra le attività dell’agente. La console visualizza lo stesso stream come chat.
L’applicazione è quindi disponibile all’indirizzo http://s-<sandbox id>-3000.preview.localhost, dove 3000 è la porta richiesta. Se il sandbox era inattivo, la prima richiesta raggiunge il catch-all di Traefik. sandboxd avvia il container, attende che la porta risponda e visualizza una breve pagina di avvio che si aggiorna automaticamente e apre l’applicazione. Un’anteprima che rimane su quella pagina indica che il processo interno non è in ascolto sulla porta dichiarata nel sandbox.yaml dell’applicazione.
Pubblicare le anteprime su un dominio reale con HTTPS
Ogni sandbox riceve il proprio hostname, quindi un unico record DNS wildcard è sufficiente per coprirle tutte. Indirizza *.preview.yourdomain.com all'indirizzo IP del server tramite un record A. Imposta quindi le variabili delle anteprime in .env, all'interno di ~/.sandboxd/src:
PREVIEW_DOMAIN=yourdomain.com
PREVIEW_ENTRYPOINT=websecure
PREVIEW_TLS=true
SANDBOXD_API_AUTH_DISABLED=falseTraefik deve avere la configurazione corrispondente: abilita l'entrypoint websecure in traefik/traefik.yml e aggiungi un certificate resolver. Usa la challenge DNS-01, perché un unico certificato wildcard copre tutti gli hostname delle anteprime. Con HTTP-01 ogni nuova sandbox richiederebbe una propria emissione del certificato; una sessione intensa di build può quindi raggiungere rapidamente i rate limit di Let's Encrypt. Certificati wildcard tramite la challenge DNS-01 descrive la configurazione DNS.
cd ~/.sandboxd/src
docker compose up -dGli URL delle anteprime diventano https://s-<id>-3000.preview.yourdomain.com. Apri le porte 80 e 443 nel firewall e lascia chiusa la porta 9090 verso Internet: consulta regole firewall di base per ufw. Ricorda che chiunque riesca a indovinare un hostname di anteprima può caricare l'applicazione; considera quindi le anteprime come pubbliche.
Dove viene salvato il codice generato e si può esportare?
Sul server host, nella directory dei dati. Ogni workspace è una directory normale in /var/lib/sandboxed/workspaces/<id>/, montata nel container tramite bind mount; i file dell'applicazione si trovano in /home/sandbox/workspace/app all'interno della sandbox. Lo stato del control plane è contenuto in un unico file SQLite in state/sandboxd.db, mentre le credenziali dell'agent, cifrate, si trovano in agent-auth/. Nulla è nascosto in un layer del container, quindi un backup consiste nella copia della directory e di quel file di database. backup restic su un VPS gestisce entrambi.
sudo ls /var/lib/sandboxed/workspaces
sudo du -sh /var/lib/sandboxed/workspaces/*L'esportazione Git è integrata, non aggiunta separatamente. L'API espone lo stato e il diff per la lettura, quindi consente di eseguire commit e push:
curl -s $API/v1/apps/$APP/git/status -H "$AUTH"
curl -s -XPOST $API/v1/apps/$APP/git/commit -H "$AUTH" \
-H 'content-type: application/json' \
-d '{"message":"todo list, first pass"}'
curl -s -XPOST $API/v1/apps/$APP/git/push -H "$AUTH" \
-H 'content-type: application/json' -d '{"branch":"main"}'Un remote privato richiede un personal access token, da impostare una sola volta nella console, in Settings, Git credentials. Il token viene memorizzato cifrato e resta fuori dalla sandbox, quindi l'agent non può leggerlo né usarlo per eseguire push senza autorizzazione. Esegui il push presto e spesso. Fino a quel momento, la directory del workspace è l'unica copia del codice e DELETE /v1/apps/<id> la elimina senza possibilità di ripristino.
Quanto costa una build in token del modello?
sandboxd non misura la spesa, quindi il dato rilevante è disponibile nella console del provider. I modelli gratuiti OpenCode Zen non hanno costi, ma sono più lenti e meno efficaci rispetto a un modello a pagamento. La differenza si nota dal numero maggiore di cicli di correzione necessari per qualsiasi progetto più complesso di un'applicazione di prova.
L'importo dipende dal funzionamento del ciclo dell'agente. A ogni turno viene reinviato il contesto necessario, quindi il costo dipende dal numero di turni, non dal numero di applicazioni. Un prompt che produce subito il risultato costa poco. Quindici cicli con richieste come "ora correggi la spaziatura" su un progetto con cinquanta file costano invece molto di più, perché il contenuto dei file viene trasmesso ogni volta. I token di input e output hanno prezzi diversi, e quanto costa un agente di coding per sessione fornisce un intervallo realistico. Imposta un limite di spesa rigido nella console del provider prima di affidare le credenziali a un ciclo eseguito senza supervisione.
Pulizia delle sandbox obsolete
Il processo di pulizia delle sandbox inattive arresta ogni sandbox rimasta inattiva oltre SANDBOXD_IDLE_THRESHOLD_SECONDS, che per impostazione predefinita corrisponde a 2100 secondi, ovvero 35 minuti. In questo modo libera la RAM ma conserva i file; la richiesta successiva all’URL di anteprima riattiva il container. Imposta un valore inferiore sui server con poche risorse, perché 35 minuti di container inattivi equivalgono a 35 minuti di memoria non disponibile.
Arrestare una sandbox non significa eliminarla, ed è in questa fase che i dischi si riempiono senza segnali evidenti. Una sandbox arrestata conserva il proprio workspace e il proprio container. La rimozione della sandbox mantenendo l’applicazione è un DELETE sulla sandbox e rimuove sia il container sia il workspace. La rimozione dell’applicazione elimina tutto in modo permanente.
curl -s -XPOST $API/v1/sandboxes/$SB/stop -H "$AUTH" # frees RAM, keeps files
curl -s -XDELETE $API/v1/sandboxes/$SB -H "$AUTH" # container and workspace gone
curl -s -XDELETE $API/v1/apps/$APP -H "$AUTH" # app and everything under itDopo alcune settimane di esperimenti, docker system df mostrerà più spazio recuperabile dalle immagini di quanto previsto, perché ogni applicazione che ha scaricato il proprio toolchain ha lasciato dei layer. docker image prune rimuove quelli dangling. Controlla prima GET /v1/apps, perché un’immagine ancora utilizzata da una sandbox inattiva non è garbage.
Cosa offre e cosa non offre l'isolamento del container
Ogni sandbox viene eseguita con un utente senza privilegi, un filesystem root in sola lettura, tutte le capability Linux rimosse, no-new-privileges impostato, un limite di memoria e un limite al numero di processi. Il progetto chiarisce i limiti di questo approccio: un container Linux con kernel condiviso è un forte confine di isolamento, ma un confine di sicurezza debole. Una vulnerabilità nel kernel compromette l'host.
Due aspetti richiedono interventi. Nel build self-hosted, il traffico in uscita dalla sandbox è consentito: il codice generato può raggiungere Internet, la rete locale e gli endpoint dei metadati cloud. Nel codice sorgente esiste un sottosistema nftables per il traffico in uscita, ma è disabilitato in fase di compilazione nel build portabile Docker Compose. I limiti devono quindi essere applicati dal firewall dell'host. Inoltre, l'API del control plane equivale di fatto a root sull'host, perché gestisce il socket Docker. Per impostazione predefinita è in ascolto su 127.0.0.1:9090; SANDBOXD_API_AUTH_DISABLED deve rimanere false e l'API non deve mai essere pubblicata su Internet.
Se prevedi di consentire ad altre persone di inviare prompt al tuo server, questo modello non offre una protezione sufficiente da solo. Il progetto indica gVisor con SANDBOXD_RUNTIME=runsc, che inserisce un kernel in userspace tra la sandbox e l'host, con un costo di circa 1.7-4 volte in più per le operazioni ad alto uso di syscall. La soluzione più sicura consiste nell'assegnare una macchina a ogni tenant. È lo stesso principio descritto in eseguire agenti di programmazione in una VM effimera.
Conviene basarsi su un progetto vecchio di due mesi?
Per un ambiente di sviluppo personale, sì, adottando le precauzioni ovvie: blocca SANDBOXD_REF, esegui il backup di /var/lib/sandboxed e invia a un repository Git remoto ogni applicazione che vuoi conservare. Per qualsiasi progetto utilizzato da un cliente, aspetta la versione 1.0 oppure prevedi possibili malfunzionamenti, perché i maintainer dichiarano chiaramente che nella serie 0.x possono essere introdotte modifiche incompatibili. Ad agosto 2026 i maintainer offrono anche un'installazione gestita a 79 dollari al mese. È un dato utile per valutare se il progetto ha prospettive di continuità.
Il rischio è accettabile soprattutto per il risultato prodotto. sandboxd crea un'applicazione ordinaria in un normale repository Git. Se lo sviluppo del progetto si arresta, conservi quindi il codice e perdi soltanto il wrapper. È una situazione molto più favorevole rispetto a un builder ospitato che controlla il tuo progetto. Per una panoramica più ampia di ciò che vale la pena self-hostare quest'anno, consulta cosa vale la pena self-hostare nel 2026.
FAQ
Quali sono i requisiti minimi del server per sandboxd?
Il progetto indica che 2 vCPU e 4 GB di RAM sono sufficienti per iniziare. Questa configurazione copre il control plane, Traefik e un sandbox di piccole dimensioni. Usa 8 GB di RAM e 40 GB di spazio su disco se vuoi mantenere attive più applicazioni contemporaneamente, perché ogni sandbox in esecuzione contiene un toolchain completo per Node o Python e ogni workspace conserva su disco il proprio albero delle dipendenze. Quando la memoria dell'host non è sufficiente, il pressure reaper di sandboxd arresta i sandbox per liberare memoria. Una build che supera il limite di memoria del proprio container viene terminata dal kernel: docker ps -a mostra per quel processo il codice di uscita 137.
In cosa sandboxd è diverso da Dify o OpenHands?
Producono artefatti diversi. Dify crea applicazioni che chiamano un modello durante l'esecuzione, come interfacce di chat e pipeline di retrieval. OpenHands modifica un repository già esistente, esegue comandi e propone modifiche al codice esistente. sandboxd crea lo scheletro di un progetto completamente nuovo a partire da un prompt, lo esegue nella compilazione all'interno del proprio container e lo pubblica tramite un URL di anteprima. Il risultato è una normale applicazione web che non ha bisogno di un modello per funzionare.
Dove risiede effettivamente il codice scritto dall'agent?
Nel filesystem dell'host, non all'interno di un'immagine container. Ogni applicazione riceve una directory in /var/lib/sandboxed/workspaces/<id>/, montata nel relativo sandbox tramite bind mount, e i file sono disponibili in /home/sandbox/workspace/app all'interno del sandbox. Lo stato del control plane è contenuto in un unico file SQLite in state/, nella stessa directory dei dati. Puoi eseguire il commit e il push verso un remote git dalla scheda Git della console oppure tramite gli endpoint /v1/apps/<id>/git/commit e /git/push. Un token per i remote privati viene archiviato dal control plane in forma crittografata, invece di essere passato al sandbox.
È sicuro esporre sandboxd a Internet?
Esponi gli URL di anteprima e la console, ma mai l'API del control plane. Questa API controlla Docker sull'host, quindi equivale all'accesso root. Per questo motivo, per impostazione predefinita è in ascolto su 127.0.0.1:9090. Nella build self-hosted, i sandbox hanno inoltre accesso in uscita alla rete senza restrizioni. Il codice scritto dall'agent può quindi raggiungere la rete locale e gli endpoint dei metadati cloud. Aggiungi regole firewall sull'host se il server comunica con altri sistemi che devono essere protetti. Per i prompt inviati da persone non attendibili, usa un host per tenant invece di affidarti al solo isolamento del container.