เอเจนต์เขียนโค้ดบอกว่าฟีเจอร์ใช้ได้แล้ว อาจจะจริง เทสต์ที่มันเขียนผ่านหมด ซึ่งพิสูจน์ได้น้อยกว่าที่ทุกคนอยากให้เป็น สิ่งที่คุณต้องการจริง ๆ คือเปิดดูในเบราว์เซอร์ก่อนที่มันจะเข้าใกล้ผู้ใช้ โดยไม่ต้องให้เอเจนต์แตะโปรดักชัน
สภาพแวดล้อมพรีวิวแก้ปัญหานี้ และไม่ต้องมีแพลตฟอร์มใด ๆ VPS หนึ่งเครื่องที่มี IPv4 ของตัวเอง Docker Caddy และสคริปต์สั้น ๆ สองตัว ให้ทุก branch มี URL ของตัวเองแบบ https://feature-login-form.preview.example.com อยู่หลังรหัสผ่าน และ build ใหม่ทุกครั้งที่พุช เอเจนต์ได้คีย์ SSH ที่เปิดและปิดพรีวิวได้ และทำอย่างอื่นไม่ได้
เราสร้างระบบนี้บนเซิร์ฟเวอร์ทดสอบและไล่ครบทั้งวงจรก่อนเขียน: สร้าง เปิด พุชอีกครั้ง รื้อทิ้ง และลองใช้คีย์ในทางที่ผิด
สิ่งที่ต้องมี
- VPS ที่มี IPv4 สาธารณะ AI-Agent-IP รองรับพรีวิวหลายตัวพร้อมกัน
- DNS record แบบ wildcard:
*.preview.example.com→ IP ของเซิร์ฟเวอร์ - repository ของแอปที่มี
Dockerfileซึ่งให้บริการบนพอร์ต 8080 ภาษาใดก็ได้ - Ubuntu 24.04 หรือ Debian 12/13 พร้อม
apt install -y docker.io caddy git
Clone repository หนึ่งครั้งไปที่ /srv/app ด้วย deploy key แบบอ่านอย่างเดียว
สองสคริปต์
/usr/local/bin/preview-up build branch แล้วนำขึ้นออนไลน์:
#!/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 ลบออก:
#!/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
มีบางทางเลือกที่ตั้งใจ คอนเทนเนอร์ publish เฉพาะบน 127.0.0.1 ด้วยพอร์ตสุ่ม จึงไม่มีอะไรเข้าถึงได้นอกจากผ่าน Caddy เรื่องนี้สำคัญเพราะพอร์ตที่ Docker publish จะข้าม ufw ไป พรีวิวแต่ละตัวจำกัดหน่วยความจำ 512 MB เพื่อไม่ให้ branch ที่คุมไม่อยู่ล้มตัวอื่นไปด้วย และการรัน branch เดิมซ้ำจะ build ใหม่ในที่เดิม: พุช รัน up แล้วรีเฟรช
Caddy: หนึ่งไฟล์ต่อพรีวิว หนึ่งรหัสผ่านสำหรับทั้งหมด
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 ลงใน /etc/caddy/Caddyfile:
(preview_common) {
basicauth {
reviewer PASTE-THE-HASH-HERE
}
}
import /etc/caddy/previews/*.caddy
จากนั้น systemctl reload caddy ตอนนี้ทุกพรีวิวอยู่หลังการล็อกอินเดียวกัน branch ที่ทำไม่เสร็จพร้อมเอาต์พุต debug จึงไม่เปิดโล่งบนอินเทอร์เน็ต ใน Caddy รุ่นใหม่ ๆ directive นี้สะกดเป็น basic_auth ได้ด้วย และ basicauth ยังใช้ได้ทั้งสองแบบ
คีย์ของเอเจนต์: up และ down เท่านั้น
เอเจนต์ต้องสั่งเปิดพรีวิว ไม่ใช่เป็นเจ้าของเซิร์ฟเวอร์ สร้างผู้ใช้ deploy และสคริปต์ประตูที่รับคำสั่งเพียงสองคำสั่ง:
#!/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 ผูกคีย์สาธารณะของเอเจนต์ไว้กับสคริปต์นั้น:
restrict,command="/usr/local/bin/preview-gate" ssh-ed25519 AAAA... coding-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
ในการทดสอบของเรา up build และ publish branch, up ครั้งที่สองหลัง commit ใหม่ให้บริการเวอร์ชันใหม่ และ down ลบทั้งคอนเทนเนอร์และไซต์ การลอง cat /etc/shadow, up x; id, ชื่อ branch อย่าง -x หรือ a/../b หรือการล็อกอินแบบโต้ตอบด้วยคีย์นั้น ได้รับเพียง usage: up|down <branch> ทั้งหมด ชื่อ branch ต้องขึ้นต้นด้วยตัวอักษรหรือตัวเลข และห้ามมี ..
เชื่อมเข้ากับเอเจนต์
กับ Claude Code หรือ Codex แค่บรรทัดเดียวในไฟล์คำสั่งของโปรเจกต์ก็พอ:
หลังพุช branch ให้รัน
ssh deploy@preview.example.com up <branch>แล้วใส่ URL ที่พิมพ์ออกมาในสรุปของคุณ รันdownเมื่อ branch ถูก merge หรือถูกทิ้ง
ถ้า CI ของคุณรันทุกครั้งที่พุชอยู่แล้ว ให้เรียกคำสั่งเดียวกันจากตรงนั้นและเก็บคีย์ให้พ้นสภาพแวดล้อมของเอเจนต์ทั้งหมด นั่นคือทางเลือกที่เข้มงวดกว่า และเราจะเลือกแบบนี้สำหรับทุกอย่างที่เกินโปรเจกต์ส่วนตัว
สิ่งที่ต้องคอยดู
- ดิสก์ ทุกการ build ทิ้ง layer ของอิมเมจไว้ cron รายสัปดาห์ที่รัน
docker image prune -fช่วยคุมไว้ได้ ส่วนdownลบอิมเมจของพรีวิวนั้นอยู่แล้ว - branch ที่ไม่มีใครปิด เอเจนต์เปิด branch มากกว่าคน cron ที่แสดงคอนเทนเนอร์
preview-*ที่เก่ากว่าหนึ่งสัปดาห์แล้วรันpreview-downกับมัน จะช่วยไม่ให้คุณเจอพรีวิวที่ถูกลืมยี่สิบตัว - ความลับ พรีวิวควรรันด้วยข้อมูลรับรองสำหรับทดสอบ รหัสผ่าน basic auth กันคนแปลกหน้าได้ แต่ไม่ได้ทำให้ชี้พรีวิวไปที่ฐานข้อมูลโปรดักชันได้
- เวลา build อิมเมจถูก build บนเซิร์ฟเวอร์ การ build หนัก ๆ จึงแย่ง CPU กับพรีวิวที่รันอยู่ ถ้า build นานหลายนาที ให้ build ใน CI แล้วดึงอิมเมจมาแทน
ตัวเอเจนต์เองไม่จำเป็นต้องอยู่บนเซิร์ฟเวอร์นี้ ถ้าอยู่ ดู VPS สำหรับ Claude Code และถ้าต้องการให้มันสั่งซื้อและดูแลเซิร์ฟเวอร์นั้นเอง เชื่อมต่อกับเซิร์ฟเวอร์ MCP ของ EQVPS เครื่องเดียวกันยังโฮสต์ MCP gateway ของทีมได้ด้วย สำหรับรันโค้ดที่ไม่น่าเชื่อถือโดยไม่ต้องมีเซิร์ฟเวอร์มี sandbox และแพ็กเกจที่มี IPv4 ของตัวเอง เริ่มต้นที่ $8 ต่อเดือน
ความคิดเห็น
ยังไม่มีความคิดเห็น เป็นคนแรกสิ