−25%

na Windows przy płatności rocznej, do 31.10. Do planów

EQVPS
Zacznij

VPS na podglądy agentów kodujących: każda wypchnięta gałąź dostaje własny URL

Agent kodujący kończy gałąź i mówi, że działa. Daj mu serwer, na którym da się to sprawdzić: każda gałąź jest budowana w kontenerze i otwiera się pod własną subdomeną HTTPS za hasłem, a klucz SSH agenta może tylko uruchamiać i zatrzymywać podglądy.

Agent kodujący mówi, że funkcja działa. Może i tak. Testy, które napisał, przechodzą, a to dowodzi mniej, niż wszyscy by chcieli. Tak naprawdę chcesz otworzyć to w przeglądarce, zanim zbliży się do użytkowników, i to bez proszenia agenta, żeby dotykał produkcji.

Rozwiązują to środowiska podglądu i nie potrzebujesz do nich platformy. Jeden VPS z własnym IPv4, Docker, Caddy i dwa krótkie skrypty dają każdej gałęzi własny URL, na przykład https://feature-login-form.preview.example.com, za hasłem i przebudowywany przy każdym pushu. Agent dostaje klucz SSH, którym może uruchamiać i zatrzymywać podglądy, i nic poza tym.

Zbudowaliśmy to na serwerze testowym i przeszliśmy cały cykl, zanim to opisaliśmy: utworzyć, otworzyć, wypchnąć ponownie, rozebrać, spróbować nadużyć klucza.

Czego potrzebujesz

  • VPS z publicznym IPv4. AI-Agent-IP zmieści kilka podglądów naraz.
  • Rekordu DNS z wildcardem: *.preview.example.com → IP serwera.
  • Repozytorium aplikacji z plikiem Dockerfile, który serwuje na porcie 8080. Język dowolny.
  • Ubuntu 24.04 albo Debiana 12/13 z apt install -y docker.io caddy git.

Sklonuj repozytorium raz do /srv/app, używając klucza wdrożeniowego tylko do odczytu.

Dwa skrypty

/usr/local/bin/preview-up buduje gałąź i wystawia ją 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 ją usuwa:

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

Kilka wyborów jest celowych. Kontenery publikują tylko na 127.0.0.1 z losowym portem, więc nic do nich nie dociera inaczej niż przez Caddy. To ważne, bo porty opublikowane przez Dockera omijają ufw. Każdy podgląd ma limit pamięci 512 MB, żeby jedna rozbrykana gałąź nie położyła pozostałych. A ponowne uruchomienie tej samej gałęzi przebudowuje ją na miejscu: push, up, odśwież.

Caddy: jeden plik na podgląd, jedno hasło dla wszystkich

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'

Wpisz hash do /etc/caddy/Caddyfile:

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

Potem systemctl reload caddy. Każdy podgląd jest teraz za tym samym logowaniem, więc niedokończona gałąź z wyjściem debugowania nie wisi otwarta w internecie. W nowszych wersjach Caddy dyrektywa nazywa się też basic_auth; basicauth nadal działa w obu.

Klucz agenta: up i down, nic więcej

Agent ma wywoływać podglądy, a nie posiadać serwer. Utwórz użytkownika deploy i skrypt bramki, który przyjmuje dokładnie dwa polecenia:

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

W /home/deploy/.ssh/authorized_keys przypnij klucz publiczny agenta do tego skryptu:

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

Z maszyny, na której pracuje 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

W naszym teście up zbudował i opublikował gałąź, drugi up po nowym commicie serwował nową wersję, a down usunął kontener i stronę. Próby z cat /etc/shadow, up x; id, nazwami gałęzi takimi jak -x czy a/../b albo interaktywnym logowaniem tym kluczem dostały tylko usage: up|down <branch>. Nazwa gałęzi musi zaczynać się od litery lub cyfry i nie może zawierać ...

Podpięcie do agenta

W Claude Code lub Codex wystarczy jedna linijka w pliku instrukcji projektu:

Po wypchnięciu gałęzi uruchom ssh deploy@preview.example.com up <branch> i dodaj wypisany URL do podsumowania. Uruchom down, gdy gałąź zostanie scalona lub porzucona.

Jeśli Twój CI już uruchamia się przy każdym pushu, wywołaj to samo polecenie stamtąd i trzymaj klucz całkowicie poza środowiskiem agenta. To opcja bardziej rygorystyczna i wybralibyśmy ją do wszystkiego poza projektem prywatnym.

Na co uważać

  • Dysk. Każdy build zostawia warstwy obrazów. Cotygodniowy cron z docker image prune -f trzyma to w ryzach; down już usuwa obraz samego podglądu.
  • Gałęzie, których nikt nie zamyka. Agenci otwierają więcej gałęzi niż ludzie. Zadanie cron, które wylistuje kontenery preview-* starsze niż tydzień i uruchomi na nich preview-down, oszczędzi Ci odkrycia dwudziestu zapomnianych podglądów.
  • Sekrety. Podglądy powinny działać na danych testowych. Hasło basic auth trzyma obcych z daleka, ale nie sprawia, że można podpiąć podgląd do produkcyjnej bazy danych.
  • Czas budowania. Obrazy budują się na serwerze, więc ciężki build konkuruje o CPU z działającymi podglądami. Jeśli buildy trwają minuty, buduj w CI i pobieraj gotowy obraz.

Sam agent nie musi mieszkać na tym serwerze. Jeśli jednak mieszka, zajrzyj do VPS dla Claude Code, a żeby sam zamawiał i doglądał tego serwera, podłącz go do serwera MCP EQVPS. Ta sama maszyna może też hostować bramę MCP Twojego zespołu. Do uruchamiania niezaufanego kodu bez żadnego serwera są sandboxy, a plany z własnym IPv4 zaczynają się od $8 miesięcznie.

FAQ

Dlaczego nie pozwolić agentowi wdrażać prosto na produkcję?

Bo „testy przechodzą” i „działa” to nie to samo, a agent powinien być ostatnim, który o tym decyduje. Podgląd daje człowiekowi coś do kliknięcia, zanim cokolwiek trafi do użytkowników, kosztem jednego dodatkowego polecenia.

Jaki plan pasuje?

Zależy, ile podglądów działa naraz. AI-Agent-IP ($15/mies., 4 vCPU, 4 GB RAM, 40 GB dysku) spokojnie zmieści kilka małych aplikacji webowych z limitem 512 MB każda. Najcięższe są buildy Dockera, więc przy dużych obrazach pilnuj dysku i pamięci.

Czy potrzebuję certyfikatu wildcard?

Nie. Caddy sam pobiera osobny certyfikat dla każdej subdomeny podglądu. Wildcardem jest tylko rekord DNS. Let's Encrypt pozwala na około 50 nowych certyfikatów na zarejestrowaną domenę tygodniowo, aż nadto, chyba że agent utworzy setki gałęzi.

Czy agent może zepsuć serwer swoim kluczem SSH?

Nie tym kluczem. Jest ograniczony do jednego polecenia, które przyjmuje tylko „up <gałąź>” albo „down <gałąź>” z bezpieczną nazwą gałęzi. Dostęp do powłoki, dodatkowe argumenty i przekierowanie portów są odrzucane.

Czy to działa na planie NAT?

Nie. Podglądy muszą być dostępne na portach 80 i 443 pod własnymi subdomenami, a to wymaga publicznego IPv4. Plany NAT przekierowują tylko port SSH.

Komentarze

Brak komentarzy. Bądź pierwszy.

Zostaw komentarz

Komentarze są moderowane przed pojawieniem się.