🐳 Docker Security Essentials & Best Practices
Docker makes shipping applications easy, but insecure containers can quickly become a serious risk. This edition breaks down the essential security practices every developer should know to build, run, and deploy containers safely without unnecessary complexity.
🎯 Why Docker Security Matters
Think of Docker containers like apartments in a building:
🏢 Your Server (Building)
├─ 🏠 Container 1 (Apartment A)
├─ 🏠 Container 2 (Apartment B)
└─ 🏠 Container 3 (Apartment C)
The Problem: If one apartment has weak security, burglars can:
The Solution: Proper security = Safe building for everyone!
🚨 Common Security Risks
1. Running as Root 👑
The Risk:
❌ Bad Practice:
Container runs as root user
├─ Full system access
├─ Can modify anything
└─ If hacked = Game Over!
Real Example usually Everyone does:
# ❌ DANGEROUS
FROM ubuntu
COPY app.py /app/
CMD ["python", "/app/app.py"]
# Runs as root by default!
The Fix:
# ✅ SAFE
FROM ubuntu
RUN useradd -m appuser
COPY app.py /app/
USER appuser
CMD ["python", "/app/app.py"]
# Now runs as regular user!
Why This Matters:
2. Outdated Base Images 📦
The Risk:
❌ Using old images:
FROM ubuntu:18.04
├─ 100+ known vulnerabilities
├─ No security patches
└─ Easy target for hackers
The Fix:
# ❌ BAD
FROM ubuntu:18.04
# ✅ GOOD
FROM ubuntu:22.04
# 🎯 BEST
FROM ubuntu:22.04
RUN apt-get update && \
apt-get upgrade -y
Quick Tip: 📱 Always use latest stable versions!
3. Exposed Secrets 🔑
The Risk:
# ❌ NEVER DO THIS!
FROM node:18
ENV DB_PASSWORD=secret123
ENV API_KEY=abc-xyz-789
COPY . /app
Anyone can see these:
docker inspect container_name
# Shows all your secrets❗️
The Fix:
# ✅ USE ENVIRONMENT FILES
FROM node:18
COPY . /app
# Pass secrets at runtime
Run with secrets:
# Using .env file
docker run --env-file .env myapp
# Or pass directly
docker run -e DB_PASSWORD=secret123 myapp
Even Better - Docker Secrets:
# Create secret
echo "secret123" | docker secret create db_pass -
# Use in container
docker service create \
--secret db_pass \
myapp
🛡️ Essential Best Practices
1. Use Official Images ✅
🎯 Priority Order:
1. Official Images (Best)
├─ FROM node:18
├─ FROM python:3.11
└─ FROM nginx:latest
2. Verified Publishers (Good)
└─ Docker Hub verified badge ✓
3. Random Images (Risky)
└─ FROM someuser/randomimage ⚠️
Why?
2. Minimize Image Size 📦
Large Image = More Vulnerabilities
# ❌ BAD (1.2 GB)
FROM ubuntu
RUN apt-get update && \
apt-get install -y python3 \
build-essential \
wget curl git
COPY app.py /app/
# ✅ BETTER (300 MB)
FROM python:3.11
COPY app.py /app/
# 🎯 BEST (50 MB)
FROM python:3.11-slim
COPY app.py /app/
Why Slim Images?
3. Multi-Stage Builds 🏗️
The Problem: Build tools in production = unnecessary risk!
The Solution:
# 🎯 SMART APPROACH
# Stage 1: Build
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# Stage 2: Production
FROM node:18-slim
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
USER node
CMD ["node", "dist/server.js"]
Result:
Builder stage: 1GB (discarded)
Final image: 200MB ✅
No build tools in production ✅
4. Scan for Vulnerabilities 🔍
Built-in Docker Scan:
# Scan any image
docker scan myapp:latest
# Example output:
✗ High severity vulnerability found
Package: openssl
Version: 1.1.1
Fix: Upgrade to 1.1.1n
Other Tools:
# Trivy (Popular & Free)
trivy image myapp:latest
# Snyk
snyk container test myapp:latest
# Clair
clairctl analyze myapp:latest
What to Look For:
5. Limit Container Capabilities 🔒
By Default, Containers Can Do Too Much!
# ❌ DANGEROUS (Full capabilities)
docker run myapp
# ✅ SAFE (Drop all, add only needed)
docker run \
--cap-drop=ALL \
--cap-add=NET_BIND_SERVICE \
myapp
Common Safe Capabilities:
Recommended by LinkedIn
# Web server needs port binding
--cap-add=NET_BIND_SERVICE
# App needs to change ownership
--cap-add=CHOWN
# Nothing else needed?
--cap-drop=ALL
6. Use Read-Only Filesystems 📖
# ❌ Container can modify everything
docker run myapp
# ✅ Container can't modify files
docker run --read-only myapp
# 🎯 With temp directory for logs
docker run \
--read-only \
--tmpfs /tmp \
myapp
Why?
7. Network Security 🌐
Isolate Containers:
# Create isolated network
docker network create app-network
# Put containers in same network
docker run --network app-network \
--name database postgres
docker run --network app-network \
--name webapp myapp
# webapp can talk to database
# External world cannot!
Don't Expose Unnecessary Ports:
# ❌ BAD (Exposes to world)
docker run -p 5432:5432 postgres
# ✅ GOOD (No external access)
docker run --name db postgres
# ✅ BETTER (Specific interface)
docker run -p 127.0.0.1:5432:5432 postgres
8. Resource Limits 💾
Prevent Resource Exhaustion:
# Set memory limit
docker run -m 512m myapp
# Set CPU limit
docker run --cpus="1.5" myapp
# Both together
docker run \
-m 512m \
--cpus="1.5" \
myapp
In Docker Compose:
services:
webapp:
image: myapp
deploy:
resources:
limits:
cpus: '1'
memory: 512M
reservations:
cpus: '0.5'
memory: 256M
Why This Matters:
🔐 Dockerfile Security Checklist
✅ Complete Secure Dockerfile Example
# 1. Use official slim base image
FROM python:3.11-slim AS base
# 2. Update and patch
RUN apt-get update && \
apt-get upgrade -y && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# 3. Create non-root user
RUN useradd -m -u 1000 appuser && \
mkdir -p /app && \
chown appuser:appuser /app
# 4. Set working directory
WORKDIR /app
# 5. Copy only requirements first (caching)
COPY --chown=appuser:appuser requirements.txt .
# 6. Install dependencies as root
RUN pip install --no-cache-dir -r requirements.txt
# 7. Copy application code
COPY --chown=appuser:appuser . .
# 8. Switch to non-root user
USER appuser
# 9. Expose only necessary port
EXPOSE 8000
# 10. Use HEALTHCHECK
HEALTHCHECK --interval=30s --timeout=3s \
CMD python -c "import requests; requests.get('http://localhost:8000/health')"
# 11. Run application
CMD ["python", "app.py"]
Score: 10/10 Security! 🎯
📱 Quick Reference Card
Before Deploying, Check:
✅ Using official/verified image?
✅ Running as non-root user?
✅ No secrets in Dockerfile?
✅ Image scanned for vulnerabilities?
✅ Using slim/minimal base image?
✅ Resource limits set?
✅ Network isolation configured?
✅ Read-only filesystem enabled?
✅ Unnecessary capabilities dropped?
✅ Ports properly restricted?
🚨 Real-World Security Incident
What Happened:
Company used:
FROM ubuntu
ENV AWS_KEY=AKIAIOSFODNN7EXAMPLE
Published to Docker Hub (public!)
Result:
The Fix:
FROM ubuntu
# No secrets in image!
# At runtime:
docker run \
-e AWS_KEY=$AWS_KEY \
myapp
Lesson: Never, ever commit secrets!
💡 Simple Daily Practices
1. Daily Review 🖥️
# Update your images
docker pull myapp:latest
# Scan for new vulnerabilities
docker scan myapp:latest
# If critical issues found, update!
2. Before Deployment 🚀
# Run security checks
docker scan myapp:latest
trivy image myapp:latest
# Check for secrets
docker history myapp:latest
# Verify user
docker run myapp whoami
# Should NOT say "root"!
3. Weekly Review 📅
# Remove unused images
docker image prune -a
# Check running containers
docker ps --format "table {{.Names}}\t{{.Status}}"
# Review exposed ports
docker ps --format "table {{.Names}}\t{{.Ports}}"
🎓 Key Takeaways
Take the First Step Towards Security 🧩
Easy First Steps:
Step 1: Check current images
docker images
Step 2: Scan them
docker scan image_name:tag
Step 3: Fix high-severity issues
# Update base image
# Rebuild container
# Re-deploy
Step 4: Add to CI/CD
# GitHub Actions example
- name: Scan Docker image
run: docker scan myapp:latest
📚 Additional Resources
Free Security Tools:
Learn More:
💬 Remember
Security is not a one-time task. It's a continuous practice! Start with a solid base and maintain good practices.
Found this helpful? Share with your team! ❤️
Docker makes sense once you understand why you’re containerizing, not just the commands. A clear learning path + hands-on practice helps a lot. This breakdown is useful → roadmapfinder.tech