Amiga
Оглавление курса · урок 11 из 13

Модуль 3 · В продакшен · урок 11 из 13

Деплой, CI и окружения

Пока проект живёт на ноутбуке, вайбкодинг почти ничем не рискует. Деплой меняет правила: появляются чужие данные, секреты и пользователи, которые придут утром. Агент отлично пишет Dockerfile и пайплайны, но именно здесь его ошибки дороже всего — и проявляются не сразу. В этом уроке выводим «Переговорки» на стенд и в продакшен.

Окружения и конфигурация

Три окружения: локальное, стенд (staging) и продакшен. Код один, отличается только конфигурация — через переменные окружения. Все переменные перечислены в .env.example с учебными значениями, а при старте приложение проверяет конфигурацию и падает сразу, если чего-то не хватает:

typescript · apps/api/src/config.ts
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 в том же коммите».

Образ

dockerfile · Dockerfile
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 должен проверять и соединение с базой, иначе оркестратор будет считать живым сервис, который не может ничего прочитать.

Пайплайн

yaml · .gitlab-ci.yml
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 мин на рабочем месте
  1. Добавьте в свой проект проверку конфигурации при старте и убедитесь, что приложение падает с понятным сообщением без одной из переменных.
  2. Попросите агента написать пайплайн по образцу выше, затем пройдитесь по списку «что читать в CI» и найдите хотя бы одно расхождение.
  3. Отрепетируйте откат на стенде: выкатите новую версию, затем передеплойте предыдущий тег.

Коротко

  • Один код, разные окружения; конфигурация проверяется при старте.
  • В CI те же команды проверки, что и локально.
  • Продакшен — вручную, из основной ветки, с тегом коммита.
  • Секреты заводит человек в CI/CD; агент их не видит.
  • Проверяйте пайплайн на allow_failure, вывод секретов и тег latest.
  • Откат работает только с обратно совместимыми миграциями.
Отметка хранится только в вашем браузере
Хотите связаться с владельцем
компании напрямую?
Дмитрий Тарасов
Дмитрий Тарасов
СЕО

НАПИСАТЬ

Написать в MAX