30 Days of DevOps | Day 10/30 — Docker & Containerization
A complete container security toolkit: a side-by-side vulnerable vs secure Dockerfile comparison, a 10-check automated security audit script, live hardening demos (read-only FS, capability dropping, non-root users), a restrictive seccomp profile, and Trivy CVE scanning integration.
1. Non-root user — adduser + USER instruction
2. Read-only filesystem — --read-only + --tmpfs /tmp
3. Capability dropping — --cap-drop=ALL + add only what's needed
4. No privilege escalation — --no-new-privileges
5. Hardcoded secrets — detect in ENV vars and Dockerfile
6. Image size — Alpine, multi-stage, layer optimization
7. Health checks — HEALTHCHECK instruction
8. Base image pinning — exact version or @sha256 digest
9. Trivy CVE scanning — HIGH/CRITICAL vulnerability detection
10. seccomp profile — restrict syscall surface
day-10-container-security/
├── sample-apps/
│ ├── vulnerable-app/
│ │ └── Dockerfile.vulnerable # ← What NOT to do (annotated)
│ └── secure-app/
│ ├── Dockerfile.secure # ← Best practices (annotated)
│ ├── .dockerignore # ← Excludes .env, .git, tests
│ └── src/
│ ├── index.js
│ └── package.json
├── scripts/
│ ├── audit.sh # 10-check security audit
│ └── harden_demo.sh # live before/after demos
├── tests/
│ └── test_security.sh # 15 automated tests
├── configs/
│ └── seccomp-profile.json # restrictive syscall allowlist
├── docker-compose.yml # secure service config
├── docs/
│ └── checklist.md # security checklist
├── .gitignore
└── README.md
git clone https://github.com/YOUR_USERNAME/day-10-container-security.git
cd day-10-container-security
chmod +x scripts/*.sh tests/*.sh
# Run pre-flight tests
bash tests/test_security.sh
# Run hardening demo (side-by-side secure vs insecure)
bash scripts/harden_demo.sh alpine:latest
# Run security audit on any image
bash scripts/audit.sh nginx:alpine
bash scripts/audit.sh ubuntu:latest
bash scripts/audit.sh node:18-alpine
# Audit your own image with Dockerfile check
bash scripts/audit.sh myimage:latest sample-apps/secure-app/Dockerfile.secure
# Build and audit the secure app
docker build -t day10-secure:latest \
-f sample-apps/secure-app/Dockerfile.secure \
sample-apps/secure-app/
bash scripts/audit.sh day10-secure:latest sample-apps/secure-app/Dockerfile.secure| Issue | Vulnerable | Secure |
|---|---|---|
| Base image | ubuntu:latest |
node:18.19-alpine3.18 |
| Base tag | :latest (unpinned) |
Exact version |
| User | root | appuser (UID 1001) |
| Secrets | ENV DB_PASSWORD=secret |
Injected at runtime |
| COPY | COPY . . (everything) |
Only src/ |
| CMD form | Shell form | Exec form ["node","..."] |
| HEALTHCHECK | None | Configured |
| .dockerignore | None | Excludes .env, .git, tests |
| Build stages | Single | Multi-stage (no devDeps) |
docker run \
--read-only \ # read-only root filesystem
--tmpfs /tmp:rw,size=64m \ # writable RAM-only /tmp
--user 1001:1001 \ # non-root user
--cap-drop=ALL \ # drop all Linux capabilities
--cap-add=NET_BIND_SERVICE \ # add back only what's needed
--no-new-privileges \ # no privilege escalation
--security-opt no-new-privileges:true \
--security-opt seccomp=configs/seccomp-profile.json \
--memory=256m \ # max 256MB RAM
--cpus=1.0 \ # max 1 CPU core
--pids-limit=100 \ # max 100 processes
--network my-app-net \ # specific network
-e DB_PASSWORD="${DB_PASSWORD}" \ # secret at runtime only
-p 8080:3000 \ # unprivileged internal port
day10-secure:latest# Install Trivy
brew install trivy # macOS
apt install trivy # Debian/Ubuntu
# Or via Docker (no install)
docker run aquasec/trivy image nginx:alpine
docker run aquasec/trivy image ubuntu:latest
# Scan for HIGH + CRITICAL only
trivy image --severity HIGH,CRITICAL nginx:alpine
# Scan Dockerfile for misconfigurations
trivy config sample-apps/secure-app/Dockerfile.secure- Non-root USER in Dockerfile
- Pinned base image (not
:latest) - Alpine or slim base (minimal attack surface)
- Multi-stage build (no devDeps in final image)
-
.dockerignoreexcludes.env,.git,node_modules - No
ENVsecrets in Dockerfile -
HEALTHCHECKconfigured - CMD uses exec form (
["node","..."]) -
--read-onlyat runtime -
--cap-drop=ALLat runtime -
--no-new-privilegesat runtime - Resource limits (--memory, --cpus)
- Trivy scan passing (no HIGH/CRITICAL)
- seccomp profile applied
- Specific Docker network (not default bridge)
| Day | Topic |
|---|---|
| Days 6–9 | ✅ Docker Fundamentals |
| Day 10 | ✅ Container Security Hardening (this project) |
| Day 11 | GitHub Actions CI Pipeline |
MIT — free to use, modify, and distribute.