−25%

yıllık Windows ödemesinde, 31 Ekim'e kadar. Paketlere git

EQVPS
Hemen başla

Kodlama ajanı önizlemeleri için VPS: ajanın gönderdiği her dal kendi URL'sini alır

Bir kodlama ajanı bir dalı bitirip çalıştığını söylüyor. Ona bunun kontrol edilebileceği bir sunucu verin: her dal bir konteynerde derlenir ve parola arkasında kendi HTTPS alt alan adında açılır; ajanın SSH anahtarı ise yalnızca önizleme başlatıp durdurabilir.

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 Dockerfile içeren deposu. Her dil olur.
  • apt install -y docker.io caddy git ile 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ığında down ç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 -f bunu 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 üzerlerinde preview-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.

SSS

Ajanın doğrudan canlıya dağıtım yapmasına neden izin vermiyoruz?

Çünkü “testler geçiyor” ile “çalışıyor” aynı şey değil ve buna karar verecek son kişi ajan olmalı. Önizleme, bir şey kullanıcılara ulaşmadan önce bir insana tıklayacak bir şey verir; bedeli tek bir ek komut.

Hangi paket uygun?

Aynı anda kaç önizleme çalıştığına bağlı. AI-Agent-IP (ayda $15, 4 vCPU, 4 GB RAM, 40 GB disk) her biri 512 MB sınırlı birkaç küçük web uygulamasını rahatça taşır. En ağır kısım Docker derlemeleridir; imajlarınız büyükse disk ve belleği izleyin.

Wildcard sertifika gerekir mi?

Hayır. Caddy her önizleme alt alan adı için kendi sertifikasını kendisi alır. Yalnızca DNS kaydı wildcard'dır. Let's Encrypt kayıtlı alan adı başına haftada yaklaşık 50 yeni sertifikaya izin verir; ajan yüzlerce dal açmadıkça bu fazlasıyla yeter.

Ajan SSH anahtarıyla sunucuyu bozabilir mi?

O anahtarla hayır. Anahtar, yalnızca güvenli bir dal adıyla “up <dal>” veya “down <dal>” kabul eden tek bir komutla sınırlıdır. Kabuk erişimi, ek argümanlar ve port yönlendirme reddedilir.

Bu NAT paketinde çalışır mı?

Hayır. Önizlemelerin kendi alt alan adlarında 80 ve 443 portlarından erişilebilir olması gerekir; bunun için genel IPv4 lazım. NAT paketleri yalnızca SSH portunu yönlendirir.

Yorumlar

Henüz yorum yok. İlk olun.

Bir yorum bırakın

Yorumlar görünmeden önce denetlenir.