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..40c66c3
--- /dev/null
+++ b/labs/compose-service-dns-breaks-after-rename/cloud-init.yaml
@@ -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
+
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/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
+ 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
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..1cd376e
--- /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 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
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..820be22
--- /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/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
new file mode 100755
index 0000000..39b3499
--- /dev/null
+++ b/labs/compose-service-dns-breaks-after-rename/verify.sh
@@ -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
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..bd3e177
--- /dev/null
+++ b/labs/docker-loopback-bind/verify.sh
@@ -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
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..490522a
--- /dev/null
+++ b/labs/docker-multi-stage-copy-path/verify.sh
@@ -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