Docker Images and Containers Deep Dive

Understanding Docker images and containers is fundamental to mastering containerization. This section covers everything from basic operations to advanced image management techniques.

Understanding Docker Images

Image Layers and Architecture

Docker images are built using a layered filesystem. Each instruction in a Dockerfile creates a new layer:

FROM ubuntu:20.04          # Layer 1: Base OS
RUN apt-get update         # Layer 2: Package updates
RUN apt-get install -y python3  # Layer 3: Python installation
COPY app.py /app/          # Layer 4: Application code
CMD ["python3", "/app/app.py"]  # Layer 5: Default command
# View image layers
docker history python:3.9

# Output shows each layer:
# IMAGE          CREATED BY                                      SIZE
# abc123...      /bin/sh -c #(nop) CMD ["python3"]             0B
# def456...      /bin/sh -c apt-get install -y python3         45MB
# ghi789...      /bin/sh -c apt-get update                      25MB
# jkl012...      /bin/sh -c #(nop) FROM ubuntu:20.04           72MB

Image Management Commands

# Pull images with specific tags
docker pull nginx:1.21-alpine
docker pull postgres:13.4
docker pull node:16-slim

# List all images
docker images
docker image ls

# List images with specific filters
docker images --filter "dangling=true"  # Untagged images
docker images --filter "before=nginx:latest"
docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"

# Remove images
docker rmi nginx:1.21-alpine
docker image rm postgres:13.4

# Remove multiple images
docker rmi $(docker images -q)  # Remove all images
docker image prune              # Remove unused images
docker image prune -a           # Remove all unused images

Image Inspection and Analysis

# Detailed image information
docker inspect nginx:latest

# View image configuration
docker inspect nginx:latest --format='{{.Config.Cmd}}'
docker inspect nginx:latest --format='{{.Config.ExposedPorts}}'

# Check image size and layers
docker images nginx:latest
docker history nginx:latest --no-trunc

# Analyze image vulnerabilities (if Docker Scout is available)
docker scout cves nginx:latest

Working with Containers

Container Creation and Management

# Create container without starting
docker create --name my-web nginx:latest

# Run container with various options
docker run -d \
  --name production-web \
  --restart unless-stopped \
  -p 80:80 \
  -p 443:443 \
  -v /host/data:/var/www/html \
  -e NGINX_HOST=example.com \
  nginx:latest

# Interactive container with custom command
docker run -it --rm ubuntu:20.04 /bin/bash

# Run container with resource limits
docker run -d \
  --name limited-app \
  --memory=512m \
  --cpus=1.0 \
  --memory-swap=1g \
  nginx:latest

Container Lifecycle Operations

# Start/stop containers
docker start my-web
docker stop my-web
docker restart my-web

# Pause/unpause containers
docker pause my-web
docker unpause my-web

# Kill container (force stop)
docker kill my-web

# Remove containers
docker rm my-web
docker rm -f running-container  # Force remove running container

# Container cleanup
docker container prune          # Remove stopped containers
docker rm $(docker ps -aq)     # Remove all containers

Container Monitoring and Debugging

# View container logs
docker logs my-web
docker logs -f my-web           # Follow logs
docker logs --tail 50 my-web   # Last 50 lines
docker logs --since 2h my-web  # Logs from last 2 hours

# Monitor container resources
docker stats                    # All containers
docker stats my-web            # Specific container
docker stats --no-stream      # One-time snapshot

# Execute commands in running containers
docker exec -it my-web /bin/bash
docker exec my-web ls -la /var/log
docker exec -u root my-web apt-get update

# Copy files between host and container
docker cp file.txt my-web:/tmp/
docker cp my-web:/var/log/nginx/access.log ./

Creating Custom Docker Images

Writing Dockerfiles

# Multi-stage Python application
FROM python:3.9-slim as builder

# Set working directory
WORKDIR /app

# Install build dependencies
RUN apt-get update && apt-get install -y \
    gcc \
    && rm -rf /var/lib/apt/lists/*

# Copy requirements and install Python packages
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt

# Production stage
FROM python:3.9-slim

# Create non-root user
RUN groupadd -r appuser && useradd -r -g appuser appuser

# Set working directory
WORKDIR /app

# Copy Python packages from builder stage
COPY --from=builder /root/.local /home/appuser/.local

# Copy application code
COPY --chown=appuser:appuser . .

# Switch to non-root user
USER appuser

# Set environment variables
ENV PATH=/home/appuser/.local/bin:$PATH
ENV PYTHONPATH=/app
ENV PYTHONUNBUFFERED=1

# Expose port
EXPOSE 8000

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:8000/health || exit 1

# Default command
CMD ["python", "app.py"]

Dockerfile Best Practices

# Node.js application with best practices
FROM node:16-alpine

# Install security updates
RUN apk update && apk upgrade && apk add --no-cache dumb-init

# Create app directory
WORKDIR /usr/src/app

# Create non-root user
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001

# Copy package files first (for better caching)
COPY package*.json ./

# Install dependencies
RUN npm ci --only=production && npm cache clean --force

# Copy application code
COPY --chown=nextjs:nodejs . .

# Switch to non-root user
USER nextjs

# Expose port
EXPOSE 3000

# Use dumb-init for proper signal handling
ENTRYPOINT ["dumb-init", "--"]

# Start application
CMD ["node", "server.js"]

Building and Tagging Images

# Basic build
docker build -t my-app:latest .

# Build with specific Dockerfile
docker build -f Dockerfile.prod -t my-app:prod .

# Build with build arguments
docker build \
  --build-arg NODE_ENV=production \
  --build-arg API_URL=https://api.example.com \
  -t my-app:v1.0.0 .

# Build with multiple tags
docker build -t my-app:latest -t my-app:v1.0.0 -t my-app:stable .

# Build with no cache
docker build --no-cache -t my-app:latest .

# Build with target stage (multi-stage builds)
docker build --target builder -t my-app:builder .

Advanced Container Operations

Container Networking Basics

# Run container with custom network settings
docker run -d \
  --name web-app \
  --hostname webapp \
  --add-host api.local:192.168.1.100 \
  -p 8080:80 \
  nginx:latest

# Run container with specific network
docker network create my-network
docker run -d --name app1 --network my-network nginx:latest
docker run -d --name app2 --network my-network nginx:latest

# Connect running container to network
docker network connect my-network existing-container

Volume Management

# Named volumes
docker volume create my-data
docker run -d --name db -v my-data:/var/lib/mysql mysql:8.0

# Bind mounts
docker run -d \
  --name web \
  -v /host/path:/container/path \
  -v /host/config:/etc/nginx/conf.d:ro \
  nginx:latest

# Temporary volumes
docker run -d --name app --tmpfs /tmp nginx:latest

# Volume inspection
docker volume ls
docker volume inspect my-data
docker volume prune  # Remove unused volumes

Environment Variables and Configuration

# Set environment variables
docker run -d \
  --name app \
  -e NODE_ENV=production \
  -e DATABASE_URL=postgresql://user:pass@db:5432/myapp \
  -e DEBUG=false \
  my-app:latest

# Load environment from file
echo "NODE_ENV=production" > .env
echo "PORT=3000" >> .env
docker run -d --name app --env-file .env my-app:latest

# Override default command
docker run -it my-app:latest /bin/bash
docker run my-app:latest npm test

Practical Examples

Example 1: Web Application with Database

# Create network for multi-container app
docker network create webapp-network

# Run PostgreSQL database
docker run -d \
  --name postgres-db \
  --network webapp-network \
  -e POSTGRES_DB=myapp \
  -e POSTGRES_USER=appuser \
  -e POSTGRES_PASSWORD=secretpass \
  -v postgres-data:/var/lib/postgresql/data \
  postgres:13

# Run web application
docker run -d \
  --name web-app \
  --network webapp-network \
  -p 8080:3000 \
  -e DATABASE_URL=postgresql://appuser:secretpass@postgres-db:5432/myapp \
  -e NODE_ENV=production \
  my-webapp:latest

# Run Redis cache
docker run -d \
  --name redis-cache \
  --network webapp-network \
  -v redis-data:/data \
  redis:6-alpine

# Check application logs
docker logs -f web-app

Example 2: Development Environment

# Dockerfile.dev
FROM node:16-alpine

WORKDIR /app

# Install nodemon for development
RUN npm install -g nodemon

# Copy package files
COPY package*.json ./
RUN npm install

# Copy source code
COPY . .

# Expose port
EXPOSE 3000

# Development command with hot reload
CMD ["nodemon", "server.js"]
# Build development image
docker build -f Dockerfile.dev -t my-app:dev .

# Run development container with volume mounting
docker run -d \
  --name dev-app \
  -p 3000:3000 \
  -v $(pwd):/app \
  -v /app/node_modules \
  my-app:dev

# View development logs
docker logs -f dev-app

Example 3: Multi-Container Application

# Frontend container
docker run -d \
  --name frontend \
  -p 80:80 \
  -v $(pwd)/nginx.conf:/etc/nginx/nginx.conf:ro \
  nginx:alpine

# Backend API container
docker run -d \
  --name backend \
  -p 3000:3000 \
  -e DATABASE_URL=postgresql://user:pass@database:5432/api \
  my-api:latest

# Database container
docker run -d \
  --name database \
  -e POSTGRES_DB=api \
  -e POSTGRES_USER=user \
  -e POSTGRES_PASSWORD=pass \
  -v db-data:/var/lib/postgresql/data \
  postgres:13

# Link containers (legacy approach, use networks instead)
docker run -d --name api --link database:db my-api:latest

Image Optimization Techniques

Multi-Stage Builds

# Build stage
FROM node:16 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Production stage
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Minimizing Image Size

# Use Alpine Linux for smaller base images
FROM python:3.9-alpine

# Combine RUN commands to reduce layers
RUN apk update && \
    apk add --no-cache gcc musl-dev && \
    pip install --no-cache-dir -r requirements.txt && \
    apk del gcc musl-dev

# Use .dockerignore to exclude unnecessary files
# .dockerignore content:
# node_modules
# .git
# *.md
# .env
# tests/

Caching Strategies

# Good: Copy package files first for better caching
COPY package*.json ./
RUN npm install
COPY . .

# Bad: Copy everything first (breaks cache on any file change)
COPY . .
RUN npm install

Container Security Basics

Running as Non-Root User

# Create and use non-root user
RUN groupadd -r appuser && useradd -r -g appuser appuser
USER appuser

# Or use numeric UID/GID
USER 1001:1001

Security Scanning

# Scan image for vulnerabilities
docker scout cves my-app:latest

# Use security-focused base images
FROM gcr.io/distroless/java:11  # Distroless images
FROM alpine:latest              # Minimal Alpine Linux

Troubleshooting Common Issues

Container Won’t Start

# Check container logs
docker logs container-name

# Run container interactively
docker run -it --entrypoint /bin/bash my-app:latest

# Check container configuration
docker inspect container-name

Build Failures

# Build with verbose output
docker build --progress=plain -t my-app .

# Build specific stage for debugging
docker build --target builder -t debug-build .
docker run -it debug-build /bin/bash

Performance Issues

# Monitor resource usage
docker stats

# Check container processes
docker exec container-name ps aux

# Analyze image layers
docker history my-app:latest

Summary

In this section, you learned:

Image Management

  • Understanding Docker image layers and architecture
  • Pulling, listing, and removing images
  • Image inspection and analysis techniques
  • Building custom images with Dockerfiles

Container Operations

  • Creating and managing container lifecycle
  • Monitoring and debugging containers
  • Resource management and limits
  • Networking and volume basics

Best Practices

  • Multi-stage builds for optimization
  • Security considerations (non-root users)
  • Caching strategies for faster builds
  • Image size optimization techniques

Practical Skills

  • Building real-world applications
  • Multi-container setups
  • Development environment configuration
  • Troubleshooting common issues

Key Takeaways:

  • Images are templates, containers are running instances
  • Layer caching improves build performance
  • Always run containers as non-root users when possible
  • Use multi-stage builds to minimize production image size
  • Monitor container resources and logs for troubleshooting

Next, we’ll explore Docker networking, volumes, and how to connect multiple containers to build complex applications.