AWS CDK constructs for building production AI systems on AWS. Part of the nanohype ecosystem alongside the template catalog and the protohype subsystems built from it.
Every construct here encodes a decision — fail-secure defaults, prod-vs-staging shape, how ingress is scoped, what gets retained on teardown. Consume them when you want the decision already made; reach for raw aws-cdk-lib when you need a different one.
npm install github:nanohype/cdk-constructs#v0.1.0Peer deps: aws-cdk-lib ^2.250.0, constructs ^10.6.0, Node ≥24. The prepare hook runs tsc automatically on install — you get built dist/ with type declarations. Pin to a tag for reproducible deploys; use #main during active development.
EnvelopeKey KMS customer-managed key for envelope encryption
AppSecrets Secrets Manager with seed-placeholders + preserve-on-update
BedrockLoggingDisabled Deploy-time assert: Bedrock invocation logging off
PgvectorDatabase RDS Postgres 16 with prod-vs-staging defaults
DynamoTable PAY_PER_REQUEST, TTL, PITR-in-prod, GSI/LSI support
ArchiveBucket S3 audit archive with lifecycle and block-public-access
RedisCluster ElastiCache Redis, single-node dev / multi-AZ prod
WorkerService ECS Fargate long-running worker, no ALB, optional ECS Exec
AlbWithTls ALB with three env-driven TLS shapes
CronSchedule EventBridge Scheduler cron, not legacy Rules
SqsWithDlq Primary + DLQ + depth alarm, FIFO-aware
OtelSidecar ADOT collector as Fargate sidecar
containerFromAsset fromAsset with Platform.LINUX_AMD64 pinned
grantEcsExec SSM-messages grants for ECS Exec
namePrefix <project>-<env> helper
import {
PgvectorDatabase,
RedisCluster,
ArchiveBucket,
EnvelopeKey,
} from "@nanohype/cdk-constructs";
const auditKey = new EnvelopeKey(this, "AuditKey", {
purpose: `${project} audit envelope`,
});
const db = new PgvectorDatabase(this, "Corpus", {
vpc,
computeSecurityGroup: worker.securityGroup,
databaseName: project,
isProd,
});
const redis = new RedisCluster(this, "Ratelimit", {
vpc,
computeSecurityGroup: worker.securityGroup,
replicationGroupId: `${project}-rl-${env}`,
isProd,
});
const audit = new ArchiveBucket(this, "AuditArchive", {
bucketName: `${project}-audit-${env}-${this.account}`,
isProd,
encryptionKey: auditKey.key,
});Your app runs CREATE EXTENSION IF NOT EXISTS vector; on boot against db.credentialSecret — the idempotent pattern. No custom resources, no schema gymnastics.
import {
WorkerService,
OtelSidecar,
BedrockLoggingDisabled,
containerFromAsset,
} from "@nanohype/cdk-constructs";
import * as path from "node:path";
new BedrockLoggingDisabled(this, "DisableBedrockLogging", {
identifier: project,
});
const worker = new WorkerService(this, "Classifier", {
cluster,
image: containerFromAsset(path.join(__dirname, "../..")),
environment: { AWS_REGION: this.region, OTEL_SERVICE_NAME: project },
secrets: {
DB_PASSWORD: ecs.Secret.fromSecretsManager(db.credentialSecret, "password"),
},
enableExecute: true,
});
new OtelSidecar(this, "Otel", {
taskDefinition: worker.taskDefinition,
serviceName: project,
environment: env,
});import { CronSchedule, SqsWithDlq } from "@nanohype/cdk-constructs";
const crawlQueue = new SqsWithDlq(this, "CrawlQueue", {
queueName: `${project}-crawl-${env}`,
});
new CronSchedule(this, "CrawlSecEdgar", {
scheduleName: `${project}-crawl-sec-edgar-${env}`,
scheduleExpression: "cron(0 * * * ? *)",
target: {
arn: crawlQueue.queue.queueArn,
input: JSON.stringify({ source: "sec-edgar" }),
actions: ["sqs:SendMessage"],
},
});import { AlbWithTls } from "@nanohype/cdk-constructs";
const alb = new AlbWithTls(this, "Front", {
vpc,
tls: process.env.CERT_ARN
? {
mode: "byo-cert",
certArn: process.env.CERT_ARN,
domainName: process.env.DOMAIN_NAME!,
}
: process.env.HOSTED_ZONE_ID
? {
mode: "managed-cert",
hostedZoneId: process.env.HOSTED_ZONE_ID,
hostedZoneName: process.env.HOSTED_ZONE_NAME!,
domainName: process.env.DOMAIN_NAME!,
}
: { mode: "http-only" },
});Three shapes. CDK provisions the ACM cert + Route 53 alias for managed-cert. Caller owns DNS for byo-cert. http-only is smoke mode — OAuth callbacks won't work, by design.
import { AppSecrets } from "@nanohype/cdk-constructs";
const appSecrets = new AppSecrets(this, "AppSecrets", {
secretName: `${project}/${env}/app-secrets`,
manualKeys: ["SLACK_BOT_TOKEN", "NOTION_OAUTH_CLIENT_SECRET", "SENTRY_DSN"],
generatedKeys: { STATE_SIGNING_SECRET: { length: 64 } },
});CREATE seeds placeholders (REPLACE_ME_SLACK_BOT_TOKEN) so ECS tasks start clean. Operators run aws secretsmanager put-secret-value out of band. Subsequent cdk deploy calls don't overwrite — the seed template is only applied on CREATE.
Opinionated, not configurable. Every prop that's always the same default in practice IS the default. Props exist only for settings projects actually diverge on — FIFO vs standard, prod vs staging, table key shape, TLS mode. If a knob has one reasonable value in every context we've seen, the knob isn't there.
One construct = one pattern. If two projects use a construct differently, they fork in the consumer stack — the construct doesn't grow a conditional. A construct that serves two patterns eventually serves neither well.
Fail-secure. RETAIN on destructive paths. enableKeyRotation: true on KMS. blockPublicAccess.BLOCK_ALL on S3. deletionProtection: true on prod RDS. publiclyAccessible: false on every data plane. The default is always the paranoid one.
Env-driven prod posture. An isProd prop flips a bundle of defaults (multi-AZ / single-AZ, longer backups, RETAIN vs DESTROY, TLS vs no TLS on Redis). Consumers set it once from their stage config; constructs don't probe env vars themselves.
No runtime side effects at import. Constructs are pure CDK constructs — no file I/O, no network calls, no registry shenanigans.
We're at 0.1.0 for the foundation. APIs may shift within 0.1.x as we add constructs and discover which shapes generalize. Pin to a specific tag; treat main as "nightly" until we cut 0.2.0.
What production stacks still hand-roll:
- Lambda-backed SQS→S3 archive consumer (composes
SqsWithDlq+ a Lambda +ArchiveBucket) - CDK-side pgvector
CREATE EXTENSIONbootstrap (requires bundlingpgin a custom resource — v0.1.0 documents app-side bootstrap as the default) - CloudWatch dashboard helper that accepts widget specs — today stacks build dashboards by hand
- Resource-scoped IAM grant helpers (
grantBedrock,grantDynamoRead, etc.) — today stacks write the PolicyStatement inline - Canary / smoke-test helpers that read stack outputs and probe endpoints post-deploy
All observed in almanac, marshal, palisade, watchtower. Extract when two subsystems converge on the same shape.
git clone git@github.com:nanohype/cdk-constructs.git
cd cdk-constructs
npm install
npm run typecheck
npm test
npm run buildTests use aws-cdk-lib/assertions to check resource properties, counts, and IAM policy shapes — not snapshots. Snapshot churn-to-signal is too low for a construct library.