Deployment

AWS

Deploy ctx| on AWS with the @ctxpipe/aws-cdk CtxPipe construct.

@ctxpipe/aws-cdk is the AWS self-hosting path for ctx|. It gives you one high-level construct, CtxPipe, that provisions the required infrastructure and wires runtime configuration for you.

Single-tenant only

Self-hosted ctx| on @ctxpipe/aws-cdk is single-tenant. Run one stack per organization.

Step-by-step deployment flow

1) Install @ctxpipe/aws-cdk in your CDK repo

Use an existing CDK repo, or create a new one first.

Then install:

pnpm add @ctxpipe/aws-cdk aws-cdk-lib constructs

2) Create or reuse a Route 53 hosted zone

You need a hosted zone for your custom domain because the construct configures DNS and TLS from it.

  • Reuse an existing public hosted zone if you already manage your domain in Route 53.
  • Or create a new hosted zone and delegate your domain/subdomain to it.
  • Keep both values ready:
    • customDomain.domainName (example: app.example.com)
    • customDomain.hostedZoneId (example: Z0123456789ABCDEF)

3) Import CtxPipe and configure it

Instantiate the construct in your stack. Follow the same pattern as examples/aws-cdk-self-host.

import * as cdk from "aws-cdk-lib";
import { CtxPipe } from "@ctxpipe/aws-cdk";

const app = new cdk.App();
const stack = new cdk.Stack(app, "CtxPipeStack");
const modelApiKey = app.node.tryGetContext("modelApiKey");

if (!modelApiKey) {
  throw new Error('Missing CDK context "modelApiKey". Pass -c modelApiKey=...');
}

new CtxPipe(stack, "CtxPipe", {
  orgSlug: "acme",
  size: "medium",
  customDomain: {
    domainName: "app.example.com",
    hostedZoneId: "Z0123456789ABCDEF",
  },
  modelProvider: {
    baseUrl: "https://api.openai.com/v1",
    apiKey: cdk.SecretValue.unsafePlainText(String(modelApiKey)),
    defaultModel: "gpt-4.1-mini",
  },
});

4) Run CDK deploy

cdk synth
cdk deploy -c modelApiKey="$OPENAI_API_KEY"

5) Wait for deployment and open your custom domain

After cdk deploy completes, open https://<customDomain.domainName>.

6) Complete onboarding with the same orgSlug

When you create the first organization in onboarding, use the exact same slug as the orgSlug you passed to CtxPipe.

Slug must match

If onboarding uses a different org slug than the deployed orgSlug, org-scoped graph configuration will not line up.

Bonus: automate upgrades with CI/CD

Set up CI/CD so dependency upgrades for @ctxpipe/aws-cdk trigger:

  1. dependency update (or Renovate/Dependabot PR),
  2. cdk synth validation,
  3. cdk deploy from your pipeline.

This keeps your self-hosted stack aligned with the construct version you run.

Critical org slug requirement

orgSlug is not only infra config. It must match the org you create in the product onboarding flow.

  • The construct configures org-scoped graph runtime values using this slug.
  • During first login/onboarding, create the org with the exact same slug.
  • If the onboarding org slug does not match the deployed orgSlug, org-scoped graph configuration will not line up as expected.

Why each prop exists

Required props

PropWhy it is required
orgSlugSelf-hosted AWS CDK deployments are single-tenant. The slug identifies the one org for this stack and is used for org-scoped graph runtime values such as GRAPH_DB_URI_<orgSlug>.
customDomainctx
modelProviderBackend and worker cannot process chat, ingestion, and embeddings without model provider credentials and a default model ID.

Optional props

PropBehavior
sizeCapacity profile for single-tenant deployments. Valid values: small, medium, large. Defaults to small when omitted.

Required nested fields

FieldWhy it is required
customDomain.domainNameBecomes the public app origin (AUTH_BASE_URL / CTXPIPE_PUBLIC_APP_URL) served over HTTPS. This is the URL users visit for sign-in and onboarding.
customDomain.hostedZoneIdUsed to create/validate Route 53 records for ACM certificate validation, ALB alias records, and SES domain identity/DKIM records.
modelProvider.baseUrlEndpoint for OpenAI-compatible model APIs used by backend and worker.
modelProvider.apiKeySecret used to authenticate model API requests at runtime.
modelProvider.defaultModelDefault model identifier passed into runtime settings for fast model selection.

High-level architecture

CtxPipe provisions a single AWS stack with these layers:

LayerWhat is provisionedPurpose
NetworkVPC with public/private subnets and NAT egressIsolates internal services while allowing controlled outbound access.
Edge + ingressPublic ALB + ACM certificate + Route 53 recordsExposes one HTTPS origin and routes public traffic to backend.
ComputeECS cluster + Fargate services (backend, worker, ui, codesearch)Runs application APIs, background jobs, UI, and code indexing/search workloads.
Stateful dataAurora PostgreSQL, Neptune cluster, EFS for /dataPersists relational state, graph data, and codesearch cache/indexes.
Secrets + identitySecrets Manager secrets, generated AUTH_SECRET, optional connector secretsStores runtime credentials and injects them into tasks securely.
Email deliverySES domain identity, DKIM, SMTP credentialsEnables transactional email from backend (ctxpipe-noreply@<hosted-zone-apex>).
Deploy safetyMigration custom resource (one-off ECS task) + ECS deployment rollback controlsRuns migrations during deploy and prevents partial/broken rollouts.

Only the backend is public. UI and codesearch stay internal and are reached through backend routing.

What this gives you by default

  • GRAPH_DB_PROVIDER=neptune and graph URI wiring from Neptune endpoints
  • Internal service routing (UI_PROXY_URL, CODESEARCH_URL) so only backend is public
  • Generated AUTH_SECRET and managed DATABASE_URL / SMTP secrets
  • Public app URL output and secret ARN outputs for operations

How to choose size

Use size to tune compute and database capacity for your single-tenant workload:

  • small (default): best for pilot/small-team deployments with moderate repository churn and cost sensitivity.
  • medium: better for sustained ingestion/reindex workloads where worker and codesearch need more headroom.
  • large: for high-ingestion and lower-latency expectations, with bigger tasks and scaled backend/worker replicas.

Signals that you should move up a size:

  • ingestion jobs queueing up or taking too long,
  • frequent codesearch reindex delays,
  • higher p95 response latency during active sync/index windows.

@ctxpipe/aws-cdk keeps services in private subnets for every size. NAT remains required because backend/worker/codesearch need outbound access for model providers, GitHub, and Atlassian APIs.