GitHub Actions CI/CD ¶
GitHub Actions provides powerful CI/CD capabilities directly integrated with your GitHub repository. This guide covers everything from basic deployments to advanced multi-environment setups.
Quick Start ¶ #
Create .github/workflows/deploy.yml in your repository:
name: Deploy Site
on:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
jobs:
build-and-deploy:
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- uses: actions/checkout@v4
- name: Install markata-go
run: |
wget -qO- "https://github.com/WaylonWalker/markata-go/releases/latest/download/markata-go_linux_x86_64.tar.gz" | tar xz
sudo mv markata-go /usr/local/bin/
- name: Build site
run: markata-go build --clean
env:
MARKATA_GO_URL: https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}
- name: Setup Pages
uses: actions/configure-pages@v4
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: ./public
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
Enable GitHub Pages in your repository settings:
- Go to Settings > Pages
- Under Source, select GitHub Actions
- Push to
mainto trigger the workflow
GitHub Pages ¶ #
Basic Deployment ¶ #
The workflow above deploys to GitHub Pages using the modern Actions-based approach. Key components:
Permissions - Required for the deploy-pages action:
permissions:
contents: read # Read repository
pages: write # Deploy to Pages
id-token: write # OIDC token for deployment
Environment - Links the job to the GitHub Pages environment:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
Custom Domain ¶ #
To use a custom domain:
- Create
static/CNAMEwith your domain:
example.com
- Update your workflow:
- name: Build site
run: markata-go build --clean
env:
MARKATA_GO_URL: https://example.com
- Configure DNS at your registrar:
- Apex domain: Add
Arecords pointing to GitHub’s IPs - Subdomain: Add a
CNAMErecord to<username>.github.io
- Apex domain: Add
Project Sites vs User Sites ¶ #
| Type | Repository Name | URL | Branch |
|---|---|---|---|
| User/Org | username.github.io |
https://username.github.io |
main |
| Project | Any name | https://username.github.io/repo-name |
main |
For project sites, ensure your base URL includes the repository name:
env:
MARKATA_GO_URL: https://username.github.io/repo-name
Netlify ¶ #
Deploy to Netlify using GitHub Actions for more control over the build process.
Basic Netlify Deployment ¶ #
name: Deploy to Netlify
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install markata-go
run: |
wget -qO- "https://github.com/WaylonWalker/markata-go/releases/latest/download/markata-go_linux_x86_64.tar.gz" | tar xz
sudo mv markata-go /usr/local/bin/
- name: Build site
run: markata-go build --clean
env:
MARKATA_GO_URL: ${{ github.event_name == 'push' && 'https://example.netlify.app' || '' }}
- name: Deploy to Netlify
uses: nwtgck/actions-netlify@v3
with:
publish-dir: ./public
production-branch: main
production-deploy: ${{ github.event_name == 'push' }}
deploy-message: "Deploy from GitHub Actions"
github-token: ${{ secrets.GITHUB_TOKEN }}
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
Setup:
- Create a Netlify site and note the Site ID
- Generate a Personal Access Token in Netlify (User Settings > Applications)
- Add secrets to your repository:
NETLIFY_AUTH_TOKEN- Your personal access tokenNETLIFY_SITE_ID- Your site’s API ID
Netlify with Deploy Previews ¶ #
The workflow above automatically creates deploy previews for pull requests. The preview URL is posted as a comment on the PR.
Cloudflare Pages ¶ #
Deploy to Cloudflare Pages for fast global CDN delivery.
name: Deploy to Cloudflare Pages
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
permissions:
contents: read
deployments: write
pull-requests: write
steps:
- uses: actions/checkout@v4
- name: Install markata-go
run: |
wget -qO- "https://github.com/WaylonWalker/markata-go/releases/latest/download/markata-go_linux_x86_64.tar.gz" | tar xz
sudo mv markata-go /usr/local/bin/
- name: Build site
run: markata-go build --clean
env:
MARKATA_GO_URL: ${{ github.event_name == 'push' && 'https://example.pages.dev' || '' }}
- name: Deploy to Cloudflare Pages
uses: cloudflare/pages-action@v1
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
projectName: my-site
directory: public
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
Setup:
- Create a Cloudflare Pages project
- Generate an API token with “Cloudflare Pages:Edit” permission
- Add secrets:
CLOUDFLARE_API_TOKEN- Your API tokenCLOUDFLARE_ACCOUNT_ID- Your account ID
AWS S3 ¶ #
Deploy to Amazon S3 for scalable static hosting.
name: Deploy to S3
on:
push:
branches: [main]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install markata-go
run: |
wget -qO- "https://github.com/WaylonWalker/markata-go/releases/latest/download/markata-go_linux_x86_64.tar.gz" | tar xz
sudo mv markata-go /usr/local/bin/
- name: Build site
run: markata-go build --clean
env:
MARKATA_GO_URL: https://example.com
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Sync to S3
run: |
aws s3 sync ./public s3://${{ vars.S3_BUCKET }} \
--delete \
--cache-control "max-age=31536000" \
--exclude "*.html" \
--exclude "*.xml"
# HTML and XML with shorter cache
aws s3 sync ./public s3://${{ vars.S3_BUCKET }} \
--cache-control "max-age=0, must-revalidate" \
--include "*.html" \
--include "*.xml"
- name: Invalidate CloudFront
if: vars.CLOUDFRONT_DISTRIBUTION_ID != ''
run: |
aws cloudfront create-invalidation \
--distribution-id ${{ vars.CLOUDFRONT_DISTRIBUTION_ID }} \
--paths "/*"
Setup:
- Create an S3 bucket configured for static website hosting
- Create an IAM user with S3 and CloudFront permissions
- Add secrets and variables:
AWS_ACCESS_KEY_ID- IAM access keyAWS_SECRET_ACCESS_KEY- IAM secret keyS3_BUCKET- Bucket name (variable)CLOUDFRONT_DISTRIBUTION_ID- Optional CloudFront distribution
Preview Deployments ¶ #
Create preview deployments for pull requests to review changes before merging.
GitHub Pages Preview (Custom Approach) ¶ #
Deploy PR previews to subdirectories on GitHub Pages:
name: PR Preview
on:
pull_request:
types: [opened, synchronize, reopened]
permissions:
contents: write
pull-requests: write
jobs:
preview:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install markata-go
run: |
wget -qO- "https://github.com/WaylonWalker/markata-go/releases/latest/download/markata-go_linux_x86_64.tar.gz" | tar xz
sudo mv markata-go /usr/local/bin/
- name: Build site
run: markata-go build --clean
env:
MARKATA_GO_URL: https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}/pr-${{ github.event.number }}
- name: Deploy Preview
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./public
destination_dir: pr-${{ github.event.number }}
- name: Comment on PR
uses: actions/github-script@v7
with:
script: |
const url = `https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}/pr-${{ github.event.number }}/`;
const body = `## Preview Deployment\n\nYour preview is ready!\n\n${url}`;
// Find existing comment
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
const botComment = comments.find(c =>
c.user.type === 'Bot' && c.body.includes('Preview Deployment')
);
if (botComment) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: botComment.id,
body: body,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: body,
});
}
Cleanup Preview on PR Close ¶ #
name: Cleanup PR Preview
on:
pull_request:
types: [closed]
permissions:
contents: write
jobs:
cleanup:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: gh-pages
- name: Remove preview directory
run: |
rm -rf pr-${{ github.event.number }}
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add -A
git commit -m "Remove preview for PR #${{ github.event.number }}" || exit 0
git push
Multi-Environment Deployments ¶ #
Deploy to staging and production environments with different configurations.
Staging and Production ¶ #
name: Deploy
on:
push:
branches:
- main
- develop
jobs:
build:
runs-on: ubuntu-latest
outputs:
artifact-id: ${{ steps.upload.outputs.artifact-id }}
steps:
- uses: actions/checkout@v4
- name: Install markata-go
run: |
wget -qO- "https://github.com/WaylonWalker/markata-go/releases/latest/download/markata-go_linux_x86_64.tar.gz" | tar xz
sudo mv markata-go /usr/local/bin/
- name: Determine environment
id: env
run: |
if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then
echo "name=production" >> $GITHUB_OUTPUT
echo "url=https://example.com" >> $GITHUB_OUTPUT
else
echo "name=staging" >> $GITHUB_OUTPUT
echo "url=https://staging.example.com" >> $GITHUB_OUTPUT
fi
- name: Build site
run: markata-go build --clean
env:
MARKATA_GO_URL: ${{ steps.env.outputs.url }}
- name: Upload artifact
id: upload
uses: actions/upload-artifact@v4
with:
name: site-${{ steps.env.outputs.name }}
path: ./public
retention-days: 1
deploy-staging:
needs: build
if: github.ref == 'refs/heads/develop'
runs-on: ubuntu-latest
environment:
name: staging
url: https://staging.example.com
steps:
- name: Download artifact
uses: actions/download-artifact@v4
with:
name: site-staging
path: ./public
- name: Deploy to staging
run: |
# Your staging deployment command
echo "Deploying to staging..."
deploy-production:
needs: build
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment:
name: production
url: https://example.com
steps:
- name: Download artifact
uses: actions/download-artifact@v4
with:
name: site-production
path: ./public
- name: Deploy to production
run: |
# Your production deployment command
echo "Deploying to production..."
Manual Production Deployment ¶ #
Require manual approval for production deployments:
- Go to Settings > Environments
- Create a
productionenvironment - Enable Required reviewers
- Add team members who can approve
The workflow will pause at the deploy-production job until approved.
Caching Strategies ¶ #
Speed up builds by caching dependencies and build artifacts.
Cache markata-go Binary ¶ #
- name: Cache markata-go
id: cache-markata
uses: actions/cache@v4
with:
path: /usr/local/bin/markata-go
key: markata-go-${{ runner.os }}-v0.1.0
- name: Install markata-go
if: steps.cache-markata.outputs.cache-hit != 'true'
run: |
wget -qO- "https://github.com/WaylonWalker/markata-go/releases/download/v0.1.0/markata-go_0.1.0_linux_x86_64.tar.gz" | tar xz
sudo mv markata-go /usr/local/bin/
Cache Go Modules (if building from source) ¶ #
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.22'
cache: true
- name: Install markata-go
run: go install github.com/WaylonWalker/markata-go/cmd/markata-go@latest
Full Workflow with Caching ¶ #
name: Deploy with Caching
on:
push:
branches: [main]
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: pages
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Full history for git dates
- name: Cache markata-go
id: cache-markata
uses: actions/cache@v4
with:
path: /usr/local/bin/markata-go
key: markata-go-${{ runner.os }}-v0.1.0
- name: Install markata-go
if: steps.cache-markata.outputs.cache-hit != 'true'
run: |
wget -qO- "https://github.com/WaylonWalker/markata-go/releases/download/v0.1.0/markata-go_0.1.0_linux_x86_64.tar.gz" | tar xz
sudo mv markata-go /usr/local/bin/
- name: Build site
run: markata-go build --clean
env:
MARKATA_GO_URL: https://example.com
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: ./public
deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
Scheduled Builds ¶ #
Rebuild your site on a schedule (useful for dynamic content or expired dates):
name: Scheduled Build
on:
schedule:
# Run daily at midnight UTC
- cron: '0 0 * * *'
workflow_dispatch: # Allow manual trigger
permissions:
contents: read
pages: write
id-token: write
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
# ... same as basic deployment
Troubleshooting ¶ #
Workflow Not Running ¶ #
- Check that the workflow file is in
.github/workflows/ - Verify the branch name matches your trigger
- Check Actions tab for any errors
Permission Denied ¶ #
Ensure your workflow has the required permissions:
permissions:
contents: read
pages: write
id-token: write
Deployment Shows Old Content ¶ #
- Check that
--cleanflag is used - Verify the build completed successfully
- Check for caching issues - try clearing the cache
404 on GitHub Pages ¶ #
- Verify GitHub Pages is enabled in repository settings
- Check that
index.htmlexists in the output - For project sites, ensure base URL includes repository name
Build Fails with Memory Error ¶ #
For large sites, increase available memory:
jobs:
build:
runs-on: ubuntu-latest
env:
GOMEMLIMIT: 4GiB
Next Steps ¶ #
- GitLab CI Guide - GitLab CI/CD pipelines
- Deployment Guide - Manual deployment options
- GitHub Actions Documentation - Official docs