wf-cdk CloudFront Infrastructure
Deploy production-ready CloudFront distributions using the wf-cdk library — a config-driven AWS CDK construct library with zero boilerplate.
Config-Driven
Write a cdk-config.js + config.json — the CLI handles everything else. No CDK app boilerplate.
Multi-Environment
Per-environment domain, bucket, and zone config via config.json — deploy to dev, qa, prod with one command.
Full Stack in One Config
S3 bucket, ACM cert, cache & response headers policies, CloudFront Function, distribution, Route 53 — all declared together.
Auto-Deploy Assets
Built-in BucketDeployment uploads dist/ to S3 and invalidates CloudFront cache on every deploy.
cdk-config.js documented here. The infrastructure creates itself.
Architecture Overview
The wf-cdk CLI reads your config files at the project root, builds a CDK stack inside its own bundled app, and deploys everything.
Prerequisites
Runtime for CDK CLI and build tools
npm install -g aws-cdk
Configured with valid credentials for target account
Run cdk bootstrap aws://ACCOUNT/us-east-1 once
Set to dev, qa, integration, preprod_uk, prod_uk, or prod_us
Pipeline library @Library('wf-jenkins-lib@cdk-cfn') passes all env vars at build runtime
Project Structure
cdk-cloudfront-sample/
├── src/ # Source static files (edit here)
│ ├── index.html # This documentation page
│ ├── styles.css # Stylesheet
│ └── app.js # Interactive JS
├── dist/ # Build output (npm run build → deployed to S3)
├── cdk-config.js # CDK infrastructure config (REQUIRED)
├── config.json # Per-environment values (REQUIRED)
├── cdk.json # CDK CLI context
├── package.json # Dependencies (includes wf-cdk)
├── tsconfig.json # TypeScript config
├── jenkins/
│ └── Jenkinsfile # CI/CD pipeline
├── __tests__/
│ └── example.test.tsx # Sample test
└── cdk-config.sample # Reference sample from portal
wf-cdk/
├── bin/
│ ├── cdk-deploy.js # CLI entry — wraps `npx cdk`
│ └── cdk-infra.js # CDK app — reads config, builds stack
├── lib/
│ ├── index.js / .d.ts # Public exports (L2 constructs + types)
│ ├── constructs/
│ │ ├── L2/ # Individual resource wrappers
│ │ │ ├── wf-cloudfront # WFCloudFront (Distribution + OAI)
│ │ │ ├── wf-s3 # WFS3 (Bucket + policies)
│ │ │ ├── wf-acm # WFAcm (Certificate)
│ │ │ ├── wf-route53 # WFRoute53Record (Alias A-records)
│ │ │ ├── wf-lambda # WFLambda (Function + IAM)
│ │ │ ├── wf-sns / wf-sqs # WFSNS / WFSQS
│ │ │ ├── wf-alarm / wf-dlq # WFAlarm / WFDLQ
│ │ │ ├── wf-iam-role # WFIamRole
│ │ │ ├── wf-scheduler # WFScheduler (EventBridge)
│ │ │ └── wf-neptune # WFNeptune
│ │ ├── L3/ # Stack orchestrators
│ │ │ ├── cloudfront-stack # CloudFrontStack (this project uses)
│ │ │ ├── async-lambda # AsyncLambda (default)
│ │ │ ├── s3-stack # S3Stack
│ │ │ ├── acm-stack # AcmStack
│ │ │ └── base-l3-stack # BaseL3Stack (abstract)
│ │ └── WFConstruct # Abstract base for all constructs
│ ├── types/ # TypeScript interfaces
│ │ ├── cloudfront.d.ts # CloudFront types (used in config)
│ │ ├── common.d.ts # ConstructType enum
│ │ └── index.d.ts # Re-exports all types
│ ├── const/
│ │ └── defaultValue.js # CloudFront, S3, Lambda defaults
│ └── utils/
│ ├── deployment-environments # Stage/env resolution
│ ├── shared-resources # SSM-based shared infra (hostedZoneId)
│ └── groups # ServiceGroups, ProductGroups enums
├── cdk.json # Bundled CDK context
└── package.json # wf-cdk metadata
Key Project Files
Three files drive the entire infrastructure. Here is what each one does:
cdk-config.js
The CDK infrastructure definition. Reads config.json for per-environment values and shared-resources for hostedZoneId. Defines ACM cert, cache/response policies, CloudFront function, distribution, and deployment.
config.json
Per-environment static values. Each key (dev, qa, etc.) maps to domainName, bucketName, and hostedZoneName. Read by cdk-config.js at deploy time.
cdk.json
CDK CLI context flags. Not used directly by wf-cdk CLI (it overrides --app), but useful for standalone cdk synth runs.
jenkins/Jenkinsfile
CI/CD pipeline using @Library('wf-jenkins-lib@cdk-cfn'). Calls NodeJSCloudfrontCDKPipeline() which sets TARGET_ENVIRONMENT, PRODUCT_GROUP, SERVICE_GROUP, and all other env vars at build runtime.
config.json
Click an environment tab to see the values that get injected into cdk-config.js at deploy time.
View full config.json source
{
"dev": {
"domainName": "cdk-cfn-sample.dev.afreespace.com",
"bucketName": "cdk-cfn-sample-afreespace-com",
"hostedZoneName": "dev.afreespace.com"
},
"qa": {
"domainName": "cdk-cfn-sample.qa.afreespace.com",
"bucketName": "cdk-cfn-sample-qa-afreespace-com",
"hostedZoneName": "qa.afreespace.com"
},
"integration": {
"domainName": "cdk-cfn-sample.integration.afreespace.com",
"bucketName": "cdk-cfn-sample-integration-afreespace-com",
"hostedZoneName": "integration.afreespace.com"
},
"preprod_uk": {
"domainName": "cdk-cfn-sample.preprod.afreespace.com",
"bucketName": "cdk-cfn-sample-preprod-afreespace-com",
"hostedZoneName": "preprod.afreespace.com"
},
"prod_uk": {
"domainName": "cdk-cfn-sample.afreespace.com",
"bucketName": "cdk-cfn-sample-prod-uk-afreespace-com",
"hostedZoneName": "afreespace.com"
},
"prod_us": {
"domainName": "cdk-cfn-sample.us.afreespace.com",
"bucketName": "cdk-cfn-sample-prod-us-afreespace-com",
"hostedZoneName": "us.afreespace.com"
}
}
cdk-config.js
The actual config file used by this project. Annotated inline — click sections to expand.
Imports & Environment Resolution
const cloudfront = require('aws-cdk-lib/aws-cloudfront');
const { ConstructType } = require('wf-cdk/lib/types');
const { WFCloudFrontManagedPolicies } = require('wf-cdk/lib/types/cloudfront');
const { ProductGroups, ServiceGroups } = require('wf-cdk/lib/utils/groups');
const { getSharedResources } = require('wf-cdk/lib/utils/shared-resources');
const { getStageForEnvironment } = require('wf-cdk/lib/utils/deployment-environments');
const shared = getSharedResources();
const stage = getStageForEnvironment().toString();
// Per-environment config
const targetEnv = process.env.TARGET_ENVIRONMENT || 'dev';
const envConfig = require('./config.json');
if (!envConfig[targetEnv]) {
throw new Error(
`No config entry for TARGET_ENVIRONMENT="${targetEnv}".`
+ ` Valid: ${Object.keys(envConfig).join(', ')}`,
);
}
const env = { ...envConfig[targetEnv], hostedZoneId: shared.hostedZoneId };
TARGET_ENVIRONMENT is set by Jenkins at build runtime (via NodeJSCloudfrontCDKPipeline). getSharedResources() reads SSM parameters to get shared infra values like hostedZoneId. getStageForEnvironment() maps the env to a stage name (e.g. prod_uk → prod). The result is merged with static values from config.json.
Security Headers
const SHARED_SECURITY_HEADERS = {
strictTransportSecurity: {
accessControlMaxAgeSec: 63072000, // 2 years
includeSubdomains: true,
preload: true,
override: true,
},
contentTypeOptions: { override: true },
frameOptions: { frameOption: 'DENY', override: true },
xssProtection: { protection: true, modeBlock: true, override: true },
referrerPolicy: {
referrerPolicy: 'strict-origin-when-cross-origin',
override: true,
},
};
Top-Level Config: service, groups, certificate, hostedZone
const cdkConfig = {
service: 'cdk-cfn-sample',
ServiceGroup: ServiceGroups.INFRA,
ProductGroup: ProductGroups.DEVOPS,
constructType: ConstructType.CLOUDFRONT_STACK,
certificate: {
certificateName: `${stage}-cdk-cfn-sample-cert`,
domainName: env.domainName,
hostedZoneId: env.hostedZoneId,
hostedZoneName: env.hostedZoneName,
},
hostedZone: {
hostedZoneId: env.hostedZoneId,
zoneName: env.hostedZoneName,
},
// ... cachePolicies, responseHeadersPolicies, etc.
};
Cache Policy + Response Headers Policy
cachePolicies: [
{
name: `cdk-${stage}-cfn-sample-edge-7d`,
comment: '7-day edge cache for static assets',
defaultTtl: 604800,
maxTtl: 604800,
minTtl: 0,
enableAcceptEncodingBrotli: true,
enableAcceptEncodingGzip: true,
},
],
responseHeadersPolicies: [
{
name: `cdk-${stage}-cfn-sample-browser-60s`,
comment: '60s browser cache with security headers',
corsConfig: {
accessControlAllowOrigins: ['*'],
accessControlAllowHeaders: ['*'],
accessControlAllowMethods: ['GET', 'HEAD', 'OPTIONS'],
accessControlAllowCredentials: false,
originOverride: true,
},
customHeaders: [
{ header: 'Cache-Control', value: 'public, max-age=60, stale-while-revalidate=300', override: true },
{ header: 'Server', value: 'null', override: true },
],
securityHeaders: SHARED_SECURITY_HEADERS,
},
],
CloudFront Function (SPA Routing)
cloudfrontFunctions: [
{
name: `cdk-${stage}-cfn-sample-spa-routing`,
comment: 'SPA routing - rewrite extensionless URIs to /index.html',
runtime: 'cloudfront-js-1.0',
code: `function handler(event) {
var request = event.request;
var uri = request.uri;
var hasFileExtension = /\\.[a-zA-Z0-9]+$/.test(uri);
if (!hasFileExtension) {
request.uri = '/index.html';
}
return request;
}`,
},
],
/about, /dashboard) is rewritten to /index.html at the edge, so the single-page app's client-side router handles it. Requests with extensions (e.g. /styles.css, /app.js) pass through unchanged.
Distribution + Deployment
distributions: [
{
distributionName: 'cdk-cfn-sample',
origin: {
type: 's3',
createBucket: true,
bucketName: env.bucketName,
},
aliases: [env.domainName],
comment: 'CDK CloudFront sample distribution',
defaultRootObject: 'index.html',
httpVersion: cloudfront.HttpVersion.HTTP2,
enableIpv6: false,
priceClass: cloudfront.PriceClass.PRICE_CLASS_ALL,
minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021,
createDnsRecords: true,
defaultBehavior: {
viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
allowedMethods: cloudfront.AllowedMethods.ALLOW_GET_HEAD_OPTIONS,
cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS,
compress: true,
cachePolicyName: `cdk-${stage}-cfn-sample-edge-7d`,
originRequestPolicyId: WFCloudFrontManagedPolicies.originRequest.CORS_S3_ORIGIN,
responseHeadersPolicyName: `cdk-${stage}-cfn-sample-browser-60s`,
functionAssociations: [
{
eventType: cloudfront.FunctionEventType.VIEWER_REQUEST,
functionName: `cdk-${stage}-cfn-sample-spa-routing`,
},
],
},
errorResponses: [
{ httpStatus: 403, responsePagePath: '/index.html', responseHttpStatus: 200, ttl: 60 },
{ httpStatus: 404, responsePagePath: '/index.html', responseHttpStatus: 200, ttl: 60 },
],
deployment: {
sourceDir: './dist',
invalidationPaths: ['/*'],
},
},
],
L2 Constructs — Resource Wrappers
Exported from the package entrypoint (require('wf-cdk')). Each wraps one AWS resource.
WFCloudFront
CloudFront Distribution with S3 or custom origin, OAI, and CfnOutputs.
WFS3
S3 bucket with encryption, block public access, versioning. Supports fromBucketName().
WFAcm
ACM certificate — create with DNS validation or import by ARN.
WFRoute53Record
Route 53 alias A-records. forDistribution() helper for CF aliases.
WFLambda
Lambda with IAM role, esbuild bundling, event sources, VPC support.
WFSQS
SQS queue + DLQ, SNS subscriptions, CloudWatch alarms.
WFSNS
SNS topic with alarms and subscription management.
WFAlarm
CloudWatch alarm with severity mapping (INFO/MAJOR).
WFIamRole
IAM role with inline/managed policies and static factories.
WFScheduler
EventBridge rule — everyHours(), everyDays() helpers + Lambda target.
WFDLQ
Dead Letter Queue with associated CloudWatch alarms.
WFNeptune
Neptune cluster with VPC, security groups, IAM, optional S3 bulk-load.
L3 Constructs — Stack Orchestrators
Used by the CLI via constructType in your config. Each composes multiple L2 constructs.
| constructType | Class | Resources Created |
|---|---|---|
CloudFrontStack | CloudFrontStack | ACM certs, cache policies, response header policies, CF functions, CloudFront distributions, S3 buckets, Route 53 records, BucketDeployment + invalidation |
AsyncLambda | AsyncLambda | Lambda functions, SQS queues, SNS topics, DLQs, EventBridge schedulers |
S3Stack | S3Stack | One or more WFS3 buckets |
AcmStack | AcmStack | Multiple WFAcm certificates |
NeptuneDatabase | NeptuneDatabase | Neptune cluster, VPC config, security groups, SSM parameters |
IoTLoRaWANStack | IoTLoRaWANStack | IoT device/service profiles, topic rules, destinations, SSM outputs |
constructType: ConstructType.CLOUDFRONT_STACK (or the string 'CloudFrontStack'). The CLI forces us-east-1 for CloudFront stacks.
CloudFront Type Definitions
Exported from wf-cdk/lib/types/cloudfront. These define what you can put in cdk-config.js.
CloudFrontStackProps (top-level)
interface CloudFrontStackProps {
service: string;
ServiceGroup: ServiceGroups;
ProductGroup: ProductGroups;
constructType?: ConstructType; // 'CloudFrontStack'
certificate?: WFAcmCertificateConfig;
hostedZone?: WFRoute53HostedZoneConfig;
cachePolicies?: WFCloudFrontCachePolicyConfig[];
responseHeadersPolicies?: WFCloudFrontResponseHeadersPolicyConfig[];
cloudfrontFunctions?: WFCloudFrontFunctionConfig[];
distributions: WFCloudFrontDistributionConfig[]; // REQUIRED
tags?: EnvironmentVariable;
}
WFCloudFrontDistributionConfig
interface WFCloudFrontDistributionConfig {
distributionName: string;
origin: WFCloudFrontS3Origin | WFCloudFrontCustomOrigin;
aliases?: string[];
certificateArn?: string;
defaultRootObject?: string; // Default: 'index.html'
enabled?: boolean;
httpVersion?: HttpVersion;
enableIpv6?: boolean;
priceClass?: PriceClass;
minimumProtocolVersion?: SecurityPolicyProtocol;
comment?: string;
webAclId?: string;
defaultBehavior?: { /* see below */ };
cacheBehaviors?: WFCloudFrontCacheBehavior[];
errorResponses?: WFCloudFrontErrorResponse[];
disableDefaultErrorResponses?: boolean;
createDnsRecords?: boolean;
deployment?: WFCloudFrontDeploymentConfig;
}
Origin Types (S3 + Custom)
interface WFCloudFrontS3Origin {
type: 's3';
bucketName?: string;
bucketArn?: string;
bucketRef?: s3.IBucket;
createBucket?: boolean;
bucketConfig?: WFS3BucketConfig;
accessType?: 'oai' | 'oac';
}
interface WFCloudFrontCustomOrigin {
type: 'custom';
domainName: string;
httpPort?: number;
httpsPort?: number;
protocolPolicy?: OriginProtocolPolicy;
}
interface WFCloudFrontDeploymentConfig {
sourceDir: string; // e.g. './dist'
invalidationPaths?: string[];
exclude?: string[];
prune?: boolean;
}
Cache, Response Headers & Function Config
interface WFCloudFrontCachePolicyConfig {
name: string;
defaultTtl?: number; maxTtl?: number; minTtl?: number;
enableAcceptEncodingBrotli?: boolean;
enableAcceptEncodingGzip?: boolean;
cookieBehavior?: 'none' | 'whitelist' | 'allExcept' | 'all';
queryStringBehavior?: 'none' | 'whitelist' | 'allExcept' | 'all';
}
interface WFCloudFrontResponseHeadersPolicyConfig {
name: string;
corsConfig?: { accessControlAllowOrigins, allowHeaders, allowMethods, ... };
securityHeaders?: { strictTransportSecurity, contentTypeOptions, frameOptions, xssProtection, ... };
customHeaders?: { header: string; value: string; override: boolean; }[];
removeHeaders?: string[];
}
interface WFCloudFrontFunctionConfig {
name: string;
runtime?: 'cloudfront-js-1.0' | 'cloudfront-js-2.0';
code: string; // Inline function source
autoPublish?: boolean;
}
Managed Policy IDs
const WFCloudFrontManagedPolicies = {
cache: {
CACHING_DISABLED: '4135ea2d-6df8-44a3-9df3-4b5a84be39ad',
CACHING_OPTIMIZED: '658327ea-f89d-4fab-a63d-7e88639e58f6',
CACHING_OPTIMIZED_UNCOMPRESSED:'b2884449-e4de-46a7-ac36-70bc7f1ddd6d',
},
originRequest: {
CORS_S3_ORIGIN: '88a5eaf4-2fd4-4709-b370-b4c650ea3fcf',
ALL_VIEWER: '216adef6-5c7f-47e4-b989-5492eafa07d3',
},
};
Environment Variables
| Variable | Required | Description |
|---|---|---|
TARGET_ENVIRONMENT | Required | dev, qa, integration, preprod_uk, prod_uk, prod_us |
PRODUCT_GROUP | Required | Product group enum — e.g. DEVOPS, PF (Platform) |
SERVICE_GROUP | Required | Service group enum — e.g. INFRA, CDK |
CDK_DEPLOY_ACCOUNT | Optional | AWS account ID. Falls back to CDK_DEFAULT_ACCOUNT |
CDK_DEPLOY_REGION | Optional | AWS region. CloudFront stacks are forced to us-east-1 |
ARTIFACTS | Optional | S3 bucket for assets. Enables custom synthesizer with BUILD_NUMBER |
BUILD_NUMBER | Optional | CI build number for asset prefix |
SOURCE_DIR | Optional | Override deployment.sourceDir |
INVALIDATION_PATHS | Optional | Comma-separated paths — overrides config |
NodeJSCloudfrontCDKPipeline() from @Library('wf-jenkins-lib@cdk-cfn'). For local testing, export them in your shell: export TARGET_ENVIRONMENT=dev.
CLI Usage
The wf-cdk binary wraps AWS CDK CLI. It auto-appends --all and --require-approval never.
# Deploy all stacks
TARGET_ENVIRONMENT=dev npx wf-cdk deploy
# Synthesize CloudFormation templates (dry run)
TARGET_ENVIRONMENT=dev npx wf-cdk synth
# Preview changes against deployed
TARGET_ENVIRONMENT=dev npx wf-cdk diff
# List stacks
npx wf-cdk list
# Destroy
TARGET_ENVIRONMENT=dev npx wf-cdk destroy
# Or use npm scripts (set TARGET_ENVIRONMENT first)
npm run cdk:deploy
npm run cdk:synth
npm run cdk:diff
# In Jenkins, env vars are set by the pipeline automatically:
# NodeJSCloudfrontCDKPipeline(additionalChoiceParam, "20", true)
CLI Execution Flow
wf-cdk deploy → parses args, spawns npx cdk deploy --app "node cdk-infra.js"cdk-infra.js reads env vars (set by Jenkins at build runtime)pre-deployment.ts hook (if exists)cdk-config.js (which loads config.json + shared-resources)us-east-1 for CloudFront)CloudFrontStack with merged configdist/ → invalidates cache*.assets.json → uploads manifest.json to S3Step-by-Step Deployment
Install dependencies
npm installSet environment (Jenkins does this automatically)
export TARGET_ENVIRONMENT=dev
export PRODUCT_GROUP=DEVOPS
export SERVICE_GROUP=INFRAIn CI, the Jenkins pipeline (NodeJSCloudfrontCDKPipeline) sets all required env vars automatically.
Bootstrap CDK (first time only)
npx cdk bootstrap aws://ACCOUNT_ID/us-east-1Preview (optional)
npm run cdk:synth # generates CloudFormation in cdk.out/
npm run cdk:diff # shows changes vs deployed stackDeploy
npm run cdk:deployCreates S3 bucket (cdk-cfn-sample-afreespace-com), uploads dist/, provisions ACM cert + CloudFront distribution at cdk-cfn-sample.dev.afreespace.com, creates Route 53 alias, and invalidates the cache.
Built-in Defaults
Applied by WFCloudFront when not overridden in config:
| Property | Default |
|---|---|
defaultRootObject | index.html |
httpVersion | http2 |
enableIpv6 | false |
priceClass | PriceClass_All |
minimumProtocolVersion | TLSv1.2_2021 |
compress | true |
viewerProtocolPolicy | redirect-to-https |
SPA Error Responses (auto-applied)
Unless disableDefaultErrorResponses: true:
| HTTP Status | Response Path | Response Code | TTL |
|---|---|---|---|
| 403 | /index.html | 200 | 10s |
| 404 | /index.html | 200 | 10s |
cdk-config.js for longer edge caching.