Back to notes

Everything is defined. S3, CloudFront, CodePipeline, CodeBuild, Route 53, security headers. Time to deploy the site stack. This follows the same Sync from Git process from post 6, but with a bigger template and more parameters.

The Deployment File

Your repo needs a deployment file for the site stack, separate from the cert stack’s deployment file. This file tells CloudFormation which template to use and what parameter values to pass.

template-file-path: infra.yaml
parameters:
  DomainName: example.net
  GitHubRepository: your-org/your-repo
  HostedZoneId: your-hostedzone-id

template-file-path points to your site template relative to the repo root.

DomainName is your domain. This value flows into the CloudFront aliases, the Route 53 records, and the SiteUrl output. The S3 bucket name is auto-generated and the certificate import name is fixed, so neither uses it.

GitHubRepository is the owner/repo format for your GitHub repository. The pipeline’s Source stage uses it to pull from GitHub.

HostedZoneId is the Route 53 hosted zone ID for your domain. Same value you used for the cert stack. Find it in the Route 53 console under Hosted zones.

The connection ARN is not a parameter. The site stacks import it from the connection stack (post 2) using !ImportValue Example-GitHubConnectionArn.

Creating the Stack

The process is the same as post 6.

  1. Go to CloudFormation > Create stack and choose Sync from Git.
  2. Select the same CodeConnection from post 2.
  3. Enter your repository name, branch (main), and the deployment file path for the site stack.
  4. Select the IAM role (CloudFormationGitSyncRole from post 3). This is the same complete role you used for the cert stack. It already covers everything this site stack creates, because the site stack’s service roles live under the /site/ path the role scopes to.
  5. CloudFormation reads the deployment file, finds the template, and starts the deployment.

What Happens

CloudFormation creates the resources in dependency order:

  1. S3 buckets for the site and pipeline artifacts.
  2. IAM roles for CodePipeline, CodeBuild, and the deploy project, all under the /site/ path.
  3. CloudFront OAC, the security headers policy, and the CloudFront Function for SPA routing (React) or subdirectory index rewriting (Astro), plus the www-to-apex redirect on the apex-domain templates (Astro and React inline, plain static standalone).
  4. CloudFront distribution with the OAC, certificate, security headers, and function attached.
  5. S3 bucket policy granting CloudFront read access.
  6. CodeBuild projects for build and deploy.
  7. CodePipeline with Source, Build, and Deploy stages.
  8. Route 53 alias records (A and AAAA for the apex, plus www on the apex-domain templates) pointing your domain to CloudFront.

The empty-bucket Lambda and its two custom resources from post 16 are created alongside the buckets, so deleting the stack empties and removes both the website and artifact buckets cleanly.

The stack takes a few minutes. Once it reaches CREATE_COMPLETE, the pipeline triggers automatically on the next push to main.

Verifying

Push a change to your repo’s main branch. Then check:

  1. CodePipeline in the console shows the pipeline running through Source, Build, and Deploy stages.
  2. CodeBuild logs show the build output and the S3 sync commands.
  3. Your site loads at https://your-domain.com with the custom domain and HTTPS.
  4. Browser DevTools (Network tab) shows the security headers on responses.

If the pipeline fails at the Deploy stage, check the CodeBuild logs for the deploy project. Common issues are missing S3 permissions or an incorrect bucket name.

Updating on Push

From here on, pushing changes to main does two things. Template changes trigger CloudFormation to update the stack through Git Sync. Application code changes trigger the pipeline to build and deploy your site. Both are fully automated.

Outputs

Every site template ends with an Outputs section. Each value shows up in the CloudFormation console’s Outputs tab after deployment, so you don’t have to dig through the Resources tab to find it.

Outputs:
  CloudFrontDistributionId:
    Description: CloudFront distribution ID
    Value: !Ref CloudFrontDistribution

  CloudFrontDomainName:
    Description: CloudFront distribution domain name
    Value: !GetAtt CloudFrontDistribution.DomainName

  WebsiteBucketName:
    Description: S3 bucket hosting the website assets
    Value: !Ref S3WebsiteBucket

  SiteUrl:
    Description: Live site URL
    Value: !Sub 'https://${DomainName}'

CloudFrontDistributionId and CloudFrontDomainName help debug cache or DNS issues; the distribution ID is what you pass to a manual create-invalidation. WebsiteBucketName shows the auto-generated bucket name. SiteUrl gives you a clickable link to the live site.

These are plain outputs for visibility: they display in the console but are not exported, so no other stack imports them. The distribution is only used within this stack, so there’s nothing to export.

Complete Templates

Six example repos consolidate every resource from the series into a single deployable template per stack. Pick the site repo that matches your framework.

Deploy the connection, role, and cert stacks first. The site stacks import the connection and cert exports.