AWS S3
GitHub Actions
static site deployment
CI/CD
cloud hosting

Best strategy to deploy static site to s3 on github push?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

A reliable strategy for deploying a static site to S3 on every GitHub push is to automate build and sync in GitHub Actions with least-privilege AWS access. This removes manual deploy steps and keeps production aligned with main branch commits. For most teams, the best baseline is S3 for storage and optional CloudFront for caching and TLS.

A practical production setup includes:

  • one S3 bucket for site assets
  • optional CloudFront distribution in front of S3
  • GitHub Actions workflow on push to main
  • OIDC-based role assumption instead of long-lived AWS keys

This gives secure CI authentication and repeatable deployments.

GitHub Actions Workflow Example

The workflow below builds a static site, syncs files to S3, and invalidates CloudFront cache.

yaml
1name: Deploy static site
2
3on:
4  push:
5    branches: ["main"]
6
7permissions:
8  id-token: write
9  contents: read
10
11jobs:
12  deploy:
13    runs-on: ubuntu-latest
14
15    steps:
16      - name: Checkout
17        uses: actions/checkout@v4
18
19      - name: Setup Node
20        uses: actions/setup-node@v4
21        with:
22          node-version: 20
23
24      - name: Install and build
25        run: |
26          npm ci
27          npm run build
28
29      - name: Configure AWS credentials
30        uses: aws-actions/configure-aws-credentials@v4
31        with:
32          role-to-assume: arn:aws:iam::123456789012:role/github-actions-static-site
33          aws-region: us-east-1
34
35      - name: Sync to S3
36        run: aws s3 sync ./dist s3://my-site-bucket --delete
37
38      - name: Invalidate CloudFront
39        run: aws cloudfront create-invalidation --distribution-id E1234567890ABC --paths "/*"

This is runnable with your own bucket and distribution identifiers.

IAM and Security Baseline

Use a dedicated IAM role with minimum permissions:

  • s3:PutObject, s3:DeleteObject, s3:ListBucket on the target bucket
  • cloudfront:CreateInvalidation if using CloudFront
  • trust policy restricted to your repository and branch via OIDC conditions

Least privilege limits blast radius if CI configuration is changed accidentally.

Deployment Quality Controls

Add simple quality gates before upload:

  • fail build on lint or test failure
  • keep artifact path explicit such as dist or build
  • include cache-control headers for static assets

Example for cache headers after sync:

bash
aws s3 cp s3://my-site-bucket/index.html s3://my-site-bucket/index.html   --metadata-directive REPLACE --cache-control "no-cache"

This helps ensure fresh HTML while allowing long cache on hashed assets.

Branch Strategy and Environment Isolation

Teams usually need preview, staging, and production environments. Use branch-to-bucket mapping so each environment remains isolated and safe.

yaml
1# Example strategy
2# feature/*  -> preview bucket
3# develop    -> staging bucket
4# main       -> production bucket

In GitHub Actions, you can map target bucket by branch name and avoid accidental production deploys from non-main branches.

bash
1if [ "$GITHUB_REF_NAME" = "main" ]; then
2  TARGET_BUCKET="my-site-prod"
3else
4  TARGET_BUCKET="my-site-staging"
5fi
6
7aws s3 sync ./dist "s3://$TARGET_BUCKET" --delete

This pattern keeps release flow predictable and allows safer verification before production promotion.

For rollback, keep recent build artifacts so you can redeploy a known good version quickly without rebuilding from a changed dependency graph. A simple artifact retention policy in CI is usually enough to support this.

Version your deployment workflow changes carefully so rollback automation stays trustworthy over time.

Common Pitfalls

A common issue is storing long-lived AWS keys in GitHub secrets instead of using OIDC. Key rotation becomes painful and risk increases.

Another pitfall is forgetting --delete during sync, which leaves stale files in S3 after refactors.

Teams also invalidate CloudFront on every file for every push. That works but can increase cost and hide poor cache strategy.

Finally, avoid deploying directly from feature branches to production bucket unless branch isolation is explicitly designed.

Summary

  • Use GitHub Actions with OIDC role assumption for secure automated deploys.
  • Sync built assets to S3 and optionally invalidate CloudFront.
  • Apply least-privilege IAM policies scoped to one site.
  • Add quality checks before deployment to reduce broken releases.
  • Manage caching intentionally for predictable freshness and performance.

Course illustration
Course illustration

All Rights Reserved.