Docker for Developers: Quick Guide 2025

THEJORD Team5 min read
dockerdevopscontainersdevelopment

Docker guide for developers: Dockerfile, images, containers, Docker Compose. Best practices 2025 and common patterns.

Docker for Developers: Quick Guide 2025

Introduction to Docker

Docker revolutionized software deployment by enabling developers to package applications with all their dependencies into standardized containers. These containers run consistently across any environment—from local development to production—eliminating the classic "works on my machine" problem.

This guide covers Docker fundamentals, from basic concepts to production deployment strategies, giving you the knowledge to containerize and deploy your applications effectively.

Core Docker Concepts

Images vs Containers

Understanding the distinction between images and containers is fundamental:

  • Image: A read-only template containing the application code, runtime, libraries, and dependencies. Think of it as a snapshot or blueprint.
  • Container: A running instance of an image. You can create multiple containers from the same image, each with isolated state.

Layers and Caching

Docker images are built in layers. Each instruction in a Dockerfile creates a new layer. Docker caches layers to speed up subsequent builds:

# Each line creates a layer
FROM node:20-alpine        # Base layer
WORKDIR /app               # Layer 2
COPY package*.json ./      # Layer 3
RUN npm install            # Layer 4 (cached if package.json unchanged)
COPY . .                   # Layer 5
RUN npm run build          # Layer 6

Registries

Container registries store and distribute images:

  • Docker Hub: Default public registry
  • GitHub Container Registry (ghcr.io): GitHub's container registry
  • AWS ECR: Amazon's managed registry
  • Google Container Registry: GCP's registry
  • Self-hosted: Harbor, GitLab Container Registry

Essential Docker Commands

Working with Images

# Pull an image from registry
docker pull node:20-alpine

# List local images
docker images

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

# Tag an image
docker tag myapp:1.0 username/myapp:1.0

# Push to registry
docker push username/myapp:1.0

# Remove image
docker rmi myapp:1.0

Working with Containers

# Run a container
docker run -d --name myapp -p 3000:3000 myapp:1.0

# List running containers
docker ps

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

# Stop a container
docker stop myapp

# Start a stopped container
docker start myapp

# Remove a container
docker rm myapp

# View logs
docker logs myapp
docker logs -f myapp  # Follow logs

# Execute command in running container
docker exec -it myapp sh
docker exec myapp ls -la

Common Run Options

# Port mapping
docker run -p 8080:80 nginx  # Host:Container

# Environment variables
docker run -e NODE_ENV=production myapp

# Volume mounting
docker run -v $(pwd):/app myapp  # Bind mount
docker run -v mydata:/data myapp  # Named volume

# Resource limits
docker run --memory=512m --cpus=1 myapp

# Automatic restart
docker run --restart=unless-stopped myapp

# Remove container after exit
docker run --rm myapp

Writing Dockerfiles

Basic Structure

# Start from a base image
FROM node:20-alpine

# Set working directory
WORKDIR /app

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

# Install dependencies
RUN npm ci --only=production

# Copy application code
COPY . .

# Build application (if needed)
RUN npm run build

# Expose port (documentation)
EXPOSE 3000

# Define the command to run
CMD ["node", "dist/index.js"]

Multi-stage Builds

Use multi-stage builds to create smaller production images:

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

# Production stage
FROM node:20-alpine AS production
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY --from=builder /app/dist ./dist
EXPOSE 3000
CMD ["node", "dist/index.js"]

Optimization Best Practices

# Use specific version tags, not latest
FROM node:20.10.0-alpine3.19

# Create non-root user
RUN addgroup -g 1001 appgroup && \
    adduser -S -u 1001 -G appgroup appuser
USER appuser

# Use COPY instead of ADD (unless extracting archives)
COPY . .

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

# Use .dockerignore
# .dockerignore file:
node_modules
.git
*.md
.env

Docker Compose

Docker Compose defines and manages multi-container applications:

Basic docker-compose.yml

version: '3.8'

services:
  web:
    build: .
    ports:
      - "3000:3000"
    environment:
      - DATABASE_URL=postgres://db:5432/myapp
    depends_on:
      - db
    volumes:
      - ./src:/app/src
    restart: unless-stopped

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

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"

volumes:
  postgres_data:

Docker Compose Commands

# Start all services
docker compose up -d

# View logs
docker compose logs -f

# Stop all services
docker compose down

# Rebuild and start
docker compose up -d --build

# Scale a service
docker compose up -d --scale worker=3

# View running services
docker compose ps

Networking

Network Types

  • bridge: Default network for containers on same host
  • host: Container uses host's network stack
  • none: Isolated, no network access
  • overlay: Multi-host networking (Swarm/Kubernetes)

Custom Networks

# Create a network
docker network create mynetwork

# Run containers on the network
docker run --network mynetwork --name api myapi
docker run --network mynetwork --name db postgres

# Containers can reach each other by name
# api can connect to db:5432

Volumes and Data Persistence

Volume Types

# Named volumes (managed by Docker)
docker volume create mydata
docker run -v mydata:/data myapp

# Bind mounts (host directory)
docker run -v $(pwd)/data:/data myapp

# tmpfs (memory only, Linux)
docker run --tmpfs /tmp myapp

Volume Management

# List volumes
docker volume ls

# Inspect volume
docker volume inspect mydata

# Remove unused volumes
docker volume prune

# Backup a volume
docker run --rm -v mydata:/data -v $(pwd):/backup alpine \
  tar czf /backup/mydata.tar.gz -C /data .

Security Best Practices

Image Security

  • Use official base images from trusted sources
  • Scan images for vulnerabilities (Trivy, Snyk)
  • Keep images updated with security patches
  • Use minimal base images (alpine, distroless)
# Scan with Trivy
trivy image myapp:latest

# Use distroless for production
FROM gcr.io/distroless/nodejs:20

Runtime Security

# Run as non-root user
USER 1000:1000

# Read-only filesystem
docker run --read-only myapp

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

# Security options
docker run --security-opt=no-new-privileges myapp

Secrets Management

# Docker Compose secrets
services:
  app:
    image: myapp
    secrets:
      - db_password

secrets:
  db_password:
    file: ./secrets/db_password.txt

# Access in container at /run/secrets/db_password

Development Workflow

Development vs Production

# docker-compose.yml (base)
services:
  app:
    build: .
    environment:
      - NODE_ENV=production

# docker-compose.override.yml (dev overrides, auto-loaded)
services:
  app:
    build:
      target: development
    volumes:
      - ./src:/app/src
    environment:
      - NODE_ENV=development
    command: npm run dev

Hot Reloading

# Mount source code for development
docker run -v $(pwd)/src:/app/src myapp-dev

# With nodemon/vite/etc. watching for changes

Production Deployment

Health Checks

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

# In docker-compose.yml
services:
  app:
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 3s
      retries: 3

Logging

# Configure logging driver
docker run --log-driver=json-file \
           --log-opt max-size=10m \
           --log-opt max-file=3 \
           myapp

# In docker-compose.yml
services:
  app:
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

Resource Management

# docker-compose.yml
services:
  app:
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 512M
        reservations:
          cpus: '0.25'
          memory: 256M

CI/CD Integration

GitHub Actions Example

name: Build and Push
on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Login to Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ghcr.io/${{ github.repository }}:latest

Debugging Containers

Common Debugging Commands

# Interactive shell
docker exec -it container_name sh

# View processes
docker top container_name

# Resource usage
docker stats

# Inspect container details
docker inspect container_name

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

# View container filesystem changes
docker diff container_name

Debugging a Failed Build

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

# Build up to a specific stage
docker build --target builder -t myapp-debug .

# Run failed layer for inspection
docker run -it sha256:abc123 sh

Working with Configuration

When dealing with JSON configuration files in Docker, validate them before deployment:

Quick Reference

# Build and run
docker build -t myapp .
docker run -d -p 3000:3000 --name app myapp

# Compose commands
docker compose up -d
docker compose down
docker compose logs -f

# Cleanup
docker system prune -a  # Remove all unused
docker volume prune     # Remove unused volumes

Conclusion

Docker simplifies application deployment by packaging code with dependencies into portable containers. By mastering Dockerfiles, Compose, networking, and security practices, you can build reliable, reproducible deployments across any environment.

Key takeaways:

  • Use multi-stage builds for smaller production images
  • Leverage layer caching to speed up builds
  • Run containers as non-root users
  • Use Docker Compose for multi-container applications
  • Implement health checks for production reliability
  • Scan images for security vulnerabilities

For more developer resources, explore our free online tools. For comprehensive documentation, see the official Docker documentation and Docker Compose reference.