−25%

sur Windows en paiement annuel, jusqu'au 31/10. Voir les offres

EQVPS
Commencer

VPS pour les aperçus d'agents de code : chaque branche poussée a sa propre URL

Un agent de code termine une branche et affirme qu'elle fonctionne. Donnez-lui un serveur où le vérifier : chaque branche est construite dans un conteneur et s'ouvre sur son propre sous-domaine HTTPS derrière un mot de passe, et la clé SSH de l'agent ne peut que démarrer et arrêter des aperçus.

Un agent de code affirme que la fonctionnalité marche. Peut-être bien. Les tests qu'il a écrits passent, ce qui prouve moins qu'on ne le voudrait. Ce que vous voulez vraiment, c'est ouvrir le résultat dans un navigateur avant qu'il n'approche des utilisateurs, sans demander à l'agent de toucher à la production.

Les environnements d'aperçu règlent cela, et il n'y a pas besoin d'une plateforme. Un VPS avec IPv4 propre, Docker, Caddy et deux courts scripts donnent à chaque branche sa propre URL, comme https://feature-login-form.preview.example.com, derrière un mot de passe, reconstruite à chaque push. L'agent reçoit une clé SSH qui peut démarrer et arrêter des aperçus, et rien d'autre.

Nous avons monté cela sur un serveur de test et parcouru tout le cycle avant de l'écrire : créer, ouvrir, pousser à nouveau, démonter, tenter d'abuser de la clé.

Ce qu'il vous faut

  • Un VPS avec IPv4 publique. AI-Agent-IP a de la place pour plusieurs aperçus simultanés.
  • Un enregistrement DNS wildcard : *.preview.example.com → l'IP du serveur.
  • Le dépôt de votre application avec un Dockerfile qui sert sur le port 8080. N'importe quel langage convient.
  • Ubuntu 24.04 ou Debian 12/13 avec apt install -y docker.io caddy git.

Clonez le dépôt une fois dans /srv/app, avec une clé de déploiement en lecture seule.

Les deux scripts

/usr/local/bin/preview-up construit une branche et la met en ligne :

#!/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 la supprime :

#!/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

Certains choix sont délibérés. Les conteneurs ne publient que sur 127.0.0.1 avec un port aléatoire, donc rien ne les atteint sauf via Caddy. C'est important, car les ports publiés par Docker contournent ufw. Chaque aperçu a une limite mémoire de 512 Mo, pour qu'une branche emballée n'emporte pas les autres. Et relancer la même branche la reconstruit sur place : push, up, rafraîchir.

Caddy : un fichier par aperçu, un mot de passe pour tous

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'

Mettez le hash dans /etc/caddy/Caddyfile :

(preview_common) {
	basicauth {
		reviewer PASTE-THE-HASH-HERE
	}
}
import /etc/caddy/previews/*.caddy

Puis systemctl reload caddy. Tous les aperçus sont désormais derrière le même identifiant, donc une branche à moitié finie avec sa sortie de débogage n'est pas exposée sur Internet. Dans les versions récentes de Caddy, la directive s'écrit aussi basic_auth ; basicauth fonctionne toujours dans les deux.

La clé de l'agent : up et down, rien d'autre

L'agent doit pouvoir déclencher des aperçus, pas posséder le serveur. Créez un utilisateur deploy et un script de contrôle qui accepte exactement deux commandes :

#!/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

Dans /home/deploy/.ssh/authorized_keys, liez la clé publique de l'agent à ce script :

restrict,command="/usr/local/bin/preview-gate" ssh-ed25519 AAAA... coding-agent

Depuis la machine où travaille l'agent :

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

Lors de notre test, up a construit et publié la branche, un second up après un nouveau commit a servi la nouvelle version, et down a supprimé le conteneur et le site. Les tentatives avec cat /etc/shadow, up x; id, des noms de branche comme -x ou a/../b, ou une connexion interactive avec cette clé n'ont obtenu que usage: up|down <branch>. Un nom de branche doit commencer par une lettre ou un chiffre et ne peut pas contenir ...

Brancher cela sur l'agent

Avec Claude Code ou Codex, une ligne dans le fichier d'instructions du projet suffit :

Après avoir poussé une branche, exécute ssh deploy@preview.example.com up <branch> et ajoute l'URL affichée à ton résumé. Exécute down quand la branche est fusionnée ou abandonnée.

Si votre CI tourne déjà à chaque push, appelez la même commande depuis la CI et gardez entièrement la clé hors de l'environnement de l'agent. C'est l'option la plus stricte, et nous la choisirions pour tout ce qui dépasse un projet personnel.

Points à surveiller

  • Disque. Chaque build laisse des couches d'image. Un cron hebdomadaire avec docker image prune -f garde cela sous contrôle ; down supprime déjà l'image propre à l'aperçu.
  • Branches que personne ne ferme. Les agents ouvrent plus de branches que les humains. Un cron qui liste les conteneurs preview-* de plus d'une semaine et lance preview-down dessus vous évite de retrouver vingt aperçus oubliés.
  • Secrets. Les aperçus doivent tourner avec des identifiants de test. Le mot de passe basic auth tient les inconnus à l'écart ; il ne rend pas acceptable de brancher un aperçu sur la base de production.
  • Temps de build. Les images sont construites sur le serveur, donc un build lourd dispute le CPU aux aperçus en cours. Si les builds prennent des minutes, construisez dans la CI et récupérez l'image.

L'agent lui-même n'a pas besoin de vivre sur ce serveur. S'il y vit, voyez VPS pour Claude Code, et pour qu'il commande et entretienne ce serveur lui-même, connectez-le au serveur MCP d'EQVPS. La même machine peut aussi héberger la passerelle MCP de votre équipe. Pour exécuter du code non fiable sans aucun serveur, il y a les sandboxes, et les offres avec IPv4 propre commencent à $8 par mois.

FAQ

Pourquoi ne pas laisser l'agent déployer directement en production ?

Parce que « les tests passent » et « ça marche » ne sont pas la même chose, et que l'agent est le dernier qui devrait en décider. Un aperçu donne à une personne quelque chose à ouvrir avant que quoi que ce soit n'atteigne les utilisateurs, pour une seule commande de plus.

Quelle offre convient ?

Cela dépend du nombre d'aperçus simultanés. AI-Agent-IP ($15/mois, 4 vCPU, 4 Go de RAM, 40 Go de disque) accueille sans peine plusieurs petites applications web avec une limite de 512 Mo chacune. Les builds Docker sont la partie la plus lourde : surveillez disque et mémoire si vos images sont grosses.

Faut-il un certificat wildcard ?

Non. Caddy obtient tout seul un certificat distinct pour chaque sous-domaine d'aperçu. Seul l'enregistrement DNS est en wildcard. Let's Encrypt autorise environ 50 nouveaux certificats par domaine enregistré et par semaine, largement assez sauf si l'agent crée des centaines de branches.

L'agent peut-il casser le serveur avec sa clé SSH ?

Pas avec cette clé. Elle est limitée à une seule commande qui n'accepte que « up <branche> » ou « down <branche> » avec un nom de branche sûr. L'accès shell, les arguments supplémentaires et la redirection de ports sont refusés.

Cela fonctionne-t-il sur une offre NAT ?

Non. Les aperçus doivent être joignables sur les ports 80 et 443 à leurs propres sous-domaines, ce qui exige une IPv4 publique. Les offres NAT ne redirigent que le port SSH.

Commentaires

Pas encore de commentaires. Soyez le premier.

Laisser un commentaire

Les commentaires sont modérés avant leur publication.