Deploying a Static Site to Github Pages
GitHub Pages is the easiest place on the internet to put a static file, and after the last chapter’s talk of gates and reviewers, that’s exactly the point of starting here: no protection rules, no cloud account, no secrets to generate. Just a workflow, an environment named github-pages, and a page that’s live in under a minute.
The Site You’re Deploying
You’ll deploy the same tiny page in this chapter and the next two, to three different real destinations, so you can compare how the deployment mechanics differ while the app itself stays trivial. Create a new repository (or a fresh branch in an existing one) with this single file:
index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>devstuffs CI/CD practice</title>
<style>
body { font-family: system-ui, sans-serif; background: #0B0E14; color: #E4E2DC; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; }
.card { text-align: center; }
code { color: #E8A33D; }
</style>
</head>
<body>
<div class="card">
<h1>Hello from devstuffs 👋</h1>
<p>This page was deployed by GitHub Actions.</p>
<p><code id="target">deployment target: unknown</code></p>
</div>
</body>
</html>Notice the deployment target: unknown line. You’ll never edit that by hand — the workflow itself stamps in the real destination right before it deploys, with a one-line sed replace. It’s a small touch, but it means you can open the live URL and immediately confirm which pipeline actually put it there.
Turning On Pages for This Repo
Before the workflow can deploy anything, GitHub Pages needs to be told it’s expecting deployments from Actions, not from a branch:
- Go to the repo’s Settings → Pages.
- Under Build and deployment → Source, choose GitHub Actions (not “Deploy from a branch” — that’s the older, simpler mode this course is skipping, since it can’t participate in the Environment/Deployments machinery from the last chapter).
That’s the only manual setup step. No bucket, no DNS, nothing to provision.
The Workflow
name: Deploy to GitHub Pages
on:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Stamp deployment target
run: |
sed -i 's/deployment target: unknown/deployment target: GitHub Pages/' index.html
- name: Configure Pages
uses: actions/configure-pages@v5
- name: Upload site as Pages artifact
uses: actions/upload-pages-artifact@v4
with:
path: .
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@v4A few things worth calling out, since this is a slightly different shape than workflows you’ve written before:
- Two jobs, not one.
buildpackages the site into a Pages-formatted artifact;deploypublishes that artifact. They’re split because thedeployjob is the one that carries theenvironment:block — separating them means the Deployments tab tracks exactly the step that matters, not your whole build process. permissionsis not optional here.pages: writelets the job publish, andid-token: writelets it prove to GitHub Pages that the deployment genuinely came from this workflow run. Leave either out anddeploy-pagesfails outright.needs: buildis what makesdeploywait for the artifact to actually exist before trying to publish it — plain job ordering, nothing new.- The
urlon theenvironment:block readspage_url, an output thedeploy-pagesaction itself produces once it knows the live address — you don’t hardcode your Pages URL anywhere.
Note
actions/configure-pages doesn’t touch any files. Its whole job is to inspect your repo’s Pages settings and set a few outputs (like the base URL) that other steps in the same job can use — here, nothing downstream needs those outputs directly, but the action also validates that Pages-via-Actions is actually enabled before you waste time uploading an artifact that can’t be deployed.
Watching It Deploy
Push this to main and open the Actions tab — you’ll see build then deploy run in sequence. Once deploy finishes, the repo’s Settings → Pages page shows the live URL directly, and the Environments sidebar (from the last chapter) now has a github-pages entry with a “View deployment” link. Open it — the page should read deployment target: GitHub Pages, confirming the sed step actually ran before upload rather than you eyeballing stale content.
Cleanup
GitHub Pages costs nothing to leave running, so there’s no billing reason to tear it down. If you’re using a disposable practice repo and want to stop hosting the page:
- Settings → Pages → Build and deployment → Source, switch back to “Deploy from a branch” (or leave it — either way, deleting the repository itself removes the Pages site).
- If you want to remove just the deployment history without touching the repo, the Environments page lets you delete the
github-pagesenvironment entirely, which also removes its deployment log.