Complete deployment guide for Astro static sites (e.g., Mizuki blog) to GitHub Pages with custom domain, HTTPS enforcement, and automated CI/CD via GitHub Actions.
When to Invoke
- User wants to deploy an Astro site to GitHub Pages
- User wants to configure a custom domain on GitHub Pages
- User needs to set up GitHub Actions deployment workflow for Astro
- User encounters GitHub Pages deployment failures
- User wants to enable HTTPS on GitHub Pages custom domain
- User needs to fork a template repo and deploy with custom content
Prerequisites
Required Tools
# Verify tools are availablegit --versiongh --versionpnpm --version # or npm/nodeIf gh is missing, install via scoop (preferred per user rules):
scoop install ghRequired Information from User
Before starting, ask the user for:
- GitHub Personal Access Token (scopes:
repo,workflow,admin:repo_hook) - Custom domain (e.g.,
blog.example.com) — must already have DNS configured - Repository strategy: Fork existing template or create new repo
- Proxy address if behind a firewall (default:
http://127.0.0.1:7890)
DNS Prerequisites
The custom domain must have DNS records pointing to GitHub Pages BEFORE deployment:
Option A — CNAME Record (recommended for subdomains):
Type: CNAMEName: blogValue: username.github.ioOption B — A Records (for apex domains):
Type: AName: @Value: 185.199.108.153Value: 185.199.109.153Value: 185.199.110.153Value: 185.199.111.153Verify DNS propagation before proceeding:
Resolve-DnsName blog.example.comPhase 1: GitHub Authentication
Set Environment Variables
# Set token and proxy for all subsequent commands$env:GH_TOKEN = "ghp_YOUR_TOKEN_HERE"$env:HTTP_PROXY = "http://127.0.0.1:7890"$env:HTTPS_PROXY = "http://127.0.0.1:7890"Authenticate with gh CLI
# Login using token (recommended for automation)$env:GH_TOKEN | gh auth login --with-token
# Verify authenticationgh auth statusCommon Auth Issues
Issue: Missing read:org scope
! Missing required token scopes: 'read:org'Fix: Use GH_TOKEN environment variable instead of keyring storage. All gh commands will use the token directly.
Issue: Network timeout connecting to GitHub
Fix: Set HTTP_PROXY and HTTPS_PROXY environment variables. Verify connectivity:
Test-NetConnection -ComputerName api.github.com -Port 443Issue: Keyring token invalid
X Failed to log in to github.com account (keyring)Fix: This is expected when using GH_TOKEN env var. The keyring account can be ignored as long as the GH_TOKEN account shows as active.
Configure Git Credential Helper
gh auth setup-gitPhase 2: Repository Setup
Option A: Fork Existing Template (Recommended)
Use this when deploying a template-based project (e.g., Mizuki) while keeping upstream sync capability.
# Fork the template repositorygh repo fork owner/template-repo --clone=no
# If already cloned locally, set up remotescd project-directorygit remote add origin https://github.com/YOUR_USERNAME/repo-name.gitgit remote add upstream https://github.com/original-owner/repo-name.git
# Verify remotesgit remote -vOption B: Create New Repository
cd project-directorygh repo create repo-name --public --description "Site description" --source . --pushVerify Repository State
# Check git status is cleangit status --short
# Check local and remote are in syncgit log --oneline -3git rev-list origin/master..master --count # Should be 0Phase 3: Project Configuration
Update Site URL
Locate the site configuration file and update the URL to the custom domain.
For Mizuki-style projects (src/config/siteConfig.ts):
export const siteConfig: SiteConfig = { // ... siteURL: "https://blog.example.com/", // Must end with slash // ...};For standard Astro projects (astro.config.mjs):
export default defineConfig({ site: "https://blog.example.com", // ...});Important: Always verify the actual config file path. Some projects use
src/config.ts, others usesrc/config/siteConfig.tsor inline inastro.config.mjs. Check the project structure first.
Create Deployment Workflow
Create .github/workflows/deploy.yml:
name: Deploy to GitHub Pages
on: push: branches: [master] workflow_dispatch:
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 Node.js uses: actions/setup-node@v4 with: node-version: lts/*
- name: Setup pnpm uses: pnpm/action-setup@v4 with: run_install: false
- name: Install dependencies run: pnpm install --frozen-lockfile
- name: Build with Astro run: pnpm build env: ENABLE_CONTENT_SYNC: "false"
- name: Setup Pages uses: actions/configure-pages@v5
- name: Upload artifact uses: actions/upload-pages-artifact@v3 with: path: 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@v4Notes on Workflow Configuration
- Branch name: Change
mastertomainif the default branch ismain - Package manager: If using npm instead of pnpm, replace pnpm steps with
npm ciandnpm run build - Content sync: Set
ENABLE_CONTENT_SYNC: "false"to disable content separation during CI build - Build output: Ensure the
pathmatches Astro’s output directory (default:dist) - Lock file: Ensure
pnpm-lock.yamlexists for--frozen-lockfileto work
Commit and Push
git add -Agit commit -m "deploy: configure GitHub Pages deployment and custom domain"git push origin masterPhase 4: Enable GitHub Pages
Configure Pages via API
# Enable GitHub Pages with custom domaingh api repos/YOUR_USERNAME/REPO_NAME/pages ` -X POST ` -f source[branch]=master ` -f source[path]=/ ` -f build_type=workflow ` -f cname=blog.example.comIf Pages Already Exists (Update Instead)
gh api repos/YOUR_USERNAME/REPO_NAME/pages ` -X PUT ` -f build_type=workflow ` -f cname=blog.example.comVerify Pages Configuration
gh api repos/YOUR_USERNAME/REPO_NAME/pages --jq '{ cname, build_type, https_enforced, status, url: .html_url, https_certificate: .https_certificate.state}'Phase 5: Trigger and Monitor Deployment
Check Workflow Status
# List workflowsgh workflow list --repo YOUR_USERNAME/REPO_NAME
# List recent runsgh run list --repo YOUR_USERNAME/REPO_NAME --limit 5Trigger Deployment Manually
If no run was triggered automatically (e.g., workflow was just added):
gh workflow run "Deploy to GitHub Pages" --repo YOUR_USERNAME/REPO_NAME --ref masterMonitor Deployment
# Get the run ID from the previous command output, then:gh run watch <RUN_ID> --repo YOUR_USERNAME/REPO_NAME
# Or view run detailsgh run view <RUN_ID> --repo YOUR_USERNAME/REPO_NAMECommon Build Failures
Issue: pnpm install --frozen-lockfile fails
- Cause:
pnpm-lock.yamlis outdated or missing - Fix: Run
pnpm installlocally, commit the updated lock file, push again
Issue: Astro build fails with missing dependencies
- Fix: Check
package.jsonscripts, ensurebuildscript exists and outputs todist
Issue: Node.js 20 deprecation warning
- This is a warning, not an error. The build will still succeed.
- To fix: Update workflow actions to newer versions (e.g.,
actions/checkout@v5)
Issue: Deploy job fails with “environment not found”
- Fix: The
github-pagesenvironment is auto-created on first deployment. Re-run the workflow.
Phase 6: Enable HTTPS
Enable HTTPS Enforcement
# IMPORTANT: Use -F (not -f) for boolean values in gh apigh api repos/YOUR_USERNAME/REPO_NAME/pages -X PUT -F https_enforced=trueCritical: Use
-Ffor boolean values,-ffor strings. Using-f truewill fail with HTTP 422:"true" is not of type boolean.
Verify HTTPS Configuration
gh api repos/YOUR_USERNAME/REPO_NAME/pages --jq '{ https_enforced, https_certificate: .https_certificate.state, https_expires: .https_certificate.expires_at}'HTTPS certificate provisioning may take a few minutes after the domain is first configured. If the certificate state is pending, wait and check again.
Phase 7: Verify Deployment
Check Site Accessibility
# Verify DNS resolutionResolve-DnsName blog.example.com
# Verify HTTP response$response = Invoke-WebRequest -Uri "https://blog.example.com/" -UseBasicParsing -TimeoutSec 30Write-Host "Status: $($response.StatusCode)"Write-Host "Content Length: $($response.Content.Length)"
# Extract page titleif ($response.Content -match '<title[^>]*>([^<]+)</title>') { Write-Host "Title: $($matches[1])"}Verify HTTPS Redirect
# HTTP should redirect to HTTPStry { $response = Invoke-WebRequest -Uri "http://blog.example.com/" -UseBasicParsing -MaximumRedirection 0 -ErrorAction Stop} catch { # A 301/302 redirect is expected Write-Host "Redirect status: $($_.Exception.Response.StatusCode)"}Quick Reference: Complete Workflow
# ============================================# 1. Setup environment# ============================================$env:GH_TOKEN = "ghp_YOUR_TOKEN"$env:HTTP_PROXY = "http://127.0.0.1:7890"$env:HTTPS_PROXY = "http://127.0.0.1:7890"
# ============================================# 2. Authenticate# ============================================$env:GH_TOKEN | gh auth login --with-tokengh auth statusgh auth setup-git
# ============================================# 3. Configure project# ============================================# Edit siteConfig.ts or astro.config.mjs to set siteURL# Create .github/workflows/deploy.yml
# ============================================# 4. Commit and push# ============================================git add -Agit commit -m "deploy: configure GitHub Pages deployment and custom domain"git push origin master
# ============================================# 5. Enable Pages with custom domain# ============================================gh api repos/USER/REPO/pages -X POST ` -f source[branch]=master -f source[path]=/ ` -f build_type=workflow -f cname=blog.example.com
# ============================================# 6. Trigger deployment# ============================================gh workflow run "Deploy to GitHub Pages" --repo USER/REPO --ref master# Wait for completiongh run list --repo USER/REPO --limit 1
# ============================================# 7. Enable HTTPS# ============================================gh api repos/USER/REPO/pages -X PUT -F https_enforced=true
# ============================================# 8. Verify# ============================================gh api repos/USER/REPO/pages --jq '{cname, https_enforced, status}'Invoke-WebRequest -Uri "https://blog.example.com/" -UseBasicParsing | Select-Object StatusCodeTroubleshooting
Token Permission Issues
| Error | Cause | Fix |
|---|---|---|
Missing required token scopes: 'read:org' | Token lacks org read scope | Use GH_TOKEN env var; read:org is optional for personal repos |
HTTP 403: Resource not accessible | Token lacks repo or workflow scope | Regenerate token with repo, workflow, admin:repo_hook scopes |
gh auth login hangs | Network issue | Set proxy env vars, use --with-token instead of interactive |
API Parameter Issues
| Error | Cause | Fix |
|---|---|---|
HTTP 422: "true" is not of type boolean | Used -f for boolean | Use -F flag for boolean values |
HTTP 422: Invalid property /https_enforced | Wrong parameter format | Use -F https_enforced=true not -f https_enforced=true |
Deployment Issues
| Error | Cause | Fix |
|---|---|---|
| No workflow runs after push | Workflow file not on default branch | Ensure deploy.yml is pushed to master/main |
| Build job: 0s runtime | Syntax error in workflow YAML | Validate YAML syntax, check indentation |
| Deploy job fails | Pages not enabled or wrong build_type | Ensure build_type: workflow in Pages config |
pnpm-lock.yaml mismatch | Lock file outdated | Run pnpm install locally, commit updated lock file |
Domain Issues
| Error | Cause | Fix |
|---|---|---|
Certificate state: pending | DNS not propagated or domain new | Wait 5-15 minutes, verify DNS with Resolve-DnsName |
| Site shows GitHub 404 | Pages not deployed or wrong branch | Check gh run list for successful deployment |
| HTTP not redirecting to HTTPS | HTTPS enforcement not enabled | Run gh api ... -X PUT -F https_enforced=true |
| Domain shows “not verified” | CNAME doesn’t match | Ensure DNS CNAME points to username.github.io |
Content Separation (Optional)
For projects supporting content separation (like Mizuki), you can keep custom content in a separate repository:
Local Mode (Default — No Config Needed)
Content lives in src/content/ and public/images/, committed with the code.
Remote Content Repository
# In .env file:ENABLE_CONTENT_SYNC=trueCONTENT_REPO_URL=https://github.com/your-username/Content-Repo.gitFor CI/CD deployment, set ENABLE_CONTENT_SYNC=false in the workflow to use local content, or configure secrets for remote sync.
Upstream Sync Strategy
When using a fork, sync updates from the original template:
# Fetch and merge upstream changesgit fetch upstreamgit merge upstream/master
# Resolve any conflicts in config files, then pushgit push origin masterTip: Keep custom content (articles, images, config changes) in separate commits to make conflict resolution easier during upstream syncs.
如果这篇文章对你有帮助,欢迎分享给更多人!
部分信息可能已经过时





