Set up infrastructure
This guide shows you how to create a static website hosted on S3 with CloudFront distribution, SSL certificate, and custom domain.
Before you begin
- Read the key points from the risk assessment analysis (ROS).
- Set up an infrastructure repository with DNS infrastructure, CI/CD common and Terraform Workflows.
Step 1: Add the package
Add the CloudFront static website package, replace web-example with a stack name that fits your project:
Naming convention
We recommend using the web- prefix when naming a static web application.
Step 2: Configure the package
Configure the package. The settings depend on your application type:
- Single Page Application — the application has one HTML entrypoint. Client-side JavaScript handles routing.
- Static Website — the application maps each URL to a file.
/about/serves/about/index.html.
StackName: "web-example"
cloudfront-static-website-data.StackName: "web-example-data"
Name: "example"
RedirectAllToIndex: true # (1)!
DeploymentPipelineV2:
Enable: true
- By default, CloudFront returns a
403 Forbiddenerror when someone requests a file that doesn't exist in your S3 bucket. Enable this setting to serveindex.htmlinstead of the error page. This allows your Single Page Application (SPA) to handle client-side routing - the SPA receives the request and can display the correct content based on the URL path
StackName: "web-example"
cloudfront-static-website-data.StackName: "web-example-data"
Name: "example"
ErrorObject: 404.html # (1)!
AppendIndexToDirectories: true # (2)!
DeploymentPipelineV2:
Enable: true
- Configure where you will upload your default error page.
- When someone visits a directory URL like
/getting-started/, CloudFront needs to know which file to serve. Enable this to automatically appendindex.htmlto directory requests, so/getting-started/serves/getting-started/index.html.
Batteries included
This template is configured with defaults that work out of the box for Single Page Applications and Static Websites. The configuration options, their descriptions and default values can be viewed in the cloudfront static website README.
Step 3: Install the package
Install the package to generate Terraform files:
The command also creates a data stack (web-example-data) that provisions S3 buckets for your static files and access logs.
Step 4: Apply
We will now create and merge a PR containing our changes. Because of the dependencies between the two stacks one of the plans will expectedly fail. The plan for web-example will show errors reading SSM parameters — this is expected because the data stack hasn't created them yet. On merge, Terraform applies the data stack first, so web-example succeeds.
- Create a pull request with the changes in both
web-exampleandweb-example-data. - Verify that the errors in
web-examplematch the pattern below. The plan fails because it references S3 resources (via SSM parameters) that the data stack hasn't created yet. For a real example, see this plan comment. - Verify that the plan for
web-example-datahas no errors. - Apply both stacks by bypassing merge rules and merging to main. Applying the changes takes about 5 minutes. The majority of the time is spent setting up the CloudFront distribution.
- Verify that Terraform apply from main runs without errors. You should see the creation of S3 buckets for logs and content from
web-example-data, and that theweb-examplestack completed without errors. There will be a Terraform output,service_url, pointing to your website. - Verify the distribution is reachable at the service url from the previous step (e.g.
https://example.pirates-dev.oslo.systems). With no files uploaded yet, you should see anAccessDeniedresponse — this confirms CloudFront and DNS are wired up correctly:
Step 5: Verify the distribution works (optional)
Upload a placeholder index.html so you can confirm S3, CloudFront, and DNS are configured correctly before you add the deployment pipeline.
- Authenticate to the dev AWS account (e.g.,
aws sso login --profile pirates-dev). -
Upload an index.html file:
Replace these values:echo '<html><body><h1>Hello world!</h1></body></html>' \ | aws s3 cp - s3://<environment>-<name>/index.html --content-type "text/html" --cache-control "no-cache"Field Description Example <environment>Your environment name pirates-dev<name>Your website name example -
Refresh the URL from Step 4. You should see the Hello world page instead of the
AccessDeniederror.
The deployment pipeline will overwrite this file later, so no cleanup is needed.
Step 6: Repeat for production
Repeat the above steps for your production environment.
Next step
Create the deployment pipeline to automate deployment of your static site from GitHub Actions.