Skip to content
Mighten's Blog

Migrating to Astro: A Modern Blogging Setup with Dev Container

Migrate personal blog to Astro: Dev Containers for isolated dev environments, Claude Code for vibe coding, GitHub Actions for continuous deployment.

11 min read

Hi! Today, I will show you how to leverage a modern Dev Container setup to seamlessly develop and deploy your Astro blog.

A personal blog usually starts simple: write Markdown files, run a static site generator, and publish the generated HTML. Over time, however, the surrounding tooling becomes the real challenge.

In 2026, I decided to rebuild my blog workflow around a more modern stack:

  • Astro as the static site framework
  • Dev Container as the isolated development environment
  • Claude Code as the in-container AI pair-programmer
  • GitHub Actions as the automated deployment pipeline

The goal is not only to create a new blog, but also to establish a reproducible engineering workflow: anyone can clone the repository, open it in VS Code, start the Dev Container, and immediately get the same development environment.

Astro

Astro is a modern web framework designed for content-driven websites, especially blogs and documentation sites. Compared with traditional static site generators, Astro provides a better balance between simplicity and extensibility:

  • Content-first architecture: Markdown and Markdown-based content remain first-class citizens. Blog posts can be managed as structured content collections with front matter, tags, metadata, and custom fields.
  • Modern frontend ecosystem: Astro works naturally with modern JavaScript and TypeScript tooling. When advanced UI components are needed, frameworks such as React, Vue, or Svelte can be integrated without converting the whole website into a client-heavy application.
  • Excellent performance by default: Astro generates static HTML by default and only ships JavaScript when it is actually needed. For a personal blog, this means faster loading speed and a simpler deployment model.
  • Strong customization capability: Themes provide a quick starting point, while the underlying Astro project structure still allows full control over layouts, components, routing, and build behavior.

After comparing Hexo, Hugo, and newer frameworks, Astro provides a more future-proof foundation for a personal blog that may gradually evolve into a richer website.

Dev Container

A good development environment should be part of the project, not a hidden configuration on a personal computer.

A Dev Container is a containerized development environment defined by configuration files inside the project repository (commonly, .devcontainer/devcontainer.json). Instead of installing languages, dependencies, and tools directly on the host machine, developers open the project inside a pre-configured Docker container managed by VS Code - The idea is similar to “infrastructure as code”: the development environment itself becomes reproducible and version-controlled.

Previously, I built and previewed Hugo projects inside WSL. While WSL solved many Linux compatibility problems, the environment still accumulated dependencies and configuration over time.

Dev Container however, addresses this problem by containerizing the development environment:

  • The host machine only needs VS Code and Docker.
  • Node.js, Bun, dependencies, and CLI tools live inside the container.
  • Every contributor gets the same environment.
  • The setup can be recreated at any time from configuration files.

Automation turns a manual build process into a pipeline.

GitHub Actions

GitHub Actions is a continuous integration and continuous delivery (CI/CD) platform built directly into GitHub. It allows you to automate your build, test, and deployment workflows via YAML configuration files, triggering tasks automatically based on events like code pushes or pull requests.

Before adopting this workflow, publishing a blog meant running a build command locally and manually uploading files or pushing a compiled dist folder to a remote repository. This approach is prone to environment disparities and accidental deployment errors.

Integrating GitHub Actions into the mix solves these issues entirely:

  • Build isolation: The compilation environment is completely isolated within GitHub Runners, eliminating the need for any physical machines.
  • Hands-off publishing: You don’t need to manually build or sync your static files. A simple git push on your workspace branch triggers the pipeline to build and publish your site automatically.
  • Secure credential management: Sensitive tokens required to push to your public GitHub Pages repository are securely managed via encrypted repository secrets.

By automating the pipeline, your focus shifts entirely back to where it matters most: writing content in Markdown, letting the automation handle the rest.

So, in the following sections, I will show you how to build an Astro blog step by step.


I used to compile and preview my Hugo blog inside the Windows Subsystem for Linux (WSL), but over time, I found that this cluttered my system files. Besides, I was getting really tired of constantly manually configuring development environments.

So, now in 2026, I will revamp my environment with VS Code and Dev Container. My developing environment is that:

flowchart LR
subgraph Windows["Windows 11"]
V["VS Code"]
end
subgraph Ubuntu["Ubuntu Server 26.04 LTS"]
subgraph Docker["Docker Engine"]
D["Dev Container"]
end
end
V <--> D

The Dev Container provides all the necessary tools and utilities, while VS Code automates the environment scaffolding and communication.

Update /etc/docker/daemon.json with your proxy server addresses:

/etc/docker/daemon.json
{
"proxies": {
"http-proxy": "http://${YOUR_PROXY_ADDRESS}",
"https-proxy": "http://${YOUR_PROXY_ADDRESS}",
"no-proxy": "localhost,127.0.0.1,.local,internal.com,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16"
},
"mtu": 1450
}

NOTE:

  • Reduce mtu to 1450: If you use proxy, the size of payload may exceed MTU of 1500.
  • Replace ${YOUR_PROXY_ADDRESS}, such as 127.0.0.1:7897, according to your intranet settings.
  • This configuration proxies the Docker daemon (for pulling images and building containers)
  • The daemon can use 127.0.0.1 because it runs directly on the host

Browse https://astro.build/themes/, and choose whatever you want.

But today, I will showcase my favorite theme: Chirping Astro

create file in code repo .devcontainer/devcontainer.json:

.devcontainer/devcontainer.json
{
"name": "Chirping Astro",
"image": "mcr.microsoft.com/devcontainers/typescript-node:5-24-bookworm",
"remoteEnv": {
// Claude Code settings
"ANTHROPIC_BASE_URL": "${localEnv:ANTHROPIC_BASE_URL:https://api.deepseek.com/anthropic}",
"ANTHROPIC_AUTH_TOKEN": "${localEnv:ANTHROPIC_AUTH_TOKEN:sk-xxxxxx}",
"ANTHROPIC_MODEL": "${localEnv:ANTHROPIC_MODEL:deepseek-v4-pro[1m]}",
"CLAUDE_CODE_SUBAGENT_MODEL": "${localEnv:CLAUDE_CODE_SUBAGENT_MODEL:deepseek-v4-flash}",
"ANTHROPIC_DEFAULT_FABLE_MODEL": "${localEnv:ANTHROPIC_DEFAULT_FABLE_MODEL:deepseek-v4-pro[1m]}",
"ANTHROPIC_DEFAULT_OPUS_MODEL": "${localEnv:ANTHROPIC_DEFAULT_OPUS_MODEL:deepseek-v4-pro[1m]}",
"ANTHROPIC_DEFAULT_SONNET_MODEL": "${localEnv:ANTHROPIC_DEFAULT_SONNET_MODEL:deepseek-v4-pro[1m]}",
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "${localEnv:ANTHROPIC_DEFAULT_HAIKU_MODEL:deepseek-v4-flash}",
"CLAUDE_CODE_EFFORT_LEVEL": "${localEnv:CLAUDE_CODE_EFFORT_LEVEL:max}",
"API_TIMEOUT_MS": "1200000",
"BASH_DEFAULT_TIMEOUT_MS": "300000",
"CLAUDE_CODE_AUTO_COMPACT_WINDOW": "${localEnv:CLAUDE_CODE_AUTO_COMPACT_WINDOW:1000000}",
"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "${localEnv:CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC:1}",
"DISABLE_AUTOUPDATER": "${localEnv:DISABLE_AUTOUPDATER:1}",
"CLAUDE_CONFIG_DIR": "/home/node/.claude",
// Other system settings
"NODE_OPTIONS": "--max-old-space-size=4096",
"BUN_VERSION": "1.3.13",
"BUN_INSTALL": "/home/node/.bun",
"PATH": "/home/node/.bun/bin:${containerEnv:PATH}",
"no_proxy": "localhost,127.0.0.1,::1,.internal,host.docker.internal,.local,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16",
"http_proxy": "http://host.docker.internal:7897",
"https_proxy": "http://host.docker.internal:7897",
"all_proxy": "http://host.docker.internal:7897",
"NO_PROXY": "localhost,127.0.0.1,::1,.internal,host.docker.internal,.local,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16",
"HTTP_PROXY": "http://host.docker.internal:7897",
"HTTPS_PROXY": "http://host.docker.internal:7897",
"ALL_PROXY": "http://host.docker.internal:7897"
},
"features": {
"ghcr.io/anthropics/devcontainer-features/claude-code:1.0": {}
},
"customizations": {
"vscode": {
"extensions": [
"astro-build.astro-vscode",
"bradlc.vscode-tailwindcss",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"eamodio.gitlens"
]
}
},
// Extra flags appended to `docker run` when the Dev Container is created.
"runArgs": [
// Map `host.docker.internal` to the Docker host so the container can
// reach host-side services such as the proxy above.
"--add-host=host.docker.internal:host-gateway"
],
"forwardPorts": [4321],
"portsAttributes": {
"4321": {
"label": "Astro Dev Server",
"onAutoForward": "notify"
}
},
"postCreateCommand": "curl -fsSL https://bun.sh/install | bash -s -- bun-v1.3.13 && /home/node/.bun/bin/bun install",
"remoteUser": "node",
"init": true
}

Note:

  • Dev Container Base Image: mcr.microsoft.com/devcontainers/typescript-node:5-24-bookworm (Node.js 24 on Debian Bookworm).
  • forwardPorts and portsAttributes expose the Astro dev server on port 4321.
  • postCreateCommand installs a pinned Bun (1.3.13) and runs bun install, so packages are ready right after the container builds. Pinning Bun here keeps the dev environment in lockstep with CI (see Step 2.2).
  • Proxy for in-container traffic. The http_proxy / https_proxy / all_proxy entries (and their upper-case twins) in remoteEnv route the container’s own outbound HTTP/HTTPS through the host proxy at http://host.docker.internal:7897.
  • Reaching the host from inside the container. On Docker Desktop, host.docker.internal resolves automatically; on the Linux Docker Engine used here it does not, so runArgs passes --add-host=host.docker.internal:host-gateway, designating the pseudoname to the host’s gateway IP. Inside the container, 127.0.0.1 is the container’s own loopback; to use the proxy on the host, the container must use host.docker.internal:7897.
  • The features and remoteEnv blocks also integrate Claude Code into the container - see Step 1.3 for the full breakdown (skip it if you don’t use Claude Code).
  • remoteUser: "node" runs the container as the non-root node user, and init: true starts an init process (PID 1) for clean signal handling.

In VS Code project page, press Ctrl + Shift + P, click Dev Container: Reopen Folder in SSH to start building Dev Container.

Once the Dev Container finishes building, a fresh CLI prompt will appear, leaving you with a ready-to-go development environment:

node ➜ /workspaces/chirping-astro (main) $

Beyond the blog toolchain, this version of the Dev Container also bakes Claude Code (Anthropic’s agentic coding CLI) directly into the environment. Every fresh container then ships with an AI pair-programmer that already lives inside the project, reads the codebase, and needs no per-machine installation. Because the configuration lives in devcontainer.json, the setup is as reproducible as the rest of the environment.

The integration is two pieces that already appear in the devcontainer.json from Step 1.2.

1. Install the CLI with a devcontainer feature

"features": {
"ghcr.io/anthropics/devcontainer-features/claude-code:1.0": {}
}

The official claude-code feature installs the claude CLI at container build time, so it is on PATH the moment the container starts. There is no npm install step and no manual setup.

2. Configure it with remoteEnv

"remoteEnv": {
// Claude Code settings
"ANTHROPIC_BASE_URL": "${localEnv:ANTHROPIC_BASE_URL:https://api.deepseek.com/anthropic}",
"ANTHROPIC_AUTH_TOKEN": "${localEnv:ANTHROPIC_AUTH_TOKEN:sk-xxxxxx}",
"ANTHROPIC_MODEL": "${localEnv:ANTHROPIC_MODEL:deepseek-v4-pro[1m]}",
"CLAUDE_CODE_SUBAGENT_MODEL": "${localEnv:CLAUDE_CODE_SUBAGENT_MODEL:deepseek-v4-flash}",
"ANTHROPIC_DEFAULT_FABLE_MODEL": "${localEnv:ANTHROPIC_DEFAULT_FABLE_MODEL:deepseek-v4-pro[1m]}",
"ANTHROPIC_DEFAULT_OPUS_MODEL": "${localEnv:ANTHROPIC_DEFAULT_OPUS_MODEL:deepseek-v4-pro[1m]}",
"ANTHROPIC_DEFAULT_SONNET_MODEL": "${localEnv:ANTHROPIC_DEFAULT_SONNET_MODEL:deepseek-v4-pro[1m]}",
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "${localEnv:ANTHROPIC_DEFAULT_HAIKU_MODEL:deepseek-v4-flash}",
"CLAUDE_CODE_EFFORT_LEVEL": "${localEnv:CLAUDE_CODE_EFFORT_LEVEL:max}",
"API_TIMEOUT_MS": "1200000",
"BASH_DEFAULT_TIMEOUT_MS": "300000",
"CLAUDE_CODE_AUTO_COMPACT_WINDOW": "${localEnv:CLAUDE_CODE_AUTO_COMPACT_WINDOW:1000000}",
"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "${localEnv:CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC:1}",
"DISABLE_AUTOUPDATER": "${localEnv:DISABLE_AUTOUPDATER:1}",
"CLAUDE_CONFIG_DIR": "/home/node/.claude"
}

A few details worth calling out:

  • Override from host, fall back to file. Every ${localEnv:VAR:default} value is read from your host shell first, then falls back to the literal default after the colon. This lets you keep secrets such as ANTHROPIC_AUTH_TOKEN out of the repo (export it in your shell) while still committing sane defaults for everything else. The sk-xxxxxx here is a placeholder, so never commit a real key.
  • Any Anthropic-compatible endpoint. ANTHROPIC_BASE_URL points Claude Code at DeepSeek’s /anthropic endpoint, so it speaks the Anthropic API shape but runs DeepSeek models. Swap in https://api.anthropic.com (or any compatible provider) plus the matching model names to switch providers; nothing else needs to change.
  • Remap the model tiers. Claude Code exposes tiered model slots (Fable / Opus / Sonnet / Haiku) plus a subagent model. The ANTHROPIC_DEFAULT_*_MODEL and CLAUDE_CODE_SUBAGENT_MODEL variables map each slot to an actual provider model: here the heavy slots go to deepseek-v4-pro[1m] and the lighter ones to deepseek-v4-flash.
  • Context managements:
    • CLAUDE_CODE_EFFORT_LEVEL=max opts into the deepest reasoning
    • API_TIMEOUT_MS and BASH_DEFAULT_TIMEOUT_MS raise the request and bash-command ceilings so long agent turns and builds are not killed early
    • CLAUDE_CODE_AUTO_COMPACT_WINDOW raises the auto-compact threshold
    • CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC and DISABLE_AUTOUPDATER keep the container quiet and pin the installed version
    • CLAUDE_CONFIG_DIR fixes where Claude Code stores its settings (under the node user’s home).

The remaining remoteEnv entries are general system tuning rather than Claude Code itself:

  • NODE_OPTIONS=--max-old-space-size=4096 gives Node.js a 4 GB heap (handy for Claude Code coding agents and also for large Astro builds)
  • BUN_INSTALL points the installer at /home/node/.bun
  • PATH puts the resulting bun binary on the container’s PATH.

postCreateCommand pins the version to 1.3.13 by passing bun-v1.3.13 to the install script.

flowchart LR
subgraph Host["Host machine"]
S["Shell env (secrets)"]
end
subgraph DC["Dev Container"]
CC["Claude Code CLI"]
end
subgraph API["Anthropic-compatible API"]
M["DeepSeek models"]
end
S -->|localEnv override| CC
CC -->|ANTHROPIC_BASE_URL| M

Once the container is built, run claude in the terminal and you are pair-programming inside the project. If you do not use Claude Code, you can safely delete the features block and the Claude Code lines from remoteEnv; the rest of the setup is unaffected.

update package.json by adding --host 0.0.0.0, namely:

package.json
{
"scripts": {
"dev": "astro dev --host 0.0.0.0",
"preview": "astro preview --host 0.0.0.0",
"serve": "bun run build && bun preview --host 0.0.0.0"
// ......
}
}

Manually designate a slug in the front matter for your each of your blog posts:

## <!-- src/content/posts/en/MigratingAstroWithDevContainer.md -->
title: "Migrating to Astro: A Modern Blogging Setup with Dev Container"
slug: "migrating-to-astro-modern-blog-with-dev-container"
tags: ['Misc']
pubDate: 2026-06-16
math: false
mermaid: true
draft: false
## description: "A complete guide on migrating a personal blog from Hexo/Hugo to Astro. Learn how to isolate your development environment using VS Code Dev Containers, and automate deployments to GitHub Pages via GitHub Actions."
---

Note: slug is the canonical name of the blog post, and thus a crucial part of permalink

Add redirect entries to astro.config.mjs in your repo:

astro.config.mjs
export default defineConfig({
/**
* Redirects from old blogs - HTTP 301 Moved Permanently
*/
redirects: {
'/page/1/': {
status: 301,
destination: '/',
},
'/2022/05/hello-world/': {
status: 301,
destination: '/posts/hello-world/',
},
// ......
},
// ......
});

Since the dependencies are all set up after Dev Container initialization, we just need to run:

Terminal window
bun run dev

Then it will display the preview links:

$ astro dev --host 0.0.0.0
[astro] `markdown.remarkPlugins`, `markdown.rehypePlugins`, and `markdown.remarkRehype` are deprecated. Pass them to `unified({...})` from `@astrojs/markdown-remark` directly instead.
[vite] connected.
14:41:48 [types] Generated 0ms
[vite] connected.
14:41:49 [content] Syncing content
14:41:49 [content] Astro config changed
14:41:49 [content] Clearing content store
14:41:51 [content] Synced content
astro v6.4.3 ready in 4317 ms
┃ Local http://localhost:4321/
┃ Network http://172.17.0.2:4321/
14:41:51 watching for file changes...

To preview the rendered blog, simply Ctrl + left-click the non-localhost link(s), e.g., http://172.17.0.2:4321/. Thanks to VS Code’s port forwarding, this maps the container’s port directly to your local machine.

🎉 Congratulations, we have the new blog end-to-end tested.

All git operations happen inside the Dev Container, so the container needs a credential to push back to GitHub. Rather than reusing a personal SSH key or a Personal Access Token, register a dedicated SSH key as a repository Deploy Key with write access - it is scoped to a single repository, which keeps the blast radius small.

1. Generate an SSH key inside the container

Terminal window
# Generate RSA 4096 SSH key pair
ssh-keygen -t rsa -b 4096

Press Enter to accept the default ~/.ssh/id_rsa location and leave the passphrase empty - deploy keys aren’t passphrase-protected (see GitHub’s note below), so ssh can use the key without ssh-agent.

2. Register the public key as a Deploy Key (with write access)

Deploy keys are SSH keys that grant read-only / write access to a single repository.

So, next, let’s find the just-generated SSH public key in the Dev Container:

Terminal window
cat ~/.ssh/id_rsa.pub

Copy the output, open 👉 https://github.com/${GITHUB_REPO_HANDLE}/settings/keys (replace ${GITHUB_REPO_HANDLE} with OWNER/REPO), click Add deploy key, paste the public key, tick “Allow write access”, and save.

3. Switch origin from HTTPS to SSH

When the repository was cloned, origin was set to an https://* URL, which asks for a username/token on every push. Switch it to the SSH URL so pushes authenticate with the deploy key from step 2; this is also what lets Claude Code (Step 1.3) push commits non-interactively from inside the container.

Terminal window
# switch origin from https:// to git@github.com:
git remote set-url origin git@github.com:${GITHUB_REPO_HANDLE}.git
# verify, then push
git remote -v
git push -u origin <branch>

The key has no passphrase and lives at the default ~/.ssh/id_rsa, so ssh (and therefore git and Claude Code) picks it up automatically - no ssh-agent required. If your key isn’t at the default path, point git at it explicitly: git config core.sshCommand 'ssh -i /path/to/private/key'.

On the first push over SSH, git will ask you to trust github.com’s host key. Verify the fingerprint it reports against GitHub’s official SSH key fingerprints 👉 https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/githubs-ssh-key-fingerprints

Note:

  • /home/node/.ssh is not persisted by default, so a container rebuild loses the key (and orphans the deploy key on GitHub). Regenerate the key and replace the stale deploy key after each rebuild.
  • This is for pushing from the Dev Container; the CI pipeline in Step 2 pushes to GitHub Pages separately via MY_GITHUB_PAGES_REPO_ACCESS_TOKEN (a PAT) over HTTPS.

Next, we need to polish the blogs gradually before publishing them online.


In the second part, we design an elegant and automated workflow.

  • Use a separate code base for self-customized blog infrastructure codes and Markdown posts
  • Use CI to automatically build and publish blog online

Create a new private repo, and git push, and edit private repo secrets and variables accordingly

NameTypeDescription
PUBLIC_GITHUB_HANDLEVariablesyour GitHub username, e.g., username
SITE_URLVariablesyour GitHub Pages URL, e.g., https://username.github.io
MY_GITHUB_PAGES_REPO_HANDLEVariablesyour GitHub Pages URL, e.g., PUBLIC_GITHUB_HANDLE/PUBLIC_GITHUB_HANDLE.github.io
MY_GITHUB_PAGES_REPO_ACCESS_TOKENSecretsaccess token to push to MY_GITHUB_PAGES_REPO_HANDLE

NOTE:

  • MY_GITHUB_PAGES_REPO_ACCESS_TOKEN is generated from 👉 https://github.com/settings/personal-access-tokens
  • Remember to create a flag file named public/.nojekyll in your code base, to ensure that folders prefixed with an underscore (_) are not ignored by GitHub Pages.

Update the .github/workflows/deploy.yml file:

.github/workflows/deploy.yml
name: Deploy to GitHub Pages
on:
push:
branches:
- my-main
paths:
- 'src/**'
- 'public/**'
- 'astro.config.mjs'
- 'tsconfig.json'
- 'package.json'
- 'bun.lock'
- '.github/workflows/deploy.yml'
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: pages
cancel-in-progress: false
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true'
jobs:
build:
name: Build
runs-on: ubuntu-latest
# Bind the build to the same `github-pages` environment as the deploy
# job. This is what makes `vars.*` and `secrets.*` defined under
# _Settings → Environments → github-pages_ resolvable here at build
# time. Without it, only repository-level vars/secrets are visible.
environment: github-pages
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
# Keep CI Bun pinned so `--frozen-lockfile` is deterministic.
# Update this intentionally when you want to adopt a newer Bun.
bun-version: 1.3.13
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Build with Astro
# All values below are read from repository Variables
# (Settings → Secrets and variables → Actions → Variables).
# Any unset variable expands to an empty string, which the
# theme treats as "feature disabled" — so every var is optional
# and you only set the ones you actually use.
env:
# --- Site identity ---
# Falls back to the canonical github.io origin for the current
# owner so a brand-new fork builds successfully with zero
# configuration. Override by setting `vars.SITE_URL` in the UI.
SITE_URL: ${{ vars.SITE_URL || format('https://{0}.github.io', github.repository_owner) }}
# Sub-path for GitHub Pages. Hard-coded fallback to the repo
# name keeps the build correct even before the variable is
# configured. Override by setting `vars.BASE_PATH` in the UI.
BASE_PATH: ${{ vars.BASE_PATH }}
# --- Author / social handles (all optional) ---
PUBLIC_GITHUB_HANDLE: ${{ vars.PUBLIC_GITHUB_HANDLE || github.repository_owner }}
PUBLIC_GITHUB_REPO: ${{ vars.PUBLIC_GITHUB_REPO }}
PUBLIC_TWITTER_HANDLE: ${{ vars.PUBLIC_TWITTER_HANDLE }}
PUBLIC_LINKEDIN_HANDLE: ${{ vars.PUBLIC_LINKEDIN_HANDLE }}
PUBLIC_CONTACT_EMAIL: ${{ vars.PUBLIC_CONTACT_EMAIL }}
# --- Cloudflare Web Analytics ---
CLOUDFLARE_WEB_ANALYTICS_ENABLED: ${{ vars.CLOUDFLARE_WEB_ANALYTICS_ENABLED }}
CLOUDFLARE_WEB_ANALYTICS_TOKEN: ${{ vars.CLOUDFLARE_WEB_ANALYTICS_TOKEN }}
# --- Giscus (all optional) ---
PUBLIC_GISCUS_ENABLED: ${{ vars.PUBLIC_GISCUS_ENABLED }}
PUBLIC_GISCUS_REPO: ${{ vars.PUBLIC_GISCUS_REPO }}
PUBLIC_GISCUS_REPO_ID: ${{ vars.PUBLIC_GISCUS_REPO_ID }}
PUBLIC_GISCUS_CATEGORY: ${{ vars.PUBLIC_GISCUS_CATEGORY }}
PUBLIC_GISCUS_CATEGORY_ID: ${{ vars.PUBLIC_GISCUS_CATEGORY_ID }}
run: bun run build
- name: Upload Build Artifact
uses: actions/upload-artifact@v4
with:
name: astro-dist-files
path: ./dist
include-hidden-files: true
retention-days: 1
deploy:
name: Deploy
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
steps:
# 1. Download the artifact generated in the previous Build step
# This official Action automatically downloads and extracts the artifact.
# By default, the extracted content corresponds exactly to all files inside ./dist
- name: Download Build Artifact
uses: actions/download-artifact@v4
with:
name: astro-dist-files
path: ./dist
# 2. Deploy to the remote GitHub Pages repository with a clean, single-commit history
- name: Push to Target Remote Repo
env:
TARGET_REPO: ${{ vars.MY_GITHUB_PAGES_REPO_HANDLE }}
PERSONAL_TOKEN: ${{ secrets.MY_GITHUB_PAGES_REPO_ACCESS_TOKEN }}
COMMIT_PREFIX: '[Deployment](${{ github.ref_name }})'
COMMIT_SHA: '${{ github.sha }}'
COMMIT_MSG_FULL: '${{ github.event.head_commit.message }}'
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
cd dist
rm -rf .git
git init
git branch -m main
git add .
COMMIT_MSG_FIRST=$(echo "$COMMIT_MSG_FULL" | head -n 1)
COMMIT_MSG="$COMMIT_PREFIX: $COMMIT_MSG_FIRST ($COMMIT_SHA)"
git commit -m "$COMMIT_MSG"
git config http.extraheader "Authorization: Basic $(echo -n "x-access-token:$PERSONAL_TOKEN" | base64 -w 0)"
git push --force https://github.com/${TARGET_REPO}.git main

NOTE:

  • We use two main branches, and the default one is my-main:
    • branch main is used track upstream Astro blog framework
    • branch my-main is used for GitHub Actions to automatically build and push to GitHub Pages

From now on, every time you update blog in your my-main branch, it will automatically update your blog website


Comments