Модуль 3 · В продакшен · урок 11 из 13
Деплой, CI и окружения
Пока проект живёт на ноутбуке, вайбкодинг почти ничем не рискует. Деплой меняет правила: появляются чужие данные, секреты и пользователи, которые придут утром. Агент отлично пишет Dockerfile и пайплайны, но именно здесь его ошибки дороже всего — и проявляются не сразу. В этом уроке выводим «Переговорки» на стенд и в продакшен.
Окружения и конфигурация
Три окружения: локальное, стенд (staging) и продакшен. Код один, отличается только конфигурация — через переменные окружения. Все переменные перечислены в .env.example с учебными значениями, а при старте приложение проверяет конфигурацию и падает сразу, если чего-то не хватает:
import { z } from "zod";
const Config = z.object({
NODE_ENV: z.enum(["development", "test", "production"]),
DATABASE_URL: z.string().url(),
APP_URL: z.string().url(),
CORPORATE_DOMAIN: z.string().min(3),
SMTP_URL: z.string().url(),
SESSION_SECRET: z.string().min(32),
NOTIFY_WEBHOOK_URL: z.string().url().optional(),
});
export const config = Config.parse(process.env);Падение при старте с сообщением «нет SESSION_SECRET» лучше, чем сервис, который поднялся и подписывает сессии пустой строкой. Попросите агента сделать так с первого дня — и добавьте в AGENTS.md: «новая переменная окружения → config.ts и .env.example в том же коммите».
Образ
FROM node:22-slim AS base
RUN apt-get update && apt-get install -y --no-install-recommends openssl \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
FROM base AS build
COPY package.json package-lock.json ./
COPY apps/api/package.json apps/api/
COPY apps/web/package.json apps/web/
COPY packages/shared/package.json packages/shared/
RUN npm ci
COPY . .
RUN npm run build && npm prune --omit=dev
FROM base
ENV NODE_ENV=production
COPY --from=build --chown=node:node /app ./
USER node
EXPOSE 3000
CMD ["node", "apps/api/dist/server.js"]Что проверить в образе, который написал агент: приложение запускается не от root (USER node), в образ не попал .env (нужен .dockerignore), dev-зависимости удалены. Эндпоинт /api/health должен проверять и соединение с базой, иначе оркестратор будет считать живым сервис, который не может ничего прочитать.
Пайплайн
stages: [check, e2e, build, deploy]
default:
image: node:22
variables:
POSTGRES_USER: rooms
POSTGRES_PASSWORD: rooms
POSTGRES_DB: rooms_test
DATABASE_URL: postgresql://rooms:rooms@postgres:5432/rooms_test
check:
stage: check
services: [postgres:16]
script:
- npm ci
- npx prisma migrate deploy --schema apps/api/prisma/schema.prisma
- npm run check
e2e:
stage: e2e
# версия образа должна совпадать с версией @playwright/test в package-lock.json
image: mcr.microsoft.com/playwright:v1.55.0-noble
services:
- postgres:16
- name: axllent/mailpit
alias: mail
variables:
MAILPIT_URL: http://mail:8025
script:
- npm ci
- npx prisma migrate deploy --schema apps/api/prisma/schema.prisma
# API и web поднимает webServer из playwright.config.ts
- npm run test:e2e
artifacts:
when: on_failure
paths: [apps/web/playwright-report/]
build:
stage: build
image: docker:27
services: [docker:27-dind]
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
script:
- echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY"
- docker build -t "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA" .
- docker push "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA"
deploy_staging:
stage: deploy
environment: staging
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
script:
- ./deploy/deploy.sh staging "$CI_COMMIT_SHORT_SHA"
deploy_production:
stage: deploy
environment: production
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
when: manual
script:
- ./deploy/deploy.sh production "$CI_COMMIT_SHORT_SHA"CI запускает те же команды, что и разработчик: npm run check и npm run test:e2e. Если локально и в CI проверки разные, агент будет «чинить» то, что видит, а сломано окажется другое. Адреса сервисов в CI отличаются от локальных (mail вместо localhost), поэтому они вынесены в переменные.
Скрипт deploy/deploy.sh делает три вещи: подтягивает образ с нужным тегом, применяет миграции командой prisma migrate deploy и перезапускает сервис. Перед миграцией на продакшене — бэкап базы.
Что читать в CI, написанном агентом
Пайплайн легко сделать зелёным неправильно. Проверьте:
allow_failure: trueна шагах с тестами. Агент, которому сказали «почини пайплайн», иногда чинит его именно так.- Выкат в продакшен вручную (
when: manual) и только из основной ветки. - Секреты не печатаются. Никаких
echo $SESSION_SECRETиenvв отладочных целях; логин в реестр — через--password-stdin. - Тег образа — коммит, а не
latest. Иначе откатываться некуда.
Секреты
Секреты стенда и продакшена живут в переменных CI/CD GitLab — защищённых (только для защищённых веток) и маскированных. Их заводит человек. Агент пишет пайплайн, который на них ссылается, но значений не видит никогда: ни в рабочей копии, ни в промпте, ни в логе.
Откат и миграции
Откат — передеплой предыдущего тега. Он работает, только если миграции обратно совместимы: старая версия кода должна работать с новой схемой. Поэтому разрушительные изменения делаются в несколько релизов — «расширить и сжать», как в уроке о данных. Перед первым продакшен-релизом отрепетируйте откат на стенде.
Codex в самом CI
Следующий шаг — подключить агента к пайплайну: ревью merge request, разбор упавших тестов. Это отдельная тема с собственными рисками, и она разобрана в курсе «Codex в автоматизации». Одно правило из документации неинтерактивного режима стоит знать уже сейчас: не кладите CODEX_API_KEY в переменные всей задачи, если в ней выполняется код репозитория, — тесты и скрипты сборки смогут его прочитать.
Попробуйте сами
10–15 мин на рабочем месте- Добавьте в свой проект проверку конфигурации при старте и убедитесь, что приложение падает с понятным сообщением без одной из переменных.
- Попросите агента написать пайплайн по образцу выше, затем пройдитесь по списку «что читать в CI» и найдите хотя бы одно расхождение.
- Отрепетируйте откат на стенде: выкатите новую версию, затем передеплойте предыдущий тег.
Коротко
- Один код, разные окружения; конфигурация проверяется при старте.
- В CI те же команды проверки, что и локально.
- Продакшен — вручную, из основной ветки, с тегом коммита.
- Секреты заводит человек в CI/CD; агент их не видит.
- Проверяйте пайплайн на
allow_failure, вывод секретов и тегlatest. - Откат работает только с обратно совместимыми миграциями.