# Create new application

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)](https://github.com/oslokommune/golden-path-docs-internal/blob/main/oppsummering-ros-nytt-cicd-oppsett.md){:target="_blank"}.
- Set up an infrastructure repository with [DNS infrastructure](../../initial-setup-of-your-aws-environment/set-up-dns.md), [CI/CD common](../../initial-setup-of-your-aws-environment/set-up-cicd-common.md) and [Terraform Workflows](../../initial-setup-of-your-aws-environment/set-up-terraform-workflows.md).

## Step 1: Add the package

Add the CloudFront static website package, replace `web-example` with a stack name that fits your project:

```bash title="repo-iac/environments/dev/"
ok pkg add cloudfront-static-website web-example
```

<!-- prettier-ignore-start -->
!!! Note "Naming convention"
    The `web-` prefix is the recommended naming convention for a static web application.
<!-- prettier-ignore-end -->

## 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`.

=== "Single Page Application"

    ```yaml title="repo-iac/environments/dev/web-example/package-config.yml"
    StackName: "web-example"
    cloudfront-static-website-data.StackName: "web-example-data"
    Name: "example"
    RedirectAllToIndex: true # (1)!
    DeploymentPipelineV2:
      Enable: true
    ```

    1. By default, CloudFront returns a `403 Forbidden` error when someone requests a file that doesn't exist in your S3 bucket. Enable this setting to serve `index.html` instead 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

=== "Static Website"

    ```yaml title="repo-iac/environments/dev/web-example/package-config.yml"
    StackName: "web-example"
    cloudfront-static-website-data.StackName: "web-example-data"
    Name: "example"
    ErrorObject: 404.html # (1)!
    AppendIndexToDirectories: true # (2)!
    DeploymentPipelineV2:
      Enable: true
    ```

    1. Configure where you will upload your default error page.
    2. When someone visits a directory URL like `/getting-started/`, CloudFront needs to know which file to serve. Enable this to automatically append `index.html` to directory requests, so `/getting-started/` serves `/getting-started/index.html`.

<!-- prettier-ignore-start -->
!!! note "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](https://github.com/oslokommune/golden-path-boilerplate/blob/main/boilerplate/terraform/cloudfront-static-website/README.md).
<!-- prettier-ignore-end -->

## Step 3: Install the package

Install the package to generate Terraform files:

```bash title="repo-iac/environments/dev/web-example/"
ok pkg install
```

The command also creates a data stack (`web-example-data`) that provisions S3 buckets for your static files and access logs.

## Step 4: Apply

You'll now create and merge a PR containing your changes. Because of the dependencies between the two stacks one of the plans fails as expected. The plan for `web-example` shows 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.

1. Create a pull request with the changes in both `web-example` and `web-example-data`.
1. Verify that the errors in `web-example` match 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](https://github.com/oslokommune/pirates-iac/pull/1515#issuecomment-4268391592){:target="_blank"}.
1. Verify that the plan for `web-example-data` has no errors.
1. 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.
1. 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 the `web-example` stack completed without errors. Terraform produces an output, `service_url`, pointing to your website.
1. Verify the distribution is reachable at the service URL from the previous step, such as `https://example.pirates-dev.oslo.systems`. With no files uploaded yet, you should see an `AccessDenied` response — this confirms CloudFront and DNS are wired up correctly:

   ```xml title="Expected response"
   <Error>
     <Code>AccessDenied</Code>
     <Message>Access Denied</Message>
   </Error>
   ```

## 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.

1. Authenticate to the `dev` AWS account by running `aws sso login --profile pirates-dev`.
1. Upload an index.html file:

   ```bash
   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"
   ```

   Replace these values:

   | Field           | Description           | Example       |
   | --------------- | --------------------- | ------------- |
   | `<environment>` | Your environment name | `pirates-dev` |
   | `<name>`        | Your website name     | `example`     |

1. Refresh the URL from Step 4. You should see the Hello world page instead of the `AccessDenied` error.

The deployment pipeline overwrites 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](set-up-cicd.md) to automate deployment of your static site from GitHub Actions.
