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.
modelProvider is a union: openai-like (default) or bedrock. Both variants configure tiers through the same models prop (fast required).
OpenAI-compatible provider
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)),
models: {
fast: "gpt-4.1-mini",
// Optional: medium, high, embedding
// medium: "gpt-4.1",
// high: "gpt-4.1",
},
},
});Amazon Bedrock (task role)
For Bedrock, set kind: "bedrock" and tier model IDs only. The construct grants the backend/worker ECS task roles bedrock:InvokeModel and bedrock:InvokeModelWithResponseStream, injects MODEL_PROVIDER=bedrock and MODEL_BEDROCK_AWS_REGION, and does not create a MODEL_PROVIDER_API_KEY secret. The backend calls Bedrock Runtime with SigV4 credentials from the ECS task role.
Enable each model ID in the Amazon Bedrock console for your stack region before relying on chat or embeddings.
Bedrock embeddings (Cohere only)
Bedrock embeddings currently support Cohere embed models only (the backend uses Cohere's invoke format). Default when models.embedding is omitted: cohere.embed-v4:0. Non-Cohere Bedrock embed models (for example Titan) are not supported yet.
new CtxPipe(stack, "CtxPipe", {
orgSlug: "acme",
customDomain: {
domainName: "app.example.com",
hostedZoneId: "Z0123456789ABCDEF",
},
modelProvider: {
kind: "bedrock",
region: "us-east-1", // optional; defaults to stack region
models: {
fast: "anthropic.claude-sonnet-4-20250514-v1:0",
medium: "anthropic.claude-sonnet-4-20250514-v1:0",
high: "anthropic.claude-opus-4-20250514-v1:0",
embedding: "cohere.embed-v4:0", // optional; Cohere models only
},
},
});4) Run CDK deploy
OpenAI-like:
cdk synth
cdk deploy -c modelApiKey="$OPENAI_API_KEY"Bedrock (example context keys from examples/aws-cdk-self-host):
cdk deploy \
-c modelKind=bedrock \
-c modelFast="anthropic.claude-sonnet-4-20250514-v1:0" \
-c orgSlug=acme \
-c domainName=app.example.com \
-c hostedZoneId=Z0123456789ABCDEF5) 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 need model IDs and either an OpenAI-compatible endpoint + API key, or Bedrock via ECS task role. See union below. |
Optional props
| Prop | Behavior |
|---|---|
size | Capacity profile for single-tenant deployments. Valid values: small, medium, large. Defaults to small when omitted. |
modelProvider union
| Variant | kind | Required nested fields | Runtime auth |
|---|---|---|---|
| OpenAI-compatible | omitted or openai-like | baseUrl, apiKey, models.fast | MODEL_PROVIDER_API_KEY in Secrets Manager |
| Amazon Bedrock | bedrock | models.fast | ECS task role → native Bedrock Runtime (SigV4); no model API key secret |
Optional on both variants: models.medium, models.high, and models.embedding.
Omitted tier IDs cascade: medium → fast, high → medium. The construct maps them to MODEL_FAST_NAME, MODEL_MEDIUM_NAME, MODEL_HIGH_NAME, and MODEL_EMBEDDING_NAME. See Model configuration for what each tier is used for.
For bedrock, modelProviderSecret / modelProviderSecretArn stack outputs are omitted.
Upgrading from older @ctxpipe/aws-cdk versions
| Removed | Replacement |
|---|---|
modelProvider.defaultModel | modelProvider.models.fast (required) |
Replace defaultModel: "gpt-4.1-mini" with models: { fast: "gpt-4.1-mini" }.
Required nested fields (openai-like)
| 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.models.fast | Model ID for the fast tier (MODEL_FAST_NAME). |
Required nested fields (bedrock)
| Field | Why it is required |
|---|---|
customDomain.domainName | Same as openai-like — public HTTPS origin. |
customDomain.hostedZoneId | Same as openai-like — DNS/TLS/SES records. |
modelProvider.models.fast | Bedrock model ID for the fast tier (MODEL_FAST_NAME). Enable it in the Bedrock console for the stack region. |
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. Aurora and Neptune both usedb.t4g.medium— AWS’s minimum supported burstable size for these engines (dev/test oriented).medium: better for sustained ingestion/reindex workloads where worker and codesearch need more headroom. Aurora usesdb.t4g.large; Neptune uses memory-optimizeddb.r6g.large.large: for high-ingestion and lower-latency expectations, with bigger tasks, scaled backend/worker replicas, anddb.r6g.xlargefor both Aurora and Neptune.
See the package README sizing table for the full preset matrix.
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.