Запускайте любой OCI-образ как долгоживущий сервис: соберите его из Dockerfile или подтяните из реестра, проверьте health check, получите preview-URL, а затем переведите в production с мгновенным откатом. HTTP-сервисы получают hostname; базы данных и другие TCP-сервисы — адрес, к которому можно подключиться.
Функция — это код, который платформа выполняет внутри собственного образа рантайма, по одному вызову за раз. Приложение — произвольный контейнер, который платформа держит запущенным: без idle-таймаута, с «сырым» reverse proxy (WebSocket и SSE проходят насквозь) и с поддержкой HTTP и чистого TCP — Postgres, Redis, SMTP, SSH. Приложениями управляют на странице Apps, командой inquir apps в CLI или через REST API; словарь везде один и тот же.
Приложения, релизы, preview
- Приложение — описание сервиса: имя, порты, health check, ресурсы, переменные окружения, политика исходящего трафика. Его правка меняет следующий релиз и никогда — уже запущенный.
- Релиз — неизменяемый digest образа плюс замороженная копия runtime-спецификации и env, работающие как один контейнер. Жизненный цикл:
В очереди → Запуск → Health check → Здоров → Обслуживает → Резерв отката → Остановлен, либоОшибка. Релиз, не прошедший health check, никогда не получает трафик. - Preview — каждый новый релиз поднимается на собственном preview-URL (
{app}-{release}.preview.apps.…), чтобы его можно было проверить до того, как его увидит кто-то ещё. Непромоученные preview удаляются через 24 часа. - Promote — направляет production-трафик (production-hostname и все собственные домены) на релиз. Делается вручную и доступно только владельцам и администраторам.
- Откат — снова направляет production на предыдущий релиз. Предыдущий контейнер продолжает работать в течение drain-окна, поэтому пока действует этот резерв, откат мгновенный.
Разверните первый сервис
С установленным и авторизованным CLI HTTP-сервис — это три команды. inquir apps deploy без флагов упаковывает текущий каталог (или --dir), собирает его из Dockerfile на платформе и выпускает релиз из полученного образа; передайте --image, чтобы вместо сборки выпустить готовый образ. Команда ждёт, пока релиз станет здоровым, и печатает его preview-URL.
# 1. Create the application — an HTTP service listening on port 3000 inquir apps create web --port 3000 # 2. Deploy: builds the Dockerfile in the current directory, releases the image # and waits until the health check passes. The release gets a preview URL. inquir apps deploy web # …or release a prebuilt image instead of building inquir apps deploy web --image nginx:alpine # 3. Promote: production traffic moves to this release (owner/admin login) inquir apps promote <releaseId> inquir apps status web # endpoints, hostnames, recent releases inquir apps logs web --tail 100 # stream container logs inquir apps rollback web # back to the previous production release
В дашборде тот же путь: Новое приложение, затем Новый релиз (образ или сборка из исходников) на вкладке «Релизы», затем Promote в строке релиза. В CLI id и имена взаимозаменяемы, а --json принимается везде.
Health check
Новый релиз проходит стартовую проверку здоровья (по умолчанию до 120 с; контейнер инспектируется каждые 2 с, поэтому падение обнаруживается сразу); сервисы с HTTP-портом затем перепроверяются периодически (intervalSeconds, по умолчанию 30 с). Есть три типа проб; по умолчанию используется http, если у приложения есть HTTP-порт, и tcp в остальных случаях.
| Проба | Что доказывает |
|---|---|
http | GET по health-пути (по умолчанию /) отвечает статусом ниже 500 — сервер, отдающий 404 на корень, всё же слушает; нездоровыми считаются только ошибки соединения и 5xx. |
tcp | Порт принимает соединение и ненадолго удерживает его открытым. |
command | Команда, запущенная внутри контейнера, завершается с кодом 0. |
Для баз данных используйте command. Docker принимает соединения на опубликованном порту раньше, чем контейнер реально начинает слушать, поэтому tcp-проба может пройти, пока Postgres ещё инициализируется. --health-command "pg_isready -h 127.0.0.1" (или redis-cli ping) спрашивает сам сервис. В API это runtime.healthcheck: { type, path | command, port?, intervalSeconds, timeoutSeconds }.
Preview-URL и production-URL
У приложения с HTTP-портом есть один production-hostname ({app}.apps.… плюс собственные домены), который всегда следует за промоученным релизом, а у каждого релиза — свой preview-URL, пока его не промоутят или не остановят. TLS для обоих терминируется на границе платформы. У чисто TCP-сервиса URL нет вовсе — у него есть эндпоинты (host:port на каждый публичный TCP-порт), которые показывают вкладка «Релизы» и inquir apps status.
Promote, откат, остановка
Promote — осознанный шаг с подтверждением: UI и CLI заранее сообщают, что произойдёт:
- Если у приложения есть переменные только для production, контейнер пересоздаётся под production-профилем (с проверкой здоровья) до переключения трафика, поэтому preview никогда не несёт production-учётные данные.
- Preview-URL релиза отзывается, как только он начинает обслуживать production; production-hostname и все подтверждённые собственные домены начинают маршрутизировать на него.
- Предыдущий production-релиз продолжает работать в течение drain-окна (по умолчанию 5 мин,
drainGraceSecondsна релиз) как резерв для мгновенного отката. Текущие запросы и WebSocket на нём завершаются на своих сокетах.
Откат (inquir apps rollback <app>, POST /v1/apps/:id/rollback) возвращает production на предыдущий релиз: мгновенно (200), пока его контейнер ещё в резерве, иначе он сначала перезапускается из собственного замороженного образа (202). Откат сам по себе является promote, поэтому к релизу, с которого вы ушли, применяется то же drain-окно.
Остановка (inquir apps stop <release>, DELETE /v1/releases/:id) останавливает любой релиз, не обслуживающий production, — как preview, так и вытесненные релизы; для маршрутизируемого релиза ответ 409. Так освобождают закреплённый host-порт или место в квоте живых релизов. Архивация (inquir apps delete) останавливает все релизы и отвязывает все hostname; после неё имя снова свободно.
TCP-порты
Порты помимо основного HTTP-порта объявляются флагом --tcp name:port[:public][:hostPort] (или в runtime.ports[] с protocol: tcp). TCP-порт публикуется на host-порту, и к нему подключаются напрямую — без runtime-адаптера и без предположения об HTTP, — поэтому база данных или SSH-сервер становятся полноценным сервисом:
# A database: no HTTP port, one public TCP port pinned to host port 25432, # health-checked by asking the service itself (not by a TCP connect) inquir apps create db \ --tcp postgres:5432:public:25432 \ --health-command "pg_isready -h 127.0.0.1" \ --set POSTGRES_PASSWORD=secret inquir apps deploy db --image postgres:16-alpine # inquir apps status db -> TCP · tcp.inquir.org:25432 (postgres) psql "postgres://postgres:secret@tcp.inquir.org:25432/postgres"
publicпубликует порт за пределы loopback и даёт релизу эндпоинт видаtcp.inquir.org:25432. Без него порт привязан к loopback на хосте (полезно только для отладки). Публичный TCP-порт доступен любому, кто может достучаться до хоста — платформа не терминирует TLS и не проверяет учётные данные на чистом TCP, поэтому сервис должен сам аутентифицировать клиентов.hostPortзакрепляет опубликованный порт, чтобы строки подключения переживали перезапуск; незакреплённый порт переназначается при каждом пересоздании контейнера. Закрепление должно попадать в диапазон портов платформы (по умолчанию20000–39999; форма создания показывает актуальный диапазон) и является резервированием на весь хост: второй живой релиз с тем же портом получит409, пока держатель не остановлен.- HTTP-порты никогда не публикуются отдельно — они обслуживаются через hostname (TLS, маршрутизация по Host, жизненный цикл релиза), поэтому
publicиhostPortдля HTTP-порта отклоняются. У приложения может вообще не быть HTTP-порта.
Собственные домены
Помимо сгенерированного hostname, приложение доступно по любому имени, которое вы к нему привяжете — на вкладке Домены или из CLI. Новый hostname сначала не подтверждён: он не маршрутизируется и сертификат не выпускается, пока вы не докажете владение TXT-записью _inquir-verify.<host> с выданным токеном плюс CNAME на domains.inquir.org (или A/ALIAS на ingress). Затем подтвердите:
# Bind the hostname — it starts unverified and prints the DNS records to publish inquir apps domain shop shop.example.com # TXT _inquir-verify.shop.example.com -> <token> # CNAME shop.example.com -> domains.inquir.org # Once DNS has propagated, prove ownership: the hostname becomes routable and gets TLS inquir apps domain shop shop.example.com --verify inquir apps domain shop # list bound hostnames inquir apps domain shop shop.example.com --remove # unbind
Собственные домены — всегда production-hostname и следуют за промоученным релизом с момента подтверждения; добавить домен можно как до, так и после первого promote. Имена, принадлежащие платформе (суффиксы apps/preview, хосты маркетинга и дашборда, hostname, уже занятые собственным доменом шлюза), отклоняются. Изменения доменов переключают production-трафик, поэтому требуют роли владельца или администратора.
Переменные окружения
Задавайте переменные на приложении (--set KEY=VALUE, раздел Переменные в настройках или envVars в API). Значения шифруются при хранении, маскируются при каждом чтении и замораживаются в каждом релизе при его создании — изменение переменной влияет на следующий релиз, а не на запущенный. Чтобы подхватить новые значения, задеплойте заново («Deploy again»).
Ключи из productionOnlyEnvKeys не передаются релизу, пока он работает под профилем preview, и внедряются только при promote (контейнер сначала пересоздаётся под production-профилем). Используйте это для production-учётных данных, чтобы preview-URL, переданный коллеге или агенту, не мог их нести. PORT подставляется автоматически из основного HTTP-порта.
Исходящий трафик
--egress none|full (networkPolicy.egress) определяет, может ли контейнер вообще открывать исходящие соединения. none помещает его в изолированную сеть без маршрута наружу — правильно для базы данных, к которой только подключаются, и неправильно для всего, что вызывает внешние API. Списки разрешённых хостов доступны для функций, но пока не для приложений; API отвечает 400 на allowlist, вместо того чтобы принять политику, которую релиз не сможет соблюсти.
Квоты и значения по умолчанию
У каждого рабочего пространства есть потолок по числу живых релизов и по ресурсам на релиз; форма создания и GET /v1/apps/limits показывают действующие значения. Значения платформы по умолчанию:
| Лимит | По умолчанию |
|---|---|
| Живых релизов на рабочее пространство (preview + production + резерв) | 10 |
| Память на релиз | 512 MB (потолок 2,048 MB) |
| CPU на релиз | 0.5 vCPU (потолок 2 vCPU) |
| Дедлайн стартовой проверки здоровья | 120 s |
| Drain-окно (резерв отката) | 5 min |
| Время жизни preview-релиза | 24 h |
| Перезапусков до пометки релиза как упавшего (crash loop) | 5 |
Роли и API-ключи
Любой участник рабочего пространства — включая роль developer — может создавать приложения, деплоить релизы, смотреть логи, останавливать не-production релизы и менять настройки. Promote, откат, собственные домены, архивация и exec требуют владельца или администратора, вошедшего в систему (сессия в браузере или personal access token, выданный inquir login). Действия, недоступные текущей роли, показываются отключёнными с указанием причины.
API-ключи рабочего пространства не могут делать promote, какой бы широкой ни была их роль: promote и exec требуют пользовательской идентичности. Это сделано намеренно — ключ CI или агента со скоупом deploy может собирать и выкатывать preview, читать их статус и логи, но не может переключать production-трафик или читать секреты, которые держит живой контейнер.
REST API
Всё, что делают CLI и дашборд, идёт через /v1/apps…. Долгие операции отвечают 202 и дают URL статуса: строка релиза несёт «голую» ссылку на образ, пока digest не закреплён, затем imageDigest меняется на sha256:….
| Эндпоинт | Назначение |
|---|---|
POST /v1/apps, GET /v1/apps | Создать приложение (значения env маскируются при каждом чтении) / список с курсорной пагинацией. |
GET /v1/apps/:id, PATCH /v1/apps/:id, DELETE /v1/apps/:id | Прочитать (production-указатель, URL, preview, домены, последние релизы) / обновить — маскированные значения env сливаются, а не перезаписываются / архивировать. |
POST /v1/apps/:id/releases | Создать релиз из {buildId} или {imageRef, runtime?} → 202; pull и проверка здоровья идут в фоне. |
GET /v1/releases/:id, GET /v1/releases/:id/logs | Проекция статуса релиза / логи контейнера по SSE. |
POST /v1/releases/:id/promote, POST /v1/apps/:id/rollback | Перевести production-трафик на релиз / вернуть на предыдущий (200 мгновенно, 202 с перезапуском). Только вход владельца/администратора. |
DELETE /v1/releases/:id | Остановить любой не-production релиз; маршрутизируемый отвечает 409. |
POST /v1/releases/:id/exec | Выполнить команду внутри живого контейнера (вывод ограничен 1 MiB; по таймауту дерево процессов убивается). Только вход владельца/администратора. |
POST /v1/apps/:id/domains, POST …/domains/:hostname/verify | Привязать собственный hostname (не подтверждён, возвращает TXT-запись) / проверка владения через DNS → маршрутизация + TLS. |
POST /v1/apps/:id/builds, GET /v1/builds/:id/logs | Собрать образ из снимка исходников или base64-zip → 202 / стрим логов сборки; POST /v1/builds/:id/cancel снимает сборку из очереди. |
GET /v1/apps/limits | Квоты, значения по умолчанию и диапазон host-портов, действующие в этом рабочем пространстве. |
# Create an application (API key or login) curl -X POST "https://api.inquir.org/v1/apps" \ -H "Authorization: Bearer $INQUIR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name":"web","runtime":{"port":3000}}' # Release a prebuilt image — 202; the pull and the health gate run in the background curl -X POST "https://api.inquir.org/v1/apps/{appId}/releases" \ -H "Authorization: Bearer $INQUIR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"imageRef":"nginx:alpine"}' # -> { "id": "…", "status": "PENDING" } poll GET /v1/releases/{releaseId} # Promote — needs a user session or personal access token; API keys are refused curl -X POST "https://api.inquir.org/v1/releases/{releaseId}/promote" \ -H "Authorization: Bearer $INQUIR_PAT" # -> { "productionUrls": ["https://web.apps.inquir.org"], "previousReleaseId": "…" }
Ограничения
- Одна реплика на релиз. Перезапуск — восстановление после падения или пересоздание, которое promote выполняет при удержанных production-переменных, — означает короткое окно
503на hostname этого релиза. На production-hostname ничего не переключается, пока новый контейнер не станет здоровым. - Нет постоянных томов. Файловая система контейнера живёт столько же, сколько контейнер: релиз базы данных теряет данные при пересоздании контейнера (перезапуск после падения, повторный деплой, перезапуск при откате). Используйте приложения для stateless-сервисов и dev/test-баз, а долговременные данные храните в управляемом хранилище.
- Нет автомасштабирования. Ресурсы фиксированы на релиз (
--memory,--cpu); масштабируйтесь, промоутя релиз с бо́льшим потолком.