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.
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 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.

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 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 <--> DThe Dev Container provides all the necessary tools and utilities, while VS Code automates the environment scaffolding and communication.
Verify Your Proxy Port Configuration
Update /etc/docker/daemon.json with your proxy server addresses:
{ "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
mtuto1450: If you use proxy, the size of payload may exceed MTU of 1500. - Replace
${YOUR_PROXY_ADDRESS}, such as127.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.1because it runs directly on the host
Using a Proxy inside the Dev Container
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:
{ "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). forwardPortsandportsAttributesexpose the Astro dev server on port4321.postCreateCommandinstalls a pinned Bun (1.3.13) and runsbun 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_proxyentries (and their upper-case twins) inremoteEnvroute the container’s own outbound HTTP/HTTPS through the host proxy athttp://host.docker.internal:7897. - Reaching the host from inside the container. On Docker Desktop,
host.docker.internalresolves automatically; on the Linux Docker Engine used here it does not, sorunArgspasses--add-host=host.docker.internal:host-gateway, designating the pseudoname to the host’s gateway IP. Inside the container,127.0.0.1is the container’s own loopback; to use the proxy on the host, the container must usehost.docker.internal:7897. - The
featuresandremoteEnvblocks 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-rootnodeuser, andinit: truestarts 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 asANTHROPIC_AUTH_TOKENout of the repo (export it in your shell) while still committing sane defaults for everything else. Thesk-xxxxxxhere is a placeholder, so never commit a real key. - Any Anthropic-compatible endpoint.
ANTHROPIC_BASE_URLpoints Claude Code at DeepSeek’s/anthropicendpoint, so it speaks the Anthropic API shape but runs DeepSeek models. Swap inhttps://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_*_MODELandCLAUDE_CODE_SUBAGENT_MODELvariables map each slot to an actual provider model: here the heavy slots go todeepseek-v4-pro[1m]and the lighter ones todeepseek-v4-flash. - Context managements:
CLAUDE_CODE_EFFORT_LEVEL=maxopts into the deepest reasoningAPI_TIMEOUT_MSandBASH_DEFAULT_TIMEOUT_MSraise the request and bash-command ceilings so long agent turns and builds are not killed earlyCLAUDE_CODE_AUTO_COMPACT_WINDOWraises the auto-compact thresholdCLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFICandDISABLE_AUTOUPDATERkeep the container quiet and pin the installed versionCLAUDE_CONFIG_DIRfixes where Claude Code stores its settings (under thenodeuser’s home).
The remaining remoteEnv entries are general system tuning rather than Claude Code itself:
NODE_OPTIONS=--max-old-space-size=4096gives Node.js a 4 GB heap (handy for Claude Code coding agents and also for large Astro builds)BUN_INSTALLpoints the installer at/home/node/.bunPATHputs the resultingbunbinary on the container’sPATH.
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| MOnce 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:
{ "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: falsemermaid: 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:
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:
bun run devThen 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 content14:41:49 [content] Astro config changed14:41:49 [content] Clearing content store14: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
# Generate RSA 4096 SSH key pairssh-keygen -t rsa -b 4096Press 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:
cat ~/.ssh/id_rsa.pubCopy 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.
Security caveat for Deploy keys of Repository
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.
# switch origin from https:// to git@github.com:git remote set-url origin git@github.com:${GITHUB_REPO_HANDLE}.git
# verify, then pushgit remote -vgit 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
Key fingerprint MUST match GitHub's official fingerprints
Note:
/home/node/.sshis 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
| Name | Type | Description |
|---|---|---|
PUBLIC_GITHUB_HANDLE | Variables | your GitHub username, e.g., username |
SITE_URL | Variables | your GitHub Pages URL, e.g., https://username.github.io |
MY_GITHUB_PAGES_REPO_HANDLE | Variables | your GitHub Pages URL, e.g., PUBLIC_GITHUB_HANDLE/PUBLIC_GITHUB_HANDLE.github.io |
MY_GITHUB_PAGES_REPO_ACCESS_TOKEN | Secrets | access token to push to MY_GITHUB_PAGES_REPO_HANDLE |
NOTE:
MY_GITHUB_PAGES_REPO_ACCESS_TOKENis generated from 👉 https://github.com/settings/personal-access-tokens- Remember to create a flag file named
public/.nojekyllin your code base, to ensure that folders prefixed with an underscore (_) are not ignored by GitHub Pages.
Update the .github/workflows/deploy.yml file:
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 mainNOTE:
- We use two main branches, and the default one is
my-main:- branch
mainis used track upstream Astro blog framework - branch
my-mainis used for GitHub Actions to automatically build and push to GitHub Pages
- branch
From now on, every time you update blog in your my-main branch, it will automatically update your blog website