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

# Set up CI/CD

Set up a deployment pipeline that builds your static website once and deploys it to S3/CloudFront across environments.

## Before you begin

- Set up [CloudFront static website infrastructure](<https://km.oslo.systems/application/static-website/create-new-application/index.md>).
- Have a GitHub repository in the `oslokommune` organization for your application (not your IaC repository), preferably prefixed with your team name.

## Step 1: Protect your default branch

Go to **Settings** \> **Custom properties** and set `gp-repository-type` to `app`. Your repository then automatically inherits branch rulesets from the GitHub organization.

After setting the custom property, verify that **Settings** \> **Rulesets** shows rules inherited from the organization.

## Step 2: Create a GitHub Actions environment for production

This restricts production deployments to the default branch. This should **only** be set up for production, not for other environments.

1. Go to **Settings** \> **Environments** \> **New environment**
2. Set the name to match your production environment:

   - For an application repository: `<environment>`, such as `pirates-prod`
   - For an application in a monorepo `<environment>-<app-name>`, such as `pirates-prod-zebra`
3. Under **Deployment branches and tags**, select **Selected branches** and add your default branch (`main` or `master`)

> [!TIP]
>
> **Manual approval**
>
> To enable manual approval, open your GitHub Actions environment for production:
>
> 1. Set **Required reviewers** to your GitHub team
> 2. Click **Save protection rules**

## Step 3: Add configuration files

Create a new branch and add the following configuration files.

### Step 3.1: Add `.gp.cicd.json`

The configuration differs depending on your repository type:

- **Application repository** — the repository contains code for a single application, while the associated infrastructure lives in a separate IaC repository, such as `pirates-iac`.
- **Application monorepo** — the repository contains code for many applications, while the associated infrastructure lives in a separate IaC repository, such as `pirates-iac`.

Download `.gp.cicd.json`:

```
gh api repos/oslokommune/golden-path-templates/contents/templates/gh-cicd-app/.gp.cicd.json \
  --jq '.content' | base64 -d > .gp.cicd.json
```

Update these values (many of which can be found in `common-config.yml` in your infrastructure repository):

| Field | Description | Example |
| --- | --- | --- |
| `<team-name>` | Your team name | `pirates` |
| `<repo-iac>` | Infrastructure-as-code repository name | `pirates-iac` |
| `<dev-aws-account-id>` | AWS account ID for `dev` | `123456789012` |
| `<prod-aws-account-id>` | AWS account ID for `prod` | `987654321098` |
| `<dev-environment-name>` | Name of your `dev` AWS environment | `pirates-dev` |
| `<prod-environment-name>` | Name of your `prod` AWS environment | `pirates-prod` |
| `<dev-iac-directory>` | The directory containing IaC for your `dev` environment | `stacks/dev` |
| `<prod-iac-directory>` | The directory containing IaC for your `prod` environment | `stacks/prod` |
| `<aws-region>` | Your AWS region | `eu-west-1` |

Download `.gp.cicd.json`:

```
gh api repos/oslokommune/golden-path-templates/contents/templates/gh-cicd-app-monorepo/.gp.cicd.json \
  --jq '.content' | base64 -d > .gp.cicd.json
```

Update these values (many of which can be found in `common-config.yml` in your infrastructure repository):

| Field | Description | Example |
| --- | --- | --- |
| `<team-name>` | Your team name | `pirates` |
| `<repo-iac>` | Infrastructure-as-code repository name | `pirates-iac` |
| `<dev-aws-account-id>` | AWS account ID for `dev` | `123456789012` |
| `<prod-aws-account-id>` | AWS account ID for `prod` | `987654321098` |
| `<dev-environment-name>` | Name of your `dev` AWS environment | `pirates-dev` |
| `<prod-environment-name>` | Name of your `prod` AWS environment | `pirates-prod` |
| `<dev-iac-directory>` | The directory containing IaC for your `dev` environment | `stacks/dev` |
| `<prod-iac-directory>` | The directory containing IaC for your `prod` environment | `stacks/prod` |
| `<aws-region>` | Your AWS region | `eu-west-1` |

### Step 3.2: Add CODEOWNERS

Add a `CODEOWNERS` file to define who owns the repository and approves PRs:

repo-app/.github/CODEOWNERS

```
* @oslokommune/<github-team-name>
```

| Field | Description | Example |
| --- | --- | --- |
| `<github-team-name>` | The name of your [GitHub team](<https://github.com/orgs/oslokommune/teams>) | `utviklerflyt` |

### Step 3.3: Add Renovate configuration

Download [renovate.json5](<https://github.com/oslokommune/golden-path-templates/blob/main/templates/gh-cicd-app/renovate.json5>) to the repository root:

```
gh api repos/oslokommune/golden-path-templates/contents/templates/gh-cicd-app/renovate.json5 \
  --jq '.content' | base64 -d > renovate.json5
```

> [!NOTE]
>
> **What is Renovate?**
>
> Renovate automatically creates pull requests to keep your dependencies up to date. Think of it as a better Dependabot. Learn more in the [Renovate reference documentation](<https://km.oslo.systems/terraform-automation/renovate/index.md>).

## Step 4: Add the deployment workflow

Download the [zebra deployment workflow](<https://github.com/oslokommune/pirates-app-zebra/blob/main/.github/workflows/zebra_deployment.yml>) as a starting point and save it to `.github/workflows/<app-name>_deployment.yml`.

Address the `TODO` comments in the workflow. The main adaptation is in the `build-and-upload-artifact` job, where you customize the build and test steps to match your application.

> [!NOTE]
>
> **Build once deploy many**
>
> The deployment pipeline is designed to build, upload and promote the **exact** same immutable artifact across environments ("build once, deploy many").

```
For static websites where configuration typically differs per environment, see [how zebra handles environment configuration](https://github.com/oslokommune/pirates-app-zebra/blob/main/src/config.ts){:target="_blank"}.
```

> [!TIP]
>
> **Post-deployment testing**
>
> The deployment pipeline contains a job `test-dev` and `test-prod` that runs after deployment to development and production, respectively. Feel free to add more tests to these jobs to check the behavior of your running application in AWS after a deployment, such as smoke tests and end-to-end tests.

## Step 5: Create a pull request

Push the branch and create a pull request.

Verify that the build job succeeds and that no deployment occurs.

## Step 6: Merge and deploy

1. Merge the pull request.
2. The workflow deploys to your `dev` environment automatically. Verify the site is accessible by checking that the `Test webapp` job succeeds — a green check means the job curled your `dev` URL and got a response.
3. Approve the production deployment when prompted. Verify that the production site is accessible.
