Перейти к содержимому
Документация

Стеки: несколько сервисов из одного файла

Стек — это набор сервисов, которые работают вместе (например, API и его база данных), описанный в одном файле inquir-compose.yaml и развёртываемый одной командой. Каждый сервис становится обычным приложением в одном проекте. Платформа создаёт их в нужном порядке, генерирует пароли, связывает сервисы по приватным именам и запускает каждый, только когда работают те, от которых он зависит. Повторный запуск обновляет то, что изменилось, и не трогает остальное.

Первый стек: API и база данных#

В папке лежат файл стека и по папке на каждый сервис, который вы собираете. Папка API — обычное приложение со своим inquir.yaml и Dockerfile. PostgreSQL папка не нужна: он запускается из официального образа.

my-app/bash
my-app/
├── inquir-compose.yaml   ← the stack
└── api/
    ├── inquir.yaml       ← an ordinary one-service manifest
    ├── Dockerfile
    └── server.js

db описан прямо в файле: всё, начиная с imageRef, — ровно то, что было бы в блоке services.web файла inquir.yaml. api ссылается на свою папку. {{generated.db-password}} один раз создаётся на сервере, а {{services.db.privateHost}} — приватное имя базы:

inquir-compose.yamlbash
version: 1
metadata:
  title: { en: API with PostgreSQL, ru: API с PostgreSQL }

generate:
  db-password: { bytes: 24, encoding: hex }   # made once, on the server

services:
  db:
    title: PostgreSQL
    # postgres:17-alpine, pinned by digest
    imageRef: docker.io/library/postgres@sha256:eee6c6e1b0894fa4fe0075eb054af9fd1cf39a35321c2d4110626cfe506411fb
    env:                                      # plain values
      POSTGRES_USER: app
      POSTGRES_DB: app
      PGDATA: /var/lib/postgresql/data/pgdata
    variables:                                # secrets and references
      POSTGRES_PASSWORD: "{{generated.db-password}}"
    runtime:
      ports: [{ name: pg, port: 5432, protocol: tcp }]
      volumes: [{ name: data, mountPath: /var/lib/postgresql/data, sizeMb: 1024 }]
      resources: { memoryMb: 256, cpuVCpus: 0.25 }
      healthcheck: { type: command, command: "pg_isready -U app -d app", intervalSeconds: 10, timeoutSeconds: 3 }
      deployStrategy: recreate
    networkPolicy: { ingress: private, egress: none }
    scaling: { activation: always }

  api:
    path: ./api                               # a folder with its own inquir.yaml
    title: API
    dependsOn: [db]                           # starts once db serves
    variables:
      DATABASE_URL: "postgres://app:{{generated.db-password}}@{{services.db.privateHost}}:{{services.db.ports.pg}}/app"

entrypoint: api                               # up prints its address

Собственный inquir.yaml API остаётся таким же, каким был бы без стека: порт, проверка работоспособности, ресурсы и публичный адрес.

api/inquir.yamlbash
version: 1
services:
  web:
    networkPolicy: { ingress: public }        # a public HTTPS address
    runtime:
      port: 3000
      healthcheck: { type: http, path: /healthz }
      resources: { cpuVCpus: 0.25, memoryMb: 256 }

Проверьте файл и разверните стек:

terminalbash
cd my-app
inquir compose validate        # every error and warning, and the plan; nothing is created
inquir compose up              # project my-app: my-app-db first, then my-app-api
inquir compose status my-app

up создаёт проект с именем стека, my-app, и в нём приложения my-app-db и my-app-api. Он генерирует пароль, запускает базу, ждёт, пока она пройдёт проверку работоспособности, затем собирает API, выпускает релиз и печатает его адрес. Пароль не появляется в терминале: он хранится зашифрованным и попадает в оба контейнера как секретная переменная, доступная только продакшену.

Полный пример: inquir-examples/ai-agent-bot-postgres — ИИ-ассистент в Telegram, который хранит историю чатов в собственной базе PostgreSQL.

Файл#

Файл называется inquir-compose.yaml (подойдёт и inquir-compose.yml) и лежит в корне папки стека. Ключи верхнего уровня:

inquir-compose.yamlbash
version: 1          # required
metadata: {...}     # title, description, icon, category, docsUrl, notes
inputs: {...}       # values asked at deploy time
generate: {...}     # credentials made on the server, once per stack
project: {...}      # placement: new (default) | existing, and a name
defaults: {...}     # location, networkPolicy, scaling, runtime.resources of inline services
services: {...}     # 1 to 32 services (required)
entrypoint: api     # the public service up prints (optional)
x-common: &common   # x-* keys are ignored: a place for YAML anchors
  LOG_LEVEL: info

Любой другой ключ верхнего уровня — ошибка. Ключи, начинающиеся с x-, игнорируются, поэтому в них удобно держать YAML-якоря, ссылки на них и ключи слияния.

Сервисы#

Сервис описывается одним из двух способов:

  • Папкой, path: ./api: у папки свои inquir.yaml и Dockerfile, и она развёртывается ровно так же, как это сделал бы inquir apps deploy. Сборку, порт, проверку работоспособности, ресурсы, тома и сеть задаёт её inquir.yaml. Эту же папку по-прежнему можно развернуть отдельно.
  • Прямо в файле: настройки записаны в файле стека. Это в точности блок services.web файла inquir.yaml, и проверяется он по тем же правилам: imageRef или build, runtime, env, scale, networkPolicy, scaling, location. Сервису, описанному в файле, нужен imageRef или build.

Оба вида принимают ещё четыре ключа стека:

  • title — название в консоли и в status.
  • dependsOn — сервисы, которые должны заработать раньше этого.
  • variables — переменные приложения; кроме buildArgs, это единственное место, где работают ссылки.
  • buildArgs — аргументы сборки для этого развёртывания; без секретов.

imageRef должен быть закреплён по digest. Пишите его в полной форме, с адресом реестра: docker.io/library/postgres@sha256:… для официального образа Docker Hub, docker.io/<user>/<image>@sha256:… для другого образа Docker Hub, ghcr.io/<org>/<image>@sha256:… для другого реестра. Digest покажет docker buildx imagetools inspect postgres:17-alpine.

Обычные, несекретные значения пишите в env блока, а всё секретное или со ссылкой — в variables. env блока работает на платформе с включённым manifest env; на inquir.org он включён. Где он выключен, compose validate предупредит об этом, и такие значения нужно писать в variables.

Ключи сервисов состоят из строчных латинских букв, цифр и дефисов и начинаются с буквы: они становятся частью имён. Каждый сервис — приложение с именем <стек>-<сервис>, например my-app-db; приложение стека из одного сервиса называется так же, как стек.

Входные значения: что спрашивается при развёртывании#

inputs перечисляет то, что указывает тот, кто разворачивает стек: токены, ключи, варианты. Консоль строит по ним форму, а CLI берёт их из --input, --input-file или спрашивает в терминале.

inquir-compose.yamlbash
inputs:
  TELEGRAM_BOT_TOKEN:
    label: { en: Telegram bot token, ru: Токен Telegram-бота }
    hint: Send /newbot to @BotFather.
    secret: true                 # a password field: never echoed, stored encrypted
    required: true
  MODEL:
    label: Model
    default: gpt-5-mini
    maxLength: 128
  MODE:
    label: Mode
    options: [polling, webhook]  # a choice; or [{ value, label }, ...]
    default: polling
  OPENAI_API_KEY:
    label: OpenAI API key
    secret: true
    requiredGroup: model-key     # at least one input of the group
  ANTHROPIC_API_KEY:
    label: Anthropic API key
    secret: true
    requiredGroup: model-key
  ALLOWED_USER_IDS:
    label: Allowed Telegram user IDs
    pattern: csv-integers        # username, hostname, email, https-url, integer, csv-integers
  • label (обязательно) и hint — обычный текст или { en, ru }.
  • secret: true — поле пароля: значение не показывается и хранится зашифрованным. У секрета не бывает default.
  • required: true или default, если значение не указано.
  • options — фиксированный список значений или пар { value, label }.
  • requiredGroup — хотя бы одно значение группы должно быть задано, например один из нескольких ключей API.
  • pattern — один из username, hostname, email, https-url, integer, csv-integers; а также minLength / maxLength.

Переменная, значение которой целиком состоит из одного необязательного входного значения без значения по умолчанию, оставленного пустым, не создаётся вовсе. Если вокруг ссылки есть другой текст, переменная задаётся как получилось после подстановки. При следующем up значение, которое вы не передали, остаётся прежним.

Генерируемые значения#

generate создаёт учётные данные на сервере из криптостойкого источника случайности: bytes от 16 до 64, encoding — hex, base64 или base64url. Каждое значение создаётся один раз для стека, хранится зашифрованным и используется всеми следующими up; новое значение по запросу создаёт rotate.

inquir-compose.yamlbash
generate:
  db-password:    { bytes: 24, encoding: hex }      # 48 hex characters
  session-secret: { bytes: 32, encoding: base64 }

services:
  web:
    path: ./web
    variables:
      SESSION_SECRET: "{{generated.session-secret}}"
      BOT_NAME: "{{stack.name}}-bot"

Ссылки между сервисами#

Значения в variables и buildArgs могут содержать ссылки; они подставляются при развёртывании стека:

  • {{inputs.KEY}} — указанное значение или значение по умолчанию.
  • {{generated.key}} — сгенерированное значение.
  • {{services.db.privateHost}} — приватное имя сервиса в приватной сети вашего рабочего пространства.
  • {{services.web.publicUrl}} — его адрес https://, для сервиса с networkPolicy.ingress: public.
  • {{services.db.port}}, {{services.db.ports.pg}} — его порт или порт с именем pg.
  • {{services.db.endpoints.rw.privateHost}}, {{services.db.ordinals.0.privateHost}} — точка подключения или одна из реплик stateful-сервиса.
  • {{stack.name}} — имя стека.

Больше в файле ничего не подставляется: $VAR остаётся как написано, а {{ в блоке сервиса попадает в контейнер как есть. Одна переменная может ссылаться не больше чем на один другой сервис. Сгенерированное значение или секретное входное значение можно использовать только в variables, но не в buildArgs: аргументы сборки остаются в образе. Переменная с таким значением становится секретом, доступным только продакшену; остальные — обычные переменные. Пароль, ключ или токен, записанный в файле открытым текстом, — ошибка: используйте входное значение или generate. PORT задавать нельзя.

Порядок запуска и готовность#

dependsOn: [db] запускает сервис только после того, как заработали все перечисленные: их продакшен-релиз активен, а реплики проходят собственную проверку работоспособности. Независимые друг от друга сервисы развёртываются параллельно, а сборки одного стека идут по очереди. Цикл зависимостей — ошибка. Ссылка сама по себе порядок не задаёт, поэтому compose validate предупреждает, если сервис использует другой, но не зависит от него. down останавливает сервисы в обратном порядке.

Тома, сеть, регион и масштабирование#

Это обычные ключи inquir.yaml каждого сервиса, поэтому в стеке они значат то же, что и для одного приложения:

  • runtime.volumes: name, mountPath, sizeMb, readOnly. Том принадлежит одному сервису; общих томов у сервисов не бывает.
  • networkPolicy: ingress — private (по умолчанию) или public, egress — full (по умолчанию) или none. entrypoint должен указывать на публичный сервис.
  • location: auto или регион, например nl для Telegram-бота.
  • scaling: activation: always для базы данных или бота, http с minReplicas: 0 для сайта, которому можно засыпать. scale задаёт реплики, в том числе stateful.

defaults задаёт location, networkPolicy, scaling и runtime.resources сразу всем сервисам, описанным в файле; собственные ключи сервиса важнее. На сервис-папку defaults не действует: всё решает её inquir.yaml. С project: { placement: existing } команда up потребует --project.

Развёртывание#

Из папки. В папке стека inquir compose up упаковывает каждую папку сервиса так же, как inquir apps deploy, и загружает её; больше ничего с вашего компьютера не уходит. Имя стека по умолчанию — имя папки, задать его можно через --name: строчные латинские буквы, цифры и дефисы, до 32 символов. Новый стек получает собственный проект с тем же именем (другое имя — --project-name); --project разворачивает стек в существующий проект. Значения берутся из --input KEY=VALUE (можно повторять), из --input-file .env, или терминал спросит обязательные (секреты не отображаются). up ждёт окончания и показывает ход развёртывания; --no-wait возвращается сразу после постановки в очередь, --json выводит машиночитаемый результат.

.envbash
# .env next to inquir-compose.yaml: never commit it
TELEGRAM_BOT_TOKEN=123456789:AA...
OPENAI_API_KEY=...
MODEL=gpt-5-mini

Из GitHub. Укажите репозиторий вместо папки: https://github.com/<owner>/<repo>, а за ним при желании #<ref> (ветка, тег или коммит; иначе ветка по умолчанию) и :<папка> (папка с inquir-compose.yaml; иначе корень). Платформа читает эту папку на одном коммите и записывает коммит в стек. Публичные репозитории работают сразу, приватные — через GitHub App вашего рабочего пространства (Подключить GitHub в консоли). Без приложения число чтений ограничено несколькими в час на рабочее пространство, поэтому для репозиториев, которые вы разворачиваете часто, подключите его. Push пока не переразвёртывает стек: снова выполните up.

Из каталога. --catalog <шаблон> разворачивает шаблон из каталога приложений — последнюю версию или @<версия>: те же шаблоны, что предлагает каталог в консоли.

terminalbash
# From the stack folder (the name defaults to the folder's)
inquir compose up --name shop --input-file .env
inquir compose up --name shop --project my-project --input MODEL=gpt-5-mini

# From GitHub: owner/repo, then an optional #ref and :folder
inquir compose validate https://github.com/acme/shop#v1.2.0:deploy
inquir compose up https://github.com/Inquir-search/inquir-examples:ai-agent-bot-postgres \
  --name ai-bot --input-file .env

# From the catalog: a template, optionally @version
inquir compose up --catalog nginx --name site

Для ИИ-агентов для программирования inquir mcp предоставляет compose_validate, compose_up, compose_status и compose_down с теми же проверками и сообщениями.

Обновление стека#

Снова выполните up с тем же именем. Стек сохраняет сгенерированные значения и указанные ранее входные значения, поэтому передавайте только то, что меняется. Каждый сервис сравнивается с тем, что сейчас работает: неизменённый не трогается, изменённый пересобирается (если изменились его файлы), перенастраивается и выпускается заново. up меняет только переменные, которые записал сам стек; переменные, добавленные вами в консоли, остаются. Новый сервис в файле создаётся. Сервис, удалённый из файла, продолжает работать, и status показывает его как осиротевший; up --prune отправляет его в архив, когда остальные заработают, а его тома сохраняет. Если его ещё использует приложение вне стека, --prune не сработает.

terminalbash
# The same command again: generated values and earlier inputs are kept
inquir compose up --name shop
inquir compose up --name shop --input MODEL=gpt-5     # change one input
inquir compose up --name shop --prune                 # also archive services removed from the file

Прежде чем что-либо создать, up проверяет весь стек по вашему тарифу: приложения, тома, хранилище, процессор и память каждой реплики. Если стек не помещается, ничего не создаётся, а в ошибке перечислены все превышения. Следующий up учитывает только то, что добавляется.

Статус, остановка и удаление#

status показывает состояние стека и последнее развёртывание, а для каждого сервиса — его приложение, адрес и отметки вроде orphaned или stopped.

down останавливает стек: каждый сервис масштабируется до нуля, сначала зависимые. Приложения, переменные, сгенерированные значения и тома остаются, и следующий up снова запустит стек.

down --delete --confirm <стек> удаляет стек: приложения уходят в архив и удаляются через 30 дней, их тома хранятся по обычным правилам резервного копирования. С --delete-volumes приложения и их данные удаляются сразу и навсегда: 30 дней на восстановление уже не будет. Удаление не выполняется, пока от какого-либо сервиса стека зависит приложение вне его.

terminalbash
inquir compose status shop
inquir compose down shop                                             # stop; everything is kept
inquir compose up --name shop                                        # start again
inquir compose down shop --delete --confirm shop                     # archive, purged after 30 days
inquir compose down shop --delete --delete-volumes --confirm shop    # and delete the data now

Замена сгенерированного значения#

inquir compose rotate <стек> <ключ> [<папка>] создаёт новое значение для одного ключа generate и выполняет up: заново выпускаются только сервисы, которые его используют. Для стека, развёрнутого из папки, укажите эту папку (., если вы в папке стека): стек обновляется из неё. Стек из GitHub или каталога помнит свой источник, и папка ему не нужна.

terminalbash
# A stack deployed from a folder: run it there and pass the folder (.)
inquir compose rotate shop session-secret .

# A GitHub or catalog stack remembers its source: no folder
inquir compose rotate openclaw gateway-token

Прочитать или скопировать шаблон из каталога#

inquir compose pull <шаблон>[@<версия>] [<папка>] записывает шаблон из каталога в папку: его inquir-compose.yaml и файлы сервисов. Прочитайте, измените и разверните свою копию через up. inquir compose pack делает обратное: превращает папку в один JSON-пакет.

terminalbash
inquir compose pull nginx ./site              # the latest version, as a folder
inquir compose pull <template>@<version> ./x   # a given version
cd site && inquir compose up --name site      # deploy your copy
inquir compose pack . -o site.json            # the opposite: a folder as one bundle

Ограничения стеков#

  • От 1 до 32 сервисов в стеке; тариф обычно разрешает меньше, и up проверяет это заранее.
  • До 64 входных и 32 генерируемых значений; файл — не больше 256 КиБ.
  • Имя стека — до 32 символов; <стек>-<сервис> должно помещаться в имя приложения (64 символа).
  • Не больше 5 развёртываний стеков в очереди или в работе на рабочее пространство и по одному на стек.
  • Из репозиториев поддерживается только GitHub (github.com).
  • Каждый сервис подчиняется тем же правилам, что и любое приложение: ограничения сборки, проверки работоспособности, регионы, ресурсы тарифа.

Дальше: Контейнерные приложения — всё, что умеет один сервис, inquir.yaml — ключи сервиса, а в готовых рецептах есть Dockerfile, с которого можно начать.