Run any OCI image as a long-lived service: build it from a Dockerfile or pull it from a registry, health-check it, give it a preview URL, then promote it to production with instant rollback. HTTP services get a hostname; databases and other TCP services get a dialable endpoint.
A function is code the platform runs inside its own runtime image, one invocation at a time. An application is an arbitrary container the platform keeps running: no idle timeout, raw reverse proxy (WebSockets and SSE pass through), and HTTP and raw TCP — Postgres, Redis, SMTP, SSH. Manage applications on the Apps page, with inquir apps in the CLI, or through the REST API; the vocabulary is the same everywhere.
Applications, releases, previews
- Application — the service definition: name, ports, health check, resources, env vars, outbound policy. Editing it changes the next release, never a running one.
- Release — an immutable image digest plus a frozen copy of the runtime spec and env, running as one container. Its lifecycle is
Queued → Starting → Health check → Healthy → Serving → Rollback reserve → Stopped, orFailed. A release that fails its health check never receives traffic. - Preview — every new release comes up on its own preview URL (
{app}-{release}.preview.apps.…) so you can test it before anyone else sees it. Previews are reaped after 24 hours unless promoted. - Promote — points production traffic (the production hostname and every custom domain) at a release. Manual, and restricted to owners and admins.
- Roll back — points production at the previous release again. The previous container is kept running for the drain window, so rollback is instant while that reserve lasts.
Deploy your first service
With the CLI installed and logged in, an HTTP service is three commands. inquir apps deploy with no flags packages the current directory (or --dir), builds it from its Dockerfile on the platform, and releases the resulting image; pass --image to release a prebuilt image instead. The command waits until the release is healthy and prints its 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
The dashboard offers the same flow: New application, then New release (image or source build) on the Releases tab, then Promote on the release row. Ids and names are interchangeable in the CLI, and --json is accepted everywhere.
Health checks
A new release is gated behind a startup health check (up to 120 s by default; the container is inspected every 2 s so a crash fails fast); services with an HTTP port are re-probed periodically while they run (intervalSeconds, default 30 s). Three probe types exist; the default is http when the application has an HTTP port and tcp otherwise.
| Probe | What it proves |
|---|---|
http | GET on the health path (default /) answers with a status below 500 — a server that 404s its root is listening; only connection failures and 5xx count as unhealthy. |
tcp | The port accepts a connection and keeps it open briefly. |
command | A command run inside the container exits 0. |
Use command for databases. Docker accepts connections on a published port before the container is actually listening, so a tcp probe can pass while Postgres is still initialising. --health-command "pg_isready -h 127.0.0.1" (or redis-cli ping) asks the service itself. In the API this is runtime.healthcheck: { type, path | command, port?, intervalSeconds, timeoutSeconds }.
Preview URL vs production URL
An application with an HTTP port has one production hostname ({app}.apps.…, plus any custom domains) that always follows the promoted release, and each release has its own preview URL until it is promoted or stopped. TLS is terminated at the edge for both. A pure-TCP service has no URL at all — it has endpoints (host:port per public TCP port) shown on the Releases tab and by inquir apps status.
Promote, roll back, stop
Promotion is a deliberate, confirmed step — the UI and CLI tell you what will happen before it does:
- If the application has production-only env vars, the container is recreated under the production profile (health-gated) before traffic moves, so a preview never carried production credentials.
- The release's preview URL retires once it serves production; the production hostname and every verified custom domain start routing to it.
- The previous production release keeps running for the drain window (5 min by default,
drainGraceSecondsper release) as the instant-rollback reserve. In-flight requests and WebSockets on it finish on their existing sockets.
Roll back (inquir apps rollback <app>, POST /v1/apps/:id/rollback) flips production back to the previous release: instant (200) while its container is still in the reserve, otherwise it is restarted from its own frozen image first (202). Rolling back is itself a promotion, so the same drain window applies to the release you left.
Stop (inquir apps stop <release>, DELETE /v1/releases/:id) stops any release that is not serving production — previews and superseded releases alike; the routed release answers 409. Stopping is how you free a pinned host port or a slot in the live-release quota. Archive (inquir apps delete) stops every release and unbinds every hostname; the name is free again afterwards.
TCP ports
Ports other than the primary HTTP port are declared with --tcp name:port[:public][:hostPort] (or runtime.ports[] with protocol: tcp). A TCP port is published on a host port and dialled directly — no runtime adapter, no HTTP assumption — so a database or SSH server is a first-class service:
# 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"
publicpublishes the port beyond loopback and gives the release an endpoint such astcp.inquir.org:25432. Without it the port binds to loopback on the host (useful for debugging only). A public TCP port is reachable by anyone who can reach the host — the platform terminates no TLS and checks no credentials on raw TCP, so the service must authenticate its own clients.hostPortpins the published port so connection strings survive a restart; an unpinned port is reassigned every time the container is recreated. Pins must fall inside the platform's port range (20000–39999by default; the create form shows the live range) and are a host-wide reservation: a second live release pinning the same port is refused with409until the holder is stopped.- HTTP ports are never published separately — they are served over the hostname (TLS, Host routing, release lifecycle), so
publicandhostPortare rejected on an HTTP port. An application may have no HTTP port at all.
Custom domains
Besides its generated hostname, an application is reachable at any hostname you bind to it — from the Domains tab or the CLI. A new hostname starts unverified: it is not routed and no certificate is issued until you prove ownership with a TXT record _inquir-verify.<host> carrying the printed token, plus a CNAME to domains.inquir.org (or an A/ALIAS to the ingress). Then verify:
# 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
Custom domains are always production hostnames and follow the promoted release from the moment they verify; you can add one before or after the first promote. Platform-owned names (the apps/preview suffixes, the marketing and dashboard hosts, hostnames already claimed by a gateway custom domain) are refused. Domain changes move production traffic, so they need an owner or admin.
Environment variables
Set env vars on the application (--set KEY=VALUE, the Variables section in Settings, or envVars in the API). Values are encrypted at rest, masked on every read, and frozen into each release when it is created — changing a variable affects the next release, not a running one. Redeploy ("Deploy again") to pick up new values.
Keys listed in productionOnlyEnvKeys are withheld from a release while it runs under the preview profile and injected only when it is promoted (the container is recreated under the production profile first). Use it for production credentials, so a preview URL handed to a teammate or an agent cannot carry them. PORT is injected automatically from the primary HTTP port.
Outbound network
--egress none|full (networkPolicy.egress) sets whether the container may open outbound connections at all. none puts it on an isolated network with no route out — right for a database that should only be dialled, wrong for anything that calls an API. Per-host allowlists are available for functions but not yet for applications; the API answers 400 for allowlist here rather than accepting a policy the release could not honour.
Quotas and defaults
Every workspace has a ceiling on live releases and on resources per release; the create form and GET /v1/apps/limits report the values in force. Platform defaults:
| Limit | Default |
|---|---|
| Live releases per workspace (previews + production + reserve) | 10 |
| Memory per release | 512 MB (ceiling 2,048 MB) |
| CPU per release | 0.5 vCPU (ceiling 2 vCPU) |
| Startup health-check deadline | 120 s |
| Drain window (rollback reserve) | 5 min |
| Preview release lifetime | 24 h |
| Restarts before a release is marked failed (crash loop) | 5 |
Roles and API keys
Any workspace member — including the developer role — can create applications, deploy releases, watch logs, stop non-production releases and edit settings. Promote, roll back, custom domains, archive and exec need an owner or admin who is logged in (a browser session or a personal access token minted by inquir login). Actions the current role cannot perform are shown disabled with the reason.
Workspace API keys cannot promote, however permissive their role: promotion and exec require a user identity. That is deliberate — a CI or agent key with the deploy scope can build and ship previews and read their status and logs, but cannot move production traffic or read the secrets a live container holds.
REST API
Everything the CLI and dashboard do goes through /v1/apps…. Long operations answer 202 and expose a status URL: a release row carries the bare image ref until the digest is pinned, then imageDigest flips to sha256:….
| Endpoint | Purpose |
|---|---|
POST /v1/apps, GET /v1/apps | Create an application (env values are masked on every read) / list, cursor-paginated. |
GET /v1/apps/:id, PATCH /v1/apps/:id, DELETE /v1/apps/:id | Read (production pointer, URLs, previews, domains, recent releases) / update — masked env values are merged, not overwritten / archive. |
POST /v1/apps/:id/releases | Create a release from {buildId} or {imageRef, runtime?} → 202; the pull and health gate run in the background. |
GET /v1/releases/:id, GET /v1/releases/:id/logs | Release status projection / SSE container logs. |
POST /v1/releases/:id/promote, POST /v1/apps/:id/rollback | Move production traffic to a release / back to the previous one (200 instant, 202 restarting). Owner/admin login only. |
DELETE /v1/releases/:id | Stop any non-production release; the routed one answers 409. |
POST /v1/releases/:id/exec | Run a command inside the live container (output capped at 1 MiB; the timeout kills the process tree). Owner/admin login only. |
POST /v1/apps/:id/domains, POST …/domains/:hostname/verify | Bind a custom hostname (unverified, returns the TXT record) / DNS ownership check → routable + TLS. |
POST /v1/apps/:id/builds, GET /v1/builds/:id/logs | Build an image from a source snapshot or a base64 zip → 202 / stream build logs; POST /v1/builds/:id/cancel drops a queued build. |
GET /v1/apps/limits | Quotas, defaults and the host-port range in force for this workspace. |
# 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": "…" }
Limitations
- One replica per release. A restart — crash recovery, or the recreate that promotion performs when production-only variables are withheld — means a short window of
503on that release's hostnames. Nothing is cut over on the production hostname until the new container is healthy. - No persistent volumes. A container's filesystem lives as long as the container: a database release loses its data when the container is recreated (crash restart, redeploy, rollback restart). Use applications for stateless services and dev/test databases, and keep durable data in a managed store.
- No autoscaling. Resources are fixed per release (
--memory,--cpu); scale by promoting a release with a larger ceiling.