The frame — stage discipline, promoted to prod
The dark-factory model already separates merge from deploy: work integrates on stage behind CI gates and only promotes to main deliberately, one service at a time. This page extends that discipline downward into the runtime: the same "only intentional, verified things reach production" principle applied to secrets, network reachability, and public surface.
The honest headline: Core is already in decent shape. The gaps are mostly operational — secrets that must be rotated, a shared network that gives zero isolation, and internal services that leaked public URLs. None of it requires a rewrite; it requires a rotation runbook, a network topology, and a boot-gate flag. Every item below is risk → what to do → why, and the two lists at the end keep this honest: a prioritized checklist, and a plain statement of what's already good.
OLL_MODEL_SERVICE_TOKEN was pasted into a chat today. Exposed = burned: both must be rotated before real prod, regardless of everything else on this page.The target topology — public edge, private core
coolify network, some internal services with accidental public URLs. Target: a dedicated per-environment network where only the public edge is reachable from the internet.core.oll.am · product edges (write.oll.am, foto) · the landing. TLS at Traefik. These are the ONLY internet-reachable apps.oll-model gateway · ollama runtime. No public domain. Reached only over the internal network by Docker DNS alias.Everything that follows is in service of this picture: a small public surface, a private interior, and credentials scoped so one leak can't open everything.
The plan — ten concrete items
Rotate every secret; store as Coolify secrets; per-service tokens
P1OLL_MODEL_SERVICE_TOKEN was pasted into a chat today — treat as compromised. Worse, that token is a single shared secret across every product (services/oll-model/routes.py checks one OLL_MODEL_SERVICE_TOKEN for all callers): one leak = every product can impersonate every other, and the whole LLM-cost surface is open.STRIPE_WEBHOOK_SECRET, AUTH_JWT_SECRET, the model token). Store them only as Coolify secrets — never in code, never in a committed .env. Give each product its own model token (OLL_MODEL_TOKEN_WRITE, _FOTO, …) so blast-radius is one product, not all. Write a short rotation runbook (which key, where it lives, how to roll it, who to notify) and keep it in site/.Isolate the network — a dedicated net per environment
P1oll-core, oll-model, ollama, humaniz, specview, springular — shares Coolify's default coolify network. That is zero isolation: any container can open a socket to any other. A compromise of one unrelated app (say an old POC) can reach Core's port or the model gateway directly.ollam (prod) net distinct from a ollam-stage net, the pattern the compose files already use (docker-compose.local.yml → external ollam, docker-compose.dev.yml → ollam-dev). Attach only the services that must talk to each other, and reach them by the stable Docker-DNS alias (oll-core, oll-model) rather than a container name. Take the legacy POCs off the shared net.Internal services get no public domain
P1ollama was handed an accidental public sslip URL, and oll-model, while service-token-gated, is publicly resolvable. An unauthenticated Ollama endpoint on the internet is an open, uncapped LLM — free compute for anyone who finds it and a direct cost-blowup vector.ollama always, and ideally oll-model too (products call it over the internal net by alias, so it never needs a public hop). Publicly exposed apps are only: Core (core.oll.am), the product edges (write.oll.am, the foto edge), and the landing. Published host ports stay dev/test-only — the 5055:5000 / 5113:5003 mappings in the compose files are for local smoke tests and must not exist in prod.Harden the service-token gate
P2_check_service_token in services/oll-model/routes.py). The check itself is sound — it uses hmac.compare_digest (constant-time) and returns empty (not a default) when unset — but a single shared secret means "authenticated" says nothing about which product is calling.Prod fails fast on default secrets — APP_ENV flips the gate
P1 · mostly doneAUTH_JWT_SECRET in production means anyone can forge a valid 72h token for any user. Test defaults for Stripe keys or the service token in prod are silent footguns.core/create_app.py reads APP_ENV and raises RuntimeError if AUTH_JWT_SECRET or STRIPE_WEBHOOK_SECRET is missing when APP_ENV=production, and only warns otherwise. Extend the same pattern to the product services + the model gateway (per-service tokens, provider keys), and make setting APP_ENV=production in Coolify a mandatory step in every prod app's config — so test defaults are impossible to ship live.Auth / JWT — strong, unique-per-env, validated everywhere
P2core/modules/auth/service.py: sub+email+iat+exp) and every product validates it with the same AUTH_JWT_SECRET. That shared symmetric secret is the entire SSO trust root — a weak or cross-env-reused secret forges the whole platform's identity.AUTH_JWT_SECRET as a high-entropy random value, unique per environment (prod ≠ stage ≠ dev), stored as a Coolify secret. Confirm every consumer validates signature + expiry (not just decodes). Keep the 72h identity-only token as the recorded chosen state; a shorter TTL or revocable refresh is a separate, off-critical-path nicety.Rate-limit the public and cost-bearing endpoints
P2core/modules/auth/rate_limit.py, 5/IP/hour), but it's per-process — it resets on redeploy and doesn't share state across replicas — and the gateway has none.Containers — non-root, minimal, healthchecked, no build secrets
P3 · largely donecore/Dockerfile runs as a non-root appuser, on python:3.11-slim, with a HEALTHCHECK hitting /api/health over $PORT, and only a non-secret ARG APP_RELEASE at build time. Audit services/*/Dockerfile for the same three properties; forbid build-time ARG/ENV secrets in review.Stripe webhook — signature, idempotency, sole plan-writer
P2 · verifycore/modules/billing/): it verifies the HMAC with stripe.Webhook.construct_event, de-dupes by event_id, and _set_user_plan is the only writer of User.plan. The prod task is to keep it that way: create the parallel live webhook at core.oll.am/api/billing/webhook, set its rotated STRIPE_WEBHOOK_SECRET, and guard "sole plan-writer" with a review rule so no future route hand-rolls an entitlement flip.event.id stops a retry storm double-granting; a single writer means the plan state has exactly one, auditable, authenticated source.Backups & data isolation — Neon branch per env, PITR
P2oll_core, each product its own DB; services integrate over HTTP only, never another service's DB — ADR-008). Use a Neon branch per environment so prod and stage never share data, confirm point-in-time recovery is enabled on the prod branch, and store each service's DATABASE_URL as a per-service Coolify secret.The prioritized checklist
OLL_MODEL_SERVICE_TOKEN (pasted in chat today), then AUTH_JWT_SECRET + STRIPE_WEBHOOK_SECRET. Store as Coolify secrets only. 01ollam (prod) vs ollam-stage; take legacy POCs off the shared coolify net; reach services by DNS alias. 02ollama's accidental URL; keep oll-model private; strip dev-only published ports. 03APP_ENV=production on every prod Coolify app so the boot-gate requires real secrets (already enforced in Core). 05AUTH_JWT_SECRET per env — prod ≠ stage ≠ dev. 06core.oll.am/api/billing/webhook with the rotated secret. 09APP_ENV boot-gate to product services + the gateway (fail-closed on missing token/keys). 05DATABASE_URL as Coolify secrets. 10services/*/Dockerfile for non-root + healthcheck + no build secrets. 08site/. 01request_id. —What stays as-is — already good
core/create_app.py reads APP_ENV and raises in production if AUTH_JWT_SECRET/STRIPE_WEBHOOK_SECRET is missing; _jwt_secret() in auth/service.py also raises rather than defaulting. The prod flag we need is already built — it just has to be applied platform-wide.services/oll-model/routes.py uses hmac.compare_digest and returns empty (no allow-all default) when the token is unset. The gap is that it's shared, not that the check is weak.construct_event, idempotent by event_id, and _set_user_plan is the sole writer of the plan. This is the money path's core control and it holds.core/Dockerfile (and services/foto/Dockerfile) run non-root on python:3.11-slim, with a healthcheck and no build-time secrets.core/modules/auth/rate_limit.py caps 5/IP/hour with an OOM-safe eviction — the only gap is making it shared across replicas and extending it to more endpoints.The setup isn't insecure — it's provisional. The work is to promote stage's shortcuts into prod's guarantees: rotate what leaked, isolate the network, hide the interior, and make prod refuse to boot on a default secret.