Coding-агент каже, що фіча працює. Можливо. Тести, які він написав, проходять, і це доводить менше, ніж усім хотілося б. Насправді вам потрібно відкрити результат у браузері, перш ніж він наблизиться до користувачів, і зробити це, не просячи агента торкатися продакшну.
Це вирішують превʼю-оточення, і платформа для них не потрібна. Один VPS із власним IPv4, Docker, Caddy і два короткі скрипти дають кожній гілці власну адресу на кшталт https://feature-login-form.preview.example.com — за паролем і з перезбиранням після кожного push. Агент отримує SSH-ключ, який уміє запускати й зупиняти превʼю, і нічого більше.
Ми зібрали це на тестовому сервері й пройшли весь цикл, перш ніж описувати: створити, відкрити, запушити знову, прибрати, спробувати зловживати ключем.
Що потрібно
- VPS із публічним IPv4. На AI-Agent-IP вміщується кілька превʼю одночасно.
- Wildcard-запис DNS:
*.preview.example.com→ IP сервера. - Репозиторій застосунку з
Dockerfile, який віддає на порту 8080. Мова будь-яка. - Ubuntu 24.04 або Debian 12/13 з
apt install -y docker.io caddy git.
Один раз клонуйте репозиторій у /srv/app з deploy-ключем лише на читання.
Два скрипти
/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 МБ памʼяті, щоб одна гілка, що пішла врознос, не поклала решту. А повторний запуск тієї самої гілки перезбирає її на місці: 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 після нового коміту віддав нову версію, а 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>і додай виведену адресу до свого підсумку. Виконайdown, коли гілку змерджено або покинуто.
Якщо ваш CI вже запускається на кожен push, викликайте ту саму команду звідти й тримайте ключ повністю поза оточенням агента. Це суворіший варіант, і ми обрали б його для всього, що виходить за межі особистого проєкту.
На що зважати
- Диск. Кожна збірка лишає шари образів. Щотижневий cron із
docker image prune -fтримає це під контролем;downуже видаляє власний образ превʼю. - Гілки, які ніхто не закриває. Агенти відкривають більше гілок, ніж люди. Cron-завдання, яке перелічує контейнери
preview-*, старші за тиждень, і запускає для нихpreview-down, позбавить вас двадцяти забутих превʼю. - Секрети. Превʼю мають працювати з тестовими обліковими даними. Пароль basic auth не пускає сторонніх, але не робить прийнятним підключати превʼю до продакшн-бази.
- Час збирання. Образи збираються на сервері, тож важка збірка конкурує за CPU з робочими превʼю. Якщо збирання триває хвилини, збирайте в CI й тягніть готовий образ.
Сам агент не мусить жити на цьому сервері. Якщо живе — дивіться VPS для Claude Code, а щоб він сам замовляв і доглядав цей сервер, підключіть його до MCP-сервера EQVPS. Та сама машина може тримати й MCP-шлюз вашої команди. Для запуску недовіреного коду взагалі без сервера є пісочниці, а тарифи з власним IPv4 — від $8 на місяць.
Коментарі
Поки немає коментарів. Будьте першим.