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
Dockerfileyang 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. Jalankandownsaat 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 -fmenjaganya tetap terkendali;downsudah 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 menjalankanpreview-downpadanya 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.
Komentar
Belum ada komentar. Jadilah yang pertama.