> **Page index:** https://km.oslo.systems/llms.txt lists every page on this site, with a link to its Markdown version.

# `cloudfront-static-website`

Static website on S3 and CloudFront, with TLS and a custom domain.

> [!NOTE]
>
> This stack might take a **long time** to both **apply** and **destroy** due to the CloudFront distribution. Deployment typically takes around 5 minutes, while destruction can take 5-6 minutes.

> [!NOTE]
>
> This template also installs the [`cloudfront-static-website-data`](<#cloudfront-static-website-data>) template, which creates the S3 buckets for content and logs. Apply the data stack first.

## Architecture

This template creates the CloudFront distribution and associated resources that work with the S3 buckets created by the [data stack](<#cloudfront-static-website-data>):

- **CloudFront Distribution** - CDN with custom domain and TLS
- **Route53 Record** - DNS alias pointing to CloudFront
- **ACM Certificate** - TLS certificate for custom domain (us-east-1)
- **Deployment** (when `DeploymentPipelineV2.Enable: true`) - Terraform deploys the website bundle with the deployment Lambda function from the [`cicd-common`](<https://km.oslo.systems/terraform-automation/templates/cicd-common/index.md>) template

The S3 buckets (content and logs) are created separately in the data stack to allow for independent lifecycle management.

## Features

- HTTPS-only with custom domain
- Client-side routing support (when `RedirectAllToIndex: true`) - requests that do not match a file get `RootObject` with status `200` (similar to [react-cors-spa](<https://github.com/aws-samples/react-cors-spa>))
- Optional URL rewriting (when `AppendIndexToDirectories: true`) - adds `index.html` to directory requests and URLs without extensions, based on [AWS CloudFront Functions sample](<https://github.com/aws-samples/amazon-cloudfront-functions/tree/main/url-rewrite-single-page-apps>)
- Configurable root and error objects via `RootObject` and `ErrorObject`

## Usage

See these guides in the Golden Path documentation:

- [Create a static website](<https://km.oslo.systems/application/static-website/create-new-application/>)
- [Set up CI/CD for a static website](<https://km.oslo.systems/application/static-website/set-up-cicd/>)

## Variables

| Name | Description | Type | Default | Required |
| --- | --- | --- | --- | --- |
| `Name` | Website name. Used as the subdomain, exposing the site at `https://<Name>.<Environment>.oslo.systems`. | `string` | `n/a` | yes |
| `RedirectAllToIndex` | Serve `RootObject` with status `200` for every request that does not match a file, instead of the `ErrorObject` error page. Needed for single-page apps with client-side routing. | `bool` | `false` | no |
| `RootObject` | The object served when the site root (`/`) is requested. Also the object that `RedirectAllToIndex` and `AppendIndexToDirectories` rewrite requests to. | `string` | `index.html` | no |
| `ErrorObject` | The object served with status `404` when a requested file does not exist. Not used when `RedirectAllToIndex` is enabled. | `string` | `404.html` | no |
| `AppendIndexToDirectories` | Rewrite requests ending in `/` or without a file extension to append `/<RootObject>`, so `/about` serves `about/index.html`. Useful for static site generators that output one `index.html` per directory. | `bool` | `false` | no |
| `DeploymentPipelineV2` | Deploy the website with the next-generation deployment pipeline. Replaces `IamForCicd`. | `map` | `{"Enable": false}` | no |
| `IamForCicd` | Deprecated: use `DeploymentPipelineV2` instead. Lets GitHub Actions in the website's repository (`GitHubRepo`, under the `oslokommune` org) deploy the site. The workflow must run in the GitHub environment `<Environment>-<Name>`. | `map` | `{"Enable": false, "GitHubRepo": null}` | no |

## `cloudfront-static-website-data`

This template also installs the `cloudfront-static-website-data` template into its own stack directory, next to this one.

S3 buckets for the content and the logs of the `cloudfront-static-website` template. That template installs it.

### Resources

- **S3 Content Bucket**: Stores the static website content
- **S3 Logs Bucket**: Stores CloudFront access logs

### Discovery

Other stacks can discover these resources via SSM parameters:

- `/${environment}/cloudfront-static-website/${name}/s3-bucket-id`
- `/${environment}/cloudfront-static-website/${name}/s3-bucket-arn`
- `/${environment}/cloudfront-static-website/${name}/s3-bucket-regional-domain-name`
- `/${environment}/cloudfront-static-website/${name}/logs-s3-bucket-id`

### Destroying Stacks with S3 Content

S3 buckets with versioning enabled (which these are) cannot be destroyed if they contain objects. If you encounter an error like:

```
Error: deleting S3 Bucket: operation error S3: DeleteBucket, 
api error BucketNotEmpty: The bucket you tried to delete is not empty. 
You must delete all versions in the bucket.
```

#### Option 1: Force Destroy (Recommended for complete stack destruction)

If you're destroying the entire stack:

1. Set `force_destroy = true` in your configuration
2. Run `terraform apply`
3. Run `terraform destroy`

#### Option 2: Manual Bucket Cleanup (For partial destruction)

If you've already partially destroyed resources:

1. Use the AWS Console to empty the S3 buckets manually
2. Go to the S3 service in the AWS Console
3. Select the bucket and choose "Empty" from the Actions menu
4. Follow the prompts to delete all object versions
5. Then run `terraform destroy` again

### Variables (`cloudfront-static-website-data`)

| Name | Description | Type | Default | Required |
| --- | --- | --- | --- | --- |
| `Name` | Name for the CloudFront static website data resources | `string` | `n/a` | yes |
| `AutoForwardLogs` | Automatically forward any CloudWatch log groups or S3 logs created by this template to Datadog if the current account is integrated with Datadog (via the `datadog-common` template). | `map` | `{"Enable": true}` | no |
| `AwsProviderVersion` | Version constraint for the `hashicorp/aws` Terraform provider. | `string` | `>= 6.24.0, < 7.0.0` | no |
| `S3EnforceSecureTransport` | Require HTTPS for all requests to S3 buckets created by this template. | `bool` | `false` | no |
