Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions labs/compose-service-dns-breaks-after-rename/cloud-init.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#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
<h1>BrokenOps backend is alive</h1>
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/index.html
<h1>BrokenOps proxy is serving content</h1>
<p>Upstream host is configured separately.</p>
EOF
- |
cat <<'EOF' > /opt/brokenops/compose-service-dns-breaks-after-rename/proxy/Dockerfile
FROM python:3.11-alpine
WORKDIR /srv
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
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
- 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
16 changes: 16 additions & 0 deletions labs/compose-service-dns-breaks-after-rename/lab.yaml
Original file line number Diff line number Diff line change
@@ -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 keeps an outdated upstream hostname 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: true
11 changes: 11 additions & 0 deletions labs/compose-service-dns-breaks-after-rename/question.md
Original file line number Diff line number Diff line change
@@ -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/`
19 changes: 19 additions & 0 deletions labs/compose-service-dns-breaks-after-rename/solution.md
Original file line number Diff line number Diff line change
@@ -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.
4 changes: 4 additions & 0 deletions labs/compose-service-dns-breaks-after-rename/solution.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/bash
set -euo pipefail
cd /opt/brokenops/compose-service-dns-breaks-after-rename
sed -i 's/UPSTREAM_HOST=web/UPSTREAM_HOST=backend/' proxy/Dockerfile
10 changes: 10 additions & 0 deletions labs/compose-service-dns-breaks-after-rename/verify.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/bin/bash
set -euo pipefail
cd /opt/brokenops/compose-service-dns-breaks-after-rename
if grep -q '^ENV UPSTREAM_HOST=backend$' proxy/Dockerfile; then
echo "SUCCESS: Compose uses the updated backend hostname."
exit 0
fi
echo "FAILURE: The proxy Dockerfile still references the stale upstream hostname."
grep '^ENV UPSTREAM_HOST=' proxy/Dockerfile || true
exit 1
26 changes: 26 additions & 0 deletions labs/docker-entrypoint-flags/cloud-init.yaml
Original file line number Diff line number Diff line change
@@ -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
13 changes: 13 additions & 0 deletions labs/docker-entrypoint-flags/lab.yaml
Original file line number Diff line number Diff line change
@@ -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"
10 changes: 10 additions & 0 deletions labs/docker-entrypoint-flags/question.md
Original file line number Diff line number Diff line change
@@ -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 <image>`
- `docker inspect <image>`
19 changes: 19 additions & 0 deletions labs/docker-entrypoint-flags/solution.md
Original file line number Diff line number Diff line change
@@ -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.
4 changes: 4 additions & 0 deletions labs/docker-entrypoint-flags/solution.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/bash
set -euo pipefail
cd /opt/brokenops/docker-entrypoint-flags
sed -i 's/--bind/--host/' Dockerfile
11 changes: 11 additions & 0 deletions labs/docker-entrypoint-flags/verify.sh
Original file line number Diff line number Diff line change
@@ -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
24 changes: 24 additions & 0 deletions labs/docker-loopback-bind/cloud-init.yaml
Original file line number Diff line number Diff line change
@@ -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
16 changes: 16 additions & 0 deletions labs/docker-loopback-bind/lab.yaml
Original file line number Diff line number Diff line change
@@ -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
11 changes: 11 additions & 0 deletions labs/docker-loopback-bind/question.md
Original file line number Diff line number Diff line change
@@ -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 <image>`
- `curl http://127.0.0.1:8080/`
- `docker logs <container>`
19 changes: 19 additions & 0 deletions labs/docker-loopback-bind/solution.md
Original file line number Diff line number Diff line change
@@ -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.
4 changes: 4 additions & 0 deletions labs/docker-loopback-bind/solution.sh
Original file line number Diff line number Diff line change
@@ -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
14 changes: 14 additions & 0 deletions labs/docker-loopback-bind/verify.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/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
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
15 changes: 15 additions & 0 deletions labs/docker-multi-stage-copy-path/cloud-init.yaml
Original file line number Diff line number Diff line change
@@ -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
13 changes: 13 additions & 0 deletions labs/docker-multi-stage-copy-path/lab.yaml
Original file line number Diff line number Diff line change
@@ -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"
10 changes: 10 additions & 0 deletions labs/docker-multi-stage-copy-path/question.md
Original file line number Diff line number Diff line change
@@ -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 <image>`
- `sed -n '1,120p' Dockerfile`
19 changes: 19 additions & 0 deletions labs/docker-multi-stage-copy-path/solution.md
Original file line number Diff line number Diff line change
@@ -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.
4 changes: 4 additions & 0 deletions labs/docker-multi-stage-copy-path/solution.sh
Original file line number Diff line number Diff line change
@@ -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
13 changes: 13 additions & 0 deletions labs/docker-multi-stage-copy-path/verify.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/bin/bash
set -euo pipefail
cd /opt/brokenops/docker-multi-stage-copy-path
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
Loading