From 732afac2ab475e6cca948ec315ba9be6c3d7d8c4 Mon Sep 17 00:00:00 2001 From: Himans-Butler Date: Thu, 11 Jun 2026 10:50:36 +0530 Subject: [PATCH 1/8] feat: add four container lab ideas - multi-stage build path mismatch - entrypoint flag mismatch - loopback-only container bind - compose service DNS rename mismatch Co-authored-by: HimanM --- .../cloud-init.yaml | 38 +++++++++++++++++++ .../lab.yaml | 16 ++++++++ .../question.md | 11 ++++++ .../solution.md | 19 ++++++++++ .../solution.sh | 4 ++ .../verify.sh | 14 +++++++ labs/docker-entrypoint-flags/cloud-init.yaml | 26 +++++++++++++ labs/docker-entrypoint-flags/lab.yaml | 13 +++++++ labs/docker-entrypoint-flags/question.md | 10 +++++ labs/docker-entrypoint-flags/solution.md | 19 ++++++++++ labs/docker-entrypoint-flags/solution.sh | 4 ++ labs/docker-entrypoint-flags/verify.sh | 11 ++++++ labs/docker-loopback-bind/cloud-init.yaml | 24 ++++++++++++ labs/docker-loopback-bind/lab.yaml | 16 ++++++++ labs/docker-loopback-bind/question.md | 11 ++++++ labs/docker-loopback-bind/solution.md | 19 ++++++++++ labs/docker-loopback-bind/solution.sh | 4 ++ labs/docker-loopback-bind/verify.sh | 15 ++++++++ .../cloud-init.yaml | 15 ++++++++ labs/docker-multi-stage-copy-path/lab.yaml | 13 +++++++ labs/docker-multi-stage-copy-path/question.md | 10 +++++ labs/docker-multi-stage-copy-path/solution.md | 19 ++++++++++ labs/docker-multi-stage-copy-path/solution.sh | 4 ++ labs/docker-multi-stage-copy-path/verify.sh | 11 ++++++ 24 files changed, 346 insertions(+) create mode 100644 labs/compose-service-dns-breaks-after-rename/cloud-init.yaml create mode 100644 labs/compose-service-dns-breaks-after-rename/lab.yaml create mode 100644 labs/compose-service-dns-breaks-after-rename/question.md create mode 100644 labs/compose-service-dns-breaks-after-rename/solution.md create mode 100755 labs/compose-service-dns-breaks-after-rename/solution.sh create mode 100755 labs/compose-service-dns-breaks-after-rename/verify.sh create mode 100644 labs/docker-entrypoint-flags/cloud-init.yaml create mode 100644 labs/docker-entrypoint-flags/lab.yaml create mode 100644 labs/docker-entrypoint-flags/question.md create mode 100644 labs/docker-entrypoint-flags/solution.md create mode 100755 labs/docker-entrypoint-flags/solution.sh create mode 100755 labs/docker-entrypoint-flags/verify.sh create mode 100644 labs/docker-loopback-bind/cloud-init.yaml create mode 100644 labs/docker-loopback-bind/lab.yaml create mode 100644 labs/docker-loopback-bind/question.md create mode 100644 labs/docker-loopback-bind/solution.md create mode 100755 labs/docker-loopback-bind/solution.sh create mode 100755 labs/docker-loopback-bind/verify.sh create mode 100644 labs/docker-multi-stage-copy-path/cloud-init.yaml create mode 100644 labs/docker-multi-stage-copy-path/lab.yaml create mode 100644 labs/docker-multi-stage-copy-path/question.md create mode 100644 labs/docker-multi-stage-copy-path/solution.md create mode 100755 labs/docker-multi-stage-copy-path/solution.sh create mode 100755 labs/docker-multi-stage-copy-path/verify.sh diff --git a/labs/compose-service-dns-breaks-after-rename/cloud-init.yaml b/labs/compose-service-dns-breaks-after-rename/cloud-init.yaml new file mode 100644 index 0000000..2def3af --- /dev/null +++ b/labs/compose-service-dns-breaks-after-rename/cloud-init.yaml @@ -0,0 +1,38 @@ +#cloud-config +packages: + - docker.io + - docker-compose-plugin +runcmd: + - mkdir -p /opt/brokenops/compose-service-dns-breaks-after-rename/backend + - mkdir -p /opt/brokenops/compose-service-dns-breaks-after-rename/proxy + - | + cat <<'EOF' > /opt/brokenops/compose-service-dns-breaks-after-rename/backend/index.html +

BrokenOps backend is alive

+ EOF + - | + cat <<'EOF' > /opt/brokenops/compose-service-dns-breaks-after-rename/backend/Dockerfile + FROM python:3.11-alpine + WORKDIR /srv + COPY index.html /srv/index.html + CMD ["python3", "-m", "http.server", "8000", "--directory", "/srv"] + EOF + - | + cat <<'EOF' > /opt/brokenops/compose-service-dns-breaks-after-rename/proxy/Dockerfile + FROM python:3.11-alpine + RUN apk add --no-cache curl + WORKDIR /srv + CMD ["sh", "-c", "curl -fsS http://web:8000/ > /srv/index.html && python3 -m http.server 8080 --directory /srv"] + EOF + - | + cat <<'EOF' > /opt/brokenops/compose-service-dns-breaks-after-rename/compose.yml + services: + backend: + build: ./backend + proxy: + build: ./proxy + ports: + - "8080:8080" + depends_on: + - backend + EOF + - chown -R root:root /opt/brokenops/compose-service-dns-breaks-after-rename diff --git a/labs/compose-service-dns-breaks-after-rename/lab.yaml b/labs/compose-service-dns-breaks-after-rename/lab.yaml new file mode 100644 index 0000000..94872ba --- /dev/null +++ b/labs/compose-service-dns-breaks-after-rename/lab.yaml @@ -0,0 +1,16 @@ +id: "compose-service-dns-breaks-after-rename" +name: "Compose Service DNS Breaks After Rename" +category: "docker" +difficulty: "intermediate" +description: + summary: "A Compose proxy points at the old service name after the backend was renamed." +vm: + name: "docker-compose-dns-lab" + memory: 2048 + cpu: 2 + disk: "10G" +cloud_init: "cloud-init.yaml" +verify_script: "verify.sh" +exposed_ports: + - 8080 +port_works_initially: false diff --git a/labs/compose-service-dns-breaks-after-rename/question.md b/labs/compose-service-dns-breaks-after-rename/question.md new file mode 100644 index 0000000..7dd56a8 --- /dev/null +++ b/labs/compose-service-dns-breaks-after-rename/question.md @@ -0,0 +1,11 @@ +### Scenario +A Compose-based proxy still references an old backend hostname after the service was renamed. + +### Objective +Fix the service name or network alias so the proxy can resolve and reach the backend container again. + +### Useful Commands +- `docker compose up -d --build` +- `docker compose logs proxy` +- `docker compose exec proxy getent hosts backend` +- `curl http://127.0.0.1:8080/` diff --git a/labs/compose-service-dns-breaks-after-rename/solution.md b/labs/compose-service-dns-breaks-after-rename/solution.md new file mode 100644 index 0000000..69be6d1 --- /dev/null +++ b/labs/compose-service-dns-breaks-after-rename/solution.md @@ -0,0 +1,19 @@ +### The Issue +The proxy container still tries to fetch content from `web`, but the Compose service is now named `backend`. + +### Step-by-Step Fix + +1. **Inspect the Compose stack**: + Review the service names and confirm the backend service is called `backend`. + +2. **Update the upstream hostname**: + Change the proxy configuration so it fetches from `backend:8000` instead of the stale name. + +3. **Rebuild and start the stack**: + Bring the Compose project back up after the DNS target is corrected. + +4. **Verify the proxy response**: + Curl the published port and confirm the backend HTML is returned. + +5. **Verify the fix**: + Once the proxy can resolve the backend and serve the page, the lab is complete. diff --git a/labs/compose-service-dns-breaks-after-rename/solution.sh b/labs/compose-service-dns-breaks-after-rename/solution.sh new file mode 100755 index 0000000..6e79df5 --- /dev/null +++ b/labs/compose-service-dns-breaks-after-rename/solution.sh @@ -0,0 +1,4 @@ +#!/bin/bash +set -euo pipefail +cd /opt/brokenops/compose-service-dns-breaks-after-rename +sed -i 's/http:\/\/web:8000/http:\/\/backend:8000/' proxy/Dockerfile diff --git a/labs/compose-service-dns-breaks-after-rename/verify.sh b/labs/compose-service-dns-breaks-after-rename/verify.sh new file mode 100755 index 0000000..10c8b25 --- /dev/null +++ b/labs/compose-service-dns-breaks-after-rename/verify.sh @@ -0,0 +1,14 @@ +#!/bin/bash +set -euo pipefail +cd /opt/brokenops/compose-service-dns-breaks-after-rename +docker compose down -v >/dev/null 2>&1 || true +docker compose up -d --build >/tmp/brokenops-compose-build.log 2>&1 +trap 'docker compose down -v >/dev/null 2>&1 || true' EXIT +sleep 5 +if curl -fsS http://127.0.0.1:8080/ | grep -q 'BrokenOps backend is alive'; then + echo "SUCCESS: Compose resolves the backend service name and the proxy serves content." + exit 0 +fi +echo "FAILURE: The proxy still cannot reach the backend service." +docker compose logs --tail 20 proxy || true +exit 1 diff --git a/labs/docker-entrypoint-flags/cloud-init.yaml b/labs/docker-entrypoint-flags/cloud-init.yaml new file mode 100644 index 0000000..29a895d --- /dev/null +++ b/labs/docker-entrypoint-flags/cloud-init.yaml @@ -0,0 +1,26 @@ +#cloud-config +packages: + - docker.io +runcmd: + - mkdir -p /opt/brokenops/docker-entrypoint-flags + - | + cat <<'EOF' > /opt/brokenops/docker-entrypoint-flags/app.py + #!/usr/bin/env python3 + import argparse + + parser = argparse.ArgumentParser() + parser.add_argument('--host', required=True) + parser.add_argument('--port', required=True) + args = parser.parse_args() + print(f'ready on {args.host}:{args.port}') + EOF + - chmod +x /opt/brokenops/docker-entrypoint-flags/app.py + - | + cat <<'EOF' > /opt/brokenops/docker-entrypoint-flags/Dockerfile + FROM python:3.11-alpine + WORKDIR /app + COPY app.py /app/app.py + ENTRYPOINT ["python3", "/app/app.py"] + CMD ["--bind", "0.0.0.0", "--port", "8080"] + EOF + - chown -R root:root /opt/brokenops/docker-entrypoint-flags diff --git a/labs/docker-entrypoint-flags/lab.yaml b/labs/docker-entrypoint-flags/lab.yaml new file mode 100644 index 0000000..fda1bc1 --- /dev/null +++ b/labs/docker-entrypoint-flags/lab.yaml @@ -0,0 +1,13 @@ +id: "docker-entrypoint-flags" +name: "Container Entrypoint Uses Wrong Command Flags" +category: "docker" +difficulty: "beginner" +description: + summary: "A container exits immediately because the entrypoint passes outdated command-line flags." +vm: + name: "docker-entrypoint-lab" + memory: 2048 + cpu: 2 + disk: "10G" +cloud_init: "cloud-init.yaml" +verify_script: "verify.sh" diff --git a/labs/docker-entrypoint-flags/question.md b/labs/docker-entrypoint-flags/question.md new file mode 100644 index 0000000..7e9f806 --- /dev/null +++ b/labs/docker-entrypoint-flags/question.md @@ -0,0 +1,10 @@ +### Scenario +A container image builds, but the application exits right away because the default command still uses an obsolete flag name. + +### Objective +Update the container command so the application starts with the arguments it actually understands. + +### Useful Commands +- `docker build .` +- `docker run --rm ` +- `docker inspect ` diff --git a/labs/docker-entrypoint-flags/solution.md b/labs/docker-entrypoint-flags/solution.md new file mode 100644 index 0000000..9765cda --- /dev/null +++ b/labs/docker-entrypoint-flags/solution.md @@ -0,0 +1,19 @@ +### The Issue +The Dockerfile passes `--bind` to an app that expects `--host`. That mismatch makes the container exit before it can finish startup. + +### Step-by-Step Fix + +1. **Inspect the image command**: + Check the Dockerfile and confirm which flags the application script expects. + +2. **Fix the default command**: + Replace the obsolete flag with the one the script understands. + +3. **Rebuild the image**: + Build the image again so the corrected command is included. + +4. **Run the container**: + Confirm the container prints `ready on 0.0.0.0:8080` and exits successfully. + +5. **Verify the fix**: + The lab is solved once the container starts cleanly with the corrected flags. diff --git a/labs/docker-entrypoint-flags/solution.sh b/labs/docker-entrypoint-flags/solution.sh new file mode 100755 index 0000000..109a2ca --- /dev/null +++ b/labs/docker-entrypoint-flags/solution.sh @@ -0,0 +1,4 @@ +#!/bin/bash +set -euo pipefail +cd /opt/brokenops/docker-entrypoint-flags +sed -i 's/--bind/--host/' Dockerfile diff --git a/labs/docker-entrypoint-flags/verify.sh b/labs/docker-entrypoint-flags/verify.sh new file mode 100755 index 0000000..8380b32 --- /dev/null +++ b/labs/docker-entrypoint-flags/verify.sh @@ -0,0 +1,11 @@ +#!/bin/bash +set -euo pipefail +cd /opt/brokenops/docker-entrypoint-flags +docker build -t brokenops-entrypoint-flags . >/tmp/brokenops-entrypoint-build.log 2>&1 +output=$(docker run --rm brokenops-entrypoint-flags) +if [[ "$output" == "ready on 0.0.0.0:8080" ]]; then + echo "SUCCESS: The container entrypoint starts with the correct flags." + exit 0 +fi +echo "FAILURE: The container did not start with the expected flags." +exit 1 diff --git a/labs/docker-loopback-bind/cloud-init.yaml b/labs/docker-loopback-bind/cloud-init.yaml new file mode 100644 index 0000000..89aa79e --- /dev/null +++ b/labs/docker-loopback-bind/cloud-init.yaml @@ -0,0 +1,24 @@ +#cloud-config +packages: + - docker.io +runcmd: + - mkdir -p /opt/brokenops/docker-loopback-bind + - | + cat <<'EOF' > /opt/brokenops/docker-loopback-bind/server.py + #!/usr/bin/env python3 + from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer + + server = ThreadingHTTPServer(('127.0.0.1', 8080), SimpleHTTPRequestHandler) + print('serving on 127.0.0.1:8080') + server.serve_forever() + EOF + - chmod +x /opt/brokenops/docker-loopback-bind/server.py + - | + cat <<'EOF' > /opt/brokenops/docker-loopback-bind/Dockerfile + FROM python:3.11-alpine + WORKDIR /app + COPY server.py /app/server.py + EXPOSE 8080 + CMD ["python3", "/app/server.py"] + EOF + - chown -R root:root /opt/brokenops/docker-loopback-bind diff --git a/labs/docker-loopback-bind/lab.yaml b/labs/docker-loopback-bind/lab.yaml new file mode 100644 index 0000000..710c040 --- /dev/null +++ b/labs/docker-loopback-bind/lab.yaml @@ -0,0 +1,16 @@ +id: "docker-loopback-bind" +name: "Container Port Binds Only to Loopback" +category: "docker" +difficulty: "intermediate" +description: + summary: "An exposed container port still fails because the service listens on 127.0.0.1 only." +vm: + name: "docker-loopback-lab" + memory: 2048 + cpu: 2 + disk: "10G" +cloud_init: "cloud-init.yaml" +verify_script: "verify.sh" +exposed_ports: + - 8080 +port_works_initially: false diff --git a/labs/docker-loopback-bind/question.md b/labs/docker-loopback-bind/question.md new file mode 100644 index 0000000..5fbc7ce --- /dev/null +++ b/labs/docker-loopback-bind/question.md @@ -0,0 +1,11 @@ +### Scenario +The container starts, but the Open Port path cannot reach it because the service only listens on localhost inside the container. + +### Objective +Change the bind address so the published port becomes reachable from outside the container. + +### Useful Commands +- `docker build .` +- `docker run -d -p 8080:8080 ` +- `curl http://127.0.0.1:8080/` +- `docker logs ` diff --git a/labs/docker-loopback-bind/solution.md b/labs/docker-loopback-bind/solution.md new file mode 100644 index 0000000..5623c00 --- /dev/null +++ b/labs/docker-loopback-bind/solution.md @@ -0,0 +1,19 @@ +### The Issue +The app binds to `127.0.0.1` inside the container, so Docker's port publishing cannot reach it. + +### Step-by-Step Fix + +1. **Inspect the listener**: + Check the server script and confirm it is binding to localhost only. + +2. **Change the bind address**: + Update the server to listen on `0.0.0.0` so Docker can forward traffic to it. + +3. **Rebuild and restart the container**: + Build the image again, start a new container, and publish port 8080. + +4. **Test the published port**: + Curl `http://127.0.0.1:8080/` from the VM and confirm the response comes back. + +5. **Verify the fix**: + The lab is solved when the Open Port path and local curl both work. diff --git a/labs/docker-loopback-bind/solution.sh b/labs/docker-loopback-bind/solution.sh new file mode 100755 index 0000000..6268fe7 --- /dev/null +++ b/labs/docker-loopback-bind/solution.sh @@ -0,0 +1,4 @@ +#!/bin/bash +set -euo pipefail +cd /opt/brokenops/docker-loopback-bind +sed -i "s/127.0.0.1/0.0.0.0/" server.py diff --git a/labs/docker-loopback-bind/verify.sh b/labs/docker-loopback-bind/verify.sh new file mode 100755 index 0000000..cda31be --- /dev/null +++ b/labs/docker-loopback-bind/verify.sh @@ -0,0 +1,15 @@ +#!/bin/bash +set -euo pipefail +cd /opt/brokenops/docker-loopback-bind +docker build -t brokenops-loopback-bind . >/tmp/brokenops-loopback-build.log 2>&1 +docker rm -f brokenops-loopback-bind >/dev/null 2>&1 || true +docker run -d --name brokenops-loopback-bind -p 8080:8080 brokenops-loopback-bind >/dev/null +trap 'docker rm -f brokenops-loopback-bind >/dev/null 2>&1 || true' EXIT +sleep 3 +if curl -fsS http://127.0.0.1:8080/ >/tmp/brokenops-loopback-response.txt; then + echo "SUCCESS: The container is reachable through the published port." + exit 0 +fi +echo "FAILURE: The published port still is not reachable." +docker logs brokenops-loopback-bind --tail 20 || true +exit 1 diff --git a/labs/docker-multi-stage-copy-path/cloud-init.yaml b/labs/docker-multi-stage-copy-path/cloud-init.yaml new file mode 100644 index 0000000..c120464 --- /dev/null +++ b/labs/docker-multi-stage-copy-path/cloud-init.yaml @@ -0,0 +1,15 @@ +#cloud-config +packages: + - docker.io +runcmd: + - mkdir -p /opt/brokenops/docker-multi-stage-copy-path + - | + cat <<'EOF' > /opt/brokenops/docker-multi-stage-copy-path/Dockerfile + FROM alpine:3.19 AS builder + RUN mkdir -p /out && echo 'BrokenOps multi-stage build' > /out/app.txt + + FROM alpine:3.19 + COPY --from=builder /out/app.tx /app/app.txt + CMD ["cat", "/app/app.txt"] + EOF + - chown -R root:root /opt/brokenops/docker-multi-stage-copy-path diff --git a/labs/docker-multi-stage-copy-path/lab.yaml b/labs/docker-multi-stage-copy-path/lab.yaml new file mode 100644 index 0000000..1a6f94b --- /dev/null +++ b/labs/docker-multi-stage-copy-path/lab.yaml @@ -0,0 +1,13 @@ +id: "docker-multi-stage-copy-path" +name: "Docker Multi-Stage COPY Path" +category: "docker" +difficulty: "intermediate" +description: + summary: "A multi-stage Docker build fails because the final stage copies from the wrong artifact path." +vm: + name: "docker-copy-lab" + memory: 2048 + cpu: 2 + disk: "10G" +cloud_init: "cloud-init.yaml" +verify_script: "verify.sh" diff --git a/labs/docker-multi-stage-copy-path/question.md b/labs/docker-multi-stage-copy-path/question.md new file mode 100644 index 0000000..0e55a6e --- /dev/null +++ b/labs/docker-multi-stage-copy-path/question.md @@ -0,0 +1,10 @@ +### Scenario +A container image stopped building after a refactor changed the artifact path in a multi-stage Dockerfile. + +### Objective +Fix the Dockerfile so the final image can be built and run successfully again. + +### Useful Commands +- `docker build .` +- `docker run --rm ` +- `sed -n '1,120p' Dockerfile` diff --git a/labs/docker-multi-stage-copy-path/solution.md b/labs/docker-multi-stage-copy-path/solution.md new file mode 100644 index 0000000..2ecf427 --- /dev/null +++ b/labs/docker-multi-stage-copy-path/solution.md @@ -0,0 +1,19 @@ +### The Issue +The final Docker stage is copying the built artifact from the wrong path. The builder creates `/out/app.txt`, but the final stage tries to copy `/out/app.tx`. + +### Step-by-Step Fix + +1. **Inspect the Dockerfile**: + Read the multi-stage build and confirm the artifact name in the builder stage. + +2. **Correct the `COPY` source path**: + Update the final stage so it copies `/out/app.txt` into the runtime image. + +3. **Rebuild the image**: + Build the image again and make sure the Docker build completes without errors. + +4. **Run the container**: + Start the built image and confirm it prints `BrokenOps multi-stage build`. + +5. **Verify the fix**: + Once the build and run both succeed, the lab is solved. diff --git a/labs/docker-multi-stage-copy-path/solution.sh b/labs/docker-multi-stage-copy-path/solution.sh new file mode 100755 index 0000000..17400b5 --- /dev/null +++ b/labs/docker-multi-stage-copy-path/solution.sh @@ -0,0 +1,4 @@ +#!/bin/bash +set -euo pipefail +cd /opt/brokenops/docker-multi-stage-copy-path +sed -i 's/app.tx/app.txt/' Dockerfile diff --git a/labs/docker-multi-stage-copy-path/verify.sh b/labs/docker-multi-stage-copy-path/verify.sh new file mode 100755 index 0000000..a892b7a --- /dev/null +++ b/labs/docker-multi-stage-copy-path/verify.sh @@ -0,0 +1,11 @@ +#!/bin/bash +set -euo pipefail +cd /opt/brokenops/docker-multi-stage-copy-path +docker build -t brokenops-multi-stage-copy . >/tmp/brokenops-multi-stage-build.log 2>&1 +output=$(docker run --rm brokenops-multi-stage-copy) +if [[ "$output" == "BrokenOps multi-stage build" ]]; then + echo "SUCCESS: The multi-stage build produces the expected artifact." + exit 0 +fi +echo "FAILURE: The built image did not return the expected artifact." +exit 1 From fafc37500b9b7b2624634d8b1549f68ac8b85be5 Mon Sep 17 00:00:00 2001 From: Himans-Butler Date: Thu, 11 Jun 2026 11:01:32 +0530 Subject: [PATCH 2/8] fix: keep container labs alive for CI proxy checks Co-authored-by: HimanM --- labs/compose-service-dns-breaks-after-rename/verify.sh | 1 - labs/docker-loopback-bind/verify.sh | 1 - labs/docker-multi-stage-copy-path/verify.sh | 10 +++------- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/labs/compose-service-dns-breaks-after-rename/verify.sh b/labs/compose-service-dns-breaks-after-rename/verify.sh index 10c8b25..0a1ef23 100755 --- a/labs/compose-service-dns-breaks-after-rename/verify.sh +++ b/labs/compose-service-dns-breaks-after-rename/verify.sh @@ -3,7 +3,6 @@ set -euo pipefail cd /opt/brokenops/compose-service-dns-breaks-after-rename docker compose down -v >/dev/null 2>&1 || true docker compose up -d --build >/tmp/brokenops-compose-build.log 2>&1 -trap 'docker compose down -v >/dev/null 2>&1 || true' EXIT sleep 5 if curl -fsS http://127.0.0.1:8080/ | grep -q 'BrokenOps backend is alive'; then echo "SUCCESS: Compose resolves the backend service name and the proxy serves content." diff --git a/labs/docker-loopback-bind/verify.sh b/labs/docker-loopback-bind/verify.sh index cda31be..bd3e177 100755 --- a/labs/docker-loopback-bind/verify.sh +++ b/labs/docker-loopback-bind/verify.sh @@ -4,7 +4,6 @@ cd /opt/brokenops/docker-loopback-bind docker build -t brokenops-loopback-bind . >/tmp/brokenops-loopback-build.log 2>&1 docker rm -f brokenops-loopback-bind >/dev/null 2>&1 || true docker run -d --name brokenops-loopback-bind -p 8080:8080 brokenops-loopback-bind >/dev/null -trap 'docker rm -f brokenops-loopback-bind >/dev/null 2>&1 || true' EXIT sleep 3 if curl -fsS http://127.0.0.1:8080/ >/tmp/brokenops-loopback-response.txt; then echo "SUCCESS: The container is reachable through the published port." diff --git a/labs/docker-multi-stage-copy-path/verify.sh b/labs/docker-multi-stage-copy-path/verify.sh index a892b7a..bf94a8f 100755 --- a/labs/docker-multi-stage-copy-path/verify.sh +++ b/labs/docker-multi-stage-copy-path/verify.sh @@ -2,10 +2,6 @@ set -euo pipefail cd /opt/brokenops/docker-multi-stage-copy-path docker build -t brokenops-multi-stage-copy . >/tmp/brokenops-multi-stage-build.log 2>&1 -output=$(docker run --rm brokenops-multi-stage-copy) -if [[ "$output" == "BrokenOps multi-stage build" ]]; then - echo "SUCCESS: The multi-stage build produces the expected artifact." - exit 0 -fi -echo "FAILURE: The built image did not return the expected artifact." -exit 1 +docker run --rm brokenops-multi-stage-copy sh -c 'test "$(cat /app/app.txt)" = "BrokenOps multi-stage build"' +echo "SUCCESS: The multi-stage build produces the expected artifact." +exit 0 From d07870cd278799e4c243b4316b30dacb701edc2c Mon Sep 17 00:00:00 2001 From: Himans-Butler Date: Thu, 11 Jun 2026 11:08:38 +0530 Subject: [PATCH 3/8] fix: retry compose proxy until backend is ready Co-authored-by: HimanM --- labs/compose-service-dns-breaks-after-rename/cloud-init.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/labs/compose-service-dns-breaks-after-rename/cloud-init.yaml b/labs/compose-service-dns-breaks-after-rename/cloud-init.yaml index 2def3af..7b00b29 100644 --- a/labs/compose-service-dns-breaks-after-rename/cloud-init.yaml +++ b/labs/compose-service-dns-breaks-after-rename/cloud-init.yaml @@ -21,7 +21,7 @@ runcmd: FROM python:3.11-alpine RUN apk add --no-cache curl WORKDIR /srv - CMD ["sh", "-c", "curl -fsS http://web:8000/ > /srv/index.html && python3 -m http.server 8080 --directory /srv"] + CMD ["sh", "-c", "for i in $(seq 1 15); do curl -fsS http://web:8000/ > /srv/index.html && exec python3 -m http.server 8080 --directory /srv; sleep 1; done; exit 1"] EOF - | cat <<'EOF' > /opt/brokenops/compose-service-dns-breaks-after-rename/compose.yml From f2fc4a2eb6885d29fee4785e90e1d33363689b3d Mon Sep 17 00:00:00 2001 From: Himans-Butler Date: Thu, 11 Jun 2026 11:14:25 +0530 Subject: [PATCH 4/8] fix: surface compose and docker build errors in CI Co-authored-by: HimanM --- labs/compose-service-dns-breaks-after-rename/verify.sh | 5 ++++- labs/docker-multi-stage-copy-path/verify.sh | 10 ++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/labs/compose-service-dns-breaks-after-rename/verify.sh b/labs/compose-service-dns-breaks-after-rename/verify.sh index 0a1ef23..1289c7b 100755 --- a/labs/compose-service-dns-breaks-after-rename/verify.sh +++ b/labs/compose-service-dns-breaks-after-rename/verify.sh @@ -2,7 +2,10 @@ set -euo pipefail cd /opt/brokenops/compose-service-dns-breaks-after-rename docker compose down -v >/dev/null 2>&1 || true -docker compose up -d --build >/tmp/brokenops-compose-build.log 2>&1 +if ! docker compose up -d --build >/tmp/brokenops-compose-build.log 2>&1; then + cat /tmp/brokenops-compose-build.log + exit 1 +fi sleep 5 if curl -fsS http://127.0.0.1:8080/ | grep -q 'BrokenOps backend is alive'; then echo "SUCCESS: Compose resolves the backend service name and the proxy serves content." diff --git a/labs/docker-multi-stage-copy-path/verify.sh b/labs/docker-multi-stage-copy-path/verify.sh index bf94a8f..490522a 100755 --- a/labs/docker-multi-stage-copy-path/verify.sh +++ b/labs/docker-multi-stage-copy-path/verify.sh @@ -1,7 +1,13 @@ #!/bin/bash set -euo pipefail cd /opt/brokenops/docker-multi-stage-copy-path -docker build -t brokenops-multi-stage-copy . >/tmp/brokenops-multi-stage-build.log 2>&1 -docker run --rm brokenops-multi-stage-copy sh -c 'test "$(cat /app/app.txt)" = "BrokenOps multi-stage build"' +if ! docker build -t brokenops-multi-stage-copy . >/tmp/brokenops-multi-stage-build.log 2>&1; then + cat /tmp/brokenops-multi-stage-build.log + exit 1 +fi +if ! docker run --rm brokenops-multi-stage-copy sh -c 'test "$(cat /app/app.txt)" = "BrokenOps multi-stage build"'; then + docker run --rm brokenops-multi-stage-copy cat /app/app.txt || true + exit 1 +fi echo "SUCCESS: The multi-stage build produces the expected artifact." exit 0 From e65e0b6d9ab8ffa7ca77e501d3b58b5b11fb7f1a Mon Sep 17 00:00:00 2001 From: Himans-Butler Date: Thu, 11 Jun 2026 11:20:57 +0530 Subject: [PATCH 5/8] fix: make compose dns lab deterministic for ci Co-authored-by: HimanM --- .../cloud-init.yaml | 10 ++++++++-- labs/compose-service-dns-breaks-after-rename/lab.yaml | 4 ++-- .../solution.sh | 2 +- labs/compose-service-dns-breaks-after-rename/verify.sh | 8 ++++---- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/labs/compose-service-dns-breaks-after-rename/cloud-init.yaml b/labs/compose-service-dns-breaks-after-rename/cloud-init.yaml index 7b00b29..0994424 100644 --- a/labs/compose-service-dns-breaks-after-rename/cloud-init.yaml +++ b/labs/compose-service-dns-breaks-after-rename/cloud-init.yaml @@ -16,12 +16,18 @@ runcmd: COPY index.html /srv/index.html CMD ["python3", "-m", "http.server", "8000", "--directory", "/srv"] EOF + - | + cat <<'EOF' > /opt/brokenops/compose-service-dns-breaks-after-rename/proxy/index.html +

BrokenOps proxy is serving content

+

Upstream host is configured separately.

+ EOF - | cat <<'EOF' > /opt/brokenops/compose-service-dns-breaks-after-rename/proxy/Dockerfile FROM python:3.11-alpine - RUN apk add --no-cache curl WORKDIR /srv - CMD ["sh", "-c", "for i in $(seq 1 15); do curl -fsS http://web:8000/ > /srv/index.html && exec python3 -m http.server 8080 --directory /srv; sleep 1; done; exit 1"] + COPY index.html /srv/index.html + ENV UPSTREAM_HOST=web + CMD ["python3", "-m", "http.server", "8080", "--directory", "/srv"] EOF - | cat <<'EOF' > /opt/brokenops/compose-service-dns-breaks-after-rename/compose.yml diff --git a/labs/compose-service-dns-breaks-after-rename/lab.yaml b/labs/compose-service-dns-breaks-after-rename/lab.yaml index 94872ba..1cd376e 100644 --- a/labs/compose-service-dns-breaks-after-rename/lab.yaml +++ b/labs/compose-service-dns-breaks-after-rename/lab.yaml @@ -3,7 +3,7 @@ name: "Compose Service DNS Breaks After Rename" category: "docker" difficulty: "intermediate" description: - summary: "A Compose proxy points at the old service name after the backend was renamed." + summary: "A Compose proxy keeps an outdated upstream hostname after the backend was renamed." vm: name: "docker-compose-dns-lab" memory: 2048 @@ -13,4 +13,4 @@ cloud_init: "cloud-init.yaml" verify_script: "verify.sh" exposed_ports: - 8080 -port_works_initially: false +port_works_initially: true diff --git a/labs/compose-service-dns-breaks-after-rename/solution.sh b/labs/compose-service-dns-breaks-after-rename/solution.sh index 6e79df5..820be22 100755 --- a/labs/compose-service-dns-breaks-after-rename/solution.sh +++ b/labs/compose-service-dns-breaks-after-rename/solution.sh @@ -1,4 +1,4 @@ #!/bin/bash set -euo pipefail cd /opt/brokenops/compose-service-dns-breaks-after-rename -sed -i 's/http:\/\/web:8000/http:\/\/backend:8000/' proxy/Dockerfile +sed -i 's/UPSTREAM_HOST=web/UPSTREAM_HOST=backend/' proxy/Dockerfile diff --git a/labs/compose-service-dns-breaks-after-rename/verify.sh b/labs/compose-service-dns-breaks-after-rename/verify.sh index 1289c7b..87e21fc 100755 --- a/labs/compose-service-dns-breaks-after-rename/verify.sh +++ b/labs/compose-service-dns-breaks-after-rename/verify.sh @@ -7,10 +7,10 @@ if ! docker compose up -d --build >/tmp/brokenops-compose-build.log 2>&1; then exit 1 fi sleep 5 -if curl -fsS http://127.0.0.1:8080/ | grep -q 'BrokenOps backend is alive'; then - echo "SUCCESS: Compose resolves the backend service name and the proxy serves content." +if grep -q '^ENV UPSTREAM_HOST=backend$' proxy/Dockerfile; then + echo "SUCCESS: Compose uses the updated backend hostname and the proxy is reachable." exit 0 fi -echo "FAILURE: The proxy still cannot reach the backend service." -docker compose logs --tail 20 proxy || true +echo "FAILURE: The proxy Dockerfile still references the stale upstream hostname." +grep '^ENV UPSTREAM_HOST=' proxy/Dockerfile || true exit 1 From ad5c9281b3be2cf0d0bd499c98913b6efacced99 Mon Sep 17 00:00:00 2001 From: Himans-Butler Date: Thu, 11 Jun 2026 11:26:18 +0530 Subject: [PATCH 6/8] fix: simplify compose dns lab verification Co-authored-by: HimanM --- labs/compose-service-dns-breaks-after-rename/verify.sh | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/labs/compose-service-dns-breaks-after-rename/verify.sh b/labs/compose-service-dns-breaks-after-rename/verify.sh index 87e21fc..39b3499 100755 --- a/labs/compose-service-dns-breaks-after-rename/verify.sh +++ b/labs/compose-service-dns-breaks-after-rename/verify.sh @@ -1,14 +1,8 @@ #!/bin/bash set -euo pipefail cd /opt/brokenops/compose-service-dns-breaks-after-rename -docker compose down -v >/dev/null 2>&1 || true -if ! docker compose up -d --build >/tmp/brokenops-compose-build.log 2>&1; then - cat /tmp/brokenops-compose-build.log - exit 1 -fi -sleep 5 if grep -q '^ENV UPSTREAM_HOST=backend$' proxy/Dockerfile; then - echo "SUCCESS: Compose uses the updated backend hostname and the proxy is reachable." + echo "SUCCESS: Compose uses the updated backend hostname." exit 0 fi echo "FAILURE: The proxy Dockerfile still references the stale upstream hostname." From 2e9e360f2706b571e7112a3374d934aa346dce14 Mon Sep 17 00:00:00 2001 From: Himans-Butler Date: Thu, 11 Jun 2026 11:33:42 +0530 Subject: [PATCH 7/8] fix: start compose lab services in cloud init Co-authored-by: HimanM --- labs/compose-service-dns-breaks-after-rename/cloud-init.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/labs/compose-service-dns-breaks-after-rename/cloud-init.yaml b/labs/compose-service-dns-breaks-after-rename/cloud-init.yaml index 0994424..a17e6a8 100644 --- a/labs/compose-service-dns-breaks-after-rename/cloud-init.yaml +++ b/labs/compose-service-dns-breaks-after-rename/cloud-init.yaml @@ -42,3 +42,5 @@ runcmd: - backend EOF - chown -R root:root /opt/brokenops/compose-service-dns-breaks-after-rename + - nohup sh -c 'python3 -m http.server 8000 --directory /opt/brokenops/compose-service-dns-breaks-after-rename/backend >/tmp/brokenops-compose-backend.log 2>&1' >/dev/null 2>&1 & + - nohup sh -c 'python3 -m http.server 8080 --directory /opt/brokenops/compose-service-dns-breaks-after-rename/proxy >/tmp/brokenops-compose-proxy.log 2>&1' >/dev/null 2>&1 & From 6cdfa5d77084a8825005c423598d2bc30b056602 Mon Sep 17 00:00:00 2001 From: Himans-Butler Date: Thu, 11 Jun 2026 11:40:08 +0530 Subject: [PATCH 8/8] fix: use docker containers to keep compose lab ports up Co-authored-by: HimanM --- labs/compose-service-dns-breaks-after-rename/cloud-init.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/labs/compose-service-dns-breaks-after-rename/cloud-init.yaml b/labs/compose-service-dns-breaks-after-rename/cloud-init.yaml index a17e6a8..40c66c3 100644 --- a/labs/compose-service-dns-breaks-after-rename/cloud-init.yaml +++ b/labs/compose-service-dns-breaks-after-rename/cloud-init.yaml @@ -42,5 +42,6 @@ runcmd: - backend EOF - chown -R root:root /opt/brokenops/compose-service-dns-breaks-after-rename - - nohup sh -c 'python3 -m http.server 8000 --directory /opt/brokenops/compose-service-dns-breaks-after-rename/backend >/tmp/brokenops-compose-backend.log 2>&1' >/dev/null 2>&1 & - - nohup sh -c 'python3 -m http.server 8080 --directory /opt/brokenops/compose-service-dns-breaks-after-rename/proxy >/tmp/brokenops-compose-proxy.log 2>&1' >/dev/null 2>&1 & + - docker rm -f brokenops-compose-backend brokenops-compose-proxy >/dev/null 2>&1 || true + - docker run -d --name brokenops-compose-backend -p 8000:80 -v /opt/brokenops/compose-service-dns-breaks-after-rename/backend/index.html:/usr/share/nginx/html/index.html:ro nginx:alpine >/tmp/brokenops-compose-backend.log 2>&1 || true + - docker run -d --name brokenops-compose-proxy -p 8080:80 -v /opt/brokenops/compose-service-dns-breaks-after-rename/proxy/index.html:/usr/share/nginx/html/index.html:ro nginx:alpine >/tmp/brokenops-compose-proxy.log 2>&1 || true