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
# 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/.
my-site/ ├── Dockerfile ← new ├── nginx.conf ← new ├── inquir.yaml ← new ├── package.json ├── package-lock.json ├── index.html └── src/
# 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
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; } }
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.
# 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.
- Push the project, with the new files, to a GitHub repository.
- 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.
- 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. - Press Deploy application. Inquir builds the project and puts it live; every push to that branch deploys again.
- 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.
# 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.jsondoesn't matchpackage.json: runnpm installon 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:
listenin nginx.conf,portin 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: 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).
my-page/ ├── Dockerfile ├── inquir.yaml └── public/ ← your site: index.html, CSS, images, JS ├── index.html └── style.css
# 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
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
<!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.
# 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.
- In the console open Apps and press Create in browser. Enter an Application name and press Create application: an editor opens in the browser.
- Create the files from this recipe in the editor and paste their contents.
- Press Deploy in the bottom panel. Inquir builds the files and puts them live.
- 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.htmlmust exist. - One page gives 404. File names are case-sensitive on the server:
About.htmlandabout.htmlare 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.
{ "name": "my-api", "private": true, "type": "module", "scripts": { "start": "node server.js" }, "dependencies": { "express": "^5.2.1" } }
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)));
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"]
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
# 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
PORTon all addresses:app.listen(port), notapp.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.
fastapi==0.141.1 uvicorn[standard]==0.54.0
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()}
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='*'"]
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
# 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 default127.0.0.1is 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:
// 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]); });
# 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
# 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
# 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.