Checkpoint Lab — Codespaces, Dev Containers, GitHub Pages, Documentation, and Cloud Developer Environments
Integrate a tiny documentation project, devcontainer metadata, local verification, Pages publishing, controlled lifecycle failure, remediation, and a production cloud-development policy.
Checkpoint outcomes
- Build a clean documentation project and dev-container configuration, then record predictions before hosted changes.
- Verify identical source content locally and through GitHub Pages, with the Pages build tied to an exact commit.
- Inject a harmless lifecycle failure, preserve the original exit status/cause, repair it, and verify a clean command.
- Optionally inspect the same commit in a Codespace without requiring paid quota or real development secrets.
- Produce a cloud-development policy covering repository trust, secret scope, ports, machine/idle/retention cost, prebuilds, Pages provenance, and cleanup.
1. Scenario, preflight, and required assumptions
You are handing a small documentation service to another team. They need a portable dev-container definition, a public Pages site, and a policy that keeps cloud development from silently accumulating authority or cost. The mandatory path uses a disposable public personal repository; a Codespace remains optional after quota/billing verification.
2. Create the checkpoint repository and deterministic source
gh auth status
OWNER="$(gh api -H 'X-GitHub-Api-Version: 2026-03-10' user --jq .login)"
REPO="github-cloud-dev-checkpoint"
FULL="$OWNER/$REPO"
gh repo create "$FULL" --public --clone --description "Disposable Chapter 30 checkpoint"
cd "$REPO"
mkdir -p docs .devcontainer policy evidence
cat > docs/index.html <<'HTML'
<!doctype html>
<html lang="en">
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Chapter 30 checkpoint</title></head>
<body><main><h1>Chapter 30 checkpoint</h1><p>Repository-defined documentation environment.</p></main></body>
</html>
HTML
cat > .devcontainer/devcontainer.json <<'JSON'
{
"name": "chapter-30-checkpoint",
"image": "mcr.microsoft.com/devcontainers/base:ubuntu",
"forwardPorts": [8000],
"portsAttributes": {
"8000": {"label": "docs preview", "onAutoForward": "notify"}
},
"postCreateCommand": "test -f docs/index.html && printf 'docs ready\n'"
}
JSON
python -m json.tool .devcontainer/devcontainer.json >/dev/null
git add docs .devcontainer
git commit -m "Create Chapter 30 checkpoint source"
git push -u origin HEAD
SOURCE_SHA="$(git rev-parse HEAD)"
printf '%s
' "$SOURCE_SHA" | tee evidence/source-sha.txt
This configuration deliberately contains no extra repository permission request, feature, privileged option, public port, or secret. The exercise is about traceability, not tool density.
3. Predict state transitions before performing them
Create evidence/predictions.md and write at least these
predictions in your own words before continuing:
-
Pages prediction: creating the Pages resource
will add hosted configuration pointing to
main:/docs; it will not change the Git commit. The latest completed Pages build should eventually referenceSOURCE_SHA. -
Lifecycle-failure prediction: changing a local
copy of
postCreateCommandfromdocs/index.htmltodocs/missing.htmlwill make that command exit nonzero without changing any hosted Codespaces resource. - Codespace prediction (optional): creating a Codespace will allocate hosted compute/storage and a Codespace object; stopping it ends active compute but retained storage remains until deletion/retention expiry.
cat > evidence/predictions.md <<EOF
# Predictions
- Pages source will become main:/docs without changing Git source commit $SOURCE_SHA.
- A missing-file lifecycle check will fail locally with nonzero status and no hosted Codespace mutation.
- If I create a Codespace, it will create hosted runtime/storage state; stopping and deleting are separate lifecycle actions.
EOF
git add evidence/predictions.md
git commit -m "Record checkpoint predictions"
git push
SOURCE_SHA="$(git rev-parse HEAD)"
printf '%s
' "$SOURCE_SHA" > evidence/source-sha.txt
The second commit intentionally becomes the new source identity. Always recalculate the expected commit after committing evidence or policy files; otherwise your own evidence packet becomes stale.
4. Verify content and lifecycle locally
python -m json.tool .devcontainer/devcontainer.json >/dev/null
# Execute only the harmless command body used by postCreateCommand.
sh -lc "test -f docs/index.html && printf 'docs ready\n'"
python -m http.server 8000 --directory docs >evidence/local-server.log 2>&1 &
SERVER_PID=$!
sleep 1
curl --fail --silent http://127.0.0.1:8000/ > evidence/local-index.html
kill "$SERVER_PID"
grep -F "Chapter 30 checkpoint" evidence/local-index.html
sha256sum docs/index.html evidence/local-index.html | tee evidence/content-hashes.txt
The two hash values should match because the HTTP server returns the exact static file. This is a local content invariant that you can compare with the hosted page later.
5. Publish through Pages and prove source commit
cat > /tmp/pages-source.json <<'JSON'
{"source":{"branch":"main","path":"/docs"}}
JSON
gh api --method POST -H "X-GitHub-Api-Version: 2026-03-10" "repos/$FULL/pages" --input /tmp/pages-source.json
rm -f /tmp/pages-source.json
gh api -H "X-GitHub-Api-Version: 2026-03-10" "repos/$FULL/pages" --jq '{status,html_url,build_type,source,https_enforced}' | tee evidence/pages-config.json
# Query manually after the build has had time to finish.
gh api -H "X-GitHub-Api-Version: 2026-03-10" "repos/$FULL/pages/builds/latest" --jq '{status,commit,created_at,updated_at,error}' | tee evidence/pages-build.json
HOSTED_SHA="$(gh api -H 'X-GitHub-Api-Version: 2026-03-10' "repos/$FULL/pages/builds/latest" --jq .commit)"
EXPECTED_SHA="$(git rev-parse origin/main)"
printf 'expected=%s
hosted=%s
' "$EXPECTED_SHA" "$HOSTED_SHA"
test "$EXPECTED_SHA" = "$HOSTED_SHA"
PAGE_URL="$(gh api -H 'X-GitHub-Api-Version: 2026-03-10' "repos/$FULL/pages" --jq .html_url)"
curl --fail --silent --location "$PAGE_URL" > evidence/hosted-index.html
grep -F "Chapter 30 checkpoint" evidence/hosted-index.html
If status is still queued/building, do not mark the
checkpoint failed; wait and query again. The acceptance criterion is
the completed build/deployment identity plus content, not a fixed
number of seconds.
6. Failure injection: break the lifecycle command without allocating a broken cloud machine
The mandatory path performs the failure on a temporary local copy. This teaches the causal signal without wasting Codespaces quota.
cp .devcontainer/devcontainer.json /tmp/devcontainer-good.json
python - <<'PY'
import json
p='.devcontainer/devcontainer.json'
data=json.load(open(p,encoding='utf-8'))
data['postCreateCommand']="test -f docs/missing.html && printf 'docs ready\n'"
json.dump(data,open('/tmp/devcontainer-broken.json','w',encoding='utf-8'),indent=2)
PY
BROKEN_CMD="$(python - <<'PY'
import json
print(json.load(open('/tmp/devcontainer-broken.json',encoding='utf-8'))['postCreateCommand'])
PY
)"
set +e
sh -lc "$BROKEN_CMD" > evidence/lifecycle-failure.txt 2>&1
BROKEN_RC=$?
set -e
printf 'broken_exit=%s
' "$BROKEN_RC" | tee -a evidence/lifecycle-failure.txt
test "$BROKEN_RC" -ne 0
# Repair by returning to the reviewed repository configuration.
GOOD_CMD="$(python - <<'PY'
import json
print(json.load(open('/tmp/devcontainer-good.json',encoding='utf-8'))['postCreateCommand'])
PY
)"
sh -lc "$GOOD_CMD" | tee evidence/lifecycle-repaired.txt
rm -f /tmp/devcontainer-good.json /tmp/devcontainer-broken.json
The original failure is preserved in
evidence/lifecycle-failure.txt. The repair does not
“hide” it; it proves that the reviewed command succeeds while the
controlled broken command fails for the predicted missing-file
cause. No cloud machine had to be created to teach this diagnostic
boundary.
7. Optional Codespace parity check — only after quota, billing, and trust review
If your personal included usage is available and the repository/configuration is trusted, create the smallest suitable Codespace. Otherwise skip this section; the checkpoint remains complete.
# Read available machine choices first.
gh api -H "X-GitHub-Api-Version: 2026-03-10" "repos/$FULL/codespaces/machines" --jq '.machines[] | {name,display_name,cpus,memory_in_bytes,storage_in_bytes}'
# Optional mutation: creates billable/included hosted compute + storage state.
gh codespace create -R "$FULL" -b main --idle-timeout 30m --retention-period 1d --status
CS="$(gh codespace list -R "$FULL" --json name,state --jq '.[0].name')"
gh codespace view -c "$CS" --json name,repository,state,machineName,idleTimeoutMinutes,retentionPeriodDays,retentionExpiresAt,devcontainerPath,prebuild
gh codespace ports -c "$CS" --json sourcePort,label,visibility,browseUrl
# Inspect only safe identity/source fields; never dump the whole environment.
gh codespace ssh -c "$CS" -- 'printf "repo=%s\nsha=%s\ncodespaces=%s\n" "$GITHUB_REPOSITORY" "$(git rev-parse HEAD)" "${CODESPACES:-}"'
The repository SHA inside the Codespace should equal the intended
source commit after synchronization. Port 8000 may be
forwarded if a server runs, but this lab never changes it to public
visibility. If GitHub asks for unexpected cross-repository
permissions, choose Continue without authorizing and
diagnose the configuration before granting access.
8. Write the cloud-development and documentation policy
Create a policy that another operator can apply without reading your mind. The minimum useful policy is specific enough to test:
cat > policy/cloud-development.md <<'MD'
# Cloud development and documentation policy
## Repository trust
- Review devcontainer.json, Dockerfiles/features, lifecycle commands, dotfiles effects, and requested cross-repository permissions before creating a hosted environment.
- Do not run privileged setup from an untrusted fork/ref.
## Secrets and identity
- Codespaces development secrets are separate from Actions and Dependabot secrets.
- Scope each development secret to the smallest repository audience and never reuse production deployment credentials by default.
- Do not print environment-variable inventories or credential-bearing configuration in logs.
## Network
- Forward only required development ports. Keep ports private unless a documented review requires broader visibility.
## Cost and lifecycle
- Confirm the billing owner/quota before creation.
- Prefer the smallest adequate machine, a bounded idle timeout, and short retention for disposable work.
- Stop idle Codespaces; delete them only after verifying all work is pushed or intentionally discarded.
- Create prebuilds only when measured startup savings justify Actions/storage cost.
## Pages
- Record branch/path or workflow build type and verify the deployed commit.
- Use a custom Actions workflow only when build/test control is needed; pin actions to reviewed immutable SHAs.
- Verify custom domains and enforce HTTPS after DNS/certificate provisioning; never publish secrets or sensitive transactions.
## Cleanup and review
- Quarterly: review active Codespaces, retention/timeout defaults, secret scopes, prebuilds, Pages sources, domains, and deployment workflows.
- Incidents preserve source/configuration/deployment evidence before mutation.
MD
git add policy/cloud-development.md evidence
git commit -m "Document Chapter 30 cloud-development policy and evidence"
git push
FINAL_SHA="$(git rev-parse HEAD)"
printf 'final_policy_sha=%s
' "$FINAL_SHA" | tee evidence/final-policy-sha.txt
The policy commit intentionally occurs after the Pages-source proof. That means the latest repository commit may be newer than the build commit you captured earlier. Evidence should say which commit was the published content and which later commit added governance records rather than pretending one SHA describes every state.
9. Independent verification checklist
-
.devcontainer/devcontainer.jsonparses as JSON and its normal lifecycle command exits zero. - The controlled broken lifecycle command exits nonzero and the original stderr/exit evidence is retained.
- Local HTTP content contains the checkpoint marker and matches the repository file hash before hosted comparison.
- Pages configuration reports the intended source/build type and the captured completed build commit equals the intended repository commit for that publication.
- The hosted page contains the checkpoint marker.
- If a Codespace was created, its repository, machine, source SHA, idle timeout, retention, ports, and prebuild state were recorded without dumping credentials.
- No real development secret, Actions secret, Dependabot secret, PAT, private key, custom-domain credential, or public port was required.
- The cloud-development policy names trust, secret, network, cost, prebuild, Pages provenance, custom-domain/TLS, cleanup, and review ownership.
10. Cleanup and rollback
First preserve or push anything you intend to keep. Then remove only the disposable hosted resources.
# Optional Codespace cleanup. Stop first; inspect Git before deletion.
if [ -n "${CS:-}" ]; then
gh codespace stop -c "$CS"
gh codespace ssh -c "$CS" -- 'git status --short' 2>/dev/null || true
# DESTRUCTIVE: deletes the disposable Codespace and any unpushed state.
gh codespace delete -c "$CS"
fi
# DESTRUCTIVE TO THE PUBLIC SITE ONLY: unpublish the disposable Pages site.
gh api --method DELETE -H "X-GitHub-Api-Version: 2026-03-10" "repos/$FULL/pages"
# Safer repository cleanup: archive instead of deleting the evidence repository.
gh repo archive "$FULL" --yes
If the Codespace has already been deleted, you cannot SSH into it to recover work. That is why source-state verification precedes deletion. Repository deletion is unnecessary for this checkpoint and is intentionally omitted.
Knowledge check
Why can the repository HEAD be newer than the Pages build SHA at the end of this lab?
Because later commits may add policy/evidence without changing the publication you already verified. Evidence should identify the commit for each state transition rather than force every artifact onto one SHA.
The broken lifecycle test fails exactly as predicted. What proves the repair rather than merely hiding the error?
The failure evidence is retained, the reviewed command is restored, and the same command body then exits zero against the expected repository file.
A Codespace is stopped. Has all Codespaces cost/state disappeared?
No. Stopping ends active compute, but retained Codespace storage remains until deletion or retention expiry; prebuild storage can also persist.
A teammate proposes copying an Actions deployment secret into Codespaces because “both are GitHub secrets.” What is wrong?
They are separate secret stores and execution trust contexts. Interactive repository-controlled development code should receive only the development credential it actually needs, not CI/deployment authority by default.
Pages serves the expected text but the latest completed build references an unexpected commit. Accept the deployment?
No. Content coincidence does not establish provenance. Diagnose the configured source/build and deployment commit before accepting the hosted state.
What does Chapter 30 add to the production GitHub operating model?
A governed developer/documentation plane: repository-reviewed environment configuration, explicit hosted identity/secrets/network/cost lifecycle, and Pages deployments verified back to source commit. Chapter 31 applies similar discipline to scale, monorepos, search, Actions cost, and platform performance.
Checkpoint summary and Chapter 31 bridge
You now have a small documentation project whose development-environment metadata is reviewable in Git, whose mandatory verification works locally, whose Pages deployment can be traced to an exact commit, and whose optional Codespace path is constrained by trust, scope, lifecycle, and cost. The incident drill proved that environment failures should preserve evidence rather than trigger blind rebuilds. This adds a governed developer/documentation plane to the production GitHub operating model. Chapter 31 scales the same thinking to large repositories, monorepos, search, Actions cost controls, and platform performance.
Further reading — current primary sources
- GitHub CLI — Codespaces
- Getting the most from included Codespaces usage
- Security in GitHub Codespaces
- Configuring a Pages publishing source
- REST API — GitHub Pages
- GitHub Codespaces documentation
- Understanding the codespace lifecycle
- Managing development environment secrets
- GitHub Pages — what it is
- REST API — Codespaces
- REST API — GitHub Pages
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.