Skip to content

jeffgrosse/aws-sam-static-website

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

aws-sam-static-website

A reusable AWS SAM template that deploys a static website (apex + optional www) behind CloudFront with HTTPS, a custom domain, and a private S3 origin — using S3 + CloudFront + ACM + Route 53. No servers, no OAI (the deprecated predecessor to Origin Access Control), no click-ops.

The template itself is fully parameterized — clone it, point it at your own domain, and deploy. See docs/ARCHITECTURE.md for the diagram, the OAC circular-dependency fix, and the reasoning behind each parameter and condition. See docs/CUTOVER-GOTCHAS.md for a real DNS/alias gotcha hit while cutting a domain over onto a distribution shaped like this one.

Why SAM instead of CDK / Terraform / click-ops

This repo (SAM) CDK Terraform Click-ops
Source of truth One YAML file, readable top to bottom TypeScript/Python that generates CloudFormation HCL, own state file Console clicks, undocumented
Drift detection Native (aws cloudformation detect-stack-drift) Same (compiles to CloudFormation) Requires terraform plan + state file discipline None
Extra tooling AWS CLI + SAM CLI only Node/Python + CDK CLI Terraform CLI + a state backend None, which is the problem
Best for A small, forkable reference stack like this one Large multi-stack apps with real programming-language logic Multi-cloud or org-wide infra-as-code Never, for anything you want to reproduce

For a single-purpose stack like this — one bucket, one distribution, one cert, a handful of DNS records — a plain CloudFormation/SAM template is the least machinery that still gives you repeatability, drift detection, and a one-command sam delete teardown. CDK and Terraform both earn their overhead at larger scale; this isn't that.

Prerequisites

  • An AWS account, and the AWS SAM CLI installed (sam --version; see AWS's install docs).
  • A domain you own, with an existing Route 53 public hosted zone for it. This template does not create the hosted zone — bring your own.
    • If your domain is registered elsewhere (e.g. Namecheap) and you're creating the hosted zone in Route 53 for the first time: create the hosted zone first, note the four NS records Route 53 gives you, then log into your registrar (Namecheap, etc.) and replace its default nameservers with those four. NS propagation can take anywhere from minutes to ~48 hours. ACM's DNS validation (below) will not succeed until the NS swap has propagated.
    • If the zone is already delegated to Route 53, skip this step.

Verify the template (pre-deploy)

Lint the template before deploying or tagging a release:

bash tests/validate.sh

This runs sam validate --lint and exits non-zero on any error. (See "Verify" below for post-deploy checks against the live stack.)

Deploy

This template must be deployed to us-east-1. CloudFront only accepts ACM certificates issued in us-east-1, and this template creates the certificate in whatever region you deploy the stack to — deploying elsewhere will fail fast against a template Rules check rather than failing later during certificate creation.

sam build
sam deploy --guided

--guided will prompt for:

Parameter Example Notes
SourceDomain example.com apex domain
HostedZoneId Z0123456789ABCDEFGHIJ (find via aws route53 list-hosted-zones) must already exist
IncludeWww true also serves www.example.com
IndexDocument index.html default root object
ErrorDocument error.html served on a missing object
ErrorResponseCode 404 set to 200 (with ErrorDocument=index.html) only for a single-page app that handles routing client-side

Confirm the region prompt is us-east-1. Answers are saved to samconfig.toml (gitignored — see samconfig.toml.example for the format if you'd rather write it by hand and skip --guided on subsequent deploys).

The stack takes a few minutes, mostly waiting on ACM DNS validation and CloudFront distribution propagation.

Post-deploy: uploading site content

sam deploy provisions infrastructure only — it does not upload site/ to S3 (see docs/ARCHITECTURE.md for why). After the stack is up:

# Grab the bucket name and distribution ID from stack outputs
BUCKET=$(aws cloudformation describe-stacks --stack-name aws-sam-static-website \
  --query "Stacks[0].Outputs[?OutputKey=='WebsiteBucketName'].OutputValue" --output text)
DIST_ID=$(aws cloudformation describe-stacks --stack-name aws-sam-static-website \
  --query "Stacks[0].Outputs[?OutputKey=='DistributionId'].OutputValue" --output text)

# Upload content
aws s3 sync site/ "s3://$BUCKET/" --delete

# Invalidate the edge cache so the new content is served immediately
aws cloudfront create-invalidation --distribution-id "$DIST_ID" --paths "/*"

Repeat this two-command sequence any time site content changes — no sam deploy required unless you're changing infrastructure.

Verify

Once the stack is CREATE_COMPLETE and DNS has propagated:

# DNS: confirm the apex and www resolve to a CloudFront edge
dig +short sourcedomain.com
dig +short www.sourcedomain.com

# HTTP: confirm HTTPS, HTTP/2, and a 200
curl -I https://sourcedomain.com

# TLS: confirm the cert covers apex + www and isn't expired
curl -vI https://sourcedomain.com 2>&1 | grep -A2 "subject:\|expire"

You should see HTTP/2 200. If DNS hasn't propagated yet but you want to confirm the distribution itself is working, curl -I the DistributionDomainName stack output directly (e.g. https://d123abcxyz.cloudfront.net) — note this will 403 unless the domain's alias is already attached, since the origin only serves the aliased hostnames' cert.

Cost estimate

Roughly $0.50–1/month for a low-traffic site:

  • Route 53 hosted zone: $0.50/month (if you don't already have one for other records in this zone).
  • S3: storage + requests for a small static site are pennies.
  • CloudFront: PriceClass_100 data transfer/requests — negligible at personal-site volume, largely covered by the always-free tier.
  • ACM certificate: free.

If you already have a Route 53 hosted zone for this domain (e.g. it also hosts email records), this stack adds close to nothing to your bill.

Cleanup

# Empty the bucket first - CloudFormation will not delete a non-empty bucket
BUCKET=$(aws cloudformation describe-stacks --stack-name aws-sam-static-website \
  --query "Stacks[0].Outputs[?OutputKey=='WebsiteBucketName'].OutputValue" --output text)
aws s3 rm "s3://$BUCKET/" --recursive

sam delete

This removes the CloudFront distribution, S3 bucket, ACM certificate, and the four Route 53 record sets this stack created. It does not delete the Route 53 hosted zone itself (BYO, not owned by this stack).

Region lock

This template only deploys to us-east-1 — see "Deploy" above and docs/ARCHITECTURE.md for why.

Security / repo hygiene

No secrets, ARNs, or account IDs are committed. samconfig.toml (which would contain your real HostedZoneId and domain names) is gitignored — samconfig.toml.example shows the format with placeholder values.

About

Production-ready AWS SAM template for deploying static websites with S3, CloudFront, ACM, and Route 53. One-command deployment with infrastructure as code.

Topics

Resources

License

Stars

1 star

Watchers

0 watching

Forks

Packages

 
 
 

Contributors