Bir kodlama ajanı özelliğin çalıştığını söylüyor. Belki de çalışıyordur. Yazdığı testler geçiyor ve bu, herkesin istediğinden daha azını kanıtlar. Asıl istediğiniz, kullanıcıların yanına bile yaklaşmadan önce onu tarayıcıda açmak; üstelik bunu ajandan canlı sisteme dokunmasını istemeden yapmak.
Önizleme ortamları bunu çözer ve bunun için bir platforma gerek yoktur. Kendi IPv4'ü olan tek bir VPS, Docker, Caddy ve iki kısa betik her dala https://feature-login-form.preview.example.com gibi kendi URL'sini verir; parola arkasında, her push'ta yeniden derlenir. Ajan, önizleme başlatıp durdurabilen ve başka hiçbir şey yapamayan bir SSH anahtarı alır.
Bunu bir test sunucusunda kurduk ve yazmadan önce tüm döngüyü denedik: oluştur, aç, tekrar push et, kaldır, anahtarı kötüye kullanmayı dene.
Gerekenler
- Genel IPv4'ü olan bir VPS. AI-Agent-IP aynı anda birkaç önizlemeye yer açar.
- Wildcard bir DNS kaydı:
*.preview.example.com→ sunucunun IP'si. - Uygulamanızın, 8080 portundan hizmet veren bir
Dockerfileiçeren deposu. Her dil olur. apt install -y docker.io caddy gitile Ubuntu 24.04 veya Debian 12/13.
Depoyu salt okunur bir deploy anahtarıyla bir kez /srv/app dizinine klonlayın.
İki betik
/usr/local/bin/preview-up bir dalı derler ve yayına alır:
#!/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 onu kaldırır:
#!/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
İçindeki bazı tercihler bilinçlidir. Konteynerler yalnızca 127.0.0.1 üzerinde rastgele bir portla yayın yapar, yani Caddy dışında hiçbir şey onlara ulaşamaz. Bu önemlidir, çünkü Docker'ın yayınladığı portlar ufw'yi atlar. Her önizlemenin 512 MB bellek sınırı vardır; böylece kontrolden çıkan bir dal diğerlerini çökertemez. Aynı dalı yeniden çalıştırmak onu yerinde yeniden derler: push, up, yenile.
Caddy: önizleme başına bir dosya, hepsi için tek parola
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'
Hash'i /etc/caddy/Caddyfile dosyasına koyun:
(preview_common) {
basicauth {
reviewer PASTE-THE-HASH-HERE
}
}
import /etc/caddy/previews/*.caddy
Sonra systemctl reload caddy. Artık her önizleme aynı girişin arkasında; yarım kalmış, hata ayıklama çıktısı basan bir dal açık internette durmaz. Yeni Caddy sürümlerinde yönerge basic_auth olarak da yazılır; basicauth ikisinde de çalışır.
Ajanın anahtarı: up ve down, başka bir şey değil
Ajanın önizleme tetiklemesi gerekir, sunucuya sahip olması değil. Bir deploy kullanıcısı ve tam olarak iki komutu kabul eden bir geçit betiği oluşturun:
#!/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
/home/deploy/.ssh/authorized_keys dosyasında ajanın genel anahtarını bu betiğe bağlayın:
restrict,command="/usr/local/bin/preview-gate" ssh-ed25519 AAAA... coding-agent
Ajanın çalıştığı makineden:
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
Testimizde up dalı derleyip yayınladı, yeni bir commit'ten sonraki ikinci up yeni sürümü sundu ve down konteyneri ve siteyi kaldırdı. Bu anahtarla cat /etc/shadow, up x; id, -x ya da a/../b gibi dal adları veya etkileşimli giriş denemelerinin hepsi yalnızca usage: up|down <branch> aldı. Dal adı bir harf veya rakamla başlamalı ve .. içeremez.
Ajana bağlamak
Claude Code veya Codex'te projenin talimat dosyasına tek bir satır yeter:
Bir dalı push ettikten sonra
ssh deploy@preview.example.com up <branch>çalıştır ve yazdırılan URL'yi özetine ekle. Dal birleştirildiğinde veya bırakıldığındadownçalıştır.
CI'nız zaten her push'ta çalışıyorsa, aynı komutu oradan çağırın ve anahtarı ajanın ortamından tamamen uzak tutun. Bu daha sıkı seçenektir ve kişisel bir projenin ötesindeki her şey için bunu seçerdik.
Dikkat edilmesi gerekenler
- Disk. Her derleme imaj katmanları bırakır. Haftalık bir cron ile
docker image prune -fbunu kontrol altında tutar;downönizlemenin kendi imajını zaten siler. - Kimsenin kapatmadığı dallar. Ajanlar insanlardan çok dal açar. Bir haftadan eski
preview-*konteynerlerini listeleyip üzerlerindepreview-downçalıştıran bir cron işi, unutulmuş yirmi önizleme bulmaktan kurtarır. - Sırlar. Önizlemeler test kimlik bilgileriyle çalışmalı. Basic auth parolası yabancıları dışarıda tutar; bir önizlemeyi canlı veritabanına bağlamayı doğru kılmaz.
- Derleme süresi. İmajlar sunucuda derlenir, yani ağır bir derleme çalışan önizlemelerle CPU için yarışır. Derlemeler dakikalar sürüyorsa CI'da derleyip imajı çekin.
Ajanın kendisinin bu sunucuda yaşaması gerekmez. Yaşıyorsa Claude Code için VPS yazısına bakın; o sunucuyu kendi başına sipariş edip bakımını yapabilmesi için de onu EQVPS MCP sunucusuna bağlayın. Aynı makine ekibinizin MCP ağ geçidini de barındırabilir. Güvenilmeyen kodu hiç sunucu olmadan çalıştırmak için sandbox'lar var ve kendi IPv4'ü olan paketler ayda $8'dan başlar.
Yorumlar
Henüz yorum yok. İlk olun.