8 lab Docker và Nginx
Bộ lab này chuyển kiến thức runtime, build, security, storage, network, Compose và reverse proxy thành evidence có thể kiểm tra. Với mỗi lab, lưu image digest, command đã chạy, thời gian, log trước/sau failure và bằng chứng recovery.
chmod 777, không nhúng secret vào image, và không coi “container đang Up” là đủ để kết luận service khỏe.Lab 01 · Engine lifecycle
Prerequisites
- Docker Engine/CLI hoạt động; có quyền chạy
docker info. - Một image nhỏ như
nginx:alpinehoặc image nội bộ tương đương.
Steps
- Pull image và lưu digest bằng
docker image inspect; kiểm traRepoDigests, config và layer metadata. - Tạo container nhưng chưa start; so sánh
docker psvàdocker ps -a. - Start container, lấy container PID bằng
docker inspect -f '{{.State.Pid}}', rồi đối chiếu PID đó với process trên host. - Inspect namespace/cgroup của process bằng công cụ host phù hợp như
ps,lsns,/proc/<pid>/nsvà/proc/<pid>/cgroup. - Stop, start, restart rồi remove container. Ghi lại container ID, PID cũ/mới và state transition.
docker pull nginx:alpine
docker image inspect nginx:alpine
docker create --name lab-engine -p 8080:80 nginx:alpine
docker start lab-engine
docker inspect -f '{{.State.Pid}} {{.State.Status}}' lab-engine
docker stop lab-engine
docker start lab-engine
docker restart lab-engine
docker rm -f lab-engine
Failure injection
Start một container với published port đã bị chiếm hoặc command cố tình exit non-zero. Phân biệt lỗi create/start với lỗi application runtime.
Verification & expected evidence
- Image digest và image ID được ghi lại.
- Container ID ổn định qua stop/start, nhưng PID có thể thay đổi sau restart.
- Có ảnh/log mapping container PID với host process, namespace và cgroup.
- Có exit code/error message của failure injection và giải thích lifecycle stage bị lỗi.
Lab 02 · BuildKit cache
Prerequisites
- Một Maven/Spring Boot project nhỏ với
pom.xmlvà source Java. - BuildKit/buildx khả dụng.
Steps
- Viết Dockerfile multi-stage: stage Maven build artifact, stage runtime chỉ chứa JRE + JAR cần thiết.
- Tách copy
pom.xmlkhỏi source để dependency-resolution layer có thể reuse. - Thêm cache mount cho Maven repository và
.dockerignoređể loại.git,target, log và file tạm. - Đo ít nhất bốn lần: cold build; warm build không đổi; đổi một file source; đổi
pom.xml. Ghi layer nào cache hit/miss và thời gian. - Thử build secret bằng
RUN --mount=type=secret; xác nhận secret không xuất hiện trong final image/history. - Dùng
docker buildx imagetools inspecthoặc metadata tương đương để quan sát platform manifest nếu build multi-platform.
# syntax=docker/dockerfile:1
FROM maven:3-eclipse-temurin-21 AS build
WORKDIR /src
COPY pom.xml .
RUN --mount=type=cache,target=/root/.m2 mvn -q -DskipTests dependency:go-offline
COPY src ./src
RUN --mount=type=cache,target=/root/.m2 mvn -q -DskipTests package
FROM eclipse-temurin:21-jre
WORKDIR /app
COPY --from=build /src/target/*.jar app.jar
USER 10001:10001
ENTRYPOINT ["java","-jar","/app/app.jar"]
Failure injection
Đặt COPY . . quá sớm rồi thay một file không liên quan; quan sát cache bị invalidate rộng. Sau đó sửa Dockerfile để dependency layer ổn định hơn và đo lại.
Verification & expected evidence
- Bảng timing cold/warm/source-change/POM-change.
- Build log cho thấy cache hit/miss.
- Final image không chứa Maven cache/source không cần thiết.
- Secret không xuất hiện trong
docker historyhoặc filesystem final image.
Lab 03 · Non-root hardening
Prerequisites
- Một HTTP service/container tự build.
- Có thể sửa Dockerfile và command run.
Steps
- Tạo user/group cố định, ví dụ UID/GID
10001; copy artifact với ownership phù hợp và đặtUSER 10001:10001. - Run với
--read-only; mounttmpfschỉ cho path thực sự cần ghi. - Drop capabilities mặc định bằng
--cap-drop=ALL, chỉ add capability nếu có evidence bắt buộc. - Áp dụng
--pids-limit, memory và CPU limits; quan sát metrics/stats khi load. - Xuất SBOM/scan bằng công cụ có sẵn trong môi trường và lưu report.
docker run --rm --name lab-sec \
--read-only --tmpfs /tmp:rw,noexec,nosuid,size=64m \
--cap-drop=ALL --pids-limit=128 --memory=256m --cpus=1 \
-p 8080:8080 myapp:lab
Failure injection
Cho app ghi vào thư mục rootfs không writable hoặc mount volume có owner sai UID. Quan sát Permission denied. Sửa bằng ownership/UID/mount path đúng; không dùng chmod 777.
Verification & expected evidence
idtrong container chứng minh không chạy root.- Ghi trái phép vào rootfs thất bại, còn path tmpfs được phép ghi hoạt động.
- Capabilities, PID và resource limits được inspect lại từ container config.
- Có scan/SBOM artifact và danh sách finding quan trọng nếu có.
Lab 04 · Storage recovery
Steps
- Ghi cùng một file test vào bốn kiểu storage: writable layer, bind mount, named volume và tmpfs.
- Remove/recreate container; ghi lại dữ liệu nào còn, dữ liệu nào mất và ownership trên host/volume.
- Với một database hoặc app state nhỏ, tạo backup khi ứng dụng ở trạng thái consistent: stop/quiesce hoặc dùng cơ chế snapshot/backup chính thức của ứng dụng.
- Xóa/recreate volume test, restore backup, khởi động app và kiểm tra checksum/record count.
docker volume create lab-data
docker run --rm -v lab-data:/data alpine sh -c 'date > /data/evidence.txt'
docker run --rm -v lab-data:/data alpine cat /data/evidence.txt
Failure injection
Thực hiện backup trong lúc workload đang ghi liên tục rồi so sánh với backup đã quiesce. Nếu app có nhiều file/state, ghi nhận nguy cơ crash-consistent nhưng không application-consistent.
Verification & expected evidence
- Ma trận persistence của bốn storage types.
- Archive/checksum trước và sau restore.
- Record count hoặc functional check sau recovery.
- RPO/RTO đo được cho lab và giới hạn của phương pháp backup.
Lab 05 · Network forensic
Steps
- Tạo user-defined bridge và chạy
api+clientcùng network. Dùng service/container name để gọi qua DNS nội bộ. - Inspect network, IP, aliases và published ports. So sánh container port với host published port.
- Kiểm tra listener trong container bằng
ss -lntphoặc tool tương đương; xác nhận app bind0.0.0.0khi cần nhận kết nối từ container khác. - Thực hiện debug theo tầng: DNS → route/network membership → listener/bind address → application response → published port/firewall từ host.
docker network create lab-net
docker run -d --name api --network lab-net my-api:lab
docker run --rm --network lab-net curlimages/curl:latest http://api:8080/health
docker network inspect lab-net
Failure injection
- Cho API bind chỉ
127.0.0.1và thử gọi từ container khác. - Gọi
localhosttừ client container với kỳ vọng sai rằng nó trỏ tới API. - Dùng sai service name/DNS; bỏ published port rồi thử gọi từ host.
Verification & expected evidence
- Network inspect và DNS resolution.
- Listener chứng minh wrong bind trước/sau fix.
- Curl output từ đúng namespace.
- Bảng “symptom → layer → command → root cause”.
Lab 06 · Compose full stack
Prerequisites
- Spring Boot app có endpoint health và dùng PostgreSQL/Redis.
- Docker Compose hiện đại.
Steps
- Tạo Compose file gồm
app,db,redis; chỉ publish port thật sự cần cho host. - Đặt DB/Redis trên internal network, app trên network phù hợp; dùng named volume cho PostgreSQL.
- Thêm healthcheck thực sự phản ánh readiness dependency. Nếu dùng
depends_oncondition, vẫn giữ retry/backoff trong app vì runtime failure có thể xảy ra sau startup. - Tách profile cho debug/admin tooling; không bật mặc định production-like path.
- Cấp secret qua environment/file secret của môi trường lab, không bake vào image hoặc commit plaintext.
services:
app:
build: .
depends_on:
db:
condition: service_healthy
restart: unless-stopped
networks: [appnet]
db:
image: postgres:17
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 3s
retries: 12
volumes: [pgdata:/var/lib/postgresql/data]
networks: [appnet]
volumes:
pgdata:
networks:
appnet:
Failure injection
Stop PostgreSQL sau khi stack đã healthy; quan sát app behavior. Khởi động lại DB và xác nhận connection pool/retry phục hồi mà không cần rebuild image. Sau đó thử sai credential và phân biệt authentication failure với readiness delay.
Verification & expected evidence
docker compose psvà health state của từng service.- Data DB tồn tại sau
compose down+ recreate nếu không xóa volume. - Log retry có bounded backoff, không spin loop.
- Không có secret trong image history hoặc source repository.
Lab 07 · PID 1/SIGTERM
Steps
- Tạo hai image/container: shell form và exec form ENTRYPOINT/CMD. Inspect process tree để xem process nào là PID 1.
- Thêm shutdown hook trong app để log thời điểm nhận SIGTERM và hoàn thành graceful shutdown.
- Gửi request chậm/in-flight, sau đó chạy
docker stop; đo thời gian từ SIGTERM đến app exit và request có hoàn tất hay bị cắt. - Thử stop timeout ngắn hơn thời gian drain; quan sát escalation sang SIGKILL và exit code.
- Nếu app spawn child process, tạo case child exit và kiểm tra zombie/reaping. Dùng init nhỏ khi workload thực sự cần reaping/forward signal.
docker stop --time 20 lab-app
docker inspect -f '{{.State.ExitCode}} {{.State.FinishedAt}}' lab-app
Failure injection
Dùng shell wrapper không exec tiến trình Java/Nginx rồi stop container. Quan sát signal có tới đúng process hay không. Sau đó sửa wrapper dùng exec "$@" hoặc exec-form ENTRYPOINT.
Verification & expected evidence
- Process tree trước/sau fix.
- Timestamp nhận SIGTERM, bắt đầu drain, hoàn tất drain và exit.
- Phân biệt exit 143 (SIGTERM thường gặp) với 137 (SIGKILL/OOM hoặc kill -9 tùy context).
- Evidence request in-flight được hoàn tất khi timeout đủ dài.
Lab 08 · React/Nginx incident
Steps
- Build React bằng Node stage, copy artifact tĩnh sang Nginx runtime stage; không mang
node_modulesvào final image. - Chạy Nginx non-root trên unprivileged port; đảm bảo cache/static/temp paths có quyền phù hợp.
- Cấu hình SPA fallback: route client-side hợp lệ trả
index.html, nhưng asset bị thiếu vẫn phải có tín hiệu 404 phù hợp thay vì che mọi lỗi. - Thiết lập cache policy: asset fingerprinted có cache dài/immutable; HTML shell cache ngắn hoặc revalidate để tránh giữ release cũ.
- Reverse proxy
/api/tới backend; log upstream status, request time và upstream response time.
location / {
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://api:8080/;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 30s;
}
Incident drills
| Symptom | Failure injection | Evidence cần thu | Recovery |
|---|---|---|---|
| 403 | Sai permission/owner static file hoặc directory traversal permission | Nginx error log + ls -ln | Sửa ownership/mode tối thiểu cần thiết |
| 404 | Asset path sai hoặc thiếu SPA fallback | Access log URI/status + filesystem path | Sửa build path/location/try_files |
| 502 | Backend down, wrong host/port, connection refused | Error log upstream connect + backend health | Khôi phục backend/config DNS/port |
| 504 | Backend chậm hơn proxy timeout | request_time, upstream_response_time | Fix latency/root cause; chỉ tăng timeout khi có SLO rõ |
| Wrong scheme/header | Thiếu hoặc sai forwarded headers sau TLS termination | Request headers + app generated redirects | Chuẩn hóa trusted proxy/header chain |
Verification & expected evidence
- Final image chỉ chứa runtime/static artifact cần thiết.
- Nginx master/worker không chạy root nếu lab image được thiết kế non-root.
- Evidence cho đủ 403/404/502/504, gồm request, access/error log và fix.
- Cache headers của HTML shell và fingerprinted assets đúng policy.
- Rollback: có thể chuyển lại image digest/tag trước và xác nhận static/API traffic phục hồi.
Rubric
| Mức | Tiêu chí | Evidence tối thiểu |
|---|---|---|
| 1 | Images/Compose chạy happy path. | Build/run commands, health/output cơ bản, image digest. |
| 2 | Repeatable, non-root, persistent và graceful. | Recreate/restore, fixed UID, resource/storage config, SIGTERM drain và failure/recovery logs. |
| 3 · Senior | Giải thích runtime internals, security và failure evidence. | Namespace/cgroup/process reasoning; cache/security trade-off; forensic signal; measured RPO/RTO/timeout/capacity; rollback được kiểm thử. |
Tài liệu tham khảo
Docker Docs · Build secrets
Docker Docs · Building best practices
Docker Docs · Dockerfile reference
Docker Docs · Restart policies
Docker Docs · Volumes
Docker Docs · Bridge network driver
Docker Docs · Compose startup order
NGINX · HTTP proxy module
NGINX · Error log