In this tutorial you take a small HTTP service from a folder on your laptop to a production URL with its own domain and a Postgres next to it. Along the way you meet everything the Applications plane does for you: the build, the health check, preview releases, promotion, variables, volumes and rollback. Nothing here needs Kubernetes, a registry account or a YAML file.
1. Write the service#
Any program that listens on a TCP port can be an application. The only contract is: read the port from the PORT environment variable and answer HTTP on it. Create a folder, run npm init -y, and save this as server.js:
const http = require('node:http'); // The platform tells the container which port to listen on. const port = Number(process.env.PORT || 3000); http.createServer((req, res) => { if (req.url === '/healthz') { // The health check: anything below 500 means "serving". res.writeHead(200, { 'content-type': 'text/plain' }); res.end('ok'); return; } res.writeHead(200, { 'content-type': 'application/json' }); res.end(JSON.stringify({ hello: 'world', at: new Date().toISOString() })); }).listen(port, () => console.log(`listening on ${port}`));
The /healthz route is not decoration. After every deploy the platform polls a health check and only then routes traffic to the new release. A dedicated endpoint that answers quickly, without touching the database, makes deploys boring in the best sense. Next to the server put a Dockerfile:
FROM node:22-alpine WORKDIR /app COPY package*.json ./ RUN npm ci --omit=dev COPY . . ENV NODE_ENV=production EXPOSE 3000 CMD ["node", "server.js"]
That is the whole project: two files plus package.json. If you already have a service with its own Dockerfile, use it as is. Builds run on the platform, so the Dockerfile may be as elaborate as you like, including multi-stage builds.
2. Create the application#
An application is a named slot that releases land in. Create it once, with the port your program listens on and the path of the health check. With the CLI it is one line, shown in the next step together with the deploy. Ports, health checks and resource limits can be changed later.
In the console open Projects, pick a project or create one with New project, and press Create on its canvas. Choose Empty service if you will push a build from your machine, or Docker image to run an image that already exists; GitHub repository builds from a repo and is in early access, on request. Give it a name and the port, and the service appears on the canvas with a panel on the right for everything that follows.
3. Deploy#
Run the commands below inside the project folder. The deploy packs the folder, sends it to the builders, builds the Dockerfile, starts a container for the new release and waits for the health check.
# 1. The application: an HTTP service on port 3000, health-checked at /healthz inquir apps create web --port 3000 --health-path /healthz # 2. Build the Dockerfile in this directory on the platform's builders and # release the image. The command waits for the health check to pass and # prints the preview URL of the new release. inquir apps deploy web # 3. Look around inquir apps status web # releases, endpoints, hostnames inquir apps logs web --tail 100 # container logs # 4. Take production (owner or admin), then give it a public domain inquir apps promote <releaseId> inquir apps update web --ingress public
Watch the log as it goes: snapshot of your files, build with the Docker output, start, health, and the result. Every deploy creates a new immutable release; the previous ones stay around, which is what makes rollback instant later.
In the console the same thing lives under the Deploy button in the service panel: New release takes an image reference and Build from source uploads a folder; Deploy from GitHub builds a branch where GitHub deploys are enabled (early access, on request). The Deployments tab lists every release with its build log.
4. Check the release#
The release runs next to production but, like every new application, it is private: it has no public address yet. Check it from the terminal: inquir apps logs web --tail 100 shows the container output, and inquir apps status web shows releases, endpoints and hostnames. The Deployments tab in the service panel shows the same, with the logs of each release. Once the application has a public address (step 5), new releases also come up on a preview URL before you promote them, so you can click around, run smoke tests or share the link with a teammate first.
5. Promote to production#
A release becomes production when you promote it. Take the release id from the deploy output or from inquir apps status web and run inquir apps promote <releaseId>. Traffic on the production address switches to the new container; the previous release stays available for rollback. Promotion is limited to workspace owners and admins on purpose, so a preview cannot turn into production by accident.
Every application has a private address inside the workspace, web.apps.internal, that only your other applications and functions can reach. Public exposure is opt-in: inquir apps update web --ingress public gives it a public hostname with TLS. Keep internal APIs private and open only what users need.
In the console: Deployments tab, pick the release, Promote. Public access is under Settings → Networking, where Generate address issues a public hostname on the spot. If you want every successful deploy to go straight to production, deploy with --promote.
6. Configuration and secrets#
Configuration lives in variables, not in the image. Open the Variables tab of the service, add a key such as LOG_LEVEL=debug and press Save & redeploy: a new release starts with the new environment, passes the health check and takes over. Values are stored encrypted and never end up in build logs.
From the terminal, pass --set KEY=VALUE when creating or updating the application (inquir apps create, inquir apps update). Variables that only differ between preview and production are best kept out of the image as well: the same release then runs identically everywhere, which is exactly why rollback is safe.
7. Add a database#
Databases are applications too, created from templates that already have a persistent volume and a private TCP endpoint. Create a Postgres (the template starts its first release right away) and connect it to the service:
# A Postgres from the template: a persistent volume and a private TCP endpoint. # The template starts its first release by itself; there is nothing to deploy. inquir apps create db --template postgres # Wire it into the service: DATABASE_URL lands in web's variables inquir apps connect db web inquir apps deploy web # the next release picks the variable up # Later on inquir apps backups db # backups and the plan's schedule, where available inquir apps domain web shop.example.com # bind your hostname, prints the DNS records inquir apps domain web shop.example.com --verify # once the records are live: routed, TLS issued inquir apps rollback web # back to the previous production release
The connect command puts a ready DATABASE_URL into the service variables; the next release picks it up. On the canvas the two services are now joined by an edge, and the database panel shows the private address, credentials and the Backups tab. The platform backs the database's volume up on a schedule, every 6 hours today, as a crash-consistent snapshot taken during a brief freeze; inquir apps backups db lists the snapshots, and a restore is one click. An application whose volumes hold more than 2 GB in total is not included yet; keep your own copy of that data. The Node.js and Postgres tutorial shows how to keep risky schema changes safe in its backups section.
8. Your own domain#
Bind a hostname with inquir apps domain web shop.example.com. The command prints the DNS records to create at your registrar: a CNAME for the hostname and a TXT record proving you own it. Once they are saved at your DNS provider, run the same command with --verify. It asks your domain's own nameservers, so there is no waiting for public DNS caches (some providers take a few minutes to publish a change); routing switches and a certificate is issued automatically. The same lives in Settings → Custom domains, with the records shown next to the status.
9. When something goes wrong#
A bad release is undone with inquir apps rollback web or Roll back in the Deployments tab: production points at the previous release again within seconds, no rebuild needed. A few things to check when a deploy does not go through:
- Health check never green. The container starts but the platform cannot reach the port. Make sure the program listens on
0.0.0.0and onprocess.env.PORT, not a hard-coded value, and that the health path returns before any slow initialisation. - Build fails. The build log in the Deployments tab contains the full Docker output. Most failures are files missing from the snapshot (check
.dockerignore) or a dev dependency that the production install skipped. - Works on the preview, 404 on the domain. The application is still private. Open public access in Settings → Networking or with
--ingress public, then check that the DNS records are verified. - Crash on start with a missing variable. Variables added after a deploy only reach the next release. Press Save & redeploy, or run
inquir apps redeploy web.
Where to go next#
The Applications reference covers TCP endpoints, the console, resource limits and the API behind every command. The CLI page lists all flags. And if part of your system is better off as event-driven code without a server, the next tutorial writes and deploys a function and puts it behind the same gateway.