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 NOCREATEROLEapplication 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:
Create Stripe products/prices for each plan and interval, then register this 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:
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,
wwwredirect, 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.