ایجنت کدنویس میگوید قابلیت کار میکند. شاید هم درست باشد. تستهایی که نوشته پاس میشوند و این کمتر از چیزی را ثابت میکند که همه دوست دارند. چیزی که واقعاً میخواهید این است که پیش از نزدیک شدن به کاربران آن را در مرورگر باز کنید، آن هم بیآنکه از ایجنت بخواهید به محیط تولید دست بزند.
محیط پیشنمایش همین را حل میکند و برایش پلتفرم لازم نیست. یک VPS با IPv4 اختصاصی، Docker، Caddy و دو اسکریپت کوتاه به هر شاخه URL خودش را میدهند، مثل https://feature-login-form.preview.example.com، پشت رمز و با ساخت دوباره در هر push. ایجنت کلید SSHای میگیرد که میتواند پیشنمایشها را روشن و خاموش کند و کار دیگری نمیکند.
این را روی یک سرور آزمایشی ساختیم و پیش از نوشتن کل چرخه را اجرا کردیم: ساخت، باز کردن، push دوباره، برچیدن، تلاش برای سوءاستفاده از کلید.
چه لازم دارید
- یک VPS با IPv4 عمومی. روی AI-Agent-IP چند پیشنمایش همزمان جا میشوند.
- یک رکورد DNS از نوع wildcard:
*.preview.example.com← IP سرور. - مخزن اپلیکیشن شما با یک
Dockerfileکه روی پورت 8080 سرو کند. هر زبانی قبول است. - Ubuntu 24.04 یا Debian 12/13 با
apt install -y docker.io caddy git.
مخزن را یک بار با کلید deploy فقطخواندنی در /srv/app کلون کنید.
دو اسکریپت
/usr/local/bin/preview-up یک شاخه را میسازد و آنلاین میکند:
#!/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
چند انتخاب در اینها عمدی است. کانتینرها فقط روی 127.0.0.1 با پورت تصادفی منتشر میشوند، پس هیچ چیز جز از راه Caddy به آنها نمیرسد. این مهم است، چون پورتهایی که Docker منتشر میکند ufw را دور میزنند. هر پیشنمایش سقف حافظهٔ 512 MB دارد تا یک شاخهٔ افسارگسیخته بقیه را از کار نیندازد. اجرای دوبارهٔ همان شاخه آن را درجا از نو میسازد: push، اجرای 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'
هش را در /etc/caddy/Caddyfile بگذارید:
(preview_common) {
basicauth {
reviewer PASTE-THE-HASH-HERE
}
}
import /etc/caddy/previews/*.caddy
سپس systemctl reload caddy. حالا همهٔ پیشنمایشها پشت یک ورود مشترکاند، پس شاخهٔ نیمهکاره با خروجی دیباگ روی اینترنت باز نمیماند. در نسخههای جدیدتر Caddy این دستور به شکل 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 شاخه را ساخت و منتشر کرد، up دوم پس از یک commit تازه نسخهٔ جدید را نشان داد و down کانتینر و سایت را حذف کرد. تلاش با cat /etc/shadow، up x; id، نامهای شاخه مثل -x یا a/../b یا ورود تعاملی با آن کلید، همه فقط usage: up|down <branch> گرفتند. نام شاخه باید با حرف یا رقم شروع شود و نمیتواند .. داشته باشد.
وصل کردن به ایجنت
در Claude Code یا Codex یک خط در فایل دستورالعمل پروژه کافی است:
بعد از push کردن یک شاخه،
ssh deploy@preview.example.com up <branch>را اجرا کن و URL چاپشده را در خلاصهات بگذار. وقتی شاخه ادغام یا رها شد،downرا اجرا کن.
اگر CI شما همین حالا با هر push اجرا میشود، همان دستور را از آنجا صدا بزنید و کلید را کاملاً از محیط ایجنت دور نگه دارید. این گزینهٔ سختگیرانهتر است و برای هر چیزی فراتر از یک پروژهٔ شخصی همین را انتخاب میکنیم.
به چه چیزهایی باید توجه کرد
- دیسک. هر ساخت لایههای ایمیج باقی میگذارد. یک cron هفتگی با
docker image prune -fاین را مهار میکند؛downایمیج خود پیشنمایش را از قبل حذف میکند. - شاخههایی که کسی نمیبندد. ایجنتها بیشتر از انسانها شاخه باز میکنند. یک cron که کانتینرهای
preview-*قدیمیتر از یک هفته را فهرست کند و روی آنهاpreview-downاجرا کند، شما را از دیدن بیست پیشنمایش فراموششده نجات میدهد. - رازها. پیشنمایشها باید با اطلاعات ورود آزمایشی اجرا شوند. رمز basic auth غریبهها را بیرون نگه میدارد، اما وصل کردن پیشنمایش به پایگاه دادهٔ تولید را درست نمیکند.
- زمان ساخت. ایمیجها روی سرور ساخته میشوند، پس ساخت سنگین با پیشنمایشهای در حال اجرا سر CPU رقابت میکند. اگر ساخت چند دقیقه طول میکشد، در CI بسازید و ایمیج را pull کنید.
لازم نیست خود ایجنت روی این سرور زندگی کند. اگر زندگی میکند، VPS برای Claude Code را ببینید، و برای اینکه این سرور را خودش سفارش دهد و نگهداری کند، او را به سرور MCP شرکت EQVPS وصل کنید. همان ماشین میتواند درگاه MCP تیم شما را هم میزبانی کند. برای اجرای کد غیرقابلاعتماد بدون هیچ سروری سندباکسها هستند، و پلنهای دارای IPv4 اختصاصی از ماهی $8 شروع میشوند.
نظرات
هنوز نظری نیست. اولین نفر باشید.