Docker Complete Guide 2025 — Containers for Beginners to Production

Sanjeev SharmaSanjeev Sharma
6 min read

Advertisement

Introduction

Why This Matters

Docker solved one of software engineering's oldest problems: "it works on my machine." By packaging an application together with its runtime, libraries, and configuration, Docker containers behave identically across a developer's laptop, a CI server, and a production cluster. This predictability is what makes modern CI/CD pipelines reliable.

In 2025, Docker is installed on virtually every developer workstation and CI runner. Kubernetes — the dominant orchestration platform — runs Docker-compatible OCI images. Knowing Docker deeply is the prerequisite for Kubernetes, serverless containers, and cloud-native development. Container knowledge now appears in job descriptions for frontend engineers, backend developers, and infrastructure engineers alike.

Understanding Docker also makes debugging production incidents faster. Engineers who know how container networking, volumes, and process isolation work can diagnose issues in minutes that others spend hours on.

Installing Docker

Ubuntu / Debian

sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
  https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list
sudo apt-get update && sudo apt-get install -y docker-ce docker-ce-cli containerd.io
sudo usermod -aG docker $USER

macOS

Download Docker Desktop from docker.com. For CLI-only usage on macOS, install via Homebrew:

brew install docker docker-compose

Verify installation:

docker --version       # Docker version 27.x.x
docker run hello-world # Confirms the daemon is running

Images vs Containers

An image is a read-only, layered template stored in a registry. A container is a running instance of an image with a writable layer on top. Multiple containers can share the same image with zero duplication.

# Pull an official image from Docker Hub
docker pull nginx:1.27-alpine
 
# Run a container from the image
docker run -d -p 8080:80 --name web nginx:1.27-alpine
 
# List running containers
docker ps
 
# List all containers including stopped ones
docker ps -a
 
# List locally cached images
docker images
 
# Stop and remove a container
docker stop web && docker rm web

Writing Effective Dockerfiles

Basic Node.js Dockerfile

FROM node:20-alpine
 
# Set working directory
WORKDIR /app
 
# Install dependencies first (better layer caching)
COPY package*.json ./
RUN npm ci --only=production
 
# Copy application source
COPY . .
 
# Run as non-root
RUN addgroup -S app && adduser -S app -G app
USER app
 
EXPOSE 3000
CMD ["node", "server.js"]

Python Dockerfile

FROM python:3.12-slim
 
WORKDIR /app
 
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
 
COPY . .
 
RUN useradd -m -r appuser
USER appuser
 
EXPOSE 8000
CMD ["gunicorn", "main:app", "--bind", "0.0.0.0:8000"]

Building and Running Images

# Build an image with a tag
docker build -t myapp:1.0 .
 
# Build with a specific Dockerfile
docker build -f Dockerfile.prod -t myapp:prod .
 
# Run with port mapping
docker run -d -p 3000:3000 --name myapp myapp:1.0
 
# Run with environment variables
docker run -d -e NODE_ENV=production -e PORT=3000 myapp:1.0
 
# Run with a .env file
docker run -d --env-file .env myapp:1.0
 
# View container logs
docker logs -f myapp
 
# Execute a command inside a running container
docker exec -it myapp /bin/sh
 
# Copy files to/from container
docker cp myapp:/app/logs/error.log ./error.log

Docker Volumes and Data Persistence

Containers are ephemeral — data written inside them disappears when the container is removed. Volumes solve this.

# Create a named volume
docker volume create pgdata
 
# Mount a named volume
docker run -d -v pgdata:/var/lib/postgresql/data postgres:16
 
# Bind mount a host directory (useful for development)
docker run -d -v $(pwd)/src:/app/src myapp:dev
 
# List volumes
docker volume ls
 
# Inspect a volume (shows mount point on host)
docker volume inspect pgdata
 
# Remove unused volumes
docker volume prune

Docker Networking

# Create a custom network (containers can resolve each other by name)
docker network create app-network
 
# Run containers on the same network
docker run -d --network app-network --name postgres postgres:16
docker run -d --network app-network --name api myapp:1.0
 
# Inside the api container, 'postgres' resolves to the DB container's IP
# No need to use IP addresses
 
# List networks
docker network ls
 
# Inspect a network
docker network inspect app-network

Docker Compose for Local Development

# docker-compose.yml
version: '3.8'
 
services:
  api:
    build: .
    ports:
      - "3000:3000"
    environment:
      - DATABASE_URL=postgres://user:pass@db:5432/mydb
      - NODE_ENV=development
    volumes:
      - ./src:/app/src
    depends_on:
      db:
        condition: service_healthy
 
  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: mydb
    volumes:
      - pgdata:/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
    ports:
      - "6379:6379"
 
volumes:
  pgdata:
# Start all services in background
docker compose up -d
 
# View logs from all services
docker compose logs -f
 
# Scale a service
docker compose up -d --scale api=3
 
# Stop and remove containers (keeps volumes)
docker compose down
 
# Stop and remove everything including volumes
docker compose down -v

Using .dockerignore

# .dockerignore
node_modules
.git
.env
*.log
dist
coverage
.DS_Store
Dockerfile*
docker-compose*

Pushing to a Registry

# Docker Hub
docker login
docker tag myapp:1.0 username/myapp:1.0
docker push username/myapp:1.0
 
# AWS ECR
aws ecr get-login-password --region us-east-1 | \
  docker login --username AWS --password-stdin \
  123456789.dkr.ecr.us-east-1.amazonaws.com
docker tag myapp:1.0 123456789.dkr.ecr.us-east-1.amazonaws.com/myapp:1.0
docker push 123456789.dkr.ecr.us-east-1.amazonaws.com/myapp:1.0

Common Mistakes

  • Using FROM ubuntu when FROM ubuntu:22.04-slim or an Alpine variant would be 80% smaller
  • Installing dev dependencies in production images (omit --only=production in npm install)
  • Running containers as root — always add a non-root user
  • Storing secrets in environment variables baked into the image layer
  • Not using .dockerignore — bloats images with node_modules or .git directories
  • Using latest tags in production — makes rollbacks impossible

Best Practices

  • Pin base image versions: node:20.12.0-alpine3.19 not node:latest
  • Copy package.json before source code to leverage layer caching
  • Scan images for vulnerabilities with docker scout cves or Trivy
  • Keep images small: use Alpine variants, multi-stage builds, and --no-cache for package managers
  • Set memory and CPU limits: docker run --memory=512m --cpus=0.5 myapp
  • Use health checks in Dockerfiles to enable Compose and Kubernetes readiness detection

Key Takeaways

  • Docker packages apps with their dependencies into portable, isolated containers that run identically everywhere
  • Images are immutable templates; containers are running instances — one image can spawn thousands of containers
  • Layer caching is triggered by file changes, so copy dependency manifests (package.json, requirements.txt) before source code
  • Named volumes persist data across container restarts; bind mounts are best for local development hot-reload
  • Custom Docker networks let containers discover each other by service name, not IP address
  • Docker Compose orchestrates multi-container local environments with a single YAML file
  • Always run containers as non-root users and use .dockerignore to keep images lean
  • Push images to a private registry (ECR, GCR, Docker Hub) and tag with the exact commit SHA for traceability

Advertisement

Sanjeev Sharma

Written by

Sanjeev Sharma

Full Stack Engineer · E-mopro

Related reading