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 constructs2) 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:
- dependency update (or Renovate/Dependabot PR),
cdk synthvalidation,cdk deployfrom 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
| Prop | Why it is required |
|---|---|
orgSlug | Self-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>. |
customDomain | ctx |
modelProvider | Backend and worker cannot process chat, ingestion, and embeddings without model provider credentials and a default model ID. |
Optional props
| Prop | Behavior |
|---|---|
size | Capacity profile for single-tenant deployments. Valid values: small, medium, large. Defaults to small when omitted. |
Required nested fields
| Field | Why it is required |
|---|---|
customDomain.domainName | Becomes 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.hostedZoneId | Used to create/validate Route 53 records for ACM certificate validation, ALB alias records, and SES domain identity/DKIM records. |
modelProvider.baseUrl | Endpoint for OpenAI-compatible model APIs used by backend and worker. |
modelProvider.apiKey | Secret used to authenticate model API requests at runtime. |
modelProvider.defaultModel | Default model identifier passed into runtime settings for fast model selection. |
High-level architecture
CtxPipe provisions a single AWS stack with these layers:
| Layer | What is provisioned | Purpose |
|---|---|---|
| Network | VPC with public/private subnets and NAT egress | Isolates internal services while allowing controlled outbound access. |
| Edge + ingress | Public ALB + ACM certificate + Route 53 records | Exposes one HTTPS origin and routes public traffic to backend. |
| Compute | ECS cluster + Fargate services (backend, worker, ui, codesearch) | Runs application APIs, background jobs, UI, and code indexing/search workloads. |
| Stateful data | Aurora PostgreSQL, Neptune cluster, EFS for /data | Persists relational state, graph data, and codesearch cache/indexes. |
| Secrets + identity | Secrets Manager secrets, generated AUTH_SECRET, optional connector secrets | Stores runtime credentials and injects them into tasks securely. |
| Email delivery | SES domain identity, DKIM, SMTP credentials | Enables transactional email from backend (ctxpipe-noreply@<hosted-zone-apex>). |
| Deploy safety | Migration custom resource (one-off ECS task) + ECS deployment rollback controls | Runs 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=neptuneand graph URI wiring from Neptune endpoints- Internal service routing (
UI_PROXY_URL,CODESEARCH_URL) so only backend is public - Generated
AUTH_SECRETand managedDATABASE_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.