You are viewing a free preview of this lesson.
Subscribe to unlock all 10 lessons in this course and every other course on LearningBro.
Containers share the host kernel, so a misconfigured container can expose the entire host. This lesson covers the essential security practices every Docker user should follow — from running as non-root and using minimal base images, to scanning for vulnerabilities, managing secrets, and applying resource limits.
Container security operates at multiple layers:
+-----------------------------------------------+
| 1. Image Security |
| - Minimal base images |
| - No secrets in images |
| - Vulnerability scanning |
+-----------------------------------------------+
| 2. Build Security |
| - Trusted base images |
| - .dockerignore |
| - Multi-stage builds |
+-----------------------------------------------+
| 3. Runtime Security |
| - Non-root user |
| - Read-only filesystem |
| - Resource limits |
| - Dropped capabilities |
+-----------------------------------------------+
| 4. Network Security |
| - Network isolation |
| - Least-privilege port exposure |
+-----------------------------------------------+
| 5. Secrets Management |
| - Never bake secrets into images |
| - Use environment variables or secret mgmt |
+-----------------------------------------------+
By default, containers run as root — this is the single most common Docker security mistake.
FROM node:20-alpine
# Create a non-root user and group
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
COPY --chown=appuser:appgroup . .
RUN npm ci --production
# Switch to non-root user
USER appuser
EXPOSE 3000
CMD ["node", "server.js"]
# Override the user at runtime
docker run --user 1000:1000 my-app:latest
# Verify the running user
docker exec my-container whoami
# appuser (not root)
Why it matters: If an attacker exploits a vulnerability in your application, running as non-root limits the damage they can do — they cannot modify system files or escalate to host-level access as easily.
Prevent containers from writing to the filesystem. This blocks many attack vectors including malware installation.
# Run with a read-only root filesystem
docker run --read-only my-app:latest
# Allow specific writable directories with tmpfs
docker run --read-only \
--tmpfs /tmp:size=64m \
--tmpfs /app/cache:size=32m \
my-app:latest
services:
api:
image: my-api:latest
read_only: true
tmpfs:
- /tmp:size=64m
- /app/cache:size=32m
Without resource limits, a single container can consume all host resources — whether through a bug, a memory leak, or a denial-of-service attack.
# Limit memory and CPU
docker run -d \
--memory=512m \
--memory-swap=512m \
--cpus=1.5 \
my-app:latest
# Limit the number of processes (prevents fork bombs)
docker run --pids-limit 100 my-app:latest
services:
api:
image: my-api:latest
deploy:
resources:
limits:
cpus: "1.5"
memory: 512M
reservations:
cpus: "0.5"
memory: 256M
pids_limit: 100
Smaller images have fewer packages, which means a smaller attack surface.
| Base Image | Size | Packages | Use Case |
|---|---|---|---|
ubuntu:24.04 | ~77 MB | Many | Full OS environment |
debian:bookworm-slim | ~74 MB | Moderate | General purpose |
alpine:3.19 | ~7 MB | Minimal | Most containerised apps |
distroless | ~2-20 MB | Almost none | Production (no shell) |
scratch | 0 MB | None | Static binaries (Go, Rust) |
# Build stage
FROM node:20 AS builder
WORKDIR /app
COPY . .
RUN npm ci && npm run build
Subscribe to continue reading
Get full access to this lesson and all 10 lessons in this course.