Setting Up GitLab CI/CD for Astro Blog Deployment to Cloudflare Pages

Setting Up GitLab CI/CD for Astro Blog Deployment to Cloudflare Pages

· 13 min read
gitlab-for-your-team

Automate your Astro blog’s build, test, and deploy workflow with GitLab CI/CD and Cloudflare Pages.

What is GitLab CI/CD?

GitLab’s built-in CI/CD automates your development workflows. For an Astro blog:

  • Automated Builds: Build on every push
  • Automated Tests: Verify your site builds correctly
  • Automated Deployments: Ship to Cloudflare Pages hands-free
  • Pipeline Visualization: Real-time build status
  • Artifact Storage: Keep outputs for debugging and rollbacks

Key Components:

ComponentPurpose
PipelineFull workflow from push to deployment
JobsIndividual tasks (build, test, deploy)
StagesSequential job groups (build → test → deploy)
RunnersServers that execute jobs
ArtifactsJob outputs (built site, test reports)
Pipeline EditorWeb IDE for .gitlab-ci.yml
Pipeline SchedulesCron-like periodic builds

Why Use GitLab CI/CD for Your Astro Blog?

Benefits for Your Blog:

  1. Zero Manual Deployment: Push and forget
  2. Build Verification: Catch errors before production
  3. Consistent Builds: Same environment every time
  4. Fast Rollbacks: Previous build artifacts always available
  5. Team Collaboration: Multiple authors, no deployment conflicts
  6. Security: Secrets stored securely in GitLab

Real-World Scenarios:

  • Typo Fix: Push → build → deploy in 2 minutes
  • New Post: Add post → pipeline runs → live in minutes
  • Dependency Updates: Update Astro → pipeline tests it → safe deploy
  • Multiple Environments: Stage first, then production

When Should You Use GitLab CI/CD?

Perfect For:

  • Frequent Updates: Regular publishing

  • Multiple Contributors: Team-driven content

  • Quality Assurance: Build verification before deploy

  • Professional Workflow: Production-grade deployment

  • Complex Builds: MDX, TypeScript, custom processing

  • Scheduled Deployments: Time-based publishing

Not Necessary For:

  • Static Rarely-Updated Sites: Monthly updates don’t justify the setup

  • Solo Developer Learning: Start with simple uploads first

  • No Build Step: Pure HTML/CSS with nothing to compile

How to Set Up GitLab CI/CD

Prerequisites

You need:

  • Self-hosted GitLab instance with your Astro blog repo
  • GitLab Runner configured (shared or project-specific)
  • Cloudflare Pages project created
  • Cloudflare API token with Pages edit permissions
  • Basic YAML knowledge

Architecture Overview

Diagram

Step 1: Create GitLab CI/CD Configuration

Create .gitlab-ci.yml in your repo root:

# .gitlab-ci.yml
# GitLab CI/CD pipeline for Astro blog deployment to Cloudflare Pages

# Define the Docker image to use for all jobs
# Using Node.js 20 as Astro requires Node 18+
image: node:20

# Define pipeline stages (run in order)
stages:
  - install # Install dependencies
  - build # Build the Astro site
  - test # Run tests
  - deploy # Deploy to Cloudflare Pages

# Cache node_modules between pipeline runs
# This speeds up subsequent builds
cache:
  key:
    files:
      - package-lock.json # Cache invalidates when package-lock.json changes
  paths:
    - node_modules/
    - .npm/

# Global variables available to all jobs
variables:
  npm_config_cache: '$CI_PROJECT_DIR/.npm'

# ==========================================
# STAGE 1: Install Dependencies
# ==========================================
install_dependencies:
  stage: install
  script:
    - echo "Installing dependencies..."
    - npm ci --prefer-offline --no-audit
  artifacts:
    expire_in: 1 hour
    paths:
      - node_modules/
  only:
    - branches # Run on all branches

# ==========================================
# STAGE 2: Build Astro Site
# ==========================================
build_site:
  stage: build
  dependencies:
    - install_dependencies
  script:
    - echo "Building Astro site..."
    - npm run build
    - echo "Build completed successfully!"
    - ls -lah dist/
  artifacts:
    expire_in: 1 week
    paths:
      - dist/ # Built site
      - package.json # Needed for deployment
  only:
    - branches

# ==========================================
# STAGE 3: Test (Optional but Recommended)
# ==========================================
test_build:
  stage: test
  dependencies:
    - build_site
  script:
    - echo "Running build verification tests..."
    # Check if dist directory exists and has content
    - test -d dist || (echo "dist directory not found!" && exit 1)
    - test -f dist/index.html || (echo "index.html not found!" && exit 1)
    # Check for required files
    - test -d dist/_astro || (echo "Assets directory missing!" && exit 1)
    - echo "Build verification passed!"
  only:
    - branches

# ==========================================
# STAGE 4: Deploy to Cloudflare Pages
# ==========================================

# Deploy to staging (feature branches)
deploy_staging:
  stage: deploy
  dependencies:
    - build_site
  before_script:
    - npm install -g wrangler
  script:
    - echo "Deploying to Cloudflare Pages (Staging)..."
    - |
      wrangler pages deploy dist \
        --project-name=blog-stack101 \
        --branch=$CI_COMMIT_REF_NAME \
        --commit-hash=$CI_COMMIT_SHORT_SHA \
        --commit-message="$CI_COMMIT_MESSAGE"
    - echo "Staging deployment complete!"
    - echo "Preview URL will be displayed above ⬆️"
  environment:
    name: staging/$CI_COMMIT_REF_NAME
    url: https://$CI_COMMIT_REF_SLUG.blog-stack101.pages.dev
  only:
    - branches
  except:
    - main # Don't deploy staging for main branch

# Deploy to production (main branch only)
deploy_production:
  stage: deploy
  dependencies:
    - build_site
  before_script:
    - npm install -g wrangler
  script:
    - echo "Deploying to Cloudflare Pages (Production)..."
    - |
      wrangler pages deploy dist \
        --project-name=blog-stack101 \
        --branch=main \
        --commit-hash=$CI_COMMIT_SHORT_SHA \
        --commit-message="$CI_COMMIT_MESSAGE"
    - echo "Production deployment complete!"
    - echo "Site is live at https://blog.stack101.dev 🚀"
  environment:
    name: production
    url: https://blog.stack101.dev
  only:
    - main # Only deploy to production from main branch
  when: manual # Require manual approval for production deployments

Step 2: Configure GitLab CI/CD Variables

Store credentials securely in GitLab.

  1. Go to your GitLab project
  2. Click SettingsCI/CD
  3. Expand Variables section
  4. Click Add Variable

Add Cloudflare Credentials:

Variable NameValueProtectedMaskedDescription
CLOUDFLARE_API_TOKENYour API token✅ Yes✅ YesPages edit permissions token
CLOUDFLARE_ACCOUNT_IDYour account ID✅ Yes❌ NoFrom Cloudflare dashboard URL

How to Get Cloudflare API Token:

  1. Go to Cloudflare Dashboard
  2. Click My ProfileAPI Tokens
  3. Click Create Token
  4. Use template: Edit Cloudflare Workers
  5. Or create a custom token with:
    • Account - Cloudflare Pages: Edit
  6. Copy the token immediately (shown only once)
  7. Add it to GitLab CI/CD variables

Step 3: Configure GitLab Runner

Option A: Use Shared Runners (Easier)

If your instance has shared runners:

  1. Go to SettingsCI/CDRunners
  2. Enable Shared runners
  3. Done

Option B: Set Up Project Runner (More Control)

For a dedicated runner:

# On your GitLab Runner server (can be same as GitLab host)

# Install GitLab Runner
curl -L "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh" | sudo bash
sudo apt-get install gitlab-runner

# Register the runner
sudo gitlab-runner register

# Follow the prompts:
# GitLab instance URL: https://your-gitlab.com
# Registration token: (from Settings → CI/CD → Runners)
# Description: blog-stack101-runner
# Tags: docker,astro,nodejs
# Executor: docker
# Default Docker image: node:20

Step 4: Create wrangler.toml Configuration

Add wrangler.toml to your project root:

# wrangler.toml
# Cloudflare Pages configuration

name = "blog-stack101"
compatibility_date = "2024-12-06"

# Pages-specific configuration
pages_build_output_dir = "dist"

# Environment variables (non-sensitive)
[env.production]
NODE_VERSION = "20"

[env.preview]
NODE_VERSION = "20"

Step 5: Test Your Pipeline

First Pipeline Run:

  1. Commit and push:

    git add .gitlab-ci.yml wrangler.toml
    git commit -m "feat: add GitLab CI/CD pipeline for automated deployment"
    git push origin feature/ci-cd
  2. Watch the pipeline: Go to CI/CDPipelines, click the running pipeline, and follow each job live.

  3. Read the results:

    • ✅ Green = Success
    • ❌ Red = Failed (click for logs)
    • 🔵 Blue = Running
    • ⏸️ Gray = Manual action needed

Expected Pipeline Flow:

┌─────────────────┐
│ install_deps    │  ~30s  ✅
└────────┬────────┘

┌────────▼────────┐
│ build_site      │  ~45s  ✅
└────────┬────────┘

┌────────▼────────┐
│ test_build      │  ~10s  ✅
└────────┬────────┘

┌────────▼────────┐
│ deploy_staging  │  ~20s  ✅
└─────────────────┘

Preview: https://feature-ci-cd.blog-stack101.pages.dev

Step 6: Configure Pipeline Schedules (Optional)

Rebuild your blog on a schedule. Useful for posts with future publish dates — they need a rebuild to go live.

  1. Go to CI/CDSchedules
  2. Click New schedule
  3. Configure:
    • Description: Nightly rebuild for scheduled posts
    • Interval pattern: 0 2 * * * (2 AM daily)
    • Cron timezone: Your timezone
    • Target branch: main
    • Variables: (none needed)
  4. Click Save pipeline schedule

Common Cron Patterns:

PatternDescription
0 2 * * *Daily at 2:00 AM
0 */6 * * *Every 6 hours
0 0 * * 0Weekly on Sunday at midnight
0 9 * * 1-5Weekdays at 9:00 AM

Step 7: Understanding Pipeline Artifacts

What Are Artifacts?

Files generated by pipeline jobs, kept for later use:

  • Build artifacts: The dist/ folder with your built site
  • Test reports: Automated test results
  • Logs: Build and deployment logs

Accessing Artifacts:

  1. Go to CI/CDPipelines
  2. Click a successful pipeline
  3. Click Download next to the job
  4. Select artifacts to download

Artifact Benefits:

  • Debugging: Inspect failed builds locally
  • Rollbacks: Redeploy previous versions
  • Auditing: Track what was deployed and when
  • Testing: Test builds locally before promoting

Advanced Configuration

Parallel Jobs for Faster Builds

Run tests simultaneously:

# Run multiple test jobs simultaneously
test_links:
  stage: test
  script:
    - npm run test:links # Check for broken links

test_a11y:
  stage: test
  script:
    - npm run test:accessibility # Accessibility tests

test_performance:
  stage: test
  script:
    - npm run test:lighthouse # Lighthouse performance tests

Conditional Deployments

Deploy only when relevant files change:

deploy_production:
  stage: deploy
  script:
    - wrangler pages deploy dist --project-name=blog-stack101
  only:
    refs:
      - main
    changes:
      - src/**/*
      - public/**/*
      - package.json
      - astro.config.mjs

Notification Integration

Get notified on pipeline failures:

# Add to .gitlab-ci.yml
notify_failure:
  stage: .post # Runs after all other stages
  script:
    - |
      curl -X POST https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/sendMessage \
        -d chat_id=$TELEGRAM_CHAT_ID \
        -d text="🚨 Pipeline failed for $CI_PROJECT_NAME"
  when: on_failure # Only run when pipeline fails
  only:
    - main

Troubleshooting Common Issues

Issue 1: Pipeline Fails at Build Stage

Error: npm ERR! missing script: build

Solution:

# Verify package.json has build script
cat package.json | grep -A 5 "scripts"

# Should contain:
# "scripts": {
#   "build": "astro build"
# }

Issue 2: Cloudflare Deployment Fails

Error: Error: Authentication error

Solution:

  • Verify CLOUDFLARE_API_TOKEN is set correctly
  • Confirm token has Pages edit permissions
  • Check token hasn’t expired

Issue 3: Out of CI/CD Minutes

Error: Quota exceeded for runner

Solution:

  • Check SettingsUsage Quotas
  • Optimize cache to cut build times
  • Use a self-hosted runner for unlimited minutes

Issue 4: Slow Builds

Symptoms: Builds taking >5 minutes

Solutions:

# Add cache for faster installs
cache:
  key: ${CI_COMMIT_REF_SLUG}
  paths:
    - node_modules/
    - .npm/

# Use npm ci instead of npm install
script:
  - npm ci --prefer-offline --no-audit

Best Practices

1. Use Branch Protection

Require passing CI before merge:

  1. Go to SettingsRepositoryBranch protection
  2. Add rule for main branch:
    • ✅ Require approval from code owners
    • ✅ Require pipelines to succeed
    • ✅ Prevent force push

2. Tag Production Releases

Tag each production deploy:

# After successful deployment
git tag -a v1.0.0 -m "Release v1.0.0 - Added CI/CD pipeline"
git push origin v1.0.0

3. Monitor Pipeline Performance

Check AnalyticsCI/CD Analytics for:

  • Pipeline success rate
  • Average duration
  • Most failing jobs

4. Use Pipeline Templates

Extract reusable job definitions:

# .gitlab/ci/templates/deploy.yml
.deploy_template:
  before_script:
    - npm install -g wrangler
  script:
    - wrangler pages deploy dist --project-name=$PROJECT_NAME

# Use in main .gitlab-ci.yml
include:
  - local: '.gitlab/ci/templates/deploy.yml'

deploy_prod:
  extends: .deploy_template
  variables:
    PROJECT_NAME: blog-stack101

Pipeline Workflow Example

Feature Development Workflow:

# 1. Create feature branch
git checkout -b feature/new-blog-post

# 2. Write your blog post
# Edit src/content/blog/my-new-post.mdx

# 3. Commit and push
git add .
git commit -m "feat: add new blog post about GitLab CI/CD"
git push origin feature/new-blog-post

# 4. Pipeline automatically:
#    ✅ Installs dependencies
#    ✅ Builds site
#    ✅ Runs tests
#    ✅ Deploys to staging preview
#    📧 Sends you preview URL

# 5. Review preview:
# https://feature-new-blog-post.blog-stack101.pages.dev

# 6. Create merge request
# GitLab → Merge Requests → New merge request

# 7. After approval and merge to main:
#    ✅ Pipeline runs again on main branch
#    ⏸️ Waits for manual approval
#    ✅ Click "Deploy to Production"
#    🚀 Live at https://blog.stack101.dev

Monitoring and Maintenance

Weekly:

  • Fix failed pipelines
  • Check CI/CD usage quota
  • Address dependency security alerts
  • Clean up old artifacts

Monthly:

  • Update GitLab Runner
  • Optimize cache config
  • Verify Cloudflare API token expiration
  • Bump Node.js version if needed

Cost Considerations

GitLab CI/CD Free Tier:

  • 400 minutes/month CI/CD minutes
  • 10 GB artifact storage
  • 5 concurrent jobs

Your Blog’s Usage:

A typical pipeline takes ~2 minutes.

  • 10 posts/month: ~20 min (5% of quota)
  • 50 posts/month: ~100 min (25% of quota)
  • Daily rebuilds: ~60 min (15% of quota)

At ~40% of free quota, this costs $0/month.

If You Exceed Free Tier:

Option 1: Self-hosted runner (unlimited, free) Option 2: GitLab Premium ($29/user/month, 10,000 min) Option 3: Optimize pipeline to reduce minutes

Conclusion

Your pipeline now:

  • ✅ Builds on every commit
  • ✅ Tests to catch errors early
  • ✅ Deploys previews for feature branches
  • ✅ Requires manual approval for production
  • ✅ Stores artifacts for debugging and rollbacks
  • ✅ Provides full deployment history

Every deploy is tested and validated automatically.

Next Steps

Enhance your pipeline with:

  1. Automated Testing: Link checking, accessibility, performance
  2. SEO Validation: Meta tag and sitemap verification
  3. Image Optimization: Automatic compression during build
  4. Content Validation: Spelling, grammar, broken link checks
  5. Analytics Integration: Deployment success rate tracking

Resources