How to Automate Jekyll Builds with GitHub Actions
Set up GitHub Actions to automatically build and deploy your Jekyll site — with caching, HTML validation, link checking, and multi-environment deployments.
GitHub Actions can build, test, and deploy your Jekyll site automatically on every push. This guide covers everything from the basic setup to advanced workflows with caching, link checking, and staging environments.
Why Use GitHub Actions for Jekyll?
GitHub Pages has a built-in Jekyll build system, but it only supports a limited set of plugins. GitHub Actions removes this restriction — you can use any Jekyll plugin, any Ruby version, and any build configuration.
Additional benefits:
- Build once, deploy anywhere — deploy to GitHub Pages, Netlify, Cloudflare Pages, or AWS S3
- Run tests — validate HTML, check links, run linters before deploying
- Cache dependencies — fast builds by reusing installed gems between runs
- Preview branches — deploy PRs to a staging URL before merging
Basic Jekyll GitHub Actions Workflow
Create .github/workflows/deploy.yml in your repository:
name: Build and Deploy Jekyll
on:
push:
branches:
- main
pull_request:
branches:
- main
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: "pages"
cancel-in-progress: false
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.3'
bundler-cache: true # Runs bundle install and caches gems
- name: Setup Pages
id: pages
uses: actions/configure-pages@v5
- name: Build Jekyll site
run: bundle exec jekyll build --baseurl "${{ steps.pages.outputs.base_path }}"
env:
JEKYLL_ENV: production
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
if: github.ref == 'refs/heads/main'
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
What This Does
- Triggers on every push to
mainand on pull requests - Sets up Ruby with the version specified in your
.ruby-versionfile - Caches gems so subsequent builds are faster (typically 2–3× speedup)
- Builds Jekyll with
JEKYLL_ENV: productionto enable production-only features - Deploys to GitHub Pages (only on pushes to
main, not PRs)
Adding Gem Caching (Faster Builds)
The bundler-cache: true option in ruby/setup-ruby handles caching automatically. But you can also cache explicitly for more control:
- name: Cache gems
uses: actions/cache@v4
with:
path: vendor/bundle
key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }}
restore-keys: |
${{ runner.os }}-gems-
- name: Install dependencies
run: |
bundle config path vendor/bundle
bundle install --jobs 4 --retry 3
Add vendor/ to your .gitignore:
vendor/
Adding HTML Validation
Validate your built HTML before deploying to catch issues like missing alt text and broken markup:
- name: Validate HTML
run: |
gem install html-proofer
htmlproofer ./_site \
--disable-external \
--checks Links,Images,Scripts \
--allow-missing-href true \
--ignore-urls "/localhost/"
Adding Link Checking
Check for broken internal and external links:
- name: Check links
run: |
bundle exec htmlproofer ./_site \
--checks Links \
--external-only \
--ignore-urls "https://twitter.com,https://linkedin.com"
Note: External link checking adds significant build time. Consider running it on a schedule rather than every push:
on:
push:
branches: [main]
schedule:
- cron: '0 6 * * 1' # Every Monday at 6am
Multi-Environment Deployment
Staging on Pull Requests
Deploy pull requests to a preview URL so you can review changes before merging:
name: Deploy Preview
on:
pull_request:
types: [opened, synchronize]
jobs:
deploy-preview:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
ruby-version: '3.3'
bundler-cache: true
- name: Build Jekyll
run: bundle exec jekyll build
env:
JEKYLL_ENV: staging
- name: Deploy to Netlify Preview
uses: nwtgck/actions-netlify@v3
with:
publish-dir: './_site'
github-token: ${{ secrets.GITHUB_TOKEN }}
deploy-message: "PR Preview: ${{ github.event.pull_request.title }}"
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
Production Deployment with Environment Protection
Add environment protection rules so deploys to production require manual approval:
- Go to repository Settings → Environments → New environment
- Name it
production - Add Required reviewers — only approved reviewers can trigger production deploys
Then reference the environment in your workflow:
deploy:
environment:
name: production
url: https://yourdomain.com
Deploying to Netlify Instead of GitHub Pages
If you prefer Netlify for its CDN and preview deploys:
- name: Deploy to Netlify
uses: nwtgck/actions-netlify@v3
with:
publish-dir: './_site'
production-branch: main
github-token: ${{ secrets.GITHUB_TOKEN }}
deploy-message: "Deployed from GitHub Actions"
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
Get your NETLIFY_AUTH_TOKEN from Netlify’s personal settings and add both secrets to your GitHub repository Settings → Secrets and variables → Actions.
Scheduled Builds
Useful for sites with content that updates from external sources (RSS feeds, APIs):
on:
schedule:
- cron: '0 6 * * *' # Daily at 6am UTC
push:
branches: [main]
Full Production Workflow
Here’s a complete, production-ready workflow combining everything above:
name: Jekyll CI/CD
on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
- cron: '0 6 * * 1' # Weekly link check
permissions:
contents: read
pages: write
id-token: write
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
ruby-version: '3.3'
bundler-cache: true
- name: Build Jekyll
run: bundle exec jekyll build
env:
JEKYLL_ENV: production
- name: Validate HTML
run: |
bundle exec htmlproofer ./_site \
--disable-external \
--checks Links,Images
- name: Upload artifact
if: github.ref == 'refs/heads/main'
uses: actions/upload-pages-artifact@v3
deploy:
needs: build-and-test
if: github.ref == 'refs/heads/main'
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
Looking for Jekyll themes that are already configured for GitHub Actions deployment? Browse themes on JekyllHub →