Skip to content
Docs

Deploy recipes: copy, deploy, open

Ready-to-copy recipes for the most common projects. Each one is a Dockerfile, an inquir.yaml and a couple of commands: put the files into your project folder, deploy, open the address. Everything is built on Inquir, so you don't need Docker on your computer. Every recipe on this page was deployed on Inquir Compute exactly as written.

What inquir.yaml says: which port your app listens on, which address answers when the app is up (the health check), and how much CPU and memory it gets. The service is always called web. The port in inquir.yaml must match the port your server or nginx listens on — that is the one thing to keep in sync when you change something.

Pick a recipe:

React site (Vite)#

A React app built with Vite and served by nginx. You get your site at https://my-site-xxxxxx.apps.inquir.org with HTTPS; links like /about open directly, files are compressed and cached by browsers. You need Node.js 20.19+ on your computer only to create the project — the site itself is built on Inquir.

Full example: inquir-examples/static-react-vite on GitHub.

1. Create the project

terminalbash
# Skip this if you already have a React project
npm create vite@9.2.1 my-site -- --template react
cd my-site
npm install

2. Add three files next to package.json

Copy them as they are. The only values you may want to change: the port (8080 in all three files), and the build folder if your project does not build to dist/.

filesbash
my-site/
├── Dockerfile       ← new
├── nginx.conf       ← new
├── inquir.yaml      ← new
├── package.json
├── package-lock.json
├── index.html
└── src/
Dockerfilebash
# Step 1: build the site with Node.js
FROM node:22.23.4-alpine@sha256:fe4f6d74e20370acb04ad889077d14d0f64a9c8ea6a42bc60cddf1e844699914 AS build
WORKDIR /app
COPY package*.json ./
RUN if [ -f package-lock.json ]; then npm ci; else npm install; fi
COPY . .
# Vite writes the finished site to dist/
RUN npm run build

# Step 2: serve the finished files with nginx (runs as a regular user, port 8080)
FROM nginxinc/nginx-unprivileged:1.30.6-alpine@sha256:f5116ed4606f3eb015dd4a8e28cb9b351476dc0ad4bc6e9f93b19cec130cdf03
COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=build /app/dist/ /usr/share/nginx/html/
EXPOSE 8080
nginx.confbash
server {
    listen 8080;                 # the same port as runtime.port in inquir.yaml
    server_name _;
    root /usr/share/nginx/html;
    index index.html;
    absolute_redirect off;

    # Compress text files. gzip_proxied is needed: requests reach nginx through a proxy.
    gzip on;
    gzip_proxied any;
    gzip_vary on;
    gzip_types text/css application/javascript text/javascript application/json image/svg+xml text/plain;

    # Health check: the platform calls it before sending visitors to a new version.
    location = /healthz {
        access_log off;
        default_type text/plain;
        return 200 "ok\n";
    }

    # Files in /assets/ have a hash in their names: browsers may keep them for a year.
    location /assets/ {
        add_header Cache-Control "public, max-age=31536000, immutable";
        try_files $uri =404;
    }

    # Any other path: the file if it exists, otherwise index.html (React Router links work).
    location / {
        add_header Cache-Control "no-cache";
        try_files $uri $uri/ /index.html;
    }
}
inquir.yamlbash
version: 1
services:
  web:                      # always "web"
    runtime:
      port: 8080            # the port nginx listens on (nginx.conf)
      healthcheck:
        type: http
        path: /healthz
      resources:            # plenty for a static site
        cpuVCpus: 0.25
        memoryMb: 128

3. Deploy

From the terminal. Pick any name instead of my-site; it becomes part of the address.

terminalbash
# In the my-site folder
inquir apps create my-site --ingress public   # once: a new app with a public address
inquir apps deploy my-site --promote          # build, start, then send visitors to it

# Build succeeded in 34692ms
# Release ef51e99f is serving
# Production now serves ef51e99f
#   https://my-site-2mk7b4.apps.inquir.org      <- your site

# After every change: the same command
inquir apps deploy my-site --promote

From the console, with GitHub.

  1. Push the project, with the new files, to a GitHub repository.
  2. In the console open Apps, open a project (or create one with New project) and choose Create → GitHub repository. The first time, press Connect GitHub and allow access to the repository.
  3. Pick the repository and set Internal HTTP port to 8080. Leave the branch, the root directory and the Dockerfile path as they are when the files are at the top of the repository.
  4. Press Deploy application. Inquir builds the project and puts it live; every push to that branch deploys again.
  5. Give it a public address: in the app's Settings → Networking → Public access press Generate address.

Open the site. The address is printed at the end of the deploy and shown under Settings → Networking. You should see the Vite + React start page; /healthz answers ok.

Your own domain. Bind it from the terminal (below) or under Settings → Networking, add the two DNS records it shows where you bought the domain, then verify — HTTPS is issued automatically. All the details: Custom domains.

terminalbash
# Prints two DNS records to add where you bought the domain
inquir apps domain my-site www.example.com
# Once the records are live: the site answers on your domain, with HTTPS
inquir apps domain my-site www.example.com --verify

If something doesn't work

  • The build fails at npm ci. package-lock.json doesn't match package.json: run npm install on your computer and deploy again. When a build fails, the command prints how to open its log.
  • The deploy waits and then fails the health check. The port must be the same everywhere: listen in nginx.conf, port in inquir.yaml and, with GitHub, Internal HTTP port.
  • A page opens from the menu but gives 404 after a refresh. The last block of nginx.conf (try_files … /index.html) is missing or was changed.
  • The address doesn't open. An app is private until it gets a public address: inquir apps update <app> --ingress public, or Generate address in Settings → Networking.
More details

Caching. Vite puts JS and CSS into /assets/ with a hash in every file name, so browsers may keep them for a year. index.html is sent with no-cache, so visitors get the new version right after a deploy.

Compression. Keep gzip_proxied any: requests reach nginx through the platform's proxy, and without that line nginx sends files uncompressed.

Create React App. The same files; in the Dockerfile change /app/dist/ to /app/build/, and in nginx.conf change /assets/ to /static/.

Next.js as a static site. Add output: 'export' to next.config, change /app/dist/ to /app/out/ in the Dockerfile, and in nginx.conf change /assets/ to /_next/static/ and the last try_files to $uri $uri.html $uri/ /index.html. A Next.js app with server rendering or API routes is not a static site: it runs as a Node.js server, like the Node.js API recipe.

Settings baked in at build time (VITE_*). Vite writes VITE_* values into the files during the build, so variables set on the running app don't reach them. Put them under build.args in inquir.yaml and pass them through the Dockerfile:

inquir.yaml + Dockerfilebash
# inquir.yaml: values baked into the site at build time
version: 1
services:
  web:
    build:
      args:
        VITE_API_URL: https://api.example.com
    runtime:
      port: 8080
      healthcheck: { type: http, path: /healthz }

# Dockerfile, in step 1 before "RUN npm run build":
ARG VITE_API_URL
ENV VITE_API_URL=$VITE_API_URL

Static HTML site#

The shortest way to put HTML, CSS and JavaScript files online: nginx serves your public/ folder. You need nothing but the files.

Full example: inquir-examples/static-html on GitHub.

1. Put your site into public/ and add two files

Change nothing unless you rename the public/ folder (then change it in the Dockerfile too).

filesbash
my-page/
├── Dockerfile
├── inquir.yaml
└── public/          ← your site: index.html, CSS, images, JS
    ├── index.html
    └── style.css
Dockerfilebash
# nginx that runs as a regular user and listens on port 8080
FROM nginxinc/nginx-unprivileged:1.30.6-alpine@sha256:f5116ed4606f3eb015dd4a8e28cb9b351476dc0ad4bc6e9f93b19cec130cdf03
# Your site: everything in the public/ folder
COPY public/ /usr/share/nginx/html/
EXPOSE 8080
inquir.yamlbash
version: 1
services:
  web:                      # always "web"
    runtime:
      port: 8080            # nginx listens here
      healthcheck:
        type: http
        path: /             # the home page must answer
      resources:
        cpuVCpus: 0.25
        memoryMb: 128
public/index.htmlbash
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>My site</title>
    <link rel="stylesheet" href="/style.css" />
  </head>
  <body>
    <h1>Hello from Inquir Compute</h1>
    <p>Edit public/index.html and deploy again.</p>
  </body>
</html>

2. Deploy

From the terminal. Pick any name instead of my-site; it becomes part of the address.

terminalbash
# In the my-page folder
inquir apps create my-page --ingress public
inquir apps deploy my-page --promote
#   https://my-page-6w79j4.apps.inquir.org      <- your site

From the console, without GitHub.

  1. In the console open Apps and press Create in browser. Enter an Application name and press Create application: an editor opens in the browser.
  2. Create the files from this recipe in the editor and paste their contents.
  3. Press Deploy in the bottom panel. Inquir builds the files and puts them live.
  4. Give it a public address: in the app's Settings → Networking → Public access press Generate address.

Open the site at the printed address. To change it, edit the files and deploy again.

Your own domain. Bind it from the terminal (below) or under Settings → Networking, add the two DNS records it shows where you bought the domain, then verify — HTTPS is issued automatically. All the details: Custom domains.

If something doesn't work

  • The deploy fails the health check. The health check opens the home page, so public/index.html must exist.
  • One page gives 404. File names are case-sensitive on the server: About.html and about.html are different files.
  • The address doesn't open. An app is private until it gets a public address: inquir apps update <app> --ingress public, or Generate address in Settings → Networking.
More details

Need compression, long-term caching, or a single-page app with its own routes? Take nginx.conf from the React recipe, add COPY nginx.conf /etc/nginx/conf.d/default.conf to the Dockerfile and set the health check path to /healthz.

Node.js API (Express)#

An Express API on the port Inquir gives it in the PORT variable. You get https://my-api-xxxxxx.apps.inquir.org/api/hello. Fastify or Hono work the same way: keep the Dockerfile and change CMD.

Full example: inquir-examples/node-api on GitHub.

1. The project: four files

Run npm install once, so a package-lock.json appears and every build installs the same versions. The port is set in inquir.yaml — Inquir passes it to the app in PORT; the 3000 in server.js is only the default for running it on your computer.

package.jsonjson
{
  "name": "my-api",
  "private": true,
  "type": "module",
  "scripts": {
    "start": "node server.js"
  },
  "dependencies": {
    "express": "^5.2.1"
  }
}
server.jsjs
import express from 'express';

const app = express();
app.use(express.json());

// Health check: the platform calls it before sending traffic to a new version.
app.get('/healthz', (req, res) => res.send('ok'));

app.get('/api/hello', (req, res) => {
  res.json({ message: 'Hello from Inquir Compute', time: new Date().toISOString() });
});

// The platform sets PORT to runtime.port from inquir.yaml.
const port = Number(process.env.PORT) || 3000;
const server = app.listen(port, () => console.log(`listening on ${port}`));

// Finish open requests before a new version replaces this one.
process.on('SIGTERM', () => server.close(() => process.exit(0)));
Dockerfilebash
FROM node:22.23.4-alpine@sha256:fe4f6d74e20370acb04ad889077d14d0f64a9c8ea6a42bc60cddf1e844699914
WORKDIR /app
ENV NODE_ENV=production
COPY package*.json ./
RUN if [ -f package-lock.json ]; then npm ci --omit=dev; else npm install --omit=dev; fi
COPY . .
# Run as the regular "node" user that the image ships with
USER node
EXPOSE 3000
CMD ["node", "server.js"]
inquir.yamlbash
version: 1
services:
  web:                      # always "web"
    runtime:
      port: 3000            # the port server.js listens on
      healthcheck:
        type: http
        path: /healthz
      resources:
        cpuVCpus: 0.25
        memoryMb: 256

2. Deploy and call it

terminalbash
# In the my-api folder (npm install once, so package-lock.json exists)
inquir apps create my-api --ingress public
inquir apps deploy my-api --promote

curl https://my-api-88n5um.apps.inquir.org/api/hello
# {"message":"Hello from Inquir Compute","time":"2026-10-22T18:16:37.443Z"}

From the console: the same steps as for the React site — GitHub with Internal HTTP port 3000, or Create in browser with these files.

If something doesn't work

  • The deploy fails the health check. The server must listen on PORT on all addresses: app.listen(port), not app.listen(port, 'localhost').
  • The app starts and stops. Read what it printed: inquir apps logs <app> --tail 100. A missing package means it is not in the dependencies file.
  • The address doesn't open. An app is private until it gets a public address: inquir apps update <app> --ingress public, or Generate address in Settings → Networking.

Python API (FastAPI)#

A FastAPI app served by uvicorn. You get https://my-api-xxxxxx.apps.inquir.org/api/hello, and the interactive API docs at /docs.

Full example: inquir-examples/python-fastapi on GitHub.

1. The project: four files

Add new packages to requirements.txt with a fixed version. The port is set in inquir.yaml; the Dockerfile reads it from PORT.

requirements.txtbash
fastapi==0.141.1
uvicorn[standard]==0.54.0
main.pypython
from datetime import datetime, timezone

from fastapi import FastAPI

app = FastAPI()


# Health check: the platform calls it before sending traffic to a new version.
@app.get("/healthz")
def healthz():
    return {"ok": True}


@app.get("/api/hello")
def hello():
    return {"message": "Hello from Inquir Compute", "time": datetime.now(timezone.utc).isoformat()}
Dockerfilebash
FROM python:3.12.15-slim@sha256:cb33f7f771ecec2ef1225cf39b159591a040391bd73e4c01f00fdfc4c47611d2
ENV PYTHONUNBUFFERED=1 PIP_NO_CACHE_DIR=1 PIP_DISABLE_PIP_VERSION_CHECK=1
WORKDIR /app
COPY requirements.txt ./
RUN pip install -r requirements.txt
COPY . .
# Run as a regular user, not root
RUN useradd --create-home --uid 10001 app
USER app
EXPOSE 8000
# The platform sets PORT to runtime.port from inquir.yaml
CMD ["sh", "-c", "exec uvicorn main:app --host 0.0.0.0 --port ${PORT:-8000} --proxy-headers --forwarded-allow-ips='*'"]
inquir.yamlbash
version: 1
services:
  web:                      # always "web"
    runtime:
      port: 8000            # the port uvicorn listens on
      healthcheck:
        type: http
        path: /healthz
      resources:
        cpuVCpus: 0.25
        memoryMb: 256

2. Deploy and call it

terminalbash
# In the my-api folder
inquir apps create my-api --ingress public
inquir apps deploy my-api --promote

curl https://my-api-uqprbk.apps.inquir.org/api/hello
# {"message":"Hello from Inquir Compute","time":"2026-10-22T18:16:43.033814+00:00"}

From the console: the same steps as for the React site — GitHub with Internal HTTP port 8000, or Create in browser with these files.

If something doesn't work

  • The deploy fails the health check. uvicorn must listen on --host 0.0.0.0, as in the Dockerfile; the default 127.0.0.1 is unreachable from outside the container.
  • The app starts and stops. Read what it printed: inquir apps logs <app> --tail 100. A missing package means it is not in the dependencies file.
  • The address doesn't open. An app is private until it gets a public address: inquir apps update <app> --ingress public, or Generate address in Settings → Networking.
More details

Flask or Django. Keep the Dockerfile and change the last line to gunicorn: CMD ["sh", "-c", "exec gunicorn app:app --bind 0.0.0.0:${PORT:-8000}"] (Django: myproject.wsgi instead of app:app), with gunicorn in requirements.txt.

Add a Postgres database#

Any of the API recipes gets a Postgres database in a few steps. The database is private: only your apps can connect to it, and its password never shows up in your terminal. The free trial has room for one database.

1. Use DATABASE_URL in your code

Add the driver and an endpoint that reads the address from the DATABASE_URL variable:

server.jsjs
// npm install pg
import pg from 'pg';

const db = new pg.Pool({ connectionString: process.env.DATABASE_URL, max: 5 });

app.get('/api/db', async (req, res) => {
  const { rows } = await db.query('select now() as now, version() as version');
  res.json(rows[0]);
});
main.pypython
# requirements.txt: psycopg[binary]==3.3.6
import os

import psycopg


@app.get("/api/db")
def db_time():
    with psycopg.connect(os.environ["DATABASE_URL"]) as conn:
        now, version = conn.execute("select now(), version()").fetchone()
    return {"now": now.isoformat(), "version": version}

2. Create the database and connect it to the app

terminalbash
# A Postgres 16 database with its own disk, in the same project as my-api
# (inquir apps create my-api printed the project's name)
inquir apps create my-db --template postgres --project <project>

# Hand my-api the address: DATABASE_URL appears in its variables.
# "no verified private address yet"? Wait a minute and run it again.
inquir apps connect my-db my-api

3. Deploy the new code

terminalbash
# In the my-api folder: the new code picks DATABASE_URL up
inquir apps deploy my-api --promote

curl https://my-api-88n5um.apps.inquir.org/api/db
# {"now":"2026-10-22T18:21:23.413Z","version":"PostgreSQL 16.15 on x86_64-pc-linux-musl, ..."}

# Later, after changing only a variable (not the code): inquir apps redeploy my-api

In the console: on the project canvas choose Create → Database, type postgres, and press Create database. When it is running, open its Database tab, press Connect and copy the Private connection string (Inside the Inquir network). Then open your API, add DATABASE_URL with that value on the Variables tab, save, and deploy the new code (a push to GitHub, or Redeploy when the code is already there).

If something doesn't work

  • connect answers "no verified private address yet". The database is still starting: wait a minute and run the command again.

Next: the step-by-step tutorial explains previews, promotion and rollback; Container applications covers everything inquir.yaml and the console can do.