Skip to main content
The Campaign Assistant runs on the same VPS as NNACT Pro. NNACT’s Caddy container owns ports 80 and 443 there, so it also publishes this project’s two hostnames and this repo ships no reverse proxy of its own: The stack joins NNACT Pro’s Docker network (openfieldpro_internal, the default PROXY_NETWORK) so the proxy can reach its containers by name. Service names are prefixed with campaign- because Docker DNS aliases are shared across that network — an unprefixed api would collide with NNACT Pro’s own API.

One-time setup

Behaviour

  • Every push to main runs ci.yml (typecheck, tests, build, image build, OpenAPI freshness) and then deploy-production.yml, which deploys only if CI is green and fails if the deployed HEAD does not contain the pushed commit. A docs-only change needs no new images: change-detect.mjs maps it to no rebuilds.
  • The seed is the schema step. The API refuses to synchronize in production (synchronize: config.env !== 'production'), and the seed refuses to run without ALLOW_SCHEMA_PUSH=true, which the pipeline exports. On the first deploy (no marker in data/.deployed-sha) and whenever an API path changes, the pipeline runs the seed: it creates or alters tables to match the shipped entities and (re)writes the two demo accounts idempotently.
  • Change-aware builds. scripts/change-detect.mjs maps the changed file set to the images that must be rebuilt, so an api-only fix does not rebuild the web app. The marker at data/.deployed-sha records the last deployed commit.
  • Public verification is off by default (VERIFY_PUBLIC=false). Once DNS resolves, set VERIFY_PUBLIC=true in DME_CAMPAIGN_ENV so every deploy asserts both public URLs answer.

Operations

The API’s liveness probe is GET /api/health (public, unversioned). It reports process liveness only — the database has its own healthcheck in the stack.

Security notes

  • .env is never copied into an image (.dockerignore), and CI only ever sends it to the VPS over SSH.
  • CORS_ORIGINS is allow-listed to the web origin; the API never echoes an arbitrary Origin.
  • Called once per generation, so an abuse of the demo accounts can burn tokens — the demo password is a fixed value by choice. Ship without seeding the demo accounts before exposing this publicly.
  • The model default is claude-sonnet-5-5 because claude-sonnet-4-5 was deprecated on 2026-09-30 (retirement 2026-11-30). Pin a different model with ANTHROPIC_MODEL.
Last modified on October 6, 2026