Homelab Academy / Modules
Guided practice + break/fix + lab gate.
Module: Module 1: Infrastructure & Addressing
Activity type: Lab (Practice)
Objective: Given a multi-step docker run intent, the learner can write a Compose file with services, ports, environment, and persistent mounts, then recreate the stack without data loss.
Where to run: Prefer the Linux VM from Class 1 (or a Linux Docker host). Docker Desktop on Windows works for this class’s CLI, but later ARR path labs assume Linux paths.
Create a project folder and the files below. Use the OS toggle for host commands.
Make the lab directory and Compose file.
mkdir $HOME\compose-lab\site -Force
cd $HOME\compose-lab
@'
name: compose-lab
services:
web:
image: nginx:stable
ports:
- "8080:80"
volumes:
- ./site:/usr/share/nginx/html:ro
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/"]
interval: 30s
timeout: 5s
retries: 3
'@ | Set-Content -Encoding utf8 compose.yaml
@'
<!doctype html><title>compose-lab</title>
<h1>Otaconskeep compose-lab UNIQUE-PHRASE-001</h1>
'@ | Set-Content -Encoding utf8 site\index.html
Get-ChildItem -Recurse
If the image healthcheck fails because curl is missing in nginx, edit compose.yaml and replace the healthcheck with:
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost/"]
or remove the healthcheck for this learning lab and note that in the workbook.
mkdir -p ~/compose-lab/site
cd ~/compose-lab
cat > compose.yaml <<'YAML'
name: compose-lab
services:
web:
image: nginx:stable
ports:
- "8080:80"
volumes:
- ./site:/usr/share/nginx/html:ro
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/"]
interval: 30s
timeout: 5s
retries: 3
YAML
printf '%s\n' '<!doctype html><title>compose-lab</title>' \
'<h1>Otaconskeep compose-lab UNIQUE-PHRASE-001</h1>' > site/index.html
ls -la site
If healthcheck fails for missing curl, switch the test to wget -qO- http://localhost/ or remove healthcheck and document it.
Validate the Compose model before starting anything.
cd $HOME\compose-lab
docker compose config
docker compose config --quiet
cd ~/compose-lab
docker compose config
docker compose config --quiet && echo CONFIG_OK
Fix any YAML/indent errors until config exits 0.
Start the stack and confirm the container is up.
docker compose up -d
docker compose ps
docker compose logs --tail=50 web
docker compose up -d
docker compose ps
docker compose logs --tail=50 web
Hit the published port from the host.
curl.exe -I http://127.0.0.1:8080/
curl.exe -s http://127.0.0.1:8080/ | findstr UNIQUE-PHRASE
curl -I http://127.0.0.1:8080/
curl -s http://127.0.0.1:8080/ | grep UNIQUE-PHRASE
Also open http://DOCKER-HOST:8080 in a browser if the host is remote. Record the URL in the workbook.
Prove the bind mount: edit the host file, refresh, see the change.
(Get-Content site\index.html) -replace 'UNIQUE-PHRASE-001','UNIQUE-PHRASE-002' | Set-Content site\index.html
curl.exe -s http://127.0.0.1:8080/ | findstr UNIQUE-PHRASE
sed -i 's/UNIQUE-PHRASE-001/UNIQUE-PHRASE-002/' site/index.html
curl -s http://127.0.0.1:8080/ | grep UNIQUE-PHRASE
Recreate the container and prove content survives.
docker compose down
docker compose up -d
curl.exe -s http://127.0.0.1:8080/ | findstr UNIQUE-PHRASE-002
docker compose ps
docker compose down
docker compose up -d
curl -s http://127.0.0.1:8080/ | grep UNIQUE-PHRASE-002
docker compose ps
Capture evidence for the gate.
docker compose ps
docker inspect --format "{{.State.Health.Status}}" $(docker compose ps -q web)
docker compose logs --tail=20 web
docker compose ps
docker inspect --format '{{.State.Health.Status}}' "$(docker compose ps -q web)"
docker compose logs --tail=20 web
Later classes use one shared data tree so downloaders and importers see the same files:
/data
/torrents
/usenet
/media
/movies
/tv
Avoid unrelated mounts such as /downloads in one container and /incoming in another unless you fully understand remote paths and hardlinks.
Perform these faults one at a time. Restore before the next fault.
"8080:80" to a port your host already uses (or start a second compose on 8080). Read the bind error, restore 8080.docker compose up -d
# read error, then fix compose.yaml and:
docker compose up -d
# After editing the port wrongly:
docker compose up -d
# restore port, then:
docker compose up -d
YAML indentation error. Break indent under services:, run docker compose config, fix from the error line.
Permission denied on a writable mount. Create ./writable, mount it read-write, chmod 000 writable (Linux) or remove write ACL (Windows), recreate, read logs, restore permissions only.
mkdir -p writable && chmod 000 writable
# after observing failure:
chmod 755 writable
nginx:stable, change to another explicit tag, pull + up -d, verify, then restore nginx:stable and prove the page still loads.All boxes must be true before the next class unlocks. If you fail: feedback → targeted review → new practice → reassess.
down and up.