Take a look around. The site you’re browsing right now is powered by Jekyll, a static site generator that transforms plain text into fast websites. What keeps me coming back to Jekyll is its perfect blend of simplicity and customization potential, especially as a Ruby developer. In this guide, I’ll walk through automating Jekyll deployments using GitHub Actions, then serving your site through AWS S3 and CloudFront for a cost-effective, scalable hosting solution.

Deploy Workflow

The Plan

So, you built a Jekyll site. Now we need to deploy and serve this website. Here is what we need:

  • a AWS S3 bucket hosting our site files
  • a AWS CloudFront distribution to serve these files fast worldwide
  • a Github Actions workflow to orchestrate and automate deployments

Hosting on S3

Let’s start by creating a home for your website files. Head over to AWS S3 and:

Create your bucket

Name it something obvious like your-website-name.com. AWS requires unique names globally.

Turn on static hosting

In the bucket’s Properties tab, scroll to “Static website hosting” and enable it. Set index.html as your default page.

Adjust permissions carefully

In the Permissions tab:

  • Uncheck “Block all public access” (don’t worry – we’ll add safety measures next)
  • Add this bucket policy to allow visitors to view your site without letting them accidentally delete anything:
{  
  "Version": "2012-10-17",  
  "Statement": [{  
    "Effect": "Allow",  
    "Principal": "*",  
    "Action": "s3:GetObject",  
    "Resource": "arn:aws:s3:::your-website-name.com/*"  
  }]  
}

You’ve now got a secure space to host your site files. Double-check everything by uploading a test index.html and visiting your S3 website URL (you’ll find it in the Static Website Hosting section).

Serving with CloudFront

Now that your site’s files are in S3, you might notice it loads a bit slowly for visitors far from your bucket’s region. This is where Amazon’s CDN comes in. Here’s how to set it up:

  • In the AWS Console, navigate to CloudFront and click “Create distribution.”
  • Choose your S3 bucket from the dropdown (the one you named earlier)
  • Under “Viewer protocol policy”, select “Redirect HTTP to HTTPS.” This ensures visitors always use a secure connection
  • In the “Allowed HTTP methods” section, choose GET and HEAD.

This keeps things simple and secure for static sites. Once created, it might take 5-10 minutes for CloudFront to deploy your distribution. You’ll know it’s ready when the status changes from “In Progress” to “Deployed” – then test your new CDN URL!

Creating Least-Privilege AWS Credentials

To restrict permissions to only your specific S3 bucket and CloudFront distribution:

Create an IAM Policy

In AWS IAM, go to Policies > Create policy and paste this JSON, replacing YOUR_BUCKET_NAME and YOUR_CLOUDFRONT_DISTRIBUTION_ID with your actual values:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:PutObject",
        "s3:GetObject",
        "s3:ListBucket",
        "s3:DeleteObject"
      ],
      "Resource": [
        "arn:aws:s3:::YOUR_BUCKET_NAME",
        "arn:aws:s3:::YOUR_BUCKET_NAME/*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": "cloudfront:CreateInvalidation",
      "Resource": "arn:aws:cloudfront::YOUR_ACCOUNT_ID:distribution/YOUR_CLOUDFRONT_DISTRIBUTION_ID"
    }
  ]
}

Attach to a New IAM User

  • Create a user named github-actions-deploy
  • Attach the custom policy you just created

Add to GitHub Secrets

Use the generated access key ID and secret key in your repository’s secrets as AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY.

Why This Matters

  • The S3 permissions allow syncing files (PutObject, DeleteObject) and listing bucket contents (ListBucket).
  • CloudFront access is limited to cache invalidation only for your specific distribution
  • No access to other buckets, services, or administrative actions

Automating the whole thing

Let’s set up automatic deployments so your site updates whenever you push changes to GitHub. Create a new file in your repository at .github/workflows/main.yml with this configuration:

name: Deploy

on:
  push:
    branches:
    - main

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
    - name: Checkout
      uses: actions/checkout@v1

    - name: Set up Ruby
      uses: ruby/setup-ruby@v1
      with:
        bundler-cache: true
    
    - name: Build Jekyll site
      run: JEKYLL_ENV=production bundle exec jekyll build

    - name: Configure AWS Credentials
      uses: aws-actions/configure-aws-credentials@v1
      with:
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        aws-region: your-aws-region

    - name: Deploy static site to S3 bucket
      run: aws s3 sync ./_site/ ${{ secrets.AWS_S3_BUCKET_NAME }} --delete

    - name: Invalidate CloudFront cache
      run: aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_CLOUDFRONT_DISTRIBUTION_ID }} --paths "/*"

Let’s break this down:

Checkout

Grabs your latest code from the GitHub repository so the workflow can access it.

Set up Ruby

Prepares the Ruby environment and installs dependencies (including Jekyll) using Bundler for efficient package management.

Build Jekyll Site

Compiles your static site files using Jekyll’s build command, ready for deployment.

Configure AWS Credentials

Securely connects to your AWS account using credentials stored in GitHub’s Secrets vault (never exposed in plain text).

Deploy to S3

Synchronizes the generated _site folder with your S3 bucket, removing any outdated files automatically.

Clear CDN Cache

Tells CloudFront to refresh its cached content worldwide, ensuring visitors see your updates immediately.

Final thoughts

This setup creates a reliable way to deploy your Jekyll site. GitHub Actions automates the build and deployment process, S3 securely hosts your files, and CloudFront ensures faster loading times globally. While the initial configuration requires careful steps, the system runs quietly in the background once everything’s connected.

You’ll only need to focus on updating your site’s content while the workflow handles the rest whenever you push changes. I hope this guide helps you. Let me know in the comments if you have any questions. Happy blogging!