Codespaces, Dev Containers, GitHub Pages, Documentation, and Cloud Developer Environments: Guided Hands-On Workflow and Core Operations
Create a disposable documentation repository, validate a minimal dev container, optionally inspect a Codespace, publish with Pages, and prove the exact hosted source commit.
Learning objectives
- Create a disposable public documentation repository and record repository/commit identity before changing hosted settings.
-
Add a minimal
devcontainer.json, validate its JSON locally, and preview the same static documentation without Codespaces. - Optionally create and inspect a Codespace only after confirming included quota/billing ownership and requested permissions.
-
Enable GitHub Pages from
main:/docs, query the Pages resource/build, and prove the hosted build commit equals the intended Git commit. - Compare local, Dev Container, and Codespace execution boundaries and perform explicit cleanup.
1. Scenario, availability, and shell conventions
You are creating github-cloud-dev-lab, a disposable
public project containing one static documentation page and one
development-container configuration. The
mandatory path needs GitHub Free, GitHub CLI, Git,
and Python 3. GitHub Pages is available for public repositories on
GitHub Free. A Codespace is optional: create one only if your
personal account still has included usage or you have deliberately
configured billing/spending.
export, heredocs, test, or command
substitution are written for Bash/Git Bash. On Windows PowerShell
either run them in Git Bash or translate environment
variables/heredocs; the GitHub/UI concepts are shell-independent.
2. Preflight: prove account, target name, and existing cloud inventory
gh auth status
OWNER="$(gh api -H 'X-GitHub-Api-Version: 2026-03-10' user --jq .login)"
REPO="github-cloud-dev-lab"
FULL="$OWNER/$REPO"
printf 'owner=%s repo=%s
' "$OWNER" "$FULL"
gh repo view "$FULL" --json nameWithOwner,visibility,url 2>/dev/null || echo "repository not present"
gh codespace list --json name,repository,state,machineName,lastUsedAt
# Inspect Codespaces secret scope metadata only; values are never returned.
gh secret list -R "$FULL" --app codespaces --json visibility,numSelectedRepos --jq '{repository_codespaces_secret_count:length}' 2>/dev/null || true
gh secret list --user --app codespaces --json visibility,numSelectedRepos --jq 'map({visibility,numSelectedRepos})' 2>/dev/null || true
Prediction 1: creating the repository will create a hosted
repository object and default branch only after the first push. It
will not create a Codespace or Pages site. Prediction 2: adding
.devcontainer/devcontainer.json changes repository
content only; it does not allocate a VM until an environment is
created. The secret-list commands show only scope/count metadata;
they do not reveal encrypted secret values. If the repository does
not exist yet, its repository-secret query can legitimately fail and
is ignored during this preflight.
3. Create the repository, documentation, and minimal development-container metadata
gh repo create "$FULL" --public --clone --description "Disposable Chapter 30 cloud development and Pages lab"
cd "$REPO"
mkdir -p docs .devcontainer
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>Cloud Dev Lab</title></head>
<body>
<main>
<h1>Cloud Dev Lab</h1>
<p id="evidence">This page is published from the repository's docs directory.</p>
</main>
</body>
</html>
HTML
cat > .devcontainer/devcontainer.json <<'JSON'
{
"name": "chapter-30-docs",
"image": "mcr.microsoft.com/devcontainers/base:ubuntu",
"forwardPorts": [8000],
"portsAttributes": {
"8000": {
"label": "documentation preview",
"onAutoForward": "notify"
}
},
"postCreateCommand": "printf 'chapter-30 environment ready\n'"
}
JSON
python -m json.tool .devcontainer/devcontainer.json >/dev/null
git add docs .devcontainer
git commit -m "Add reproducible docs environment"
git push -u origin HEAD
BASE_SHA="$(git rev-parse HEAD)"
printf 'baseline commit=%s
' "$BASE_SHA"
The first independent verification is local Git state:
git show --stat HEAD must contain both the
documentation and the dev-container config. The config does not
contain credentials and does not request access to any other
repository.
4. Verify the static site locally before involving GitHub Pages
Use the same checked-out bytes that will become the branch-source Pages input. This tests content, not GitHub hosting:
python -m http.server 8000 --directory docs >local-pages.log 2>&1 &
SERVER_PID=$!
sleep 1
curl --fail --silent http://127.0.0.1:8000/ | grep -F "Cloud Dev Lab"
kill "$SERVER_PID"
rm -f local-pages.log
Expected observation: curl exits zero and prints the
heading line. If it fails here, fix the repository content before
debugging Pages.
If the open-source Dev Container CLI (or VS Code Dev Containers extension) and a local container runtime are already available, exercise the same configuration locally before spending Codespaces quota:
# Optional local portability proof; requires a local container runtime and Dev Container CLI.
devcontainer up --workspace-folder .
devcontainer exec --workspace-folder . sh -lc 'test -f docs/index.html && printf "container parity ok\n"'
VS Code users can instead choose Dev Containers: Reopen in Container. The key evidence is that the repository-defined configuration can create a local development container without depending on Codespaces-specific billing, secrets, or VM policy.
5. Optional live Codespace: check quota first, then create with bounded lifecycle
Personal accounts include a monthly quota, but the remaining amount is account-specific. Confirm billing/usage in GitHub settings before creating anything. If you proceed, use a small available machine, short idle timeout, and short retention period. Do not add a secret for this lab.
# Inspect Codespaces secret metadata/scope without revealing any values.
gh secret list -R "$FULL" --app codespaces --json visibility,numSelectedRepos --jq 'map({visibility,numSelectedRepos})'
gh secret list --user --app codespaces --json visibility,numSelectedRepos --jq 'map({visibility,numSelectedRepos})'
# Then inspect available machines/default attributes.
gh api -H "X-GitHub-Api-Version: 2026-03-10" "repos/$FULL/codespaces/machines" --jq '.machines[] | {name,display_name,storage_in_bytes,memory_in_bytes,cpus}'
# Let GitHub choose an available machine; bound inactivity and stopped-storage lifetime.
gh codespace create -R "$FULL" -b main --idle-timeout 30m --retention-period 1d --status
gh codespace list -R "$FULL" --json name,displayName,state,machineName,lastUsedAt
CS="$(gh codespace list -R "$FULL" --json name --jq '.[0].name')"
gh codespace view -c "$CS" --json name,repository,state,machineName,idleTimeoutMinutes,retentionPeriodDays,prebuild
gh codespace ports -c "$CS" --json sourcePort,visibility,label,browseUrl
gh codespace ssh -c "$CS" -- 'printf "repo=%s\n" "$GITHUB_REPOSITORY"; git rev-parse HEAD; printf "CODESPACES=%s\n" "$CODESPACES"'
The SSH command deliberately prints only non-secret identity/state.
Never run env, set, or a recursive context
dump in a course lab because a future Codespaces secret could
appear. If port 8000 is forwarded, its default visibility is
private; do not make it public for this exercise.
If gh codespace create is blocked by quota or policy,
record that as an availability result and continue. The mandatory
learning outcome—the repository-defined environment plus Pages
deployment—does not depend on allocating a Codespace.
6. Enable Pages from main:/docs and verify the hosted
source
For this static site, branch publishing is the smallest safe model. The Pages resource is a hosted configuration; enabling it changes no Git ref.
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}'
Expected state: the Pages object reports the configured source
branch main and path /docs. The build may
be queued or building immediately after creation. Do not treat the
site URL becoming reachable as enough evidence; correlate the build
record with the commit.
# Poll manually a few times rather than hammering the API.
gh api -H "X-GitHub-Api-Version: 2026-03-10" "repos/$FULL/pages/builds/latest" --jq '{status,commit,created_at,updated_at,error}'
HOSTED_SHA="$(gh api -H 'X-GitHub-Api-Version: 2026-03-10' "repos/$FULL/pages/builds/latest" --jq .commit)"
printf 'local=%s
hosted=%s
' "$BASE_SHA" "$HOSTED_SHA"
test "$BASE_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" | grep -F "Cloud Dev Lab"
Those two checks prove two different things: the API proves the build was associated with the intended commit, and the HTTP request proves the public endpoint serves the expected page. If the build record has not completed yet, wait manually and query again; do not create a tight loop that consumes API requests.
7. Compare portability gaps explicitly
| Question | Conventional local | Local Dev Container | Codespace |
|---|---|---|---|
| Repository bytes | Local clone/ref | Local clone/ref mounted into container | GitHub-created environment from repository/ref |
| Container config | Optional | Primary environment description | Primary container description inside hosted VM |
| Identity to GitHub | Local credential manager/SSH | Usually host/injected tooling-specific auth | GitHub-managed Codespaces repository token plus approved extra access |
| Secrets | Local OS/tooling | Local injection strategy | Codespaces user/repo/org secret stores |
| Ports | Local OS firewall | Container-to-host forwarding | Codespaces forwarding with private/org/public visibility policy |
| Billing | Local hardware | Local hardware/container runtime | Hosted compute + storage / included quota or billing owner |
| Offline use | Yes | Yes after images/deps exist locally | No; requires service/network access |
Write one portability gap into
docs/environment-notes.md. For example, “Codespaces
supplies hosted repository authentication; local Dev Containers rely
on workstation authentication.” Commit that statement rather than
hiding it behind an assumption of perfect parity.
8. Challenge: choose the control, not the memorized command
For each request, choose the surface before looking at syntax:
- A developer wants an API key only in their Codespaces for this repository → user Codespaces secret scoped to that repository.
-
The project wants every environment to expose port 8000 privately
→ repository
devcontainer.jsonport metadata plus organization restrictions if applicable. - The docs require a custom static-site build before publishing → Pages Actions deployment rather than branch-source publishing.
- A reviewer wants proof of the live documentation version → Pages build/deployment commit plus the corresponding Git commit.
- Finance wants cloud development cost capped → Codespaces machine/timeout/retention and account/org spending policy, not a Git setting.
9. Verification and cleanup
git status --short
git rev-parse HEAD
gh api -H "X-GitHub-Api-Version: 2026-03-10" "repos/$FULL/pages" --jq '{html_url,source,status}'
# Optional Codespace cleanup, if one was created.
if [ -n "${CS:-}" ]; then
gh codespace stop -c "$CS" || true
gh codespace delete -c "$CS" # destructive to unpushed Codespace-only work; confirm the lab is clean
fi
# Optional: unpublish this disposable Pages site before archiving.
# SECURITY-SENSITIVE/DESTRUCTIVE hosted-setting change; only for this lab.
gh api --method DELETE -H "X-GitHub-Api-Version: 2026-03-10" "repos/$FULL/pages"
gh repo archive "$FULL" --yes
Before deleting a Codespace, git status inside it
should be clean and all wanted changes must be pushed. Deleting a
Codespace removes its unpushed work. Archiving the disposable
repository prevents accidental future modification; delete the
repository only if you intentionally want full removal and
understand that deletion is a separate destructive operation.
Knowledge check
Why did the lab enable Pages only after the local HTTP check passed?
It narrows causality. If local bytes are already wrong, a hosted deployment cannot fix them; separating content validation from Pages configuration makes failures easier to diagnose.
Which two observations prove the intended Pages version is live?
The Pages build/deployment record should reference the expected Git commit, and the public endpoint should serve the expected content. Either observation alone is weaker.
Why is creating a Codespace optional even on a public repository?
Availability and cost depend on the user account’s remaining included usage, billing/spending configuration, and organization policy. The course must not assume quota or payment.
Why does the lab avoid printing all environment variables inside the Codespace?
Development environment secrets are exposed as environment variables. Dumping the environment can leak credentials into terminal history or logs.
If a forwarded port is private, may you assume it can never become public?
No. Port visibility can be changed unless organization policy restricts it. Verify runtime visibility and keep public exposure out of the lab.
Summary
You built one versioned source tree that works as local documentation, carries portable dev-container metadata, can optionally become a Codespace, and publishes through a separately inspectable Pages resource. The important evidence is the relationship among repository/ref, environment configuration, hosted runtime state, and Pages build commit—not the success of a button click. Lesson 3 turns those mechanics into design policy.
Further reading — current primary sources
- GitHub CLI — gh codespace
- GitHub CLI — gh codespace create
- Forwarding ports in Codespaces
- REST API — GitHub Pages site/builds
- GitHub Codespaces documentation
- Security in GitHub Codespaces
- Understanding the codespace lifecycle
- Managing development environment secrets
- GitHub Pages — what it is
- Configuring a publishing source for GitHub Pages
- 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.