A simple production-ready template for deploying Frontend applications to AWS S3 + Cloudflare, focusing on automated CI/CD pipelines and infrastructure as code.
This project leverages Reusable Workflows and Terraform Modules from these upstream repositories: carlssonk/cicd-toolkit, carlssonk/terraform-modules
This repository provides a complete setup for modern web application deployment with:
- Frontend: Scaffolded vite-react-typescript app
- Infrastructure: Terraform-managed AWS S3 + Cloudflare
- CI/CD: GitHub Actions workflows for automated deployments, rollbacks, and releases
- Custom Runner: Custom RunsOn runner for faster workflows
- Multi-Environment: Support for dev, staging, and production environments
- Versioned Deployments: Immutable deployments with rollback capabilities
- Security: OIDC authentication for AWS (no long-lived credentials)
This template is built around trunk-based development principles:
- Single Branch Deployments - All changes merge to
mainand deploy automatically to staging and production - Fast Rollbacks - Instant rollback to any previous deployment without rebuilding
- Immutable Deployments - Each commit creates a versioned deployment in S3, never overwritten
- Confidence Through Testing in Production - Gradual rollouts let you test new versions with real traffic before full deployment
This guide will help you configure the repository after cloning.
- Create a new AWS account for the environment you want to bootstrap. (or use an existing account; you can reset it with aws-nuke)
- Create a new IAM bootstrap user and add this as inline policy (replace
AWS_ACCOUNT_IDandAWS_REGIONplaceholders) - Create a secret access key from the bootstrap user and save the access key and access secret for this step (or simply complete that step now so you dont need to save the keys)
- Done
- Create a Cloudflare account
- Add your domain name and make sure DNS records are empty and you have added the cloudflare nameservers to your domain register
- Retrieve your API token at your Cloudflare dashboard. You need at least these permissions:
- Transform Rules: Edit
- Zone Settings: Edit
- Zone: Edit
- Cache Purge: Purge
- DNS: Edit
- Add
CLOUDFLARE_API_TOKENto your environment secret. - Done
Go to Settings → Environments and create the following environments:
dev(Optional)staging(Optional)productioninfra-approval(Optional but recommended; used for manual approval of production infrastructure changes)
Recommended Protection Rules for infra-approval:
- Required reviewers (add yourself or team members)
Go to Settings → Secrets and variables → Actions → Variables tab and create variables with environment suffixes:
| Variable Name | Description | Example Value |
|---|---|---|
AWS_ACCOUNT_ID_DEV |
AWS account ID for dev environment | 123456789012 |
AWS_ACCOUNT_ID_STAGING |
AWS account ID for staging environment | 123456789012 |
AWS_ACCOUNT_ID_PRODUCTION |
AWS account ID for production environment | 123456789012 |
S3_BUCKET_DEV |
S3 bucket name for dev environment | dev.yourdomain.com |
S3_BUCKET_STAGING |
S3 bucket name for staging environment | staging.yourdomain.com |
S3_BUCKET_PRODUCTION |
S3 bucket name for production environment | www.yourdomain.com |
AWS_REGION |
AWS region for all environments | eu-north-1 |
CLOUDFLARE_ZONE_ID |
Cloudflare Zone ID | 6161a4811420882a6eb7d8ec1006645c |
For each environment (dev, staging, production), go to Settings → Environments → select environment → Secrets and add:
| Secret Name | Description | How to Get |
|---|---|---|
BOOTSTRAP_AWS_ACCESS_KEY |
AWS access key for initial Terraform setup | Create IAM user with admin permissions |
BOOTSTRAP_AWS_ACCESS_SECRET |
AWS secret key for initial Terraform setup | From the same IAM user |
Note: Bootstrap secrets are only needed for the initial Terraform state setup. After bootstrap, workflows use OIDC for AWS authentication.
Go to Settings → Secrets and variables → Actions → Secrets tab and create:
| Secret Name | Description | How to Get |
|---|---|---|
CLOUDFLARE_API_TOKEN |
Cloudflare API token for DNS/Rules | Create at Cloudflare Dashboard → My Profile → API Tokens |
RUNS_ON_LICENSE_KEY |
Used for custom RunsOn runner (Optional) | Visit https://runs-on.com/pricing/ |
Note: Make sure your Cloudflare API token has the proper permissions (If unsure what permissions you need, just look at the 403 messages from the pipeline).
Update the following files with your own values:
environment = "staging"
root_domain = "yourdomain.com" # Change this
subdomain = "staging.app-template" # Change thisenvironment = "production"
root_domain = "yourdomain.com" # Change this
subdomain = "app-template" # Change thisBe sure to also update the values these files as well to fit your needs
terraform/global/main.tfterraform/devops/main.tf
- Create the GitHub environments you need. Available options: (dev, staging, production, infra-approval)
- Configure repository variables (AWS_ACCOUNT_ID_, S3_BUCKET_, AWS_REGION, CLOUDFLARE_ZONE_ID)
- Configure environment secrets for each environment (BOOTSTRAP_AWS_ACCESS_KEY & BOOTSTRAP_AWS_ACCESS_SECRET)
- Configure repository secrets (CLOUDFLARE_API_TOKEN, RUNS_ON_LICENSE_KEY)
- Update the Terraform
.tfvarsfiles and othermain.tffiles - Run the Terraform bootstrap workflow to set up remote state (You can run this workflow multiple times if needed)
- Push a commit to
main(needs **.tf,**.tfvars changes) to deploy infrastructure (staging, production) - Push a commit to
main(needs src/** changes) to deploy app (staging, production)