Docs / deployment/codex-deploy-brief.md

Codex Deploy Brief — Chimti Web Cutover to app.chimti.ai + admin.chimti.ai

You are deploying Chimti's new merged web app and its API updates on the Otlu

server (Coolify). Everything below is complete and self-contained — follow it

top to bottom. Do not improvise product changes; this is an infra task.

Context (30 seconds)

(`chimti-user-app`, `chimti-admin-app`). Same repo builds two variants via a

Docker build arg: `VITE_APP_VARIANT=user` or `admin`.

**https://admin.chimti.ai** (admin app).

default to this base URL (baked at build time). Do NOT move the API in this

task.

1) read-only billing routes (`/v1/invoices`, `/v1/plans`, `/v1/subscriptions`)

2) a background worker layer (`src/worker.js` + `src/workers/`, 21 jobs,

runs as a separate process off the same image; `npm run test:workers` = 25

tests green).

deployed untouched as rollback. Do not delete them.

Step 0 — Preconditions (verify before touching Coolify)

Confirm these commits exist on the deploy branch of each repo (Jagjeet pushes

them; if missing, stop and tell him):

`src/worker.js` + `src/workers/` (7 files) + `test/workers.test.js` exist;

`package.json` has `"worker": "node src/worker.js"` and `"test:workers"`.

`src/` app code; `npm test` passes locally (token lint + module contract +

render smoke, both variants).

Sanity on a checkout: `cd chimti-api && node test/workers.test.js` → expect

"all worker tests green — 25 passed".

Step 1 — Deploy chimti-api update

1. Coolify → existing `chimti-api` app → Redeploy (same Dockerfile, no config

change needed for this step).

2. Verify:


curl -s https://chimti-api.otlu.io/v1/health

# expect JSON containing "service":"chimti-api"



# login with a real internal account, then:

TOKEN="<accessToken from POST /v1/auth/login>"

curl -s -H "Authorization: Bearer $TOKEN" https://chimti-api.otlu.io/v1/plans | head -c 300

curl -s -H "Authorization: Bearer $TOKEN" https://chimti-api.otlu.io/v1/invoices | head -c 300

# expect {"items":[...]} from both (empty items array is fine)

Step 2 — CORS for the new domains

In the chimti-api app's env (Coolify env editor), APPEND to `CORS_ORIGIN`

(comma-separated, keep every existing origin):


https://app.chimti.ai,https://admin.chimti.ai

Redeploy/restart chimti-api after saving. Verify preflight:


curl -s -o /dev/null -w '%{http_code}\n' -X OPTIONS https://chimti-api.otlu.io/v1/health \

  -H 'Origin: https://app.chimti.ai' -H 'Access-Control-Request-Method: GET'

# expect 204/200, not 4xx

Step 3 — Worker process (new Coolify service)

1. Coolify → create a new app `chimti-worker` from the SAME `chimti-api` repo

(same Dockerfile). No domain, no public port.

2. Override the start command to: `npm run worker`

(If using the Dockerfile CMD instead, set custom command — the image's

default CMD starts the API; the worker MUST run `node src/worker.js`.)

3. Env = copy ALL of chimti-api's env (it needs `DATABASE_URL` etc.), PLUS:


WORKERS_ENABLED=1

WORKER_ALERT_EMAIL=me@jagjeetsinghsethi.com

WORKER_DIGEST_HOUR=8

WORKER_SNAPSHOT_HOUR=2

WORKER_DEMO_RESET_HOUR=4

(Optional per-job kill switches: `WORKER_<KEY>=0`; cadence overrides:

`WORKER_<KEY>_INTERVAL_SECONDS=...` — keys are in

`chimti-docs/architecture/workers-automation-plan.md`.)

4. Deploy, then verify from logs: lines `"chimti worker starting"` and

`"job scheduled"` (21 jobs). On first boot it creates `worker_heartbeats`

and `worker_snapshots` tables itself — no migration step.

5. DB check (psql on the server):


SELECT name, last_run_at, last_ok_at, last_error FROM worker_heartbeats ORDER BY name;

-- within ~2 minutes most short-interval jobs should have last_ok_at set

Step 4 — DNS for chimti.ai

At the chimti.ai DNS provider add two records pointing at the Otlu server

(same IP the *.otlu.io apps use):


app.chimti.ai    A  <server-ip>     (or CNAME to the Coolify proxy hostname)

admin.chimti.ai  A  <server-ip>

TTL 300 while cutting over. Coolify/Traefik will issue Let's Encrypt certs

automatically once the apps below exist — no manual cert work.

Step 5 — Two web apps from chimti-web-app

Create TWO Coolify apps from the `chimti-web-app` repo, Build Pack =

Dockerfile (repo root). The Dockerfile is nginx-static; no runtime env needed.

**App 1 — `chimti-web-user`**

(`VITE_BACKEND_API_BASE_URL` defaults to `https://chimti-api.otlu.io` inside

the Dockerfile — do not set it unless pointing at staging.)

**App 2 — `chimti-web-admin`**

Deploy both. Build should end with vite `✓ built` and an nginx image.

Step 6 — Smoke checklist

User app — https://app.chimti.ai:

the variant-guard message (expected behavior)

agent, Print tags reaches localhost:47844

app.chimti.ai origin)

Admin app — https://admin.chimti.ai:

API/worker after 24h (bonus check):

Step 7 — Old domains policy

untouched for now (instant rollback).

301 redirects from the old domains to app/admin.chimti.ai (Coolify redirect

rule or a tiny nginx app), and archive the old app repos.

Rollback

or delete the DNS records for app/admin.chimti.ai.

history. Billing routes and workers are additive — no schema rollback needed

(worker tables are standalone and harmless).

Explicitly OUT of scope for this task

new web builds with the new base URL).

Reference docs in the repos

domains; this brief supersedes the domain choice)

Report back with: each step's status, the smoke checklist ticked, and the

worker_heartbeats output.