DevOps & Infrastructure

Complete GitHub Actions CI/CD Tutorial for Astro: Deploy Fast Static Sites with Zero Downtime

Connor O'Dea · · 7 min read
GitHub Actions CI/CD Astro DevOps Deployment

Setting up a robust CI/CD pipeline for your Astro site shouldn’t be rocket science. Yet we see teams struggling with broken builds, slow deployments, and manual processes that waste hours every week. This GitHub Actions CI/CD tutorial for Astro will show you exactly how to build a production-ready pipeline that deploys your static site automatically, handles errors gracefully, and scales with your team.

We’ve implemented this exact workflow across multiple projects at Odea Works, including our own sites and client work. The pipeline we’re building handles everything from dependency caching to multi-environment deployments, taking your Astro site from git push to live in under 3 minutes.

Why Astro + GitHub Actions Makes Perfect Sense

Astro generates blazingly fast static sites, but you need a deployment pipeline that matches that speed. GitHub Actions provides free CI/CD minutes, integrates directly with your repository, and offers powerful caching mechanisms that can cut your build times by 60% or more.

Here’s what makes this combination particularly effective:

  • Zero configuration overhead — Your CI/CD lives right in your repository
  • Built-in secrets management — No external tools needed for API keys
  • Parallel job execution — Run tests, builds, and deployments simultaneously
  • Rich ecosystem — Thousands of pre-built actions for common tasks
  • Free tier — 2,000 minutes per month for public repos, 500 for private

Setting Up Your Astro Project for CI/CD

Before diving into GitHub Actions, let’s ensure your Astro project follows CI/CD best practices. Here’s the project structure we recommend:

your-astro-site/
├── .github/
│   └── workflows/
│       └── deploy.yml
├── src/
├── public/
├── astro.config.mjs
├── package.json
├── package-lock.json
└── README.md

Your package.json should include these essential scripts:

{
  "scripts": {
    "dev": "astro dev",
    "start": "astro dev",
    "build": "astro build",
    "preview": "astro preview",
    "test": "vitest",
    "test:ci": "vitest run",
    "lint": "eslint . --ext .js,.ts,.astro",
    "lint:fix": "eslint . --ext .js,.ts,.astro --fix"
  }
}

The key here is the test:ci script — this runs your tests in CI mode without the interactive watcher, essential for automated environments.

Building Your First GitHub Actions Workflow

Create .github/workflows/deploy.yml in your repository. This workflow will trigger on pushes to your main branch and pull requests:

name: Deploy Astro Site

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

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

      - name: Install dependencies
        run: npm ci

      - name: Run linter
        run: npm run lint

      - name: Run tests
        run: npm run test:ci

  build:
    runs-on: ubuntu-latest
    needs: test
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

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

      - name: Install dependencies
        run: npm ci

      - name: Build Astro site
        run: npm run build

      - name: Upload build artifacts
        uses: actions/upload-artifact@v4
        with:
          name: astro-build
          path: dist/

This basic workflow separates testing and building into different jobs. The needs: test ensures builds only run after tests pass, saving compute resources and failing fast on test failures.

Advanced Caching Strategies

The real magic happens when you optimize caching. Here’s an enhanced version that caches both npm dependencies and Astro’s build cache:

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

      - name: Cache Astro build
        uses: actions/cache@v4
        with:
          path: |
            .astro
            node_modules/.astro
          key: ${{ runner.os }}-astro-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('src/**/*.{astro,ts,js}') }}
          restore-keys: |
            ${{ runner.os }}-astro-${{ hashFiles('**/package-lock.json') }}-
            ${{ runner.os }}-astro-

This caching setup can reduce build times from 2-3 minutes down to 30-45 seconds for incremental changes. The key includes both dependency and source file hashes, so the cache invalidates appropriately when either changes.

Deployment Strategies

Option 1: GitHub Pages Deployment

For simple static sites, GitHub Pages offers the easiest deployment path:

  deploy:
    runs-on: ubuntu-latest
    needs: build
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    
    permissions:
      contents: read
      pages: write
      id-token: write

    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}

    steps:
      - name: Download build artifacts
        uses: actions/download-artifact@v4
        with:
          name: astro-build
          path: dist

      - name: Setup Pages
        uses: actions/configure-pages@v4

      - name: Upload to GitHub Pages
        uses: actions/upload-pages-artifact@v3
        with:
          path: dist

      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v4

Option 2: Netlify Deployment

For more advanced features like form handling and edge functions:

  deploy:
    runs-on: ubuntu-latest
    needs: build
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    
    steps:
      - name: Download build artifacts
        uses: actions/download-artifact@v4
        with:
          name: astro-build
          path: dist

      - name: Deploy to Netlify
        uses: nwtgck/actions-netlify@v3.0
        with:
          publish-dir: './dist'
          production-branch: main
          github-token: ${{ secrets.GITHUB_TOKEN }}
          deploy-message: "Deploy from GitHub Actions"
        env:
          NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
          NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}

You’ll need to add your Netlify tokens to GitHub Secrets under Settings → Secrets and variables → Actions.

Option 3: Custom Server Deployment

For deployment to your own servers, here’s a pattern we use for client projects:

  deploy:
    runs-on: ubuntu-latest
    needs: build
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    
    steps:
      - name: Download build artifacts
        uses: actions/download-artifact@v4
        with:
          name: astro-build
          path: dist

      - name: Deploy to server via rsync
        uses: burnett01/rsync-deployments@7.0.1
        with:
          switches: -avzr --delete
          path: dist/
          remote_path: /var/www/html/
          remote_host: ${{ secrets.DEPLOY_HOST }}
          remote_user: ${{ secrets.DEPLOY_USER }}
          remote_key: ${{ secrets.DEPLOY_SSH_KEY }}

Multi-Environment Deployments

Real projects need staging and production environments. Here’s how we handle that:

name: Deploy Astro Site

on:
  push:
    branches: [ main, staging ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      # ... test steps same as before

  build:
    runs-on: ubuntu-latest
    needs: test
    steps:
      # ... build steps same as before

  deploy-staging:
    runs-on: ubuntu-latest
    needs: build
    if: github.ref == 'refs/heads/staging' && github.event_name == 'push'
    environment: staging
    steps:
      - name: Download build artifacts
        uses: actions/download-artifact@v4
        with:
          name: astro-build
          path: dist

      - name: Deploy to staging
        run: |
          echo "Deploying to staging environment"
          # Your staging deployment commands here

  deploy-production:
    runs-on: ubuntu-latest
    needs: build
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    environment: production
    steps:
      - name: Download build artifacts
        uses: actions/download-artifact@v4
        with:
          name: astro-build
          path: dist

      - name: Deploy to production
        run: |
          echo "Deploying to production environment"
          # Your production deployment commands here

The environment key enables GitHub’s environment protection rules, letting you require manual approvals for production deployments or restrict who can deploy.

Error Handling and Notifications

Production pipelines need robust error handling. Here’s how we add Slack notifications for deployment status:

  notify:
    runs-on: ubuntu-latest
    needs: [test, build, deploy-production]
    if: always()
    steps:
      - name: Notify deployment status
        uses: 8398a7/action-slack@v3
        with:
          status: ${{ job.status }}
          channel: '#deployments'
          text: |
            Astro site deployment ${{ job.status }}
            Branch: ${{ github.ref_name }}
            Commit: ${{ github.sha }}
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

For email notifications, use GitHub’s built-in notification settings or add a custom step:

      - name: Send email on failure
        if: failure()
        uses: dawidd6/action-send-mail@v3
        with:
          server_address: smtp.gmail.com
          server_port: 587
          username: ${{ secrets.EMAIL_USERNAME }}
          password: ${{ secrets.EMAIL_PASSWORD }}
          subject: 'Deployment Failed: ${{ github.repository }}'
          to: team@yourcompany.com
          from: github-actions@yourcompany.com
          body: |
            Deployment failed for ${{ github.repository }}
            Branch: ${{ github.ref_name }}
            Commit: ${{ github.sha }}
            View logs: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}

Performance Optimization

Parallel Job Execution

Run independent tasks simultaneously to cut total pipeline time:

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      # ... linting steps

  test:
    runs-on: ubuntu-latest
    steps:
      # ... testing steps

  build:
    runs-on: ubuntu-latest
    needs: [lint, test]  # Wait for both to complete
    steps:
      # ... build steps

Matrix Builds

Test across multiple Node.js versions:

  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [16, 18, 20]
    steps:
      - name: Setup Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'
      # ... rest of test steps

Docker Layer Caching

For complex builds with Docker:

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          cache-from: type=gha
          cache-to: type=gha,mode=max

Real-World Example: Our Production Workflow

Here’s the complete workflow we use at Odea Works for client Astro sites. This handles everything from code quality checks to multi-environment deployments:

name: Deploy Astro Site

on:
  push:
    branches: [ main, staging ]
  pull_request:
    branches: [ main ]

env:
  NODE_VERSION: 18

jobs:
  quality:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run linter
        run: npm run lint

      - name: Run tests
        run: npm run test:ci

      - name: Check build
        run: npm run build

  security:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Run security audit
        run: npm audit --audit-level moderate

  build:
    runs-on: ubuntu-latest
    needs: [quality, security]
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'

      - name: Cache Astro build
        uses: actions/cache@v4
        with:
          path: |
            .astro
            node_modules/.astro
          key: ${{ runner.os }}-astro-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('src/**/*.{astro,ts,js}') }}

      - name: Install dependencies
        run: npm ci

      - name: Build site
        run: npm run build

      - name: Upload build artifacts
        uses: actions/upload-artifact@v4
        with:
          name: astro-build-${{ github.sha }}
          path: dist/
          retention-days: 30

  deploy-staging:
    runs-on: ubuntu-latest
    needs: build
    if: github.ref == 'refs/heads/staging'
    environment: staging
    steps:
      - name: Download artifacts
        uses: actions/download-artifact@v4
        with:
          name: astro-build-${{ github.sha }}
          path: dist

      - name: Deploy to staging
        uses: nwtgck/actions-netlify@v3.0
        with:
          publish-dir: './dist'
          production-deploy: false
          github-token: ${{ secrets.GITHUB_TOKEN }}
          deploy-message: "Staging deploy from GitHub Actions"
        env:
          NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
          NETLIFY_SITE_ID: ${{ secrets.NETLIFY_STAGING_SITE_ID }}

  deploy-production:
    runs-on: ubuntu-latest
    needs: build
    if: github.ref == 'refs/heads/main'
    environment: production
    steps:
      - name: Download artifacts
        uses: actions/download-artifact

More from the blog

Need reliable infrastructure?

We build CI/CD pipelines, monitoring systems, and deployment infrastructure engineered for reliability.

Get our AI implementation playbook

A practical guide to evaluating, planning, and deploying AI in your business. Free, no spam.