−25%

untuk Windows tahunan, hingga 31 Okt. Lihat paket

EQVPS
Mulai

VPS untuk pratinjau agen koding: setiap branch yang di-push agen mendapat URL sendiri

Agen koding selesai dengan sebuah branch dan bilang semuanya jalan. Beri ia server tempat hal itu bisa diperiksa: setiap branch dibangun menjadi container dan terbuka di subdomain HTTPS sendiri di balik kata sandi, dan kunci SSH agen hanya bisa menyalakan dan menghentikan pratinjau.

Agen koding bilang fiturnya sudah jalan. Mungkin memang begitu. Tes yang ia tulis lolos, dan itu membuktikan lebih sedikit daripada yang kita semua harapkan. Yang sebenarnya Anda inginkan adalah membukanya di browser sebelum ia mendekati pengguna, tanpa meminta agen menyentuh produksi.

Lingkungan pratinjau menyelesaikan itu, dan Anda tidak perlu platform untuknya. Satu VPS dengan IPv4 sendiri, Docker, Caddy, dan dua skrip pendek memberi setiap branch URL sendiri seperti https://feature-login-form.preview.example.com, di balik kata sandi, dibangun ulang di setiap push. Agen mendapat kunci SSH yang bisa menyalakan dan menghentikan pratinjau dan tidak lebih.

Kami membangunnya di server uji dan menjalankan seluruh siklus sebelum menuliskannya: membuat, membuka, push lagi, membongkar, mencoba menyalahgunakan kuncinya.

Yang Anda butuhkan

  • VPS dengan IPv4 publik. AI-Agent-IP muat untuk beberapa pratinjau sekaligus.
  • Record DNS wildcard: *.preview.example.com → IP server.
  • Repositori aplikasi Anda dengan Dockerfile yang melayani di port 8080. Bahasa apa pun bisa.
  • Ubuntu 24.04 atau Debian 12/13 dengan apt install -y docker.io caddy git.

Clone repositori sekali ke /srv/app dengan deploy key yang hanya bisa membaca.

Dua skrip

/usr/local/bin/preview-up membangun branch dan menayangkannya:

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

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

Beberapa pilihan di sana disengaja. Container hanya dipublikasikan di 127.0.0.1 dengan port acak, jadi tidak ada yang bisa menjangkaunya kecuali lewat Caddy. Ini penting karena port yang dipublikasikan Docker melewati ufw. Setiap pratinjau dibatasi memori 512 MB, supaya satu branch yang lepas kendali tidak menjatuhkan yang lain. Dan menjalankan branch yang sama lagi akan membangunnya ulang di tempat: push, up, muat ulang.

Caddy: satu file per pratinjau, satu kata sandi untuk semua

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'

Taruh hash-nya di /etc/caddy/Caddyfile:

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

Lalu systemctl reload caddy. Kini setiap pratinjau berada di balik login yang sama, jadi branch setengah jadi dengan keluaran debug tidak terbuka di internet. Pada rilis Caddy yang lebih baru, direktif ini juga dieja basic_auth; basicauth tetap berfungsi di keduanya.

Kunci agen: up dan down, tidak lebih

Agen perlu memicu pratinjau, bukan memiliki server. Buat pengguna deploy dan skrip gerbang yang menerima tepat dua perintah:

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

Di /home/deploy/.ssh/authorized_keys, ikat kunci publik agen ke skrip itu:

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

Dari mesin tempat agen bekerja:

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

Dalam uji kami, up membangun dan memublikasikan branch, up kedua setelah commit baru menyajikan versi baru, dan down menghapus container dan situsnya. Percobaan cat /etc/shadow, up x; id, nama branch seperti -x atau a/../b, atau login interaktif dengan kunci itu semuanya hanya mendapat usage: up|down <branch>. Nama branch harus diawali huruf atau angka dan tidak boleh berisi ...

Menghubungkannya ke agen

Dengan Claude Code atau Codex, satu baris di file instruksi proyek sudah cukup:

Setelah push sebuah branch, jalankan ssh deploy@preview.example.com up <branch> dan cantumkan URL yang tercetak di ringkasanmu. Jalankan down saat branch di-merge atau ditinggalkan.

Jika CI Anda sudah berjalan di setiap push, panggil perintah yang sama dari sana dan jauhkan kuncinya sepenuhnya dari lingkungan agen. Itu pilihan yang lebih ketat, dan kami akan memilihnya untuk apa pun di luar proyek pribadi.

Yang perlu diawasi

  • Disk. Setiap build meninggalkan layer image. Cron mingguan dengan docker image prune -f menjaganya tetap terkendali; down sudah menghapus image milik pratinjau itu sendiri.
  • Branch yang tak pernah ditutup. Agen membuka lebih banyak branch daripada manusia. Cron yang mendaftar container preview-* yang lebih tua dari seminggu dan menjalankan preview-down padanya menghindarkan Anda dari dua puluh pratinjau yang terlupakan.
  • Rahasia. Pratinjau sebaiknya berjalan dengan kredensial uji. Kata sandi basic auth mencegah orang asing masuk, tetapi tidak membuat pratinjau boleh diarahkan ke database produksi.
  • Waktu build. Image dibangun di server, jadi build yang berat bersaing CPU dengan pratinjau yang sedang berjalan. Jika build memakan waktu beberapa menit, bangun di CI dan tarik image-nya.

Agennya sendiri tidak harus tinggal di server ini. Jika ya, lihat VPS untuk Claude Code, dan agar ia bisa memesan dan merawat server itu sendiri, hubungkan ke server MCP EQVPS. Mesin yang sama juga bisa menampung gateway MCP tim Anda. Untuk menjalankan kode tak tepercaya tanpa server sama sekali ada sandbox, dan paket dengan IPv4 sendiri mulai $8 per bulan.

Pertanyaan umum

Kenapa agen tidak dibiarkan deploy langsung ke produksi?

Karena „tesnya lolos“ dan „berfungsi“ bukan hal yang sama, dan agen seharusnya yang terakhir memutuskan itu. Pratinjau memberi manusia sesuatu untuk diklik sebelum apa pun sampai ke pengguna, dengan biaya satu perintah tambahan.

Paket mana yang cocok?

Tergantung berapa pratinjau yang berjalan bersamaan. AI-Agent-IP ($15/bln, 4 vCPU, 4 GB RAM, disk 40 GB) dengan nyaman menampung beberapa aplikasi web kecil dengan batas 512 MB masing-masing. Build Docker adalah bagian terberat, jadi perhatikan disk dan memori jika image Anda besar.

Apakah saya butuh sertifikat wildcard?

Tidak. Caddy mengambil sendiri sertifikat terpisah untuk setiap subdomain pratinjau. Hanya record DNS-nya yang wildcard. Let's Encrypt mengizinkan sekitar 50 sertifikat baru per domain terdaftar per minggu, lebih dari cukup kecuali agen membuat ratusan branch.

Bisakah agen merusak server dengan kunci SSH-nya?

Tidak dengan kunci itu. Kunci tersebut dibatasi pada satu perintah yang hanya menerima „up <branch>“ atau „down <branch>“ dengan nama branch yang aman. Akses shell, argumen tambahan, dan port forwarding ditolak.

Apakah ini jalan di paket NAT?

Tidak. Pratinjau harus bisa diakses di port 80 dan 443 pada subdomain masing-masing, dan itu butuh IPv4 publik. Paket NAT hanya meneruskan port SSH.

Komentar

Belum ada komentar. Jadilah yang pertama.

Tinggalkan komentar

Komentar dimoderasi sebelum muncul.