#Полный деплой приложения
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_info — ls -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 — личный ключ
# 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-архив (минимальный набор полей)
# Канонический 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 — произвольный стек без рантайма
# Деплой без 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-приложение
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 — личный ключ
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-приложение
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):
{
"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:
{
"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удаляется перед следующим деплоем:- Передавать
envв каждом/deploy— перегенерируется автоматически (рекомендуется). - Хранить
.envвнеextractTo— например,/var/lib/myapp/.env. Эта директория не трогаетсяcleanDeploy. Вstartуказать явный путь. cleanDeploy: false— накопительный (merge) деплой,.envпереживёт, но вместе с ним останутся устаревшие файлы прошлых деплоев.
- Передавать
Автоматическая подгрузка
.envв systemd. В юните прописанEnvironmentFile=-{extractTo}/.env(знак-делает файл опциональным). Приложение через systemd получит переменные само. Но приsystemd: falseили командах изinstall/preStartenv подгружать не будет — передавайте явно.Если смешаны
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:
require('http').createServer((_req, res) => res.end('ok')).listen(3000) // дальше — основная логика приложения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. Агент и команды деплоя выполняются от имениroot—sudoписать не нужно и это не вызовет ошибку.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слот не занимают.
#Смотрите также
- Список рантаймов —
GET /v1/infra/runtimes— все доступные ID. - Выполнить команду — ручные команды вместо автоматической цепочки.
- Логи сервиса —
?service={serviceName}для проверки старта. - Снять зависший лок — при
EXEC_BUSYпосле обрыва. - Задать порт — если приложение слушает не 3000 (не рекомендуется).