Skip to content

Legacy Compose deployment reference

This is retained for development and migration reference, not as a supported self-hosted v1 product. The canonical deployment uses Cloudflare for the landing site and AWS with Istio; see docs/deployment-istio.md and charts/gettier-hosted. This reference deploys the authenticated dashboard and ingestion API at app.<domain> behind Caddy. The marketing site is always deployed independently to Cloudflare. PostgreSQL stays outside the host. The customer-side relay remains a separate installation surface; telemetry and product visibility live in Gettier's dashboard.

The repository's root Dockerfile and Helm chart describe the earlier proxy-first topology. For the hosted control plane, use only deploy/Dockerfile.hosted and deploy/compose.yaml. This direction follows the hosted decision in ADR 0002 and the commercial contracts in docs/hosted-control-plane.md.

1. Infrastructure and DNS

Provision:

  • one Linux host with Docker Engine and the Compose plugin;
  • a managed PostgreSQL database with automated backups and point-in-time recovery;
  • an admin/migration database role and a separate NOSUPERUSER NOCREATEDB NOCREATEROLE application role; and
  • outbound HTTPS access for OIDC, Stripe, Resend, and image pulls.

Point A/AAAA records for the apex, www, and app hostnames to the deployment host. Open inbound TCP 80 and TCP/UDP 443. Caddy obtains and renews certificates automatically; do not start the stack until public DNS resolves to the host.

2. Configure production services

Create the OIDC application with this exact callback:

https://app.example.com/api/auth/oidc/callback

Create Stripe products/prices for each plan and interval, then register this webhook:

https://app.example.com/api/billing/webhook

Subscribe it to the events listed in docs/hosted-control-plane.md. Verify the sending domain in Resend and publish its required DNS records before enabling invitations.

On the host, from a tagged Gettier checkout:

cp deploy/.env.production.example deploy/.env.production
chmod 600 deploy/.env.production

Replace every example and REPLACE value. Generate the session secret with a password manager or openssl rand -base64 48. DATABASE_URL must use the restricted application role. GETTIER_MIGRATE_URL and GETTIER_BILLING_DATABASE_URL use the privileged role and must never be exposed to the browser or customer relay.

3. Create the app role and launch

Create the application role once through the managed provider's SQL console. Adapt the name and password to match the environment file:

CREATE ROLE gettier_app LOGIN PASSWORD 'generated-secret'
  NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT;

The migration container applies schema migrations and the exact least-privilege grants to GETTIER_APP_DATABASE_ROLE before the app starts.

docker compose --env-file deploy/.env.production -f deploy/compose.yaml config --quiet
docker compose --env-file deploy/.env.production -f deploy/compose.yaml up -d --build
docker compose --env-file deploy/.env.production -f deploy/compose.yaml ps
curl --fail https://example.com/api/healthz
curl --fail https://app.example.com/api/healthz

The app endpoint reports ready only when production OIDC/session configuration is valid and PostgreSQL answers. It never returns database, identity, tenant, or secret details.

4. Bootstrap the first customer

The project command prints the raw ingestion key once. Store it in a secret manager before closing the terminal. Use the OIDC provider's immutable sub claim—not an email address—as the member subject.

docker compose --env-file deploy/.env.production -f deploy/compose.yaml run --rm migrate \
  node_modules/.bin/tsx src/provision-project-cli.ts tenant-id organization project production

# A demo with no checkout to send anyone to: set the trial past the end of the demo, or
# ingestion starts returning 402 trial_expired on day 15.
docker compose --env-file deploy/.env.production -f deploy/compose.yaml run --rm \
  -e GETTIER_TRIAL_DAYS=365 migrate \
  node_modules/.bin/tsx src/provision-project-cli.ts tenant-id organization project production

docker compose --env-file deploy/.env.production -f deploy/compose.yaml run --rm migrate \
  node_modules/.bin/tsx src/provision-membership-cli.ts organization oidc-subject owner@example.com owner "Owner Name"

Sign in, confirm the organization/project/environment selector, send one test envelope, and verify that it appears in Overview before inviting public users.

5. Operations and rollback

Before each release, take or verify a managed-database restore point. Deploy an immutable Git tag, retain the previous image/tag, and inspect service logs after launch:

docker compose --env-file deploy/.env.production -f deploy/compose.yaml logs --tail=200 web migrate caddy

Application rollback is a checkout of the previous tag followed by the same up -d --build command. Database migrations are forward-only, so application changes must remain compatible with the deployed schema; use managed point-in-time recovery only for a true data incident, not as a normal code rollback.

Back up the environment file through an encrypted secret manager. Caddy's named volumes contain certificate state, but losing them is recoverable because certificates can be reissued. The PostgreSQL service is the durable system of record.

Release gate

Do not announce general availability until all of these pass:

  • apex, www redirect, app domain, TLS, canonical metadata, robots, and sitemap;
  • OIDC login/logout and membership revocation;
  • project provisioning, envelope ingestion, dashboards, and team invitations;
  • Stripe test-clock coverage followed by one live low-value purchase, portal, cancellation, webhook replay, quota enforcement, and invoice reconciliation;
  • restore rehearsal, alerting for 5xx/readiness/webhook failures, and an on-call owner;
  • privacy policy, terms, support contact, incident process, and billing/refund policy.

The containers and runbook make the service deployable; the final operational and legal checks above remain release-owner responsibilities.