Un agente di coding dice che la funzionalità va. Forse è vero. I test che ha scritto passano, e questo dimostra meno di quanto tutti vorrebbero. Quello che vuoi davvero è aprirla nel browser prima che si avvicini agli utenti, e vuoi farlo senza chiedere all'agente di toccare la produzione.
Gli ambienti di anteprima risolvono il problema, e non serve una piattaforma. Un VPS con IPv4 propria, Docker, Caddy e due brevi script danno a ogni branch il suo URL, come https://feature-login-form.preview.example.com, dietro password e ricostruito a ogni push. L'agente riceve una chiave SSH che può avviare e fermare anteprime e nient'altro.
L'abbiamo costruito su un server di test e percorso tutto il ciclo prima di scriverlo: creare, aprire, pushare di nuovo, smontare, provare ad abusare della chiave.
Cosa ti serve
- Un VPS con IPv4 pubblica. AI-Agent-IP ha spazio per più anteprime insieme.
- Un record DNS wildcard:
*.preview.example.com→ l'IP del server. - Il repository della tua app con un
Dockerfileche serve sulla porta 8080. Va bene qualsiasi linguaggio. - Ubuntu 24.04 o Debian 12/13 con
apt install -y docker.io caddy git.
Clona il repository una volta in /srv/app, con una deploy key in sola lettura.
I due script
/usr/local/bin/preview-up costruisce un branch e lo mette online:
#!/usr/bin/env bash
# preview-up <branch> — build the branch and open it at https://<name>.preview.example.com
set -euo pipefail
BRANCH="$1"
NAME=$(printf '%s' "$BRANCH" | tr '[:upper:]' '[:lower:]' | tr -c 'a-z0-9' '-' | sed 's/--*/-/g; s/^-//; s/-$//' | cut -c1-40)
DIR=/srv/previews/$NAME
git -C /srv/app fetch --quiet origin "$BRANCH"
rm -rf "$DIR"; git -C /srv/app worktree prune
git -C /srv/app worktree add --quiet --force --detach "$DIR" FETCH_HEAD
docker build --quiet -t "preview-$NAME" "$DIR" >/dev/null
docker rm -f "preview-$NAME" >/dev/null 2>&1 || true
docker run -d --name "preview-$NAME" --restart unless-stopped --memory 512m -p 127.0.0.1::8080 "preview-$NAME" >/dev/null
PORT=$(docker port "preview-$NAME" 8080/tcp | head -1 | awk -F: '{print $NF}')
cat > "/etc/caddy/previews/$NAME.caddy" <<CADDY
$NAME.preview.example.com {
import preview_common
reverse_proxy 127.0.0.1:$PORT
}
CADDY
systemctl reload caddy
echo "https://$NAME.preview.example.com"
/usr/local/bin/preview-down lo rimuove:
#!/usr/bin/env bash
# preview-down <branch> — remove the branch preview
set -euo pipefail
NAME=$(printf '%s' "$1" | tr '[:upper:]' '[:lower:]' | tr -c 'a-z0-9' '-' | sed 's/--*/-/g; s/^-//; s/-$//' | cut -c1-40)
docker rm -f "preview-$NAME" >/dev/null 2>&1 || true
docker image rm "preview-$NAME" >/dev/null 2>&1 || true
rm -f "/etc/caddy/previews/$NAME.caddy"; rm -rf "/srv/previews/$NAME"; git -C /srv/app worktree prune
systemctl reload caddy
Alcune scelte sono intenzionali. I container pubblicano solo su 127.0.0.1 con porta casuale, quindi niente li raggiunge se non tramite Caddy. Conta perché le porte pubblicate da Docker scavalcano ufw. Ogni anteprima ha un limite di memoria di 512 MB, così un branch fuori controllo non abbatte gli altri. E rilanciare lo stesso branch lo ricostruisce sul posto: push, up, ricarica.
Caddy: un file per anteprima, una password per tutte
mkdir -p /etc/caddy/previews /srv/previews
chmod +x /usr/local/bin/preview-up /usr/local/bin/preview-down
caddy hash-password --plaintext 'choose-a-review-password'
Metti l'hash in /etc/caddy/Caddyfile:
(preview_common) {
basicauth {
reviewer PASTE-THE-HASH-HERE
}
}
import /etc/caddy/previews/*.caddy
Poi systemctl reload caddy. Ogni anteprima ora sta dietro lo stesso login, quindi un branch a metà con l'output di debug non è esposto su internet. Nelle versioni più recenti di Caddy la direttiva si scrive anche basic_auth; basicauth funziona ancora in entrambe.
La chiave dell'agente: up e down, nient'altro
L'agente deve poter lanciare anteprime, non possedere il server. Crea un utente deploy e uno script di controllo che accetti esattamente due comandi:
#!/usr/bin/env bash
# /usr/local/bin/preview-gate — the only thing the agent's key may run
set -euo pipefail
read -r ACTION BRANCH EXTRA <<< "${SSH_ORIGINAL_COMMAND:-}"
[[ -z "${EXTRA:-}" && "${BRANCH:-}" =~ ^[A-Za-z0-9][A-Za-z0-9._/-]{0,99}$ && "$BRANCH" != *..* ]] || { echo "usage: up|down <branch>" >&2; exit 2; }
case "$ACTION" in
up) exec sudo /usr/local/bin/preview-up "$BRANCH" ;;
down) exec sudo /usr/local/bin/preview-down "$BRANCH" ;;
*) echo "usage: up|down <branch>" >&2; exit 2 ;;
esac
useradd -m -s /bin/bash deploy
echo 'deploy ALL=(root) NOPASSWD: /usr/local/bin/preview-up, /usr/local/bin/preview-down' > /etc/sudoers.d/preview
chmod 440 /etc/sudoers.d/preview && visudo -cf /etc/sudoers.d/preview
chmod 755 /usr/local/bin/preview-gate
In /home/deploy/.ssh/authorized_keys, lega la chiave pubblica dell'agente a quello script:
restrict,command="/usr/local/bin/preview-gate" ssh-ed25519 AAAA... coding-agent
Dalla macchina dove lavora l'agente:
ssh deploy@preview.example.com up feature/login-form
# https://feature-login-form.preview.example.com
ssh deploy@preview.example.com down feature/login-form
Nel nostro test, up ha costruito e pubblicato il branch, un secondo up dopo un nuovo commit ha servito la nuova versione e down ha rimosso container e sito. I tentativi con cat /etc/shadow, up x; id, nomi di branch come -x o a/../b, o un login interattivo con quella chiave hanno ottenuto solo usage: up|down <branch>. Un nome di branch deve iniziare con una lettera o una cifra e non può contenere ...
Collegarlo all'agente
Con Claude Code o Codex basta una riga nel file di istruzioni del progetto:
Dopo aver pushato un branch, esegui
ssh deploy@preview.example.com up <branch>e metti l'URL stampato nel tuo riepilogo. Eseguidownquando il branch viene unito o abbandonato.
Se la tua CI gira già a ogni push, chiama lo stesso comando da lì e tieni la chiave del tutto fuori dall'ambiente dell'agente. È l'opzione più rigorosa, e la sceglieremmo per qualsiasi cosa vada oltre un progetto personale.
Cosa tenere d'occhio
- Disco. Ogni build lascia layer di immagine. Un cron settimanale con
docker image prune -ftiene la situazione sotto controllo;downrimuove già l'immagine propria dell'anteprima. - Branch che nessuno chiude. Gli agenti aprono più branch delle persone. Un cron che elenca i container
preview-*più vecchi di una settimana ed eseguepreview-downsu di essi ti evita di trovare venti anteprime dimenticate. - Segreti. Le anteprime dovrebbero girare con credenziali di test. La password basic auth tiene fuori gli estranei, ma non rende accettabile puntare un'anteprima sul database di produzione.
- Tempo di build. Le immagini vengono costruite sul server, quindi un build pesante si contende la CPU con le anteprime attive. Se i build durano minuti, costruisci in CI e scarica l'immagine.
L'agente non deve per forza vivere su questo server. Se lo fa, vedi VPS per Claude Code, e per lasciargli ordinare e curare da solo quel server, collegalo al server MCP di EQVPS. La stessa macchina può ospitare anche il gateway MCP del tuo team. Per eseguire codice non affidabile senza alcun server ci sono le sandbox, e i piani con IPv4 propria partono da $8 al mese.
Commenti
Ancora nessun commento. Sii il primo.