DEV Community

Cover image for I'm building Inquir Compute. Here's how deployment works.
Aleksandr Kubarskii
Aleksandr Kubarskii

Posted on

I'm building Inquir Compute. Here's how deployment works.

I'm building Inquir Compute, a cloud platform for apps, databases, and background jobs.

The kind of setup I have in mind is pretty common: an API, PostgreSQL, and maybe a worker. I can put those on a VPS, but then I'm also setting up HTTPS, collecting logs, and maintaining deployment scripts. For a small project, I'd rather spend that time on the app.

Inquir lets you deploy from a GitHub repository, a Dockerfile, or an existing container image. The app can stay a regular HTTP server. You don't have to rewrite it around a platform-specific handler.

Here's how deployment works with a small Node.js app. We'll deploy it, check a second version on a preview URL, and then roll back.

The app

You'll need Node.js 22 or later for the CLI and an Inquir account with available resources. Use a workspace where you're the owner or an admin so you can promote releases. There's a note about account access at the end.

Create a directory:

mkdir inquir-demo
cd inquir-demo
Enter fullscreen mode Exit fullscreen mode

Add server.mjs:

import { createServer } from 'node:http';

const port = Number(process.env.PORT ?? 3000);
if (!Number.isInteger(port) || port < 1 || port > 65535) {
  throw new Error('PORT must be an integer between 1 and 65535');
}

const version = 'v1';
const server = createServer((req, res) => {
  if (req.method === 'GET' && req.url === '/healthz') {
    res.writeHead(200, { 'content-type': 'text/plain; charset=utf-8' });
    return res.end('ok');
  }

  if (req.method === 'GET' && req.url === '/') {
    res.writeHead(200, { 'content-type': 'application/json' });
    return res.end(JSON.stringify({ message: 'Hello from Inquir', version }));
  }

  res.writeHead(404, { 'content-type': 'text/plain; charset=utf-8' });
  res.end('Not found');
});

server.on('error', (error) => {
  console.error(error);
  process.exit(1);
});
server.listen(port, '0.0.0.0', () => console.log(`Listening on ${port}`));

for (const signal of ['SIGTERM', 'SIGINT']) {
  process.once(signal, () => {
    server.close(() => process.exit(0));
    setTimeout(() => process.exit(1), 5000).unref();
  });
}
Enter fullscreen mode Exit fullscreen mode

The app returns its version at / and responds to health checks at /healthz. It listens on 0.0.0.0, reads PORT from the environment, and handles shutdown signals. There are no external dependencies.

Add a Dockerfile:

FROM node:22-alpine
WORKDIR /app
ENV NODE_ENV=production
COPY --chown=node:node server.mjs ./server.mjs
USER node
EXPOSE 3000
CMD ["node", "server.mjs"]
Enter fullscreen mode Exit fullscreen mode

To try it locally:

node server.mjs
Enter fullscreen mode Exit fullscreen mode

Then, in another terminal:

curl http://localhost:3000/healthz
curl http://localhost:3000/
Enter fullscreen mode Exit fullscreen mode

You should get ok from the health endpoint and JSON containing "version": "v1" from /.

Deploying it

Install the CLI and log in:

npm install -g @inquir/compute-cli
inquir login
inquir workspaces
inquir use WORKSPACE_SLUG
Enter fullscreen mode Exit fullscreen mode

Replace WORKSPACE_SLUG with the slug or ID shown by inquir workspaces.

Create a project and select it:

inquir projects create devto-demo --slug devto-demo
inquir projects use devto-demo
Enter fullscreen mode Exit fullscreen mode

Then create the app and deploy it from the directory containing the Dockerfile:

inquir apps create web --port 3000 --health-path /healthz
inquir apps deploy web
Enter fullscreen mode Exit fullscreen mode

The build runs on Inquir, so you don't need Docker installed locally. Once the release starts and passes its health check, you get a preview URL.

Open it and check that the app returns v1. To see its status and logs:

inquir apps status web
inquir apps logs web --tail 100
Enter fullscreen mode Exit fullscreen mode

When you're happy with the preview, promote the release and make the app public:

inquir apps promote RELEASE_ID
inquir apps update web --ingress public
inquir apps status web
Enter fullscreen mode Exit fullscreen mode

Use the release ID from the deployment or status output for RELEASE_ID. It isn't the app name. The production URL will appear in the status output too.

Promotion and public access are separate settings. You can keep a service internal even after promoting its release.

Deploying a second version

Change this line in server.mjs:

const version = 'v1';
Enter fullscreen mode Exit fullscreen mode

to:

const version = 'v2';
Enter fullscreen mode Exit fullscreen mode

Deploy again:

inquir apps deploy web
Enter fullscreen mode Exit fullscreen mode

This app has no persistent volume, so the new release can run alongside the old one. The preview URL should return v2. Production still returns v1.

That gives you time to check the actual app before switching traffic. Passing /healthz only tells us the server can respond. It could still be returning the wrong data or failing on another endpoint.

To put the new version into production:

inquir apps promote NEW_RELEASE_ID
Enter fullscreen mode Exit fullscreen mode

Use the ID of the new release here. To go back to the previous one:

inquir apps rollback web
Enter fullscreen mode Exit fullscreen mode

If the previous container is still running on standby, traffic can switch back to it. Otherwise, Inquir has to start it again and wait for the health check.

One important detail: rollback only restores the application release. It won't undo a database migration, recover deleted records, or take back a request you've already sent to another service.

Adding a database or a worker

You can create PostgreSQL from a template with a persistent volume. The inquir apps connect command puts its connection string into the app's environment as DATABASE_URL. You need to deploy a new release for that variable to take effect.

Your code still needs a database driver or ORM. Inquir handles the connection configuration, not your queries or migrations.

A worker can run as a long-lived container in the same project. Functions and pipelines are also available for individual calls, webhooks, and scheduled tasks. You don't have to squeeze a process that needs to stay running into a function.

Current limits

There are a couple of things worth knowing before putting a real project on it.

Writable persistent volumes are tied to a single host. An app using one has one replica, with no automatic volume replication or live migration. Updating it means stopping the old container before starting the new one, so there is downtime. The side-by-side deployment above applies to our stateless example, not to an app writing to a persistent volume.

Container scaling is manual for now. You can set resources and the supported number of replicas, but the platform won't automatically add replicas when traffic increases.

Trying it with your app

I'm looking for developers willing to try a small project and tell me what gets in the way. An API with a database, a background worker, or an internal tool would all be useful to test. Start with something non-critical and try updating it too, not just getting the first version online.

The deployment docs have more detail on the commands above.

What are you using to host your apps now, and what part of the setup would you most like to stop doing yourself?

Top comments (0)