Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: Deploy static site

on:
push:
branches:
- main

permissions:
contents: read
id-token: write

jobs:
deploy:
name: Build and deploy to S3
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v6

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm

- name: Install dependencies
run: npm ci

- name: Build static site
run: npm run build

- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v5
with:
role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
aws-region: ${{ secrets.AWS_REGION }}

- name: Sync static site to S3
run: aws s3 sync out "s3://${{ secrets.S3_BUCKET_NAME }}" --delete
29 changes: 25 additions & 4 deletions infra/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ This folder creates the S3 bucket that will store the exported Next.js files fro

- Region: `eu-central-1`
- Bucket name: generated from `var.bucket_name_prefix` plus a Terraform-managed random suffix
- Current bucket name: `alper-dev-cc4edc59`
- Access strategy: private S3 bucket with all public access blocked
- CloudFront strategy: use the bucket regional domain name as the CloudFront origin and grant access later with CloudFront Origin Access Control (OAC)

Expand All @@ -19,9 +18,9 @@ The bucket is not configured as a public S3 website endpoint. Static files shoul
## CloudFront Configuration

- Public entry point: CloudFront distribution
- Distribution ID: `E2M2CTUFRRX4GS`
- Distribution domain: `d1fmmfkzwx4n74.cloudfront.net`
- Distribution URL: `https://d1fmmfkzwx4n74.cloudfront.net`
- Distribution ID: available from `terraform output cloudfront_distribution_id`
- Distribution domain: available from `terraform output cloudfront_domain_name`
- Distribution URL: available from `terraform output cloudfront_url`
- Origin: private S3 bucket regional domain name
- S3 access: CloudFront Origin Access Control (OAC)
- Default root object: `index.html`
Expand Down Expand Up @@ -56,3 +55,25 @@ Useful output for the future CloudFront task:
## Static Hosting Notes

Upload the contents of the Next.js `out/` directory to the bucket after running `npm run build`. The bucket should remain private; public traffic should go through CloudFront.

## Deployment Workflow

The GitHub Actions deployment workflow builds the static site and syncs `out/` to S3 on pushes to `main`.

Required GitHub secrets:

- `AWS_ROLE_TO_ASSUME`: IAM role ARN from `terraform output github_actions_deploy_role_arn`.
- `AWS_REGION`: AWS region for deployment.
- `S3_BUCKET_NAME`: target bucket name from `terraform output bucket_name`.

The workflow uses `aws s3 sync out s3://$S3_BUCKET_NAME --delete` so removed files are deleted from S3 during deployment. CloudFront invalidation is intentionally out of scope.

## GitHub OIDC

Terraform creates an IAM OIDC provider for `token.actions.githubusercontent.com` and a deployment role that can only be assumed by this repository's `main` branch:

- Repository: `replakcan/alper-dev`
- Branch: `main`
- Role output: `github_actions_deploy_role_arn`

The role allows only the S3 permissions needed by the deployment workflow: list the static site bucket and get, put, or delete objects in it. It does not grant CloudFront invalidation permissions.
74 changes: 74 additions & 0 deletions infra/github_oidc.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
data "aws_iam_policy_document" "github_actions_assume_role" {
statement {
actions = ["sts:AssumeRoleWithWebIdentity"]

principals {
type = "Federated"
identifiers = [aws_iam_openid_connect_provider.github.arn]
}

condition {
test = "StringEquals"
variable = "token.actions.githubusercontent.com:aud"
values = ["sts.amazonaws.com"]
}

condition {
test = "StringEquals"
variable = "token.actions.githubusercontent.com:sub"
values = ["repo:${var.github_repository}:ref:refs/heads/main"]
}
}
}

data "aws_iam_policy_document" "github_actions_deploy" {
statement {
sid = "ListStaticSiteBucket"

actions = ["s3:ListBucket"]

resources = [
aws_s3_bucket.site.arn,
]
}

statement {
sid = "SyncStaticSiteObjects"

actions = [
"s3:DeleteObject",
"s3:GetObject",
"s3:PutObject",
]

resources = [
"${aws_s3_bucket.site.arn}/*",
]
}
}

resource "aws_iam_openid_connect_provider" "github" {
url = "https://token.actions.githubusercontent.com"

client_id_list = [
"sts.amazonaws.com",
]
}

resource "aws_iam_role" "github_actions_deploy" {
name = "${var.project_name}-${var.environment}-github-actions-deploy"
assume_role_policy = data.aws_iam_policy_document.github_actions_assume_role.json

tags = {
Name = "${var.project_name}-${var.environment}-github-actions-deploy"
Environment = var.environment
Project = var.project_name
ManagedBy = "Terraform"
}
}

resource "aws_iam_role_policy" "github_actions_deploy" {
name = "${var.project_name}-${var.environment}-s3-deploy"
role = aws_iam_role.github_actions_deploy.id
policy = data.aws_iam_policy_document.github_actions_deploy.json
}
5 changes: 5 additions & 0 deletions infra/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,8 @@ output "cloudfront_url" {
description = "CloudFront URL for the static website."
value = "https://${aws_cloudfront_distribution.site.domain_name}"
}

output "github_actions_deploy_role_arn" {
description = "IAM role ARN that GitHub Actions should assume through OIDC for S3 deployment."
value = aws_iam_role.github_actions_deploy.arn
}
1 change: 1 addition & 0 deletions infra/terraform.tfvars.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ aws_region = "eu-central-1"
bucket_name_prefix = "alper-dev"
environment = "dev"
project_name = "alper-dev"
github_repository = "replakcan/alper-dev"
6 changes: 6 additions & 0 deletions infra/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,9 @@ variable "project_name" {
type = string
default = "alper-dev"
}

variable "github_repository" {
description = "GitHub repository allowed to assume the deployment role through OIDC."
type = string
default = "replakcan/alper-dev"
}
Loading