Deployment
Axiom is one Hono application over platform ports (apps/api/src/deps.ts). It runs on Cloudflare Workers today; a Node entry point and Google Cloud adapters exist for the GCP target. Business logic never imports platform types, so both runtimes enforce the same contract.
Hostnames
| Host | Serves | Deployed from |
|---|---|---|
sectigoaxiom.com, app.sectigoaxiom.com | Landing page, free public services, console, Control, approval pages | apps/web (static-assets Worker, SPA fallback) |
api.sectigoaxiom.com | REST API /v1/*, OAuth 2.1 server, JWKS, well-known metadata | apps/api |
gw.sectigoaxiom.com | Hosted Gateway proxy: /p/{project_id}/http/* and /p/{project_id}/mcp | apps/api (same Worker) |
mcp.sectigoaxiom.com | Axiom MCP server at /mcp | apps/api (same Worker) |
docs.sectigoaxiom.com | This site | apps/docs (static-assets Worker) |
sample.sectigoaxiom.com | Acme Billing sample provider | apps/sample-billing |
api, gw and mcp are one Worker routed by hostname, so they share bindings and the single execution claim. In local development everything runs on localhost: the API on :8787 with the gateway under /gw and the MCP server at /mcp, the sample provider on :8788, the console on :5173 and the docs on :5174.
Runtime ports
| Concern | Cloudflare (now) | GCP (target) |
|---|---|---|
| HTTP runtime | Workers | Cloud Run (apps/api/src/platform/node/server.ts) |
| Relational store | D1 via kysely-d1 | Cloud SQL Postgres via Kysely PostgresDialect |
| Atomic execution claim | Conditional UPDATE … WHERE state = 'approved' | Same statement |
| Queue (approval delivery, readback, webhooks, reconciliation, SIEM, monitors) | Cloudflare Queues axiom-work with dead-letter queue axiom-work-dlq | Pub/Sub push subscription |
| Cache (rate limits, simulated connector records, MCP upstream era) | KV | A cache_entries table in Cloud SQL |
| Blobs (evidence exports, incident bundles) | R2 bucket axiom-evidence | Cloud Storage |
| Signing keys | Worker secrets holding private JWKs (env:AXIOM_KEY_<ROLE>) | Cloud KMS asymmetric keys (kms: references) |
| Scheduled maintenance | Cron Trigger every 5 minutes | Cloud Scheduler → POST /internal/maintenance |
| Cloudflare Email Service binding | SendGrid | |
| CA | Sectigo SCM adapter (mock mode by default), dev CA | Same |
Cloudflare
Local development
git clone --recurse-submodules https://github.com/hassard0/sectigo-axiom.git
cd sectigo-axiom
npm install
npm run dev-keys -w @axiom/api # local signing keys → apps/api/.dev.vars, apps/sample-billing/.dev.vars
npm run db:migrate:local -w @axiom/api
npm run db:migrate:local -w @axiom/sample-billing
npm run dev:api # http://localhost:8787
npm run dev:sample # http://localhost:8788
npm run dev:web # http://localhost:5173
npm run dev:docs # http://localhost:5174dev-keys writes one ES256 key per signing role (identity, grant, observation, policy, event) and the sample provider's outcome key. These files are never committed.
Provision and deploy
Create resources.
node scripts/provision-cloudflare.mjscreates (or finds) the API's D1 databaseaxiom, KV namespaceaxiom-cache, R2 bucketaxiom-evidenceand queuesaxiom-workandaxiom-work-dlq, plus the sample provider's D1 databaseaxiom-sample-billing, and writes their IDs intoapps/api/wrangler.jsoncandapps/sample-billing/wrangler.jsonc. It is idempotent: only the all-zero placeholder IDs are replaced. The web app and docs need no resources.Set secrets for the API Worker, one private JWK per signing role:
shcd apps/api for role in IDENTITY GRANT OBSERVATION POLICY EVENT; do npx wrangler secret put AXIOM_KEY_$role --env production doneOptional:
AXIOM_SAMPLE_OUTCOME_JWK(the sample provider's public outcome key),AXIOM_CA_KEK(wraps the dev CA keys, required in production for the CA in mock mode), and the SCM settings below.Migrate and deploy the API:
npm run db:migrate:remote -w @axiom/api, thennpx wrangler deploy --env productioninapps/api. The production environment bindsapi.sectigoaxiom.com,gw.sectigoaxiom.comandmcp.sectigoaxiom.comas custom domains and runs the maintenance cron every 5 minutes.Deploy the web app:
npm run build -w @axiom/web, thennpx wrangler deploy --env productioninapps/web(custom domainssectigoaxiom.comandapp.sectigoaxiom.com).Deploy the sample provider: apply its migrations (
npx wrangler d1 migrations apply axiom-sample-billing --remote --env productioninapps/sample-billing), setwrangler secret put SAMPLE_OUTCOME_PRIVATE_JWK --env production, and deploy.Deploy the docs:
npm run deploy -w @axiom/docs -- --env productionbuilds this site and deploysapps/docs/wrangler.jsonctodocs.sectigoaxiom.com.
Configuration
API variables (apps/api/wrangler.jsonc, apps/api/src/config.ts):
| Variable | Meaning | Production value |
|---|---|---|
AXIOM_ENV | development enables dev conveniences (logged magic links, dev_link on invitations, HTTP webhooks) | production |
AXIOM_ISSUER | Issuer in tokens, receipts and dispatch authorisations | https://api.sectigoaxiom.com |
AXIOM_API_ORIGIN, AXIOM_GATEWAY_ORIGIN, AXIOM_MCP_ORIGIN | Public origins | https://api.…, https://gw.…, https://mcp.… |
AXIOM_WEB_ORIGIN, AXIOM_DOCS_ORIGIN | Allowed CORS origins; links in emails | https://app.sectigoaxiom.com, https://docs.sectigoaxiom.com |
AXIOM_SAMPLE_ORIGIN | Upstream of seeded sample projects | https://sample.sectigoaxiom.com |
AXIOM_COOKIE_DOMAIN, AXIOM_RP_ID | Session cookie domain and WebAuthn relying party | .sectigoaxiom.com, sectigoaxiom.com |
AXIOM_MAIL_FROM | Sender for magic links and approval notices | Sectigo Axiom <no-reply@sectigoaxiom.com> |
AXIOM_DEV_AUTO_VERIFY_ORGS | Development only (AXIOM_ENV=development): true marks organisations verified without a CA validation, so Control can be tried locally. The evidence records "NOT validated by Sectigo"; ignored in staging and production | unset |
CA variables (apps/api/src/ca/index.ts): AXIOM_CA (sectigo-scm, the default, or dev), AXIOM_SCM_MODE (mock, the default, or live), AXIOM_SCM_BASE_URL, AXIOM_SCM_AUTH (password or oauth), AXIOM_SCM_LOGIN, AXIOM_SCM_PASSWORD, AXIOM_SCM_CUSTOMER_URI, AXIOM_SCM_CLIENT_ID, AXIOM_SCM_CLIENT_SECRET, AXIOM_SCM_TOKEN_URL, AXIOM_SCM_ORG_ID, AXIOM_SCM_CERT_TYPE, AXIOM_SCM_TRUST_ANCHORS_PEM.
Mock mode
Until SCM credentials are configured and AXIOM_SCM_MODE=live is set, the SCM adapter runs in mock mode backed by a local development CA that issues real X.509 certificates. Organisation verification and agent certificates issued in mock mode are not Sectigo-validated. See Sectigo's CA role.
Google Cloud
The GCP target runs the same app on Cloud Run with Cloud SQL Postgres, Pub/Sub, Cloud Storage, Cloud KMS (one key per signing role), Secret Manager and Cloud Scheduler. The runbook is docs/deploy-gcp.md in the repository and is still being completed. It covers: project, APIs and service accounts; Cloud SQL (collation C, because timestamps are compared as RFC 3339 text); Pub/Sub; Cloud Storage; Cloud KMS keys per role; Secret Manager; build and deploy; the Pub/Sub push subscription and Cloud Scheduler calling /internal/* with OIDC tokens; custom domains; cutover from the hosted Cloudflare deployment; the environment reference; and cost notes.
Status
The Node entry point (apps/api/src/platform/node) and the GCP adapters (apps/api/src/platform/gcp) are in active development. Treat GCP as a target, not a supported production deployment, until that runbook is complete.
Customer-hosted gateways
Control already registers customer-hosted gateways and adapters, signs their policy bundles, collects heartbeats and acknowledgements, and moves the single executor between gateways (see Fleet, gateways and cutover). The deployable customer-hosted gateway container and the Kong, Apigee and AWS API Gateway adapters are not part of this repository yet.