−25%

auf Windows bei Jahreszahlung, bis 31.10. Zu den Tarifen

EQVPS
Loslegen

VPS für Vorschauen von Coding-Agenten: Jeder Branch bekommt eine eigene URL

Ein Coding-Agent ist mit einem Branch fertig und sagt, er funktioniere. Geben Sie ihm einen Server, auf dem sich das prüfen lässt: Jeder Branch wird zu einem Container gebaut und öffnet sich unter einer eigenen HTTPS-Subdomain hinter einem Passwort, und der SSH-Schlüssel des Agenten kann nur Vorschauen starten und stoppen.

Ein Coding-Agent sagt, das Feature funktioniert. Vielleicht stimmt das. Die Tests, die er geschrieben hat, laufen durch, und das beweist weniger, als allen lieb wäre. Was Sie eigentlich wollen: das Ding im Browser öffnen, bevor es Nutzern auch nur nahekommt, und zwar ohne den Agenten an die Produktion zu lassen.

Vorschau-Umgebungen lösen das, und dafür brauchen Sie keine Plattform. Ein VPS mit eigener IPv4, Docker, Caddy und zwei kurze Skripte geben jedem Branch eine eigene URL wie https://feature-login-form.preview.example.com, hinter einem Passwort, bei jedem Push neu gebaut. Der Agent bekommt einen SSH-Schlüssel, mit dem er Vorschauen starten und stoppen kann und sonst nichts.

Wir haben das auf einem Testserver gebaut und den ganzen Ablauf durchgespielt, bevor wir es aufgeschrieben haben: erstellen, öffnen, erneut pushen, abbauen, den Schlüssel zu missbrauchen versuchen.

Was Sie brauchen

  • Einen VPS mit öffentlicher IPv4. AI-Agent-IP hat Platz für mehrere Vorschauen gleichzeitig.
  • Einen Wildcard-DNS-Eintrag: *.preview.example.com → IP des Servers.
  • Das Repository Ihrer App mit einem Dockerfile, das auf Port 8080 ausliefert. Jede Sprache funktioniert.
  • Ubuntu 24.04 oder Debian 12/13 mit apt install -y docker.io caddy git.

Klonen Sie das Repository einmal nach /srv/app, mit einem Deploy-Schlüssel mit Lesezugriff.

Die zwei Skripte

/usr/local/bin/preview-up baut einen Branch und stellt ihn 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 entfernt ihn:

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

Einige Entscheidungen darin sind bewusst. Container veröffentlichen nur auf 127.0.0.1 mit zufälligem Port, also erreicht sie nichts außer über Caddy. Das ist wichtig, weil veröffentlichte Docker-Ports ufw umgehen. Jede Vorschau bekommt ein Speicherlimit von 512 MB, damit ein entgleister Branch die anderen nicht mitreißt. Und derselbe Branch noch einmal gestartet wird an Ort und Stelle neu gebaut: pushen, up ausführen, neu laden.

Caddy: eine Datei pro Vorschau, ein Passwort für alle

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'

Tragen Sie den Hash in /etc/caddy/Caddyfile ein:

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

Dann systemctl reload caddy. Jede Vorschau liegt jetzt hinter demselben Login, ein halbfertiger Branch mit Debug-Ausgabe steht also nicht offen im Internet. In neueren Caddy-Versionen heißt die Direktive auch basic_auth; basicauth funktioniert weiterhin in beiden.

Der Schlüssel des Agenten: up und down, sonst nichts

Der Agent muss Vorschauen auslösen, nicht den Server besitzen. Legen Sie einen Benutzer deploy und ein Gate-Skript an, das genau zwei Befehle annimmt:

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

Binden Sie in /home/deploy/.ssh/authorized_keys den öffentlichen Schlüssel des Agenten an dieses Skript:

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

Von der Maschine, auf der der Agent arbeitet:

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

In unserem Test hat up den Branch gebaut und veröffentlicht, ein zweites up nach einem neuen Commit lieferte die neue Version aus, und down entfernte Container und Seite. Versuche mit cat /etc/shadow, up x; id, Branch-Namen wie -x oder a/../b oder einer interaktiven Anmeldung mit diesem Schlüssel bekamen alle nur usage: up|down <branch>. Ein Branch-Name muss mit einem Buchstaben oder einer Ziffer beginnen und darf kein .. enthalten.

Einbindung in den Agenten

Bei Claude Code oder Codex reicht eine Zeile in der Anweisungsdatei des Projekts:

Nach dem Pushen eines Branches führe ssh deploy@preview.example.com up <branch> aus und nimm die ausgegebene URL in deine Zusammenfassung auf. Führe down aus, wenn der Branch gemergt oder verworfen ist.

Läuft Ihr CI bereits bei jedem Push, rufen Sie denselben Befehl stattdessen von dort auf und halten den Schlüssel ganz aus der Umgebung des Agenten heraus. Das ist die strengere Variante, und wir würden sie für alles jenseits eines privaten Projekts wählen.

Worauf man achten sollte

  • Festplatte. Jeder Build hinterlässt Image-Layer. Ein wöchentlicher Cron mit docker image prune -f hält das im Griff; down entfernt das eigene Image der Vorschau bereits.
  • Branches, die niemand schließt. Agenten öffnen mehr Branches als Menschen. Ein Cron-Job, der preview-*-Container älter als eine Woche auflistet und für sie preview-down ausführt, erspart Ihnen zwanzig vergessene Vorschauen.
  • Geheimnisse. Vorschauen sollten mit Test-Zugangsdaten laufen. Das Basic-Auth-Passwort hält Fremde draußen; es macht es nicht in Ordnung, eine Vorschau auf die Produktionsdatenbank zeigen zu lassen.
  • Build-Zeit. Images werden auf dem Server gebaut, ein schwerer Build konkurriert also mit laufenden Vorschauen um CPU. Dauern Builds Minuten, bauen Sie im CI und ziehen das Image stattdessen.

Der Agent selbst muss nicht auf diesem Server leben. Tut er es doch, lesen Sie VPS für Claude Code, und damit er diesen Server selbst bestellen und betreuen kann, verbinden Sie ihn mit dem EQVPS MCP-Server. Dieselbe Maschine kann auch das MCP-Gateway Ihres Teams hosten. Um nicht vertrauenswürdigen Code ganz ohne Server auszuführen, gibt es Sandboxen, und die Tarife mit eigener IPv4 beginnen bei $8 im Monat.

FAQ

Warum lässt man den Agenten nicht direkt in die Produktion deployen?

Weil „die Tests laufen“ und „es funktioniert“ nicht dasselbe sind und der Agent der Letzte sein sollte, der das entscheidet. Eine Vorschau gibt einem Menschen etwas zum Anklicken, bevor etwas bei Nutzern landet, und kostet nur einen zusätzlichen Befehl.

Welcher Tarif passt?

Das hängt davon ab, wie viele Vorschauen gleichzeitig laufen. AI-Agent-IP ($15/Monat, 4 vCPU, 4 GB RAM, 40 GB Festplatte) hält bequem mehrere kleine Web-Apps mit je 512 MB Limit. Docker-Builds sind der schwerste Teil, achten Sie also bei großen Images auf Platte und Speicher.

Brauche ich ein Wildcard-Zertifikat?

Nein. Caddy holt sich für jede Vorschau-Subdomain selbst ein eigenes Zertifikat. Nur der DNS-Eintrag ist ein Wildcard. Let's Encrypt erlaubt etwa 50 neue Zertifikate pro registrierter Domain und Woche, das reicht, solange der Agent nicht Hunderte Branches anlegt.

Kann der Agent mit seinem SSH-Schlüssel den Server kaputt machen?

Nicht über diesen Schlüssel. Er ist auf einen einzigen Befehl beschränkt, der nur „up <branch>“ oder „down <branch>“ mit einem sicheren Branch-Namen annimmt. Shell-Zugang, zusätzliche Argumente und Portweiterleitung werden abgelehnt.

Läuft das auf einem NAT-Tarif?

Nein. Vorschauen müssen auf den Ports 80 und 443 unter eigenen Subdomains erreichbar sein, dafür braucht es eine öffentliche IPv4. NAT-Tarife leiten nur den SSH-Port weiter.

Kommentare

Noch keine Kommentare. Sei der Erste.

Kommentar hinterlassen

Kommentare werden vor der Anzeige moderiert.