layero.json — конфигурация в репозитории
Необязательный файл в корне приложения. Он задаёт то, что Layero обычно определяет сам, и лежит рядом с кодом — значит, едет вместе с ним и может отличаться по веткам.
Правило одно:
Всё, что вы указали в файле, Layero обязан применить. Всё, чего вы не указали, Layero определяет сам.
Сначала деплой без файла
Первый деплой делайте без layero.json. По данным платформы за 90 дней,
84 % новых проектов публикуются с первой сборки без файла: Layero сам
находит фреймворк, пакетный менеджер, скрипт сборки, папку результата, версию Node, а у серверного
приложения — команду запуска и порт.
Файл — это ответ на конкретный отказ: один симптом — одно поле.
- Каждое записанное поле перестаёт определяться автоматически и закрывается в панели. Значение, скопированное «на всякий случай», станет неправдой в тот день, когда проект сменит сборщик.
- Около 60 % упавших сборок файл не лечит — они чинятся в другом месте, см. чего файл не лечит.
Порядок действий, когда сборка или запуск упали:
- Прочитайте настоящую ошибку, а не карточку деплоя: в логе сборки
(
npx layero@latest logs, для вывода работающего приложения —logs --runtime) или черезnpx layero@latest diagnose. - Найдите симптом в таблице ниже и поменяйте ровно одно.
- Проверьте по логу, что значение применилось и файл не дал замечаний.
- Та же ошибка второй раз подряд — остановитесь и перечитайте лог, а не деплойте в третий. После десяти одинаковых отказов подряд Layero приостанавливает автодеплой из git.
Файл оправдан и без отказа, когда конфигурация должна ехать вместе с кодом: отличаться по веткам или проходить код-ревью.
Примеры: один симптом — одно поле
Не та папка результата — и больше ничего:
{ "outputDirectory": "dist/client" }
Проект определился как «Static (no build)», а это приложение на Vite:
{ "framework": "vite", "outputDirectory": "dist" }
Свой скрипт сборки без известного фреймворка:
{ "framework": "generic", "buildCommand": "node build.mjs", "outputDirectory": "public" }
Node-сервер, который Layero принял за статический сайт:
{ "runtime": "node_web", "startCommand": "node dist/server.js", "port": 3000 }
FastAPI с нестандартной точкой входа:
{ "runtime": "python_web", "startCommand": "uvicorn app.main:app --host 0.0.0.0 --port $PORT" }
Все поля необязательны. Пустой файл {} допустим и означает «определяй всё
сам».
"$schema": "https://layero.ru/schema/layero-v2.json" подключает
автодополнение и подсветку ошибок в редакторах с поддержкой JSON Schema:
VS Code, JetBrains, Neovim. На сборку это поле не влияет.
Все ключи
Существуют только эти имена. Неизвестный ключ сборку не роняет — он пропускается с замечанием в логе, см. ошибки в самом файле.
| Ключ (короткое имя) | К чему относится | Что задаёт |
|---|---|---|
framework | сборка | Имя фреймворка. Перекрывает автоопределение. Значения — в списке фреймворков. |
installCommand (install) | сборка | Команда установки зависимостей. |
buildCommand (build) | сборка | Команда сборки целиком. |
outputDirectory (output) | только статика | Папка с index.html после сборки. |
nodeVersion (node) | сборка | Версия Node.js. |
runtime | тип приложения | Запускать приложение в контейнере, а не раздавать файлами. |
startCommand (start) | только контейнер | Команда запуска внутри контейнера. |
port | только контейнер | Порт, который слушает приложение. |
memory_mb, cpu_quota, idle_timeout_s, preload | только контейнер | Ресурсы контейнера и время простоя до засыпания. |
env | только контейнер | Несекретные переменные окружения. |
layout | раскладка | "fullstack" — явно объявить две половины. |
frontend | фуллстек | Половина, которая раздаётся файлами. |
backend | фуллстек | Половина, которая работает в контейнере. |
apiPrefix (api_prefix) | фуллстек | Префикс пути, который уходит на бэкенд, например "/api". |
framework
Имя фреймворка. Задавайте, только когда автоопределение ошиблось: Storybook рядом с приложением на Vite, «Static (no build)» у проекта, которому нужна сборка.
Два значения стоит различать:
static— сборка не запускается вовсе. Ни установки, ни сборки:buildCommandс ним игнорируется, публикуется то, что лежит в папке.generic— свой скрипт сборки. Известного фреймворка нет, но собрать проект нужно: Layero исполнит вашиbuildCommandиoutputDirectory.
installCommand
Команда установки зависимостей. По умолчанию Layero берёт воспроизводимую
команду вашего пакетного менеджера: npm ci, yarn install --frozen-lockfile,
pnpm install --frozen-lockfile.
Сначала чините репозиторий: закоммитьте актуальный lock-файл и точный
packageManager — и только потом задавайте поле. npm ci без lock-файла —
готовый отказ.
У приложения в контейнере поле действует только для Python: заменяет шаг
pip install -r requirements.txt. Node-сервер (node_web, ssr_next) его
не исполняет — установка выбирается по lock-файлу.
buildCommand
Команда сборки целиком: pnpm build:prod, а не build:prod. Не пишите в
файл npm run build — это и так умолчание, а запись закроет поле в панели.
outputDirectory
Папка с готовой статикой относительно корня приложения — та, где после
сборки лежит index.html. Например: dist, build, out, dist/client,
dist/my-app/browser. Указывается папка, а не файл. Layero исполняет
написанное и не подменяет заданную папку более удачной догадкой.
Поле действует только для сайтов, которые Layero раздаёт файлами. У приложения
в контейнере оно принимается, но не применяется — и замечания об этом нет.
Такому приложению задайте startCommand.
nodeVersion
Версия Node.js, на которой собирать. Принимает мажор "22", точную версию
"22.14.0" или "lts". Поле перекрывает .nvmrc и engines.node: если
версия уже задана там, правьте её там же, иначе у проекта появятся два
источника правды.
runtime
Тип приложения, которое Layero запускает в контейнере: ssr_next,
node_web, python_web, streamlit, gradio (двум последним нужен
app.py). Других значений нет.
Обычно Layero сам переключает проект между статикой и контейнером, когда
узнаёт сервер: Next без output: 'export', Express, Fastify, FastAPI, Flask,
Django. Поле нужно, когда Layero сервер не узнал. runtime в корне файла сильнее типа
проекта в настройках — в логе будет the file wins. Одна строка меняет способ
раздачи сайта.
Разовая замена без файла: npx layero@latest deploy -t node_web. Подробнее о
контейнерах — Когда нужен runtime.
startCommand
Команда запуска приложения в контейнере. Без runtime (или серверного типа
проекта) ничего не делает.
Рабочая папка приложения в контейнере — /app: туда попадает ваш код, там
же Layero устанавливает зависимости и собирает проект. Пути в команде
отсчитываются от неё: node server.js запустит /app/server.js. Файл из
команды должен существовать после сборки.
Если поле не задано, Layero определяет команду сам: у Node-приложения это
обычно npm start из package.json. Скрипта start нет — задайте
startCommand.
Приложение обязано слушать 0.0.0.0 и порт из переменной $PORT, например
uvicorn main:app --host 0.0.0.0 --port $PORT. Сервер на 127.0.0.1 снаружи
контейнера недоступен. ASGI-приложению нужен uvicorn, а не gunicorn app:app.
port
Порт, который слушает приложение, если он отличается от умолчания: Node — 3000, Next — 8080, Python — 8000, Streamlit — 8501, Gradio — 7860.
memory_mb, cpu_quota, idle_timeout_s, preload
Ресурсы контейнера и время простоя, после которого приложение засыпает.
memory_mb — память работающего контейнера, а не сборки: нехватку памяти
на сборке поле не лечит.
env
Несекретные переменные для приложения в контейнере — и на сборке, и при запуске. Имя, заданное здесь, перекрывает одноимённую переменную проекта. Статическая сборка это поле не читает.
Файл лежит в git — секретам в нём не место. Секреты задаются переменными окружения проекта.
Фуллстек: фронт и бэк в одном репозитории
Фронтенд раздаётся файлами, бэкенд работает в контейнере, API живёт под
/api:
{
"frontend": { "root": "frontend", "output": "dist" },
"backend": { "root": "backend", "framework": "fastapi" },
"apiPrefix": "/api"
}
- Внутри половин — короткие имена. У
frontend:root,framework,install,build,output,node. Уbackend:root,framework,runtime,install,start,node,port,memory_mb,cpu_quota,idle_timeout_s,preload,env. Длинные имена из корня файла (buildCommand,outputDirectory,startCommand) внутри блоков не действуют. - Нужны оба блока. Один блок раскладку не включает — с
"layout": "fullstack"или без него. - Бэкенду обычно хватает
rootиframework(fastapi,express,django…): для FastAPI, Flask, Django и Express команда запуска и порт выводятся сами.startиportдобавляйте, только если запуск упал. root— существующая папка. У половины, которая лежит в корне репозитория,rootпустой или не задан. Если папки нет, сборка упадёт, а ошибка перечислит настоящие папки — берите путь оттуда.backend.runtimeтип контейнера не выбирает: решает настройка проекта, а о расхождении сказано только в логе.- Блоки
frontendиbackendцеликом заменяют фуллстек-настройки из панели. Блоки с выдуманными ключами (dir,build_cmd,start_cmd) выключат эти настройки и ничего не дадут взамен: проект, который собирался, перестанет собираться. Если проект уже собирается по настройкам панели, блоки не добавляйте.
Короткие имена работают всегда
У шести полей есть короткая форма, и она не устаревшая: файлы, написанные короткими именами, будут работать всегда.
| Основное имя | Короткое |
|---|---|
installCommand | install |
buildCommand | build |
outputDirectory | output |
nodeVersion | node |
startCommand | start |
apiPrefix | api_prefix |
Если в файле окажутся оба имени одного поля, применится основное. Про второе Layero скажет в логе сборки: иначе вы правили бы не ту строку.
Симптом → что править
Моноширинным шрифтом набрано то, что вы дословно увидите в карточке деплоя, событии CLI или логе сборки. Сначала посмотрите, на какой стадии упало. Стадия подсказывает, где живёт исправление:
| Стадия | Где чинить |
|---|---|
detect | Форма приложения: корневая папка, тип проекта, runtime. Не код. |
install | Lock-файл и packageManager в репозитории, затем installCommand. |
build | Строка ошибки из лога: обычно код, иногда buildCommand или nodeVersion. |
verify, upload | outputDirectory. |
launch | startCommand, port, переменные окружения. |
clone, timeout, activate | Платформа или доступ к источнику. Один повтор. |
Что лечит файл
| Симптом | Что править |
|---|---|
собранный сайт не содержит index.html в '.' · сборка отработала, но папки 'dist' нет. После сборки в каталоге есть: … · [output] в 'dist' нет index.html | outputDirectory — папка, где после сборки лежит index.html. Ошибка перечисляет, что есть на диске, — путь берите оттуда. |
Сборка НЕ запускалась: проект собирается как статический сайт (фреймворк «static») … · в логе [config] ВНИМАНИЕ: buildCommand из layero.json (…) НЕ выполняется или static framework: skipping install/build | Сборка не запускалась: framework: "static" значит «без установки и без сборки». Поставьте "framework": "generic". Путь к папке тут ни при чём. |
npm error Missing script: "build" · не знаем, чем собирать этот проект: команда сборки не задана | buildCommand с существующим скриптом. Сборка не нужна вовсе — "framework": "static". Если это сервер или бот — следующая строка. |
| Сервер на Node или Python опубликован как статика: файлы отдаются, ничего не работает | runtime + startCommand, при необходимости port. Без файла: deploy -t node_web. |
launch/boot: container failed to start in time, в логе ERR_MODULE_NOT_FOUND /app/… или приложение слушает 127.0.0.1 | startCommand: путь от /app, файл существует после сборки, адрес 0.0.0.0, порт $PORT. Другой порт — port. |
ERR_UNKNOWN_BUILTIN_MODULE · EBADENGINE · Node.js 18 снят с поддержки и закрыт для новых сборок | "nodeVersion": "22" — но сначала посмотрите строку [config] node=…: если версию задаёт .nvmrc или engines.node, правьте там. |
npm error code EUSAGE · ERR_PNPM_… | Сначала lock-файл и packageManager в репозитории, потом installCommand. |
/bin/sh: 1: run: not found | Команда записана обрывком. Нужна целая: npm run build, а не run build. |
Can't resolve '@scope/shared' в воркспейсе | Деплойте из корня воркспейса, а не с --root apps/web: иначе соседний пакет не попадёт в сборку. В файле нужны три поля: "framework": "generic", buildCommand, который сначала собирает зависимости (pnpm --filter @scope/shared build && pnpm --filter @scope/web build), и "outputDirectory": "apps/web/dist". Задайте framework явно: CLI до 0.11.0 записывал корень воркспейса в проект как static, и сборка не запускалась вовсе. |
| Фронт и бэк в одном репозитории, а опубликована только статика | Блоки frontend + backend. |
Чего файл не лечит
| Симптом | Что на самом деле чинит |
|---|---|
Монорепо: no app at the repo root, and multiple candidate app folders found (…) · root_directory not found in source · no package.json at the build root | Корень приложения — настройка проекта, а не ключ файла. npx layero@latest deploy --root apps/web (сохраняется на проекте) или поле «Папка приложения» в панели. Ключа rootDirectory не существует. |
branch 'cli' has no app to build: no package.json / layero.json / requirements.txt found at the deploy root | deploy запущен не из той папки. Перейдите в папку приложения или передайте --root. Готовая статика — --prebuilt <dir>. |
Next.js: "/app/.next/standalone": not found · project is configured for SSR, can't deploy as static | Режим Next задаётся в next.config, а не в файле: output: 'export' — статика, без output — сервер. outputDirectory: ".next" не пишите никогда. |
error TS… · Type error: · Module not found: Can't resolve '@/…' · Failed to compile | Код. Воспроизведите локально той же командой и тем же мажором Node. Частая причина — регистр в имени файла: macOS его прощает, Linux нет. |
launch/probe: container bound but not servable (5xx) | Окружение, база, код: GET / должен отвечать без 5xx. Переменные задаются в панели или npx layero@latest env set, не в файле. |
supabaseUrl is required · Failed to collect page data | Переменные окружения проекта, нужные на сборке. Секреты в файл не кладут. |
builder did not pick up the job after N retries · auth.docker.io … TLS handshake | Сбой платформы. Один повтор; повторилось — пишите в поддержку, код и файл не трогайте. |
docker-build: timeout after …s · Reached heap limit · ENOSPC | Лимиты сборки на тарифе. memory_mb — память контейнера, а не сборки. |
хост '…' не в списке разрешённых источников | Подключение источника в панели. |
Что сильнее чего
Для каждого поля отдельно:
layero.json → настройки проекта → подсказка CLI (--type) → найденное в репозитории → умолчание фреймворка
Побеждает первое заданное. Например, при таком package.json:
{ "scripts": { "build": "vite build" } }
и таком layero.json:
{ "buildCommand": "npm run build:production" }
соберётся npm run build:production.
У nodeVersion версию умеет объявлять и сам репозиторий:
layero.json → настройки проекта → .nvmrc → .node-version → engines.node → умолчание Layero
Перекрытую версию из репозитория Layero называет в логе сборки, а не применяет молча.
Файл или настройки проекта? Файл — когда значение должно ехать с кодом: в
каждую ветку и каждый клон. Настройки проекта (--type, --root, панель) —
для разовой правки и для всего, у чего нет ключа в файле.
Как проверить по логу
До выкатки npx layero@latest deploy --dry-run покажет план: фреймворк,
команду сборки, папку результата и источник каждого значения. Значения из
этого файла там помечены layero.json. Окончательное доказательство — лог
настоящей сборки. После каждой правки найдите в нём две вещи.
1. Применённое значение с пометкой источника. Статическая сборка:
[config] framework=vite (from layero.json)
[config] build=`npm run build:production` (from layero.json)
[config] install=`pnpm install` (from layero.json)
[config] output=dist/client (from layero.json)
[config] node=22.18.0 (layero.json)
У строки про Node слова from нет. Любой другой источник значит, что ваше
поле не применилось: (from dashboard), (from hint), (auto-detected),
(from package.json scripts), (from lockfile), (default for vite),
(project settings), (.nvmrc), (engines.node), (default).
Сборка контейнера:
runtime build started (kind=node_web)
start command: node dist/server.js
env: 3 from project, 1 from layero.json
start command печатается без источника — сравните текст со своим
startCommand. У Python есть ещё строка
install command: … (источник: layero.json).
2. Замечаний от файла нет. Любая из этих строк значит, что файл сделал не то, что вы думаете:
[config] layero.json: unknown keys ignored: …
[config] layero.json: invalid JSON at line N col N
[config] layero.json: `build` must be a non-empty string — ignored
[config] layero.json: `buildCommand` и `build` — одно и то же поле; применяется `buildCommand`
[config] WARNING: unknown framework '…' (layero.json). Known: …. Auto-detected as '…'.
layero.json requests runtime '…' while the project is set to '…' — the file wins
[config] в layero.json объявлен бэкенд «…», но проект настроен как «…» — собираем по настройке проекта
[config] layero.json: объявлен `layout: "fullstack"`, но `backend` не описан — раскладка не применится
[output] в '…' нет index.html. Платформа не подменяет заданный каталог
[config] предупреждение: `…` слушает только localhost — контейнер будет недоступен
Фронтенд указан в каталоге …, но такого каталога в репозитории нет
Последняя строка (или такая же про бэкенд) сборку роняет. Строка
[config] node: layero.json перекрыл .nvmrc=… — справочная: у версии теперь
два источника.
Потом проверьте не сборку, а результат: откройте адрес из события ready.
Зелёная сборка с упавшим запуском — всё равно отказ. У приложения в
контейнере первые запросы после ready могут до минуты получать заглушку 404,
пока контейнер стартует. Если 404 держится дольше минуты, это настоящий
отказ: читайте logs --runtime.
Поле из файла нельзя изменить в панели
Если поле объявлено в layero.json, панель показывает его значение и бейдж с
именем файла — вместо кнопки редактирования. Нажмите на бейдж — откроется файл
в репозитории.
Так сделано намеренно. Открытое поле — это обещание, что правка применится, а правку в панели первая же сборка отменила бы: файл сильнее. Меняйте такое значение в репозитории — там, где оно задано.
Такое поле не участвует и в сохранении: панель не запишет в настройки проекта значение, которое всё равно проиграет файлу.
Где должен лежать файл
- Обычный проект — в корне репозитория.
- Монорепо — в папке приложения, а не в корне репозитория. Указали
apps/web(флагом--rootили в настройках проекта) — Layero читаетapps/web/layero.json. Исключение — фуллстек: половины задают свои папки черезfrontend.rootиbackend.root.
Ошибки в самом файле
Layero почти никогда не отказывается собирать проект из-за layero.json:
сборку роняет только несуществующая папка в frontend.root или
backend.root. Обратная сторона: ошибка в файле не останавливает сборку, и легко поверить, что
настройка сработала. Замечания попадают в лог сборки, в мастер настройки и на
страницу деплоя — но не все и не всегда.
| Что в файле | Что делает Layero |
|---|---|
| невалидный JSON (лишняя запятая, комментарий) | файл выключается целиком, сборка идёт так, как будто его нет. Статическая сборка пишет замечание, сборка контейнера молчит |
| неизвестное имя поля | пропускает с замечанием unknown keys ignored — сборка при этом зелёная. Так незаметно пропадали static, headers, type, dir, build_cmd, output_dir, start_cmd, install_cmd, rootDirectory. Ключ type пишут по аналогии с флагом --type: он не применяется, и замечание называет верное имя — framework или runtime (deploy --dry-run предупреждает об этом до выкатки) |
| пустое значение | сообщает, поле пропускает |
неизвестное имя в framework (например, "node") | предупреждает и определяет фреймворк сам |
framework, которого нет в репозитории | предупреждает и всё равно применяет |
Проверяйте JSON до коммита. И ещё пять ошибок, о которых Layero чаще всего не скажет ничего:
- «Анкета» вместо правки. Все поля заполнены умолчаниями:
npm ci,npm run build,dist. Это самый частый файл и самый бесполезный: он ничего не меняет, но закрывает поля в панели. - Закреплённое неверное значение.
"output": "."у проекта со сборкой,"output": ".next","install": "true"— исполняется буквально, ничего не ставится. Layero делает то, что написано. outputDirectoryу приложения в контейнере иstartCommandилиportбезruntime: принимаются, хранятся, не используются — замечания нет.- Блоки
frontendиbackendотменяют фуллстек-настройки панели — даже если внутри выдуманные ключи. См. Фуллстек. - Файл не в той папке монорепо и секреты в
env.
Адрес схемы
https://layero.ru/schema/layero-v2.json
Прежний адрес продолжает работать и не будет удалён:
https://layero.ru/schema/layero-v1.json
Если в ваших репозиториях стоит v1, менять его не нужно — файл читается
одинаково. В новых проектах указывайте v2: в нём описаны основные имена полей,
и редактор не подчёркивает как ошибку то, что Layero принимает.