Lab 02 — Build Minimal and Secure Container Images
Mission Information
Section titled “Mission Information”| Item | Details |
|---|---|
| Lab ID | K8S-IMAGE-SECURITY-LAB-02 |
| Difficulty | Intermediate to Advanced |
| Estimated Time | 4–5 Hours |
| Environment | Local Docker Environment / Kubernetes Training Cluster |
| Platform | Docker Desktop, Podman, Amazon ECR, Azure ACR, Google Artifact Registry |
| Cost | Free (Local) / Cloud Registry Charges May Apply |
| Primary Role | DevSecOps Engineer |
| Supporting Roles | Kubernetes Security Engineer, Platform Engineer, Cloud Security Engineer |
| Module | Kubernetes Container Image & Supply Chain Security |
| Previous Lab | Lab 01 — Scan Container Images |
| Next Lab | Lab 03 — Secure Private Container Registries |
Mission Scenario
Section titled “Mission Scenario”CloudNova Technologies has modernised its Kubernetes platform and introduced mandatory container image scanning before deployment.
Although image scanning has reduced deployment risk, the security team discovered another major issue.
Many development teams are still building containers that include:
- Full Linux distributions
- Development tools
- Package managers
- Shell utilities
- Compilers
- Unnecessary libraries
- Root users
- Large attack surfaces
- Excessive image sizes
- Poor Dockerfile practices
During a Red Team assessment, attackers successfully exploited an application vulnerability.
Because the container image contained unnecessary tools, the attacker was able to:
- Download additional payloads
- Install new packages
- Execute shells
- Explore the container filesystem
- Increase persistence opportunities
The investigation concluded that although the application vulnerability was the initial entry point, the oversized container image significantly increased the attack surface.
The CISO has mandated that all future Kubernetes workloads must use hardened production images that follow enterprise container security standards.
Your mission is to redesign an insecure container image into a production-ready hardened image suitable for Kubernetes deployment.
Learning Objectives
Section titled “Learning Objectives”By completing this lab, you will learn how to:
- Understand secure container image design
- Build production-ready Dockerfiles
- Reduce container attack surface
- Use minimal base images
- Implement multi-stage builds
- Remove unnecessary build dependencies
- Configure non-root users
- Configure file permissions
- Protect secrets during image builds
- Optimise image size
- Generate deterministic builds
- Pin image versions
- Use immutable image digests
- Scan hardened images
- Compare insecure and secure images
- Produce an enterprise image hardening assessment
Enterprise Container Build Pipeline
Section titled “Enterprise Container Build Pipeline”Developer
│
▼
Source Code
│
▼
Multi-Stage Build
│
▼
Minimal Runtime Image
│
▼
Image Security Scan
│
▼
SBOM Generation
│
▼
Image Signing
│
▼
Private Registry
│
▼
Kubernetes DeploymentEnterprise Image Hardening Model
Section titled “Enterprise Image Hardening Model”Trusted Base Image
│
▼
Minimal Packages
│
▼
Multi-Stage Build
│
▼
Non-Root User
│
▼
Least Privilege
│
▼
Image Scan
│
▼
SBOM
│
▼
Image Signing
│
▼
DeploymentLab Outcomes
Section titled “Lab Outcomes”By the end of this lab, you will have:
- Built an insecure image
- Built a hardened production image
- Implemented multi-stage builds
- Removed unnecessary packages
- Reduced image size
- Configured non-root execution
- Hardened file permissions
- Compared image layers
- Compared attack surfaces
- Scanned both images
- Generated SBOMs
- Produced an enterprise image hardening report
Prerequisites
Section titled “Prerequisites”Before beginning ensure you have:
- Docker Desktop or Docker Engine
- Trivy
- Git
- Visual Studio Code
- Internet connectivity
- Kubernetes cluster (optional)
- Completion of Lab 01
Tools Used
Section titled “Tools Used”| Tool | Purpose |
|---|---|
| Docker | Build images |
| Trivy | Vulnerability scanning |
| Visual Studio Code | Dockerfile development |
| Git Bash / PowerShell | CLI |
| jq | JSON processing |
| Syft | SBOM generation (optional) |
Recommended Lab Structure
Section titled “Recommended Lab Structure”lab-02-build-secure-images/
├── app/│ ├── app.py│ ├── requirements.txt│ └── Dockerfile.insecure│├── secure/│ ├── Dockerfile.secure│ └── .dockerignore│├── reports/│ ├── insecure-scan.json│ ├── secure-scan.json│ ├── comparison.md│ ├── sbom.json│ └── build-report.md│└── evidence/Task 01 — Review the Insecure Dockerfile
Section titled “Task 01 — Review the Insecure Dockerfile”Create:
Dockerfile.insecureExample:
FROM python:3.11
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
EXPOSE 5000
CMD ["python","app.py"]Review problems.
Identify:
- Large base image
- Root user
- No version pinning
- No multi-stage build
- Package manager retained
- Entire build context copied
- Large attack surface
Task 02 — Build the Insecure Image
Section titled “Task 02 — Build the Insecure Image”docker build \-t insecure-demo:v1 \-f Dockerfile.insecure .Verify.
docker imagesTask 03 — Run the Insecure Container
Section titled “Task 03 — Run the Insecure Container”docker run \-it \--rm \insecure-demo:v1Inside.
idExpected.
uid=0(root)Review.
whoamiConfirm.
Application runs as root.
Task 04 — Review Image Size
Section titled “Task 04 — Review Image Size”docker imagesRecord.
| Image | Size |
|---|---|
| insecure-demo |
Task 05 — Review Image Layers
Section titled “Task 05 — Review Image Layers”docker history \insecure-demo:v1Review.
- Layer count
- Layer size
- Package installation
- Build commands
Task 06 — Scan the Insecure Image
Section titled “Task 06 — Scan the Insecure Image”trivy image \insecure-demo:v1Export.
trivy image \--format json \--output reports/insecure-scan.json \insecure-demo:v1Record:
- Critical
- High
- Medium
- Low
Task 07 — Create a Multi-Stage Dockerfile
Section titled “Task 07 — Create a Multi-Stage Dockerfile”Create.
Dockerfile.secureExample.
FROM python:3.11-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install \--no-cache-dir \-r requirements.txt
COPY . .
FROM python:3.11-slim
WORKDIR /app
COPY --from=builder /usr/local /usr/local
COPY --from=builder /app .
CMD ["python","app.py"]Review.
- Build stage
- Runtime stage
- Reduced layers
- Smaller runtime
Task 08 — Add a Non-Root User
Section titled “Task 08 — Add a Non-Root User”Update.
RUN useradd \-r \-u 10001 \appuserSwitch.
USER appuserBuild.
docker build \-t secure-demo:v1 \-f Dockerfile.secure .Task 09 — Verify Runtime User
Section titled “Task 09 — Verify Runtime User”docker run \-it \--rm \secure-demo:v1 idExpected.
uid=10001Confirm.
Application no longer runs as root.
Task 10 — Remove Package Cache
Section titled “Task 10 — Remove Package Cache”Review.
pip install \--no-cache-dirBenefits.
- Smaller image
- Less attack surface
Task 11 — Add .dockerignore
Section titled “Task 11 — Add .dockerignore”Create.
.dockerignoreExample.
.git
.env
.vscode
__pycache__
*.log
*.pem
*.keyExplain.
Sensitive files should never enter build context.
Task 12 — Protect Secrets During Build
Section titled “Task 12 — Protect Secrets During Build”Review insecure example.
COPY .env .Never do this.
Instead use:
- Kubernetes Secrets
- BuildKit Secrets
- Vault
- External Secrets Operator
Task 13 — Pin Base Image Version
Section titled “Task 13 — Pin Base Image Version”Avoid.
FROM python:latestUse.
FROM python:3.11.10-slimBetter.
FROM python@sha256:<digest>Task 14 — Configure File Ownership
Section titled “Task 14 — Configure File Ownership”COPY \--chown=appuser:appuser \. .Validate ownership.
Task 15 — Reduce Image Layers
Section titled “Task 15 — Reduce Image Layers”Combine commands.
Instead of.
RUN apt update
RUN apt install
RUN rmUse.
RUN apt update && \apt install -y curl && \rm -rf /var/lib/apt/lists/*Task 16 — Review Package Manager
Section titled “Task 16 — Review Package Manager”Review.
apt
apk
yumProduction images should avoid retaining unnecessary package managers whenever practical.
Task 17 — Build the Hardened Image
Section titled “Task 17 — Build the Hardened Image”docker build \-t secure-demo:v2 \-f Dockerfile.secure .Verify.
docker imagesTask 18 — Compare Image Size
Section titled “Task 18 — Compare Image Size”Record.
| Image | Size |
|---|---|
| insecure-demo | |
| secure-demo |
Discuss.
- Reduced size
- Faster downloads
- Lower attack surface
Task 19 — Compare Layers
Section titled “Task 19 — Compare Layers”docker history \secure-demo:v2Compare.
- Layer count
- Image size
- Build steps
Task 20 — Scan Hardened Image
Section titled “Task 20 — Scan Hardened Image”trivy image \secure-demo:v2Export.
trivy image \--format json \--output reports/secure-scan.json \secure-demo:v2Compare findings.
Task 21 — Compare Scan Results
Section titled “Task 21 — Compare Scan Results”Document.
| Finding | Insecure | Secure |
|---|---|---|
| Critical | ||
| High | ||
| Medium | ||
| Image Size | ||
| Runtime User | ||
| Layer Count |
Task 22 — Generate SBOM
Section titled “Task 22 — Generate SBOM”trivy image \--format cyclonedx \--output reports/sbom.json \secure-demo:v2Review components.
Task 23 — Review Runtime User
Section titled “Task 23 — Review Runtime User”docker inspect \secure-demo:v2Confirm.
User:
10001Task 24 — Review Image Metadata
Section titled “Task 24 — Review Image Metadata”Inspect.
docker inspect \secure-demo:v2Review.
- Labels
- Entrypoint
- User
- Exposed ports
Task 25 — Compare Attack Surface
Section titled “Task 25 — Compare Attack Surface”Compare.
| Component | Insecure | Secure |
|---|---|---|
| Root User | ✔ | ✘ |
| Multi-stage | ✘ | ✔ |
| Minimal Image | ✘ | ✔ |
| Cache Removed | ✘ | ✔ |
| Package Manager | ✔ | Reduced |
| Smaller Size | ✘ | ✔ |
Task 26 — Prepare Kubernetes Deployment
Section titled “Task 26 — Prepare Kubernetes Deployment”Use immutable digest.
Example.
image:python@sha256:<digest>Avoid.
python:latestTask 27 — Enterprise Security Assessment
Section titled “Task 27 — Enterprise Security Assessment”Review.
- Base image
- User
- Packages
- Layers
- Size
- Vulnerabilities
- SBOM
- Image digest
Assign.
- Approved
- Conditional
- Rejected
Task 28 — Produce Image Hardening Report
Section titled “Task 28 — Produce Image Hardening Report”Create.
build-report.mdInclude.
- Image comparison
- Vulnerabilities
- Improvements
- Image size reduction
- Runtime user
- Deployment recommendation
Task 29 — Evidence Collection
Section titled “Task 29 — Evidence Collection”Collect.
- Dockerfiles
- Image history
- Scan reports
- Image sizes
- Runtime UID
- SBOM
- Build report
Task 30 — Cleanup
Section titled “Task 30 — Cleanup”docker image rm \insecure-demo:v1docker image rm \secure-demo:v2Review.
docker imagesEnterprise Secure Image Checklist
Section titled “Enterprise Secure Image Checklist”| Control | Status |
|---|---|
| Trusted base image | ☐ |
| Minimal runtime | ☐ |
| Multi-stage build | ☐ |
| Non-root user | ☐ |
| No secrets | ☐ |
| Small attack surface | ☐ |
| SBOM generated | ☐ |
| Image scanned | ☐ |
| Image digest recorded | ☐ |
| Deployment approved | ☐ |
Risk Classification
Section titled “Risk Classification”Critical
Section titled “Critical”- Root user
- Embedded secrets
- Malware
- Unsupported base image
- Large attack surface
- High vulnerabilities
- Mutable tags
- Missing SBOM
Medium
Section titled “Medium”- Missing labels
- Large image
- Missing digest
- Documentation
- Metadata
- Naming
Skills Developed
Section titled “Skills Developed”After completing this lab you will be able to:
- Build production-ready container images
- Implement multi-stage builds
- Configure non-root execution
- Reduce attack surface
- Optimise Dockerfiles
- Generate SBOMs
- Compare image layers
- Scan hardened images
- Prepare images for Kubernetes deployment
Knowledge Check
Section titled “Knowledge Check”Question 1
Section titled “Question 1”Why are multi-stage builds recommended?
Answer: They remove unnecessary build tools and reduce the size and attack surface of the final runtime image.
Question 2
Section titled “Question 2”Why should production containers avoid running as root?
Answer: Running as a non-root user limits the impact of a compromise by reducing the privileges available to an attacker.
Question 3
Section titled “Question 3”Why should .dockerignore include .env and key files?
Answer: To prevent sensitive files such as environment variables and private keys from being copied into the build context or image.
Question 4
Section titled “Question 4”Why should images be referenced by digest instead of latest?
Answer: A digest is immutable and guarantees that Kubernetes deploys the exact approved image, whereas tags like latest can change over time.
Question 5
Section titled “Question 5”What is the primary security benefit of using a minimal base image?
Answer: Fewer installed packages reduce the attack surface, decrease the number of vulnerabilities, and simplify patch management.
Lab Summary
Section titled “Lab Summary”In this lab, you transformed an insecure development container into a hardened, production-ready image by applying enterprise DevSecOps best practices. You built a multi-stage Docker image, removed unnecessary build artifacts, configured a non-root runtime user, reduced the image size, protected build secrets, generated an SBOM, scanned the final image for vulnerabilities, and validated it for Kubernetes deployment.
You also compared insecure and hardened images to understand how Dockerfile design directly affects security, operational efficiency, and compliance.
These practices form a critical part of Kubernetes supply chain security and provide the foundation for secure image distribution through trusted container registries.
What’s Next?
Section titled “What’s Next?”Next Lab: Lab 03 — Secure Private Container Registries
In the next lab, you will secure enterprise container registries by configuring repository access controls, enforcing immutable image tags, enabling vulnerability scanning, implementing image-signing workflows, applying registry lifecycle policies, and integrating secure registries with Kubernetes deployments.