Skip to content

Docker & Containers


Container Fundamentals

Containers vs Virtual Machines


Dockerfile

Basic Structure

# Build stage
FROM maven:3.9-eclipse-temurin-21 AS builder
WORKDIR /app

# Cache dependencies
COPY pom.xml .
RUN mvn dependency:go-offline

# Build application
COPY src ./src
RUN mvn package -DskipTests

# Runtime stage
FROM eclipse-temurin:21-jre-alpine

# Security: Don't run as root
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser

WORKDIR /app

# Copy artifact from builder
COPY --from=builder /app/target/app.jar ./app.jar

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
  CMD wget --quiet --tries=1 --spider http://localhost:8080/health || exit 1

# Expose port
EXPOSE 8080

# Runtime configuration
ENV JAVA_OPTS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0"

# Start application
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]

Multi-Stage Builds

# Stage 1: Dependencies
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

# Stage 2: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Stage 3: Production
FROM node:20-alpine AS runner
WORKDIR /app

ENV NODE_ENV=production

# Security
RUN addgroup -S nodejs && adduser -S nextjs -G nodejs
USER nextjs

# Copy only necessary files
COPY --from=deps --chown=nextjs:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=nextjs:nodejs /app/dist ./dist

EXPOSE 3000

CMD ["node", "dist/server.js"]

Docker Commands

# Build
docker build -t myapp:1.0 .
docker build -t myapp:1.0 -f Dockerfile.prod .
docker build --no-cache -t myapp:1.0 .

# Run
docker run myapp:1.0
docker run -d myapp:1.0                    # Detached
docker run -p 8080:80 myapp:1.0           # Port mapping
docker run -e ENV_VAR=value myapp:1.0     # Environment
docker run -v /host/path:/container/path myapp:1.0  # Volume
docker run --name mycontainer myapp:1.0   # Named container
docker run --rm myapp:1.0                 # Remove after exit
docker run -it myapp:1.0 /bin/sh          # Interactive

# Container management
docker ps                    # Running containers
docker ps -a                 # All containers
docker logs container_id     # View logs
docker logs -f container_id  # Follow logs
docker exec -it container_id /bin/sh  # Execute command
docker stop container_id
docker start container_id
docker rm container_id
docker rm $(docker ps -aq)   # Remove all containers

# Images
docker images
docker pull image:tag
docker push image:tag
docker tag source:tag target:tag
docker rmi image_id
docker image prune           # Remove unused images
docker system prune -a       # Clean everything

# Debugging
docker inspect container_id
docker stats                 # Resource usage
docker top container_id      # Running processes
docker diff container_id     # Filesystem changes

Docker Compose

# docker-compose.yml
version: '3.8'

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "8080:8080"
    environment:
      - DATABASE_URL=postgres://db:5432/mydb
      - REDIS_URL=redis://redis:6379
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 512M
        reservations:
          cpus: '0.25'
          memory: 256M

  db:
    image: postgres:15-alpine
    environment:
      POSTGRES_DB: mydb
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U user -d mydb"]
      interval: 10s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7-alpine
    volumes:
      - redis_data:/data

volumes:
  postgres_data:
  redis_data:

networks:
  default:
    driver: bridge
# Docker Compose commands
docker-compose up              # Start services
docker-compose up -d           # Detached mode
docker-compose up --build      # Rebuild images
docker-compose down            # Stop and remove
docker-compose down -v         # Also remove volumes
docker-compose logs -f app     # Follow logs
docker-compose exec app sh     # Execute in running container
docker-compose ps              # List containers
docker-compose pull            # Pull latest images

Image Optimization

Image Optimization Tips

.dockerignore

# .dockerignore
.git
.gitignore
node_modules
npm-debug.log
Dockerfile*
docker-compose*
.dockerignore
.env
*.md
.idea
.vscode
coverage
dist
*.log

Layer Optimization Example

# Bad: Separate RUN commands create multiple layers
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get clean

# Good: Combined and cleaned in single layer
RUN apt-get update && \
    apt-get install -y --no-install-recommends curl && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

Security Best Practices

# 1. Don't run as root
FROM node:20-alpine
RUN addgroup -S app && adduser -S app -G app
USER app

# 2. Use specific image versions
FROM node:20.10.0-alpine3.19  # Not :latest

# 3. Use read-only filesystem
# docker run --read-only myapp

# 4. Drop capabilities
# docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE myapp

# 5. Scan for vulnerabilities
# docker scan myapp:1.0
# trivy image myapp:1.0

# 6. Don't store secrets in images
# Use runtime secrets, environment variables, or secret managers

# 7. Use COPY instead of ADD
COPY . .  # Not ADD . .

# 8. Set resource limits
# docker run --memory=512m --cpus=0.5 myapp

Security Scanning

# Docker Scout (built-in)
docker scout cve myimage:tag

# Trivy
trivy image myimage:tag

# Snyk
snyk container test myimage:tag

Networking

Docker Network Modes


Volumes

Volume Types


Registry

Push to Registry

# Tag image
docker tag myapp:1.0 myregistry.com/myapp:1.0

# Login
docker login myregistry.com

# Push
docker push myregistry.com/myapp:1.0

# Pull
docker pull myregistry.com/myapp:1.0

Private Registry

# Run local registry
docker run -d -p 5000:5000 --name registry registry:2

# Push to local
docker tag myapp:1.0 localhost:5000/myapp:1.0
docker push localhost:5000/myapp:1.0

Container Runtime Comparison

Container Runtimes


Common Interview Questions

  1. Container vs VM?
  2. Container: Shares kernel, lightweight, fast
  3. VM: Full OS, more isolation, heavier

  4. What are namespaces?

  5. Kernel feature for isolation
  6. PID, network, mount, user, UTS, IPC

  7. What are cgroups?

  8. Control groups for resource limits
  9. CPU, memory, I/O, network

  10. Multi-stage build benefits?

  11. Smaller final image
  12. Build tools not in production
  13. Separate build and runtime dependencies

  14. How to persist data?

  15. Volumes (Docker-managed)
  16. Bind mounts (host filesystem)
  17. tmpfs (memory, not persisted)

  • *