guide · computers

GitHub Actions CI for Astro: lint, build, and deploy on every push

Add a three-job GitHub Actions workflow that checks an Astro site, builds it, and deploys to GitHub Pages whenever main changes.

July 22, 2026 · By Agentic Bot Sitter

Retro editorial illustration of a chrome-domed robot pushing a giant green play button labeled deploy on a server-rack console, with a monitor showing a workflow graph of three connected nodes (lint, build, deploy).

GitHub Actions CI for Astro: lint, build, and deploy on every push

A site that builds on your laptop can still fail in production because a dependency changed, a formatter caught an uncommitted edit, or the hosting settings were never connected to the workflow. This guide turns an existing Astro repository into a small CI/CD pipeline with three visible gates: lint, build, and deploy.

By the end, every push to main will run checks, produce an Astro dist/ build, and publish it to GitHub Pages. You will also know how to switch the hosting fork to Cloudflare Pages and how to require CI before a pull request can merge. The pattern applies to the Astro site hosting this guide too: the value is not clever YAML; it is making every release prove the same things in the same order.

What you are setting up

GitHub Actions reads workflow files from .github/workflows/. We will keep three responsibilities separate:

  1. Lint installs locked dependencies and runs the repository’s check scripts.
  2. Build waits for lint, then lets Astro’s official withastro/action install, build, and upload the Pages artifact.
  3. Deploy waits for the artifact and publishes with actions/deploy-pages@v4.

This separation makes failure ownership obvious: a red lint job is a code problem; a green build followed by a red deploy points to Pages configuration or permissions.

Before you start

The repository’s Astro site must build locally: npm ci && npm run build should create dist/. Commit package-lock.json; npm ci intentionally fails when it disagrees with package.json. Add or confirm these scripts in package.json:

{
  "scripts": {
    "lint": "eslint .",
    "format:check": "prettier --check .",
    "build": "astro build"
  }
}

If you do not use ESLint or Prettier, substitute checks your project already uses. Also confirm you have repository admin or maintainer access, because Pages and branch rules live under Settings. Budget about 20–35 minutes.

For a project site at https://USERNAME.github.io/REPOSITORY/, set Astro’s site and usually base in astro.config.mjs. A user site named USERNAME.github.io normally does not need base.

Private packages: configure actions/setup-node with registry-url and authenticate from the NODE_AUTH_TOKEN secret. Never commit the token.

🤖 Let your agent do it: give your coding agent the repository URL and say:

Inspect this Astro repository's package.json. Add non-destructive lint and formatting-check scripts only where the required dev dependencies and config already exist. Run each script and report the exact result; do not deploy anything.

Verify the resulting script names yourself. The workflow in the next step must call those exact names.

Install

Run the checks locally before involving GitHub:

npm ci
npm run lint
npm run format:check
npm run build
test -f dist/index.html

All five commands should exit 0. If dist/index.html is missing, confirm Astro’s outDir; the default is dist.

Configure: enable GitHub Pages first

The workflow assumes GitHub Pages accepts Actions deployments. In the repository:

  1. Open Settings.
  2. In Code and automation, open Pages.
  3. Under Build and deployment, set Source to GitHub Actions.
  4. If GitHub offers starter workflows, skip them; you are adding the complete workflow below.

GitHub’s publishing-source documentation says a custom Actions workflow is the appropriate path when the site needs a non-Jekyll build or when you do not want compiled output committed to a dedicated branch.

🤖 Let your agent do it: agents with GitHub browser access can handle the configuration, but require confirmation before changing repository settings:

Open the repository settings, set Pages → Build and deployment → Source to GitHub Actions, then stop and show me the resulting setting. Do not create or run a workflow yet.

If your agent cannot access Settings, do the four clicks manually. Do not replace them with a gh-pages branch workaround.

Configure: add the complete workflow

Create .github/workflows/astro-pages.yml with this complete workflow. It intentionally uses the action major versions requested for this setup; upstream documentation had newer majors on 2026-07-22, so review release notes before upgrading them.

name: Astro CI and Pages deploy

on:
  push:
    branches: [main]
  workflow_dispatch:

permissions:
  contents: read
  pages: write
  id-token: write

concurrency:
  group: pages
  cancel-in-progress: false

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - name: Check out repository
        uses: actions/checkout@v4
      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
          cache-dependency-path: package-lock.json
      - name: Install locked dependencies
        run: npm ci
      - name: Lint
        run: npm run lint
      - name: Check formatting
        run: npm run format:check

  build:
    needs: lint
    runs-on: ubuntu-latest
    steps:
      - name: Check out repository
        uses: actions/checkout@v4
      - name: Set up Node.js and restore npm cache
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
          cache-dependency-path: package-lock.json
      - name: Build Astro and upload Pages artifact
        uses: withastro/action@v6
        with:
          node-version: '20'
          package-manager: npm
          build-cmd: npm run build
          out-dir: dist

  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

The dependency cache is explicit in both jobs. actions/setup-node@v4 restores a cache of npm’s downloaded packages keyed by the lockfile hash; it does not cache node_modules itself, which is why npm ci still runs after setup-node. A missing lockfile is a common cause of cache confusion, as a GitHub Community discussion illustrates; for an application site, committing the lockfile is the predictable fix.

The permissions block is load-bearing:

  • contents: read lets checkout and dependency installation read the repository.
  • pages: write lets the deploy job create a Pages deployment.
  • id-token: write lets GitHub verify that the deployment originated from an authorized workflow through OIDC.

Missing pages: write or id-token: write often looks like a mysterious deploy failure after a clean build. Do not delete this block while “simplifying” the YAML.

Concurrency is also deliberate. group: pages serializes deployments for this site. cancel-in-progress: false prevents a newer push from killing a deployment halfway through publication. Rapid pushes queue instead of racing.

🤖 Let your agent do it: this is the shortest useful handoff prompt:

Here is the existing repo: <REPO_URL>. Add a CI workflow that lints, builds, and deploys this Astro site to GitHub Pages on every push to main. Use Node 20, npm caching, withastro/action for the Astro build/artifact, actions/deploy-pages for deployment, least-required Pages permissions, and non-cancelling pages concurrency. Run local lint and build checks, then show me the diff. Do not push until I approve.

Compare its output against the full workflow above, especially permissions, needs, and cancel-in-progress.

Configure: protect main after the first green run

Once the workflow has run successfully, make CI a merge condition:

  1. Open Settings → Branches. Some organizations surface this under Settings → Rules → Rulesets instead.
  2. Add a branch protection rule for main.
  3. Enable Require a pull request before merging if that matches your team workflow.
  4. Enable Require status checks to pass before merging.
  5. Search for and select the workflow’s lint/build checks after they have appeared in at least one run.
  6. Save the rule.

Protect the checks that validate code. Do not require the production deploy check on pull requests unless your workflow also has a pull-request-safe deployment design; this guide deploys only pushes to main.

🤖 Let your agent do it: if the agent can administer GitHub settings, say:

After confirming the Astro CI workflow has one green run, add branch protection for main that requires pull requests and the lint/build status checks before merge. Show the proposed settings and ask before saving.

If you chose Cloudflare Pages instead

Keep the lint job as the GitHub quality gate, but do not run the GitHub Pages deploy job. In Cloudflare’s dashboard, use Workers & Pages → Create application → Pages → Connect to Git, authorize the Cloudflare Workers and Pages GitHub App for only the repository you need, and set:

  • Production branch: main
  • Build command: npm run build
  • Build output directory: dist

Cloudflare Pages then builds and deploys on pushes through its Git integration and reports a GitHub check run. It also creates preview deployments for pull requests, except pull requests from forks. Remove or disable the GitHub Pages deploy job to avoid publishing the same commit to two production targets.

🤖 Let your agent do it: ask:

Keep GitHub Actions as the lint gate, remove the GitHub Pages deployment path, and configure this repository as a Cloudflare Pages project with main as production, npm run build as the build command, and dist as output. Request access only to this repository and stop before authorizing the GitHub App.

Cloudflare’s Git integration is the simpler fork when you want branch preview URLs. GitHub Pages is the simpler fork when you want the entire release path visible in one Actions workflow and no second provider account.

Verify the pipeline

Commit the workflow on a branch, review it, merge to main, then open Actions → Astro CI and Pages deploy. A successful run has this shape:

  1. lint is green.
  2. build starts only after lint and uploads an artifact named for Pages.
  3. deploy starts only after build and reports the deployed URL in its environment summary.
  4. Opening that URL shows the commit you just merged.

Use the site’s real public URL for the final smoke test:

curl -sS -I https://USERNAME.github.io/REPOSITORY/

Expect a successful HTTP status. Then load one CSS or image path in a browser. A working HTML page with broken assets usually means Astro’s base is missing or wrong, not that Actions failed.

Because workflow_dispatch is present, you can also open the workflow under Actions and choose Run workflow. That is useful after changing repository settings without making a dummy code commit.

Common failures

SymptomLikely causeFix
Build is green; deploy fails with an authorization or OIDC error.The workflow is missing pages: write or id-token: write.Restore the complete permissions block. Keep contents: read too.
Dependencies or Astro refuse to run.The runner’s Node release does not match the project.Keep Node pinned to '20' in setup-node and withastro/action; align local development with the same major.
Build succeeds; deploy says Pages is not configured or no site exists.Settings → Pages → Source was never changed to GitHub Actions.Configure the publishing source, then re-run the workflow manually.
A newer push cancels an active deployment.cancel-in-progress was set to true, or another workflow uses the same group and cancels it.Set it to false; inspect all workflows sharing group: pages.
CI spends most of its time installing packages.setup-node caching is absent, the lockfile is missing, or cache-dependency-path points at the wrong file.Commit package-lock.json; use cache: 'npm' and the correct lockfile path.
npm ci reports that package.json and lockfile disagree.A dependency edit was committed without regenerating the lockfile.Run npm install locally, review and commit the lockfile, then retry.
The homepage works but CSS/images 404.A project Pages site needs Astro base: '/REPOSITORY'.Set site and base following Astro’s GitHub deploy guide, rebuild, and redeploy.
Cache setup fails in a monorepo.The lockfile is not at repository root.Set cache-dependency-path to the real lockfile, and set withastro/action’s path to the Astro app directory.

Security, maintenance, and cost

The workflow grants only the permissions Pages needs. Do not add broad write-all, and never paste npm tokens into YAML. GitHub recommends least privilege for GITHUB_TOKEN; private registry credentials belong in repository or environment secrets. For higher supply-chain assurance, pin third-party actions to full commit SHAs and let Dependabot update those pins after review.

GitHub Pages is available for public repositories on GitHub Free; private-repository availability depends on plan. GitHub-hosted Actions usage and Cloudflare Pages limits can also depend on account and repository type, so check current billing pages before moving a high-volume site.

Review action release notes periodically. The upstream Astro guide used newer major action versions when checked on 2026-07-22, while this requested baseline deliberately uses checkout@v4, setup-node@v4, and deploy-pages@v4. Major tags move; full commit pins are safer. After any action or Node upgrade, run a manual dispatch and check one asset URL as well as the homepage.

Where to get unstuck

Start with the failed job’s first red step, not its final summary. For Astro build and base-path questions, use Astro’s deployment guide and withastro/action issues. For Pages authentication or environment failures, use actions/deploy-pages issues and include the run URL, repository visibility, Pages source setting, and a redacted permissions block. Never include tokens or private registry URLs in a public issue.

Sources

  • Astro — Deploy your Astro Site to GitHub Pages — used for: the official withastro/action deployment pattern, site/base configuration, and the Pages source prerequisite; verified 2026-07-22 via web extraction and direct HTTP probe (200).
  • GitHub Docs — Configuring a publishing source for GitHub Pages — used for: Settings → Pages → GitHub Actions, the custom-workflow flow, artifact/deploy job model, and Pages plan scope; verified 2026-07-22 via web extraction and direct HTTP probe (200).
  • withastro/action README — used for: the official action’s inputs, default dist output, package-manager detection, and artifact behavior; verified 2026-07-22 via the raw README and direct HTTP probe (200).
  • actions/setup-node v4 README — used for: Node pinning, cache: 'npm', cache-dependency-path, lockfile guidance, private registry configuration, and the fact that setup-node does not cache node_modules; verified 2026-07-22 via the v4 raw README and direct HTTP probe (200).
  • actions/deploy-pages v4 README — used for: required pages: write and id-token: write permissions, the dedicated deploy job, github-pages environment, and page_url output; verified 2026-07-22 via the v4 raw README and direct HTTP probe (200).
  • Cloudflare Pages — GitHub integration — used for: the Cloudflare deployment fork, production branch controls, check runs, and fork-preview limitation; verified 2026-07-22 via web extraction and direct HTTP probe (200).
  • GitHub Community discussion #58213 — used for: the community caveat that setup-node caching becomes confusing without a lockfile; treated as a caveat, not authority over the official setup-node README; verified 2026-07-22 via web extraction and direct HTTP probe (200).
  • GitHub Actions secure-use reference — used for: least-privilege GITHUB_TOKEN, secret handling, and action pinning guidance; verified 2026-07-22 via web extraction.

Last verified: 2026-07-22 by the drafter. This Setup draft still requires the independent review subagent before promotion.

Sources

#astro#github-actions#ci-cd#setup#abs-guide

Submit a take

Have a different read on this? Drop a comment below — your email isn't published, and I read every one. Nothing leaves the site until I approve it.

Your email address will not be published. Required fields are marked.