Skip to content

Deployment

VentureKit applications are deployed to AWS using CDK through the vk CLI.

  • AWS credentials configured (aws configure or environment variables)
  • Node.js >= 20
  • VentureKit project initialized with vk init

Start a local development server with hot-reloading:

Terminal window
vk dev

This starts a fully local development server that:

  • Serves your routes via HTTP (no AWS credentials needed)
  • Starts Docker Compose services for Postgres, Redis, and MinIO
  • Loads TypeScript handlers at runtime — no build step
Terminal window
vk dev --stage my-dev
Terminal window
vk deploy
Terminal window
vk deploy --stage stage
Terminal window
vk deploy --stage prod

Deploy VentureKit at zero cost using only AWS free-tier services:

config/dev.ts
import type { EnvConfigInput } from '@venturekit/core';
export const dev: EnvConfigInput = {
preset: 'free',
dataSafety: 'relaxed',
};

The free preset automatically:

  • Uses Lambda (1M free requests/month) with 128 MB
  • Uses API Gateway HTTP API (1M free calls/month for 12 months)
  • Substitutes DynamoDB for RDS databases (25 GB always-free)
  • Skips VPC and NAT gateways (not free)
  • Skips ElastiCache — uses DynamoDB-based rate limiting instead
  • Uses SQS (1M free requests/month) and EventBridge (free)

The VENTURE_STAGE environment variable maps to your config files:

StageConfigTypical Preset
devconfig/dev.tsfree or nano
stageconfig/stage.tsmicro
prodconfig/prod.tsmedium or large
Terminal window
# These are equivalent:
vk deploy --stage prod
VENTURE_STAGE=prod vk deploy

Remove deployed resources:

Terminal window
# Remove dev (no confirmation needed)
vk remove
# Remove staging (requires confirmation)
vk remove --stage stage
# Remove production (requires confirmation, skip with --yes)
vk remove --stage prod --yes
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- uses: pnpm/action-setup@v2
with:
version: 9
- run: pnpm install
- run: vk deploy --stage prod
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_REGION: eu-west-1

Configure a custom domain in your environment config:

export const prod: EnvConfigInput = {
preset: 'medium',
api: {
customDomain: {
domainName: 'api.example.com',
certificateArn: 'arn:aws:acm:us-east-1:...',
},
},
};

vk deploy synthesizes a seven-tier multi-stack layout that splits resources by lifecycle. Every stack is named <project>-<stage>-<tier>, and a tier is only created when the intents that need it are declared:

TierOwnsCreated when
networkVPC, subnets, NAT gateways / fck-nat, VPC endpoints, flow logsdatabases, caches, or vpc.required
dataRDS + master secret (+ the opt-in RDS Proxy), ElastiCache, the runtime DynamoDB tables (rate limits, idempotency, sagas), S3 bucketsdatabases, storage, caches, tables — the runtime tables always
identityCognito User Pools, app clients, federated-provider secrets, custom-sender KMS keysauth
configsecrets[] (SSM or Secrets Manager) and billing[] credential placeholderssecrets / billing
messagingSQS queues + DLQs, SES identities + configuration sets, SNS events topics, EventBridge busesqueues / notify / events
edgeHTTP API shell, custom domains + ACM certificates, CloudFrontalways (HTTP API)
appEvery Lambda (routes, functions, queue consumers, crons, WebSocket, notify dispatcher, bounce handler, migration runner), their execution roles, the internal-HMAC secret, route registrations, event-source mappings, EventBridge rules, log groupsalways

Lower tiers publish every cross-stack value to SSM Parameter Store under /venturekit/<project>/<stage>/<tier>/…; inside one deploy CDK wires the references directly, and the SSM copies let independent deploys, external auth intents, cross-venture event buses and operator tooling read VentureKit state.

Besides the per-intent variables (AUTH_<ID>_*, STORAGE_<ID>_BUCKET, NOTIFY_<ID>_*, …), the app tier injects a fixed set into every function:

VariableValueRead by
VENTURE_PROJECT_NAME / VENTURE_STAGEThe project and stageEverything that reconstructs a resource name
VENTURE_RATE_LIMIT_TABLE<project>-rate-limits-<stage>createDefaultRateLimiter()
VENTURE_IDEMPOTENCY_TABLE / VENTURE_SAGA_TABLE<project>-idempotency-<stage> / <project>-sagas-<stage> — provisioned by the data tier for every preset, one per project and stagecreateDynamoDBIdempotencyStore() / createDynamoDBSagaStore() — see Idempotency and Saga
VENTURE_INTERNAL_HMAC_SECRET_ARNThe ARN of venturekit/<project>/<stage>/internal-hmac, a 256-bit key generated at deployinvoke() and handler() fetch it once per container; the value never appears in a function’s configuration, and only the execution roles hold secretsmanager:GetSecretValue on it
NODE_OPTIONS=--enable-source-mapsStack traces point at .ts sourcesCloudWatch

Two shapes are deliberately not default because they cost money or widen a trust boundary until you ask: databases[].proxy (RDS Proxy, ≈ $22/month per database — see the Database Intent) and the Cognito admin grants below.

By default every Lambda shares one execution role, and when an auth intent is declared that role carries the Cognito admin actions (AdminCreateUser, AdminSetUserPassword, AdminInitiateAuth, …) the sign-up, federated, passwordless and forgot-password helpers need — a synth-time warning reminds you. Declaring auth[].adminRoutes splits it: vk deploy provisions a second role that carries those grants and attaches it only to the functions whose API path ('/auth/**') or id ('fn:cognito-user-migration') matches, so a bug in an unrelated public route can never be turned into “reset anyone’s password”. Every other grant (tables, buckets, queues, the HMAC secret) is applied to both roles. See the Auth Intent for the matching rules and vk doctor --stage <stage> to check a stage’s readiness before deploying.

  • Independent lifecycles. A typical week churns Lambda code dozens of times but never touches RDS. Only the app tier turns over on a normal deploy; the stateful tiers stay untouched (and the network tier changes roughly once a year).
  • Blast-radius isolation. A bug in your edge config can’t take the database with it during a failed rollback — each tier rolls back independently, and vk deploy’s pre-flight inspects every tier’s state before synthesising.
  • Cleaner ops boundaries. The data tier is owned by your DBA / SRE; the app tier by your application engineers. The stack split mirrors that ownership.

All tiers apply the same retention rules under dataSafety: 'strict':

  • Every stateful resource (RDS, S3, Cognito, Secrets, ElastiCache, EventBridge buses, DLQs) carries DeletionPolicy: Retain + UpdateReplacePolicy: Retain.
  • Native deletion-protection is enabled where AWS supports it (RDS, DynamoDB).
  • vk remove --stage prod orphans those resources rather than destroying them.

The retention guarantees are tested independently per stack tier. See @venturekit/infra’s *-stack.test.ts files for the exact invariants pinned by tests.

Migrating a legacy single-stack deployment

Section titled “Migrating a legacy single-stack deployment”

Deployments created by pre-release versions that synthesised one <project>-<stage> stack cannot be flipped to the tiered layout by a config change — CloudFormation does not split one stack into many. The cutover requires cdk import per stateful resource, executed in order. See examples/multistack-migration-runbook.md in the VentureKit monorepo for the canonical pattern (RETAIN-flip → detach → cdk import → smoke test → decommission legacy).

After deployment, the CLI displays:

  • API URL — your API Gateway endpoint
  • WebSocket URL — if WebSocket is enabled
  • Stage and environment information
✅ Deployed to prod
API URL: https://abc123.execute-api.eu-west-1.amazonaws.com