🐳 Docker & Containers

Dockerfile Interpretation and Image Management

šŸ“„ 4.6 Interpret Contents of a Dockerfile

What is a Dockerfile?

A Dockerfile is a text file containing instructions to build a Docker image. It defines the base image, application code, dependencies, configuration, and runtime behavior.

graph LR A["Dockerfile"] --> B["docker build"] B --> C["Docker Image"] C --> D["docker run"] D --> E["Container"] A --> A1["Instructions"] C --> C1["Layers"] E --> E1["Running App"] style A fill:#fff3e0,stroke:#e65100,stroke-width:2px,color:#000 style C fill:#e3f2fd,stroke:#1976d2,stroke-width:2px,color:#000 style E fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px,color:#000

Complete Dockerfile Example

# Dockerfile - Complete Python Web Application Example

# FROM - Specify base image
FROM python:3.9-slim

# LABEL - Add metadata to image
LABEL maintainer="developer@example.com"
LABEL version="1.0"
LABEL description="Python Flask API application"

# ENV - Set environment variables
ENV APP_HOME=/app
ENV FLASK_APP=app.py
ENV FLASK_ENV=production
ENV PYTHONUNBUFFERED=1

# WORKDIR - Set working directory (creates if doesn't exist)
WORKDIR ${APP_HOME}

# COPY - Copy files from host to container
# Copy requirements first (for layer caching)
COPY requirements.txt .

# RUN - Execute commands during build
# Install Python dependencies
RUN pip install --no-cache-dir -r requirements.txt

# COPY application code
COPY . .

# RUN - Create non-root user for security
RUN useradd -m -u 1000 appuser && \
    chown -R appuser:appuser ${APP_HOME}

# USER - Switch to non-root user
USER appuser

# EXPOSE - Document which port app uses (doesn't publish)
EXPOSE 5000

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

# VOLUME - Create mount point for persistent data
VOLUME ["/app/data"]

# CMD - Default command when container starts (can be overridden)
CMD ["python", "app.py"]

# Alternative: ENTRYPOINT - Command that always runs (harder to override)
# ENTRYPOINT ["python", "app.py"]

Dockerfile Instructions Explained

FROM Base Image

Specifies the parent image to build upon. Every Dockerfile must start with FROM.

# Official images
FROM python:3.9
FROM node:18-alpine
FROM nginx:latest
FROM ubuntu:22.04

# Specific version (recommended for production)
FROM python:3.9.16-slim

# Multi-stage builds
FROM golang:1.19 AS builder
FROM alpine:latest
šŸ’” Best Practice: Use specific version tags, not "latest", for reproducible builds.

RUN Execute Commands

Executes commands during image build. Creates a new layer in the image.

# Install packages
RUN apt-get update && apt-get install -y \
    curl \
    vim \
    git

# Install Python packages
RUN pip install --no-cache-dir flask requests

# Chain commands with && to reduce layers
RUN apt-get update && \
    apt-get install -y curl && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

# Create directories
RUN mkdir -p /app/logs
āš ļø Tip: Chain commands with && to create fewer layers and reduce image size.

COPY vs ADD

Both copy files from host to container, but ADD has extra features.

# COPY - Simple file copy (recommended)
COPY requirements.txt /app/
COPY src/ /app/src/
COPY . /app/

# ADD - Copy with extra features (auto-extract tar, download URLs)
ADD archive.tar.gz /app/  # Auto-extracts
ADD https://example.com/file.zip /app/  # Downloads file

# Best practice: Use COPY unless you need ADD features
COPY app.py /app/

WORKDIR Working Directory

Sets the working directory for subsequent instructions.

# Set working directory
WORKDIR /app

# All subsequent commands run in /app
RUN pip install -r requirements.txt  # Runs in /app
COPY . .  # Copies to /app

# Can use multiple times
WORKDIR /app
WORKDIR logs  # Now in /app/logs

ENV - Environment Variables

Sets environment variables available during build and runtime.

# Set environment variables
ENV APP_PORT=5000
ENV DB_HOST=localhost
ENV DEBUG=False

# Use in other commands
ENV APP_DIR=/app
WORKDIR ${APP_DIR}

# Multiple variables
ENV APP_PORT=5000 \
    DB_HOST=localhost \
    LOG_LEVEL=INFO

EXPOSE Port Documentation

Documents which ports the application uses. Doesn't actually publish ports.

# Document ports
EXPOSE 80        # HTTP
EXPOSE 443       # HTTPS
EXPOSE 5000      # Flask default
EXPOSE 3000      # Node.js common

# Multiple ports
EXPOSE 8080 8443

# Note: Still need -p flag when running:
# docker run -p 5000:5000 myapp

CMD vs ENTRYPOINT

Define what command runs when container starts.

# CMD - Can be overridden
CMD ["python", "app.py"]
# Override: docker run myimage python other.py

# CMD - Shell form
CMD python app.py

# ENTRYPOINT - Main command (harder to override)
ENTRYPOINT ["python", "app.py"]
# Can only add args: docker run myimage --debug

# Combined ENTRYPOINT + CMD
ENTRYPOINT ["python"]
CMD ["app.py"]
# Default: python app.py
# Override CMD: docker run myimage other.py

Dockerfile Instructions Reference

Instruction Purpose Example
FROM Base image FROM python:3.9
RUN Execute command (build time) RUN pip install flask
COPY Copy files from host COPY app.py /app/
ADD Copy with extract/download ADD file.tar.gz /app/
WORKDIR Set working directory WORKDIR /app
ENV Set environment variable ENV PORT=5000
EXPOSE Document port EXPOSE 8080
CMD Default command (runtime) CMD ["python", "app.py"]
ENTRYPOINT Main executable ENTRYPOINT ["python"]
USER Set user context USER appuser
VOLUME Create mount point VOLUME ["/data"]
LABEL Add metadata LABEL version="1.0"
ARG Build-time variable ARG VERSION=1.0
HEALTHCHECK Container health check HEALTHCHECK CMD curl localhost

Best Practices for Dockerfiles

āœ… Dockerfile Best Practices:
  • Use .dockerignore: Exclude unnecessary files (like .gitignore)
  • Minimize Layers: Combine RUN commands with &&
  • Order Matters: Put changing instructions last (leverage cache)
  • Specific Base Images: Use version tags, not latest
  • Security: Don't run as root, use USER instruction
  • Clean Up: Remove temporary files in same RUN command
  • Multi-Stage Builds: Reduce final image size
  • Small Images: Use alpine or slim variants

Multi-Stage Dockerfile Example

# Multi-stage build - reduces final image size

# Stage 1: Build stage
FROM python:3.9 AS builder

WORKDIR /app
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt

COPY . .

# Stage 2: Production stage (final)
FROM python:3.9-slim

WORKDIR /app

# Copy only necessary files from builder
COPY --from=builder /root/.local /root/.local
COPY --from=builder /app .

# Update PATH
ENV PATH=/root/.local/bin:$PATH

# Run application
CMD ["python", "app.py"]

# Result: Smaller final image (no build tools, only runtime)

🐳 4.7 Utilize Docker Images in Local Development

What are Docker Images?

Docker images are read-only templates containing application code, libraries, and dependencies. Images are built from Dockerfiles and used to create containers.

graph TD A["Dockerfile"] --> B["Build Image"] B --> C["Docker Image"] C --> D["Push to Registry"] D --> E["Docker Hub/Registry"] E --> F["Pull Image"] F --> G["Run Container"] style A fill:#fff3e0,stroke:#e65100,stroke-width:2px,color:#000 style C fill:#e3f2fd,stroke:#1976d2,stroke-width:2px,color:#000 style E fill:#f3e5f5,stroke:#6a1b9a,stroke-width:2px,color:#000 style G fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px,color:#000

Essential Docker Commands

1. Building Images

# Build image from Dockerfile in current directory
docker build -t myapp:1.0 .

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

# Build with build arguments
docker build --build-arg VERSION=1.0 -t myapp:1.0 .

# Build without cache (force rebuild)
docker build --no-cache -t myapp:1.0 .

# Tag image after build
docker tag myapp:1.0 myapp:latest
docker tag myapp:1.0 registry.example.com/myapp:1.0

2. Managing Images

# List all images
docker images
docker image ls

# List with filters
docker images --filter "dangling=true"  # Untagged images
docker images python  # Images matching "python"

# Remove image
docker rmi myapp:1.0
docker image rm myapp:1.0

# Remove all unused images
docker image prune

# Remove all images (careful!)
docker rmi $(docker images -q)

# Inspect image details
docker inspect myapp:1.0

# View image history (layers)
docker history myapp:1.0

# Save image to tar file
docker save -o myapp.tar myapp:1.0

# Load image from tar
docker load -i myapp.tar

3. Working with Docker Hub / Registries

# Login to Docker Hub
docker login
docker login registry.example.com

# Pull image from registry
docker pull python:3.9
docker pull nginx:alpine
docker pull registry.example.com/myapp:1.0

# Push image to registry
docker push username/myapp:1.0
docker push registry.example.com/myapp:1.0

# Search Docker Hub
docker search python

# Tag for registry
docker tag myapp:1.0 username/myapp:1.0
docker tag myapp:1.0 username/myapp:latest

4. Running Containers from Images

# Basic run
docker run myapp:1.0

# Run in detached mode (background)
docker run -d myapp:1.0

# Run with name
docker run --name my-container myapp:1.0

# Run with port mapping
docker run -p 5000:5000 myapp:1.0
docker run -p 8080:80 nginx

# Run with environment variables
docker run -e DB_HOST=localhost -e DB_PORT=5432 myapp:1.0

# Run with volume mount
docker run -v /host/path:/container/path myapp:1.0
docker run -v $(pwd):/app myapp:1.0

# Run interactive with terminal
docker run -it ubuntu:22.04 /bin/bash

# Run and remove after exit
docker run --rm myapp:1.0

# Complete example with multiple options
docker run -d \
    --name flask-app \
    -p 5000:5000 \
    -e FLASK_ENV=development \
    -v $(pwd)/data:/app/data \
    --restart unless-stopped \
    myapp:1.0

5. Container Management

# List running containers
docker ps

# List all containers (including stopped)
docker ps -a

# Stop container
docker stop container_name
docker stop container_id

# Start stopped container
docker start container_name

# Restart container
docker restart container_name

# Remove container
docker rm container_name

# Remove running container (force)
docker rm -f container_name

# View container logs
docker logs container_name
docker logs -f container_name  # Follow logs

# Execute command in running container
docker exec container_name ls -la
docker exec -it container_name /bin/bash

# View container stats
docker stats

# Inspect container
docker inspect container_name

Docker Compose - Multi-Container Applications

docker-compose.yml Example

# docker-compose.yml - Define multi-container application
version: '3.8'

services:
  # Web application
  web:
    build: .
    image: myapp:1.0
    container_name: flask-app
    ports:
      - "5000:5000"
    environment:
      - FLASK_ENV=development
      - DB_HOST=database
    volumes:
      - ./app:/app
      - logs:/app/logs
    depends_on:
      - database
      - redis
    networks:
      - app-network
    restart: unless-stopped

  # Database
  database:
    image: postgres:14
    container_name: postgres-db
    environment:
      - POSTGRES_USER=admin
      - POSTGRES_PASSWORD=secret
      - POSTGRES_DB=myapp
    volumes:
      - db-data:/var/lib/postgresql/data
    networks:
      - app-network

  # Cache
  redis:
    image: redis:7-alpine
    container_name: redis-cache
    ports:
      - "6379:6379"
    networks:
      - app-network

volumes:
  db-data:
  logs:

networks:
  app-network:
    driver: bridge

Docker Compose Commands

# Start all services
docker-compose up

# Start in detached mode
docker-compose up -d

# Build and start
docker-compose up --build

# Stop all services
docker-compose down

# Stop and remove volumes
docker-compose down -v

# View logs
docker-compose logs
docker-compose logs -f web

# List running services
docker-compose ps

# Execute command in service
docker-compose exec web bash

# Scale service
docker-compose up -d --scale web=3

Local Development Workflow

āœ… Typical Docker Development Workflow:
  1. Write Dockerfile: Define application environment
  2. Build Image: docker build -t myapp:dev .
  3. Test Locally: docker run -p 5000:5000 myapp:dev
  4. Iterate: Make changes, rebuild, test
  5. Use Volumes: Mount code for live reload
  6. Docker Compose: Multi-container setup (app + db + cache)
  7. Tag for Registry: docker tag myapp:dev user/myapp:1.0
  8. Push to Registry: docker push user/myapp:1.0

Debugging and Troubleshooting

# Check if Docker is running
docker info
docker version

# View detailed container info
docker inspect container_name

# View container logs
docker logs container_name
docker logs --tail 100 container_name

# Access container shell
docker exec -it container_name /bin/bash
docker exec -it container_name /bin/sh  # For alpine

# View running processes in container
docker top container_name

# View resource usage
docker stats container_name

# Export container filesystem
docker export container_name > container.tar

# Copy files from container
docker cp container_name:/app/config.txt ./

# View port mappings
docker port container_name
āš ļø Common Docker Issues:
  • Port already in use: Use different port or stop conflicting service
  • Permission denied: Check file permissions or run as root (not recommended)
  • Image not found: Check image name/tag, pull from registry first
  • Container exits immediately: Check logs, CMD might be wrong
  • Out of disk space: Prune unused images/containers

šŸ Interactive Python Interpreter

šŸ’” How to use:
  • Write Python code in the editor below
  • Click "ā–¶ Run Code" to execute
  • Learn Docker concepts and workflows
šŸ“ Python Code Editor
⚔ Console Output
Ready to execute Python code...