Deployment verification¶
How to prove a push actually reached Production — not "the pipeline went green" but "the bytes users load are the ones you meant". Written from the procedure used on every deploy in September 2026; the traps listed each hid a real failure once.
The documentation site (docs service, doc.addmedad.top) deploys with the same push, once the
domain exists in Dokploy (compose app → Domains: host doc.addmedad.top, service docs, port 80,
HTTPS Let's Encrypt) and in DNS (an A record to the production host): curl -sI https://doc.addmedad.top/ must answer
302 to /BIMZONE-USER-GUIDE/, and a page you changed must show its new text.
Topology in one paragraph¶
Production is a Dokploy Compose service whose compose path is docker-compose.dokploy.yml; that
file includes the base docker-compose.yml and does exactly one thing more — attaches web to
dokploy-network so Dokploy's Traefik can reach it. Dokploy autodeploys on every push to
BIMzone_Master_push. The services are postgres, minio, redis, migrate (runs once, must
complete before api starts), api, worker (+ /metrics on 9091), web (nginx: serves the SPA and
proxies /api/, /storage/, /socket.io/, /bcf/, /scim/ to api:4000), and the dr profile's
postgres-replica + backup.
The five checks, in order¶
Each check answers a different question; passing one says nothing about the next.
1. Did the web bundle change?¶
curl -s https://bim.addmedad.top/ | grep -oE "assets/index-[A-Za-z0-9_-]+\.js"
The hash is content-addressed. If it equals the previous deploy's hash, nginx is still serving the
old image — the build failed or Dokploy has not finished. Record the new hash (e.g.
index-C1kJfkSc.js on 2026-09-17); the browser-side fix you are verifying is only live once this
changes. Then hard-refresh the app once: the SPA is versioned per deploy and a stale tab keeps the
old bundle.
2. Is the API up, and is it the NEW api container?¶
curl -s https://bim.addmedad.top/api/health # → {"ok":true,"ts":…}
ok:true proves an api answers, not which one. web proxies to api:4000 by name and nginx caches
the resolved IP: after a redeploy it can keep talking to the old container and 502 — or worse, keep
talking to an old container that still answers. Prove the new code with something only the new
build says. Two ways:
- call a route the new commit added or changed and read its own error text, e.g.
POST /api/projects/<id>/integration-connections/<id>/conflicts/resolvewith a bogus body returns the new route's validation message rather than a 404; - or, for header/CSP changes:
curl -sI https://bim.addmedad.top/ | grep -i content-security-policyand compare to the conf you shipped.
If /api/health 502s for more than a minute after the deploy finished, restart web (nginx) from
Dokploy: it re-resolves api.
3. Did the migration run?¶
The migrate service must exit 0 before api starts (depends_on: service_completed_successfully).
In Dokploy, open the deployment log and look for Migrations applied.; a failure prints
Migration failed: and the api never starts. See Migrations for what to do then.
4. Is the worker alive?¶
The worker is a separate container; the api can be perfectly healthy while nothing converts. Open
any project → upload a small IFC (or press Reconvert on a model) and watch Cloud activity in
the top bar: queued → converting → ready within a minute proves the worker, Redis and storage. If
it sits at queued, the worker is down or cannot reach Redis — check its container log for
[worker] startup lines and the Redis connection.
5. Does the UI actually render what changed?¶
Open the page you changed in a real browser session, with the console open. A React error
boundary ("Something went wrong") or a Minified React error #NNN in the console is a shipped
defect even when every curl above passed — the Integrations tab crash of 2026-09-16 passed every
server-side check. For anything visual (viewer, map, PDFs) look at it; do not infer from a 200.
Traps this procedure exists for¶
| Trap | What it looked like | Where it is written up |
|---|---|---|
Traefik cannot reach web |
TLS fine, every path returns Traefik's plain 404 page not found |
header of docker-compose.dokploy.yml |
| nginx cached the old api IP | 502 on /api/* after every deploy |
commit d72055b |
.mjs served as octet-stream |
viewer blank, tree populated, .frag 200 |
apps/web/tests/structural/asset-mime.test.ts |
/modeltiles, /pointclouds reached no nginx/Vite config |
tiles inert everywhere | commit ec74357 |
| env var not in the compose allowlist | feature silently off in the container while set in Dokploy | apps/server/tests/structural/compose-env-allowlist.test.ts |
CSP connect-src missing the basemap hosts |
Map tab black, "Failed to fetch" in the console, nothing in server logs | apps/web/tests/structural/csp-tile-hosts.test.ts |
| stale SPA tab | old bundle keeps running after a deploy | hard-refresh once |
After verifying¶
Write the bundle hash, the commit, and what you looked at into docs/PHASE-EXECUTION-LOG-2026-09.md
(the running log); the entry is the evidence that Production == HEAD.