StaticSite Stack¶
The StaticSite class is a CDK stack for deploying static websites with authentication, scheduled rebuilds, and serverless serving.
Overview¶
StaticSite deploys a static website using:
- S3 for storing built content
- Lambda (container-image) for building the site on a schedule
- Lambda (container-image) for serving files with
cognito-authauthorization - ALB with HTTPS and Cognito authentication
- EventBridge for scheduled rebuilds
- WAF integration
Architecture¶
┌──────────────────────────────────────────────────────────────────────┐
│ Static Site Stack │
│ │
│ ┌────────┐ ┌─────┐ ┌──────────────┐ ┌───────────────────┐ │
│ │Route53 │───▶│ ALB │───▶│ Cognito Auth │───▶│ Serve Lambda │ │
│ │A Record│ │ │ │ (if enabled) │ │ (container-image) │ │
│ └────────┘ │ WAF │ └──────────────┘ │ │ │
│ └─────┘ │ • cognito-auth │ │
│ │ • /.auth/user │ │
│ │ • authZ check │ │
│ │ • S3 proxy │ │
│ └────────┬──────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────┐ ┌───────────────────┐ ┌───────────────────┐ │
│ │ EventBridge │───▶│ Build Lambda │───▶│ S3 Bucket │ │
│ │ (schedule) │ │ (container-image) │ │ (static files) │ │
│ └───────────────┘ └───────────────────┘ └───────────────────┘ │
│ ▲ │
│ ┌───────────────────────────┐│ │
│ │ Custom Resource ││ │
│ │ (auto-invoke on deploy) │┘ │
│ └───────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ Shared Infrastructure (from BaseWebStack) │ │
│ │ • Route53 subdomain hosted zone + NS delegation │ │
│ │ • ACM certificate (DNS validated) │ │
│ │ • ACM cleanup Lambda (Custom Resource) │ │
│ └────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘
On deploy, a Custom Resource auto-invokes the build Lambda so the site is immediately populated.
StaticSite¶
gds_idea_cdk_constructs.static_site.stack.StaticSite
¶
Bases: BaseWebStack
A static site stack served by Lambda from S3 with ALB and optional auth.
Deploys a static website using: - S3 bucket for built content - Serve Lambda that proxies requests from ALB to S3 (with optional authZ) - Build Lambda (container-image) that runs the site build and uploads to S3 - ALB with HTTPS and optional Cognito authentication - EventBridge schedule for periodic rebuilds (optional) - Custom Resource to auto-invoke build on deploy
Source code in src/gds_idea_cdk_constructs/static_site/stack.py
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 | |
__init__
¶
__init__(
scope: Construct,
deployment_config: DeploymentConfig,
app_config: AppConfig,
authentication: AuthType = AuthType.INTERNAL_ACCESS,
docker_context_path: str = ".",
dockerfile_path: str = "site_src/Dockerfile",
static_site_props: StaticSiteProperties | None = None,
task_role: Role | None = None,
disable_waf: bool = False,
) -> None
Initialize a StaticSite stack.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
scope
|
Construct
|
The CDK app or stack to create this stack within. |
required |
deployment_config
|
DeploymentConfig
|
Environment-specific configuration including VPC, domain name, and AWS resource identifiers. |
required |
app_config
|
AppConfig
|
Application configuration including name and framework. |
required |
authentication
|
AuthType
|
Authentication strategy to use. Defaults to AuthType.INTERNAL_ACCESS. |
INTERNAL_ACCESS
|
docker_context_path
|
str
|
Path to the Docker build context directory containing the site source and Dockerfile. |
'.'
|
dockerfile_path
|
str
|
Path to the Dockerfile relative to docker_context_path. Defaults to "site_src/Dockerfile". |
'site_src/Dockerfile'
|
static_site_props
|
StaticSiteProperties | None
|
Configuration for build and serve behaviour. Required — must provide at minimum a build_command. |
None
|
task_role
|
Role | None
|
Custom IAM role for the Lambda functions. If None, a role will be created with appropriate permissions. |
None
|
disable_waf
|
bool
|
Disable WAF association with the ALB. Defaults to False. |
False
|
Example
Basic usage with internal access authentication::
from aws_cdk import Duration, aws_events as events
app = App()
deployment_config = DeploymentConfig(cdk_env)
app_config = AppConfig(app_name="my-docs", framework="static")
StaticSite(
app,
deployment_config,
app_config,
authentication=AuthType.INTERNAL_ACCESS,
docker_context_path="site_src",
dockerfile_path="site_src/Dockerfile",
static_site_props=StaticSiteProperties(
build_command="npx @11ty/eleventy --output=/tmp/_site",
build_schedule=events.Schedule.rate(Duration.hours(6)),
),
)
Source code in src/gds_idea_cdk_constructs/static_site/stack.py
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 | |
StaticSiteProperties¶
gds_idea_cdk_constructs.static_site.props.StaticSiteProperties
dataclass
¶
Configuration properties for a StaticSite stack.
Controls build Lambda behaviour, schedule, and serve Lambda settings.
Source code in src/gds_idea_cdk_constructs/static_site/props.py
build_command
instance-attribute
¶
The shell command to run inside the build container (e.g. 'npx eleventy').
build_output_dir
class-attribute
instance-attribute
¶
Directory containing built output. Must be under /tmp/ since Lambda filesystem is read-only. Defaults to '/tmp/_site'.
build_schedule
class-attribute
instance-attribute
¶
EventBridge schedule for periodic rebuilds. Use events.Schedule.rate() or events.Schedule.cron(). If None, no schedule is created.
Examples:
events.Schedule.rate(Duration.hours(6)) events.Schedule.cron(hour="6", minute="0")
build_timeout
class-attribute
instance-attribute
¶
Lambda timeout in seconds for the build function (max 900).
build_memory_size
class-attribute
instance-attribute
¶
Memory in MB allocated to the build Lambda.
build_environment_variables
class-attribute
instance-attribute
¶
Additional environment variables passed to the build Lambda.
clean_on_build
class-attribute
instance-attribute
¶
Remove stale files from S3 after build. Files uploaded by the current build are kept; all others are deleted unless protected by keep_prefixes. Set to False if external processes write to the same bucket.
keep_prefixes
class-attribute
instance-attribute
¶
S3 key prefixes to never delete during cleanup. Useful when external processes (ETL, data pipelines) write to the same bucket under known prefixes. Only relevant when clean_on_build=True. Example: ['data/', 'uploads/']
serve_memory_size
class-attribute
instance-attribute
¶
Memory in MB allocated to the serve Lambda.
index_document
class-attribute
instance-attribute
¶
Default document served for directory requests (e.g. '/' serves '/index.html').
error_document
class-attribute
instance-attribute
¶
Document served for 404 responses. Set to None to return a generic error.
Usage Examples¶
Basic Example (Eleventy)¶
from aws_cdk import App, Duration, Environment, aws_events as events
from gds_idea_cdk_constructs import AppConfig, DeploymentConfig
from gds_idea_cdk_constructs.static_site import AuthType, StaticSite, StaticSiteProperties
app = App()
cdk_env = Environment(account="992382722318", region="eu-west-2")
StaticSite(
app,
DeploymentConfig(cdk_env),
AppConfig(app_name="my-docs", framework="static"),
authentication=AuthType.INTERNAL_ACCESS,
docker_context_path="site_src",
dockerfile_path="Dockerfile",
static_site_props=StaticSiteProperties(
build_command="npx @11ty/eleventy --output=/tmp/_site",
build_output_dir="/tmp/_site",
build_schedule=events.Schedule.rate(Duration.hours(6)),
),
)
app.synth()
Public Site (No Authentication)¶
StaticSite(
app,
DeploymentConfig(cdk_env),
AppConfig(app_name="public-docs", framework="static"),
authentication=AuthType.NONE,
docker_context_path="site_src",
dockerfile_path="Dockerfile",
static_site_props=StaticSiteProperties(
build_command="npx @11ty/eleventy --output=/tmp/_site",
build_output_dir="/tmp/_site",
),
)
MkDocs Site (Python)¶
StaticSite(
app,
DeploymentConfig(cdk_env),
AppConfig(app_name="team-docs", framework="static"),
authentication=AuthType.INTERNAL_ACCESS,
docker_context_path="docs_src",
dockerfile_path="Dockerfile",
static_site_props=StaticSiteProperties(
build_command="mkdocs build --site-dir /tmp/_site",
build_output_dir="/tmp/_site",
build_schedule=events.Schedule.rate(Duration.hours(6)),
),
)
Dockerfile Structure¶
The static site uses a multi-stage Dockerfile shared between the dev container and the build Lambda:
# Base stage: install build tools and dependencies
FROM node:20-slim AS base
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
# Development: used by devcontainer for local development
FROM base AS development
EXPOSE 8080
CMD ["npx", "@11ty/eleventy", "--serve", "--port=8080"]
# Build Lambda: runs the build and uploads to S3
FROM public.ecr.aws/lambda/python:3.12 AS build
RUN dnf install -y nodejs20 npm
COPY --from=base /app /var/task/site
COPY handler.py /var/task/
WORKDIR /var/task/site
CMD ["handler.handler"]
Lambda filesystem is read-only
Lambda can only write to /tmp. Always direct build output to /tmp/ (e.g., --output=/tmp/_site) and set build_output_dir to the same path.
Build Handler¶
The handler.py in your project is a construct-managed file. It:
- Runs the configured
BUILD_COMMANDvia subprocess - Walks the
BUILD_OUTPUT_DIRdirectory - Uploads all files to S3 with correct Content-Type headers
You don't need to write this file — it's provided by the construct (managed by idea-app).
User Claims Endpoint¶
When authentication is enabled, the serve Lambda exposes a /.auth/user endpoint that returns the authenticated user's claims as JSON.
Request¶
Response¶
{
"sub": "abc123",
"email": "user@example.gov.uk",
"name": "Jane Smith",
"given_name": "Jane",
"family_name": "Smith",
"groups": ["gds-idea", "my-app-admins"],
"is_admin": true,
"email_domain": "example.gov.uk",
"email_verified": true
}
Usage in Static Site JavaScript¶
<script>
fetch('/.auth/user')
.then(r => r.ok ? r.json() : null)
.then(user => {
if (user) {
document.getElementById('user-email').textContent = user.email;
document.getElementById('user-name').textContent = user.name;
}
});
</script>
This endpoint:
- Returns user claims via
cognito-auth(includes groups from the Cognito access token) - Sets
Cache-Control: no-store(never cached) - Returns
404forAuthType.NONE(no authentication configured) - Does not require an additional authentication step (ALB already authenticated the user)
Clean Builds¶
By default, the build Lambda removes stale files from S3 after uploading new content. This ensures that deleted or renamed pages don't linger.
Default behaviour (clean_on_build=True)¶
After uploading the build output, any S3 objects that were not part of the current build are deleted. New content is uploaded first, so there is no downtime.
Protecting external files (keep_prefixes)¶
If external processes (ETL pipelines, data uploads) write to the same bucket, protect those files with keep_prefixes:
StaticSiteProperties(
build_command="npx @11ty/eleventy --output=/tmp/_site",
build_output_dir="/tmp/_site",
keep_prefixes=["data/", "uploads/"],
)
Files under data/ and uploads/ will never be deleted during cleanup.
Disabling cleanup entirely¶
If you don't want the build to delete anything:
StaticSiteProperties(
build_command="npx @11ty/eleventy --output=/tmp/_site",
build_output_dir="/tmp/_site",
clean_on_build=False,
)
Old files will persist until manually removed.
Caching¶
The serve Lambda uses two complementary caching strategies to minimise latency and reduce costs.
HTTP Cache-Control headers (browser-side)¶
Response headers tell the browser what to cache:
| File type | Header | Behaviour |
|---|---|---|
HTML (.html) |
max-age=0, must-revalidate |
Always revalidates with server |
Hashed assets (.js, .css, fonts) |
max-age=31536000, immutable |
Cached for 1 year |
| Other files | max-age=3600 |
Cached for 1 hour |
This reduces Lambda invocations per user — the browser serves cached assets locally.
In-memory LRU cache (Lambda-side)¶
S3 file reads are cached in Lambda memory using functools.lru_cache. Once a file is read from S3, subsequent requests within the same warm Lambda execution environment are served from memory — no S3 API call.
Configurable via CACHE_MAX_SIZE environment variable (default: 128 files).
Trade-offs:
- After a rebuild, previously-cached files may serve stale content until the Lambda environment recycles (typically seconds to minutes)
- Missing files (404s) are not cached — every request for a missing file re-checks S3, so a file that appears after a delayed build is served on the very next request
- Each cached file consumes Lambda memory (bounded by
maxsize)
For full rationale, see ADR-001: Use LRU cache for S3 reads.
Created Resources¶
| Resource | Purpose |
|---|---|
| S3 Bucket | Stores built static site content |
| Serve Lambda | Proxies files from S3, handles authZ and /.auth/user |
| Build Lambda (container-image) | Runs the site build and uploads output to S3 |
| Application Load Balancer | HTTPS termination, Cognito auth action |
| Target Group (Lambda) | Routes ALB traffic to the serve Lambda |
| EventBridge Rule | Triggers scheduled rebuilds (if configured) |
| Custom Resource | Auto-invokes build on deploy |
| Route53 Hosted Zone | Subdomain DNS |
| ACM Certificate | TLS certificate with DNS validation |
| WAF Association | Security (enabled by default) |
CloudFormation Outputs¶
- ApplicationURL — HTTPS URL for the static site
- ContentBucketName — S3 bucket name (for manual uploads or debugging)
- BuildLambdaArn — ARN of the build Lambda (for manual invocation)
- TaskRoleARN — IAM role ARN (can be assumed in DEV)
- CognitoClientId (Cognito auth only) — OAuth2 client ID