#Полный деплой приложения

POST /v1/infra/servers/:id/deploy

Полный цикл деплоя за один запрос: остановка предыдущего сервиса → скачивание архива → распаковка → установка рантайма → установка зависимостей → запись .env → команды preStart → создание systemd-юнита → запуск → проверка работоспособности. Используйте вместо цепочки /upload + /exec — быстрее и атомарно. По умолчанию ответ — JSON (рекомендуется для AI-агентов). Для построчного прогресса передайте ?stream=true — сервер вернёт SSE с событиями на каждом шаге.

Для AI-агентов и MCP-клиентов обязательно используйте JSON-режим (без ?stream=true).

#Параметры

Параметр В Тип Обяз. По умолч. Описание
id path string (UUID) да ID BLACKHOLE-сервера, status: running, blackholeStatus: CONNECTED
stream query string нет false true — SSE-режим; иначе — JSON-ответ

#Поля запроса (body — JSON)

Поле Тип Обяз. По умолч. Описание
source object да Источник кода — ровно один из трёх вариантов ниже (url, content или versionId)
source.url string да (или content / versionId) HTTPS-URL архива с кодом. До 500 МБ
source.content string да (или url / versionId) Base64-строка архива. До 500 МБ на тело запроса
source.versionId string да (или url / content) ID снапшота из депо приложения, например v3 — см. Хранилище исходников
start string да Команда запуска приложения (пример: cd /opt/app && node server.js)
port number да Порт, на котором приложение слушает. Всегда 3000 для BLACKHOLE
extractTo string нет /opt/app Директория распаковки
runtime string нет ID рантайма: node20, python311, php83, static и т. д. Список — `GET /v1/infra/runtimes`. Сервер создаётся из стокового образа Ubuntu 24.04 — без runtime на нём нет ни node, ни python, ни php, и start упадёт с command not found. Альтернатива — поставить тулчейн вручную через preStart (см. ниже).
install string нет Команда установки зависимостей (до 5000 символов)
preStart string нет Команда до запуска сервиса: миграции БД, seed-скрипты, сборка, ручная установка системных пакетов (например, apt-get update && apt-get install -y nodejs npm если не передан runtime). До 5000 символов
env object нет Переменные окружения { "KEY": "value" } — будут записаны в .env и подхвачены systemd
systemd boolean нет true Создавать systemd-юнит для автозапуска
cleanDeploy boolean нет true Очистить extractTo перед распаковкой. Установите false для merge-деплоя
serviceName string нет app Имя systemd-юнита. Только a-z, 0-9, -. Создаёт {serviceName}.service
healthPath string нет / Путь для проверки работоспособности — например, /health, /api/status. Только безопасные символы
debug boolean нет false Добавить диагностический шаг debug_infols -la, sha256sum файлов, file -i для отладки шагов сборки

#Поля запроса (body — multipart/form-data)

Альтернатива JSON — загрузка архива без base64. Полезно из скриптов bash/PowerShell.

Поле формы Тип Обяз. Описание
archive файл да Архив (tar.gz / zip), до 500 МБ
start string да Команда запуска
port string да Порт (строкой: "3000")
extractTo string нет По умолчанию /opt/app
runtime string нет ID рантайма
install string нет Команда установки
preStart string нет Команда до запуска
env string нет JSON-строка: {"KEY":"value"}
systemd string нет "true" / "false"
cleanDeploy string нет При multipart по умолчанию "false" (merge). Укажите "true" для чистого деплоя
serviceName string нет Имя systemd-юнита
healthPath string нет Путь для проверки работоспособности

#Шаги деплоя

Шаги выполняются последовательно. Если какой-то провалился — деплой останавливается.

Шаг Условие Что делает Таймаут
stop_existing всегда systemctl stop {serviceName} — останавливает предыдущую версию 15с
clean cleanDeploy: true Удаляет всё в extractTo 30с
download всегда Скачивает и распаковывает архив (1 повтор при ошибке) 10 мин
cleanup_metadata после extract Удаляет macOS-сайдкары (._*, .DS_Store) — молча если архив чистый
normalize_windows_paths после extract Нормализует Windows-style \ в путях ZIP из PowerShell Compress-Archive
debug_info debug: true Диагностика: ls -la, sha256sum файлов, file -i, локаль. Читает до 32 КБ в stdout
runtime runtime передан Устанавливает рантайм. При повторном деплое с тем же — переустанавливает 300с
install install передан Запускает команду установки зависимостей 300с
env env передан Пишет .env в extractTo с правами 0600
pre_start preStart передан Запускает команду до старта сервиса. Для миграций БД, seed-скриптов, сборки 300с
systemd systemd: true Создаёт {serviceName}.service и перечитывает конфигурацию systemd (systemctl daemon-reload) 30с
start всегда systemctl enable --now {serviceName} 30с
healthcheck всегда Проверка работоспособности — до 10 попыток curl localhost:{port}{healthPath} с интервалом 3 секунды; засчитывается ответ со статусом 200–399 ~30с

#Примеры

#curl — личный ключ

Terminal
# JSON, Node.js с миграциями
curl -X POST "https://vibecode.bitrix24.tech/v1/infra/servers/SERVER_ID/deploy?stream=false" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "source": { "url": "https://github.com/user/app/archive/main.tar.gz" },
    "runtime": "node20",
    "install": "cd /opt/app && npm install --production",
    "preStart": "cd /opt/app && npx prisma migrate deploy",
    "start": "cd /opt/app && node server.js",
    "port": 3000,
    "env": { "NODE_ENV": "production", "DATABASE_URL": "postgresql://localhost/mydb" }
  }'

# Multipart — локальный архив
tar -czf app.tar.gz -C ./my-app .
curl -X POST "https://vibecode.bitrix24.tech/v1/infra/servers/SERVER_ID/deploy?stream=false" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -F "archive=@app.tar.gz" \
  -F "runtime=node20" \
  -F "install=cd /opt/app && npm install --production" \
  -F "start=cd /opt/app && node server.js" \
  -F "port=3000"

#curl — inline-архив (минимальный набор полей)

Terminal
# Канонический inline-деплой: достаточно source.content + start.
# Тип архива определяется автоматически по сигнатуре первых байт —
# tar.gz, zip и tar.bz2 поддерживаются, отдельное поле формата не нужно.
B64=$(base64 < app.tar.gz | tr -d '\n')
curl -X POST "https://vibecode.bitrix24.tech/v1/infra/servers/SERVER_ID/deploy?stream=false" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{
    \"source\": { \"content\": \"$B64\" },
    \"start\": \"cd /opt/app && node server.js\",
    \"port\": 3000
  }"

Обязательны только два поля: source (один из content / url / versionId) и start. Архив можно завернуть и в zip (zip -r app.zip .), и в tar.gz (tar -czf app.tar.gz -C ./my-app .) — платформа распознаёт оба по содержимому.

#curl — произвольный стек без рантайма

Terminal
# Деплой без runtime — любое ПО ставится через preStart
curl -X POST "https://vibecode.bitrix24.tech/v1/infra/servers/SERVER_ID/deploy?stream=false" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "source": { "url": "https://example.com/my-app.tar.gz" },
    "preStart": "apt-get update -qq && apt-get install -y ffmpeg imagemagick",
    "install": "cd /opt/app && npm install --production",
    "start": "cd /opt/app && node server.js",
    "port": 3000
  }'

#curl — OAuth-приложение

Terminal
curl -X POST "https://vibecode.bitrix24.tech/v1/infra/servers/SERVER_ID/deploy?stream=false" \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "source": { "url": "https://github.com/user/fastapi/archive/main.tar.gz" },
    "runtime": "python311",
    "install": "cd /opt/app && pip install -r requirements.txt",
    "start": "cd /opt/app && python -m uvicorn main:app --host 0.0.0.0 --port 3000",
    "port": 3000
  }'

#JavaScript — личный ключ

javascript
const res = await fetch(
  `https://vibecode.bitrix24.tech/v1/infra/servers/${serverId}/deploy?stream=false`,
  {
    method: 'POST',
    headers: {
      'X-Api-Key': 'YOUR_API_KEY',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      source: { url: 'https://github.com/user/app/archive/main.tar.gz' },
      runtime: 'node20',
      install: 'cd /opt/app && npm install --production',
      start: 'cd /opt/app && node server.js',
      port: 3000,
    }),
  }
)
const body = await res.json()
if (body.success) {
  console.log(`Приложение живёт: ${body.data.appUrl}`)
} else {
  console.error(`Упал на шаге ${body.error.step}: ${body.error.message}`)
}

#JavaScript — OAuth-приложение

javascript
await fetch(
  `https://vibecode.bitrix24.tech/v1/infra/servers/${serverId}/deploy?stream=false`,
  {
    method: 'POST',
    headers: {
      'X-Api-Key': 'YOUR_APP_KEY',
      'Authorization': 'Bearer USER_SESSION_TOKEN',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      source: { url: 'https://github.com/user/app/archive/main.tar.gz' },
      runtime: 'node20',
      install: 'cd /opt/app && npm install --production',
      start: 'cd /opt/app && node server.js',
      port: 3000,
    }),
  }
)

#Поля ответа

Поле Тип Описание
success boolean true — все шаги выполнены, приложение отвечает на проверку работоспособности
data.steps array Массив шагов процесса деплоя, в порядке выполнения
data.steps[].step string Имя шага: stop_existing, download, runtime, install, env, pre_start, systemd, start, healthcheck, и т. д.
data.steps[].status string ok — успешно, error — провал (только последний шаг может иметь этот статус)
data.steps[].duration number Длительность шага в секундах. Присутствует не у всех шагов — только у тех, которые сами отмеряют время выполнения (runtime, install, pre_start, normalize_windows_paths, cleanup_metadata, debug_info)
data.steps[].stderr string Стандартный поток ошибок (stderr) шага при status: "error"
data.steps[].stdout string Стандартный вывод (stdout), заполняется для шага debug_info (до 32 КБ)
data.serviceName string Имя systemd-юнита (эхо параметра или "app")
data.status string "running" после успешного деплоя
data.appUrl string HTTPS-адрес приложения: https://{subdomain}.vibecode.bitrix24.tech

При success: false есть дополнительно error.code, error.message, error.step с именем провалившегося шага, а data.steps содержит хронологию шагов до провала.

#Пример ответа

Успешный деплой (JSON):

JSON
{
  "success": true,
  "data": {
    "steps": [
      { "step": "stop_existing", "status": "ok" },
      { "step": "download", "status": "ok" },
      { "step": "runtime", "status": "ok", "duration": 45 },
      { "step": "install", "status": "ok", "duration": 12 },
      { "step": "env", "status": "ok" },
      { "step": "systemd", "status": "ok" },
      { "step": "start", "status": "ok" },
      { "step": "healthcheck", "status": "ok" }
    ],
    "serviceName": "app",
    "status": "running",
    "appUrl": "https://app-abc12345.vibecode.bitrix24.tech"
  }
}

SSE-поток при ?stream=true:

event: step
data: {"step":"stop_existing","status":"ok"}

event: step
data: {"step":"download","status":"ok"}

event: step
data: {"step":"runtime","status":"ok","duration":45}

event: step
data: {"step":"install","status":"ok","duration":12}

event: step
data: {"step":"healthcheck","status":"ok"}

event: done
data: {"serviceName":"app","status":"running","appUrl":"https://app-abc12345.vibecode.bitrix24.tech"}

#Пример ответа при ошибке

500 — деплой упал на шаге install:

JSON
{
  "success": false,
  "error": {
    "code": "DEPLOY_FAILED",
    "message": "npm ERR! Missing dependencies",
    "step": "install"
  },
  "data": {
    "steps": [
      { "step": "stop_existing", "status": "ok" },
      { "step": "download", "status": "ok" },
      { "step": "install", "status": "error", "stderr": "npm ERR! Missing dependencies" }
    ]
  }
}

#Ошибки

HTTP Код Описание
400 VALIDATION_ERROR Нарушена схема запроса (отсутствует source.url/source.content, start, port, или неверный формат архива)
400 NOT_BLACKHOLE Сервер в режиме OPEN — Deploy API недоступен
401 MISSING_API_KEY Не передан заголовок X-Api-Key
401 INVALID_API_KEY Неверный или просроченный API-ключ
402 ACCOUNT_FROZEN Баланс Вайбкод заморожен
402 BILLING_EXHAUSTED Баланс исчерпан — пополните
404 NOT_FOUND Сервер не существует, удалён или принадлежит другому API-ключу
409 EXEC_BUSY Другая операция уже выполняется на сервере. Подождите или снимите лок через `/lock`
409 AGENT_NOT_CONNECTED Агент туннеля не в статусе CONNECTED — попробуйте `/repair`
429 RATE_LIMIT_EXCEEDED Превышен лимит 10 операций в минуту на сервер
500 DEPLOY_FAILED Упал один из шагов процесса деплоя. Поле error.step указывает, какой. data.steps[-1].stderr содержит подробности
502 GATEWAY_ERROR Gateway вернул ошибку при связи с агентом
502 RUNTIME_FAILED Установка рантайма на сервере провалилась. Попробуйте без runtime или через отдельный `/exec`
504 RUNTIME_TIMEOUT Рантайм устанавливается больше 10 минут — платформа сдалась

Полный список общих ошибок API — Ошибки.

#Известные особенности

  • Код приложения передаётся через source.content, а не через start/install/preStart. Поля-команды (start, install, preStart) — это короткие shell-команды с лимитом 5000 символов каждое (command у `/exec` — 10000). Они не предназначены для содержимого файлов: base64 целого архива туда не помещается и обрезается. Сам код (tar.gz / zip, в том числе один файл) кладите в source.content (base64-строка архива, до 500 МБ на тело запроса) либо в source.url (ссылка на архив), а в start оставляйте только команду запуска (например node server.js). Если архив большой — удобнее source.url, чтобы не раздувать тело запроса base64'ом.

  • Три стратегии для .env. Файл пишется в {extractTo}/.env (по умолчанию /opt/app/.env) и при cleanDeploy: true удаляется перед следующим деплоем:

    1. Передавать env в каждом /deploy — перегенерируется автоматически (рекомендуется).
    2. Хранить .env вне extractTo — например, /var/lib/myapp/.env. Эта директория не трогается cleanDeploy. В start указать явный путь.
    3. cleanDeploy: false — накопительный (merge) деплой, .env переживёт, но вместе с ним останутся устаревшие файлы прошлых деплоев.
  • Автоматическая подгрузка .env в systemd. В юните прописан EnvironmentFile=-{extractTo}/.env (знак - делает файл опциональным). Приложение через systemd получит переменные само. Но при systemd: false или командах из install/preStart env подгружать не будет — передавайте явно.

  • Если смешаны dependencies и devDependencies, npm install --production не поставит пакеты из devDependencies. Для cron-скриптов с tsx, ts-node и т. п. уберите --production или добавьте их отдельно через preStart.

  • Повторный деплой с тем же runtime переустанавливает его. Опустите параметр runtime при повторных деплоях — сэкономит 45+ секунд. Первый деплой с runtime обязателен для установки.

  • Проверка работоспособности может не успеть. До 10 попыток с интервалом 3 секунды — это около 30 секунд на всё. Если приложение стартует дольше, сделайте эндпоинт /health, отвечающий 200 сразу, даже до прогрева; или переместите долгую инициализацию в preStart.

  • Фоновые приложения тоже должны слушать порт 3000. Шаг healthcheck выполняется при каждом деплое — даже если приложение не обслуживает HTTP-трафик (бот, который только опрашивает события, cron-демон, обработчик очереди). Без процесса, слушающего порт 3000, шаг повторяет попытки и завершается ошибкой attempt 10/10: curl exit=7 (не удалось подключиться). Поднимите рядом с основной логикой минимальный HTTP-сервер, отвечающий кодом 200 на GET /:

    Node.js:

    javascript
    require('http').createServer((_req, res) => res.end('ok')).listen(3000)
    // дальше — основная логика приложения

    Python:

    Python
    import http.server, threading
    
    class Health(http.server.BaseHTTPRequestHandler):
        def do_GET(self):
            self.send_response(200)
            self.end_headers()
            self.wfile.write(b'ok')
        def log_message(self, *args):
            pass
    
    threading.Thread(
        target=lambda: http.server.HTTPServer(('', 3000), Health).serve_forever(),
        daemon=True,
    ).start()
    # дальше — основная логика приложения
  • Лок на 15 минут. Один /deploy блокирует /exec и другие /deploy. Если предыдущий /deploy упал на шаге healthcheck или клиент оборвал соединение по таймауту, последующие /exec и /deploy будут возвращать 409 EXEC_BUSY до автоматического истечения лока — то есть до 15 минут с момента старта прерванного деплоя. Снять лок сразу можно вызовом `DELETE /v1/infra/servers/:id/lock`. Пересоздавать сервер не нужно.

  • Долгий деплой и таймауты HTTP-клиента. Полный цикл — особенно с установкой runtime и большим npm install / pip install — реально занимает 3–15 минут. В JSON-режиме сервер удерживает соединение, отправляя HTTP 200 + chunked-пробелы каждые 15 сек до готовности (поэтому Content-Length отсутствует, а ответ выглядит «висящим»). Если ваш HTTP-клиент установит более короткий таймаут (например, curl --max-time 120 или дефолт SDK ~30–120 сек) — соединение оборвётся, но orchestrateDeploy на сервере продолжит работу. Симптомы такого обрыва: ответ обрывается на пробелах, последующие /exec и /deploy отвечают 409 EXEC_BUSY до завершения серверной части или до истечения 15-минутного лока. Что делать: (а) в curl — --max-time 900 (15 мин) или без таймаута; (б) в любом SDK — настроить request timeout ≥ 900 сек для этого эндпоинта; (в) или используйте ?stream=true (SSE) — там события идут построчно по мере прохождения шагов, прокси и SDK таймауты ведут себя стабильнее. Снять застрявший лок руками — `DELETE /lock`.

  • apt-get в preStart работает без sudo. Агент и команды деплоя выполняются от имени rootsudo писать не нужно и это не вызовет ошибку.

  • cleanDeploy: true очищает только extractTo, не всю систему. По умолчанию удаляется директория /opt/app перед распаковкой нового архива. Системные пакеты (apt), базы данных, файлы в /var/lib/, /etc/, /root/ не затрагиваются — они переживут любое число повторных деплоев.

  • Системные пакеты сохраняются при sleep/wake, reboot и repair. Все эти операции сохраняют диск виртуальной машины нетронутым. Повторно устанавливать ПО через preStart при каждом деплое не нужно — достаточно сделать это однажды; последующие деплои без runtime и без нужных preStart-команд найдут пакеты на месте.

  • /etc/systemd/system/ доступен для записи. Можно создавать кастомные systemd-юниты через preStart или /exec: systemctl daemon-reload && systemctl enable --now myservice. Стандартный юнит деплоя (app.service) не конфликтует с пользовательскими юнитами при разных именах.

  • Используйте debug: true при расхождениях /deploy и ручного пути. Добавляет шаг debug_info с sha256sum файлов и file -i — помогает сравнить состояние /deploy с ручным /upload + /exec в байтах. Типичный случай — Tailwind v4 oxide падает на неожиданном UTF-8.

  • Тип архива определяется автоматически, отдельного поля формата нет. Для source.content платформа читает сигнатуру первых байт (PK → zip, 1F 8B → tar.gz, BZ → tar.bz2), для source.url — расширение пути. Нераспознанный архив трактуется как tar.gz. Передавать тип явным полем не нужно и негде.

  • Крупные inline-архивы конкурируют за слот. source.content больше ~10 КБ держит запрос в памяти на всё время деплоя, поэтому такие запросы ограничены небольшим числом одновременных (по умолчанию 2 на бэкенд). При превышении приходит 429 с подсказкой переключиться на source.url или source.versionId — у них этого лимита нет. Мелкие inline-архивы и versionId слот не занимают.

#Смотрите также