Docker Phần 3 - Docker Compose, dàn nhạc giao hưởng của các container! 🎼
Xin chào các "cơ trưởng Docker"! 🧑✈️
Sau khi đã học cách build container đơn lẻ trong phần 1 và quản lý image với Registry trong phần 2, hôm nay chúng ta sẽ cùng khám phá Docker Compose - công cụ tuyệt vời giúp điều phối cả một "dàn nhạc giao hưởng" các container! 🎼
Hãy tưởng tượng bạn đang xây dựng một ứng dụng web hoàn chỉnh: frontend React, backend Node.js, database PostgreSQL, Redis cache, và Nginx làm reverse proxy. Thay vì phải docker run từng thằng một (và nhớ hàng tá parameters), Docker Compose sẽ giúp bạn start tất cả chỉ với một câu lệnh đơn giản! ✨
🎭 Docker Compose là "ai" và làm "gì"?
1.1. Định nghĩa đơn giản (cho người mới bắt đầu)
Docker Compose giống như một file kịch bản (docker-compose.yml) mô tả cách các container hoạt động cùng nhau. Thay vì:
# Cách cũ - phải chạy từng lệnh một (mệt mỏi quá!)
docker network create myapp-network
docker run -d --name postgres --network myapp-network -e POSTGRES_DB=myapp postgres:13
docker run -d --name redis --network myapp-network redis:6
docker run -d --name backend --network myapp-network -p 3000:3000 myapp-backend
docker run -d --name frontend --network myapp-network -p 80:80 myapp-frontend
Bạn chỉ cần:
# Cách mới - một lệnh rule them all! 🔥
docker-compose up
1.2. Docker Compose vs Docker thường
| Tiêu chí | Docker thường | Docker Compose | 
|---|---|---|
| Số container | Một container mỗi lần | Nhiều container cùng lúc | 
| Cấu hình | Qua command line | Qua file YAML | 
| Mạng | Phải tạo manually | Tự động tạo và kết nối | 
| Phụ thuộc | Không quản lý | Quản lý thứ tự khởi động | 
| Quản lý | Từng container riêng lẻ | Toàn bộ stack cùng lúc | 
| Phù hợp | App đơn giản, test nhanh | App phức tạp, nhiều service | 
🏗️ Cấu trúc file docker-compose.yml
Trước khi đi vào ví dụ, hãy hiểu cấu trúc cơ bản của file docker-compose.yml:
version: '3.8'  # Phiên bản Docker Compose
services:  # Định nghĩa các container
  service1:
    # Cấu hình cho container 1
  service2:
    # Cấu hình cho container 2
networks:  # Định nghĩa mạng (tùy chọn)
  # Cấu hình network
volumes:  # Định nghĩa volume (tùy chọn)
  # Cấu hình lưu trữ
2.1. Các thuộc tính quan trọng của services
| Thuộc tính | Mô tả | Ví dụ | 
|---|---|---|
| image | Image Docker để sử dụng | image: nginx:latest | 
| build | Build image từ Dockerfile | build: ./backend | 
| ports | Map port host:container | ports: ["3000:3000"] | 
| environment | Biến môi trường | environment: NODE_ENV=production | 
| volumes | Mount volume hoặc bind mount | volumes: ["./data:/var/lib/mysql"] | 
| depends_on | Phụ thuộc vào service khác | depends_on: ["database"] | 
| networks | Kết nối vào network | networks: ["app-network"] | 
| restart | Chính sách restart | restart: unless-stopped | 
🚀 Ví dụ thực tế: Blog App hoàn chỉnh
Hãy cùng xây dựng một blog app với architecture sau:
- Frontend: React app (port 3000)
- Backend: Node.js API (port 5000)
- Database: PostgreSQL (port 5432)
- Cache: Redis (port 6379)
- Proxy: Nginx (port 80)
3.1. Cấu trúc thư mục
my-blog-app/
├── docker-compose.yml
├── frontend/
│   ├── Dockerfile
│   ├── package.json
│   └── src/...
├── backend/
│   ├── Dockerfile
│   ├── package.json
│   └── src/...
├── nginx/
│   └── nginx.conf
└── data/
    └── postgres/  # Sẽ được tạo tự động
3.2. File docker-compose.yml siêu chi tiết
version: '3.8'
services:
  # 📊 Database - PostgreSQL
  database:
    image: postgres:13
    container_name: blog-postgres
    restart: unless-stopped
    environment:
      POSTGRES_DB: blog_db
      POSTGRES_USER: blog_user
      POSTGRES_PASSWORD: super_secret_password
      PGDATA: /var/lib/postgresql/data/pgdata
    volumes:
      - postgres_data:/var/lib/postgresql/data
      # Mount init script nếu cần
      - ./database/init.sql:/docker-entrypoint-initdb.d/init.sql
    ports:
      - "5432:5432"  # Expose để có thể kết nối từ bên ngoài (dev purpose)
    networks:
      - blog-network
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U blog_user -d blog_db"]
      interval: 30s
      timeout: 10s
      retries: 3
  # 🗄️ Cache - Redis
  redis:
    image: redis:6-alpine
    container_name: blog-redis
    restart: unless-stopped
    command: redis-server --appendonly yes --requirepass redis_password
    volumes:
      - redis_data:/data
    ports:
      - "6379:6379"
    networks:
      - blog-network
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 30s
      timeout: 10s
      retries: 3
  # 🛠️ Backend API - Node.js
  backend:
    build: 
      context: ./backend
      dockerfile: Dockerfile
    container_name: blog-backend
    restart: unless-stopped
    environment:
      NODE_ENV: production
      DATABASE_URL: postgresql://blog_user:super_secret_password@database:5432/blog_db
      REDIS_URL: redis://:redis_password@redis:6379
      JWT_SECRET: your_jwt_secret_here
      PORT: 5000
    volumes:
      # Mount source code khi development
      - ./backend:/usr/src/app
      - /usr/src/app/node_modules  # Prevent overwriting node_modules
    ports:
      - "5000:5000"
    depends_on:
      database:
        condition: service_healthy
      redis:
        condition: service_healthy
    networks:
      - blog-network
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
  # 🎨 Frontend - React
  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile
      args:
        REACT_APP_API_URL: http://localhost:5000/api
    container_name: blog-frontend
    restart: unless-stopped
    volumes:
      # Development mode
      - ./frontend:/usr/src/app
      - /usr/src/app/node_modules
    ports:
      - "3000:3000"
    depends_on:
      - backend
    networks:
      - blog-network
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000"]
      interval: 30s
      timeout: 10s
      retries: 3
  # 🌐 Reverse Proxy - Nginx
  nginx:
    image: nginx:alpine
    container_name: blog-nginx
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"  # HTTPS nếu có SSL
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/ssl:/etc/nginx/ssl:ro  # SSL certificates
      - nginx_logs:/var/log/nginx
    depends_on:
      - frontend
      - backend
    networks:
      - blog-network
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost/health"]
      interval: 30s
      timeout: 10s
      retries: 3
# 🌐 Networks
networks:
  blog-network:
    driver: bridge
    name: blog-network
# 💾 Volumes
volumes:
  postgres_data:
    name: blog-postgres-data
  redis_data:
    name: blog-redis-data
  nginx_logs:
    name: blog-nginx-logs
3.3. Dockerfile cho Backend (Node.js)
# backend/Dockerfile
FROM node:16-alpine
# Tạo app directory
WORKDIR /usr/src/app
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm ci --only=production
# Copy source code
COPY . .
# Create non-root user
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
# Change ownership
RUN chown -R nextjs:nodejs /usr/src/app
USER nextjs
# Expose port
EXPOSE 5000
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:5000/health || exit 1
# Start application
CMD ["npm", "start"]
3.4. Dockerfile cho Frontend (React)
# frontend/Dockerfile
# Multi-stage build cho production
FROM node:16-alpine AS builder
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Production stage
FROM nginx:alpine
COPY --from=builder /usr/src/app/build /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 3000
CMD ["nginx", "-g", "daemon off;"]
3.5. Cấu hình Nginx
# nginx/nginx.conf
events {
    worker_connections 1024;
}
http {
    upstream backend {
        server backend:5000;
    }
    upstream frontend {
        server frontend:3000;
    }
    server {
        listen 80;
        server_name localhost;
        # Serve frontend
        location / {
            proxy_pass http://frontend;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }
        # Proxy API requests to backend
        location /api/ {
            proxy_pass http://backend/;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }
        # Health check endpoint
        location /health {
            access_log off;
            return 200 "healthy\n";
            add_header Content-Type text/plain;
        }
    }
}
🎮 Các lệnh Docker Compose quan trọng
4.1. Lệnh cơ bản - "Bộ tứ siêu anh hùng"
# 🚀 Start tất cả services (background mode)
docker-compose up -d
# 📊 Xem trạng thái các services
docker-compose ps
# 📝 Xem logs của tất cả services
docker-compose logs
# 🛑 Stop tất cả services
docker-compose down
4.2. Lệnh nâng cao - "Ninja skills"
# 🔨 Build lại images trước khi start
docker-compose up --build
# 🧹 Stop và xóa volumes (CẢNH BÁO: sẽ mất data!)
docker-compose down -v
# 📊 Scale service (chạy nhiều instance)
docker-compose up --scale backend=3
# 🔍 Xem logs của service cụ thể
docker-compose logs backend
# 🔄 Restart service cụ thể
docker-compose restart backend
# 🚪 Exec vào container
docker-compose exec backend bash
# 📦 Pull images mới nhất
docker-compose pull
# ⚡ Start services theo thứ tự phụ thuộc
docker-compose up --no-deps backend
4.3. Troubleshooting commands
# 🔍 Debug network issues
docker-compose exec backend ping database
# 📊 Check resource usage
docker-compose top
# 🗂️ Xem config được render
docker-compose config
# 🧪 Validate file docker-compose.yml
docker-compose config --quiet
# 📋 List tất cả networks
docker network ls | grep $(basename $PWD)
# 💾 List volumes
docker volume ls | grep $(basename $PWD)
🎯 Các patterns hay sử dụng
5.1. Environment Variables với .env file
Tạo file .env:
# .env
# Database settings
POSTGRES_DB=blog_db
POSTGRES_USER=blog_user
POSTGRES_PASSWORD=super_secret_password
# Redis settings
REDIS_PASSWORD=redis_password
# Backend settings
NODE_ENV=production
JWT_SECRET=your_jwt_secret_here
# Frontend settings
REACT_APP_API_URL=http://localhost:5000/api
Sử dụng trong docker-compose.yml:
services:
  database:
    environment:
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
5.2. Multiple environments
# Development
docker-compose -f docker-compose.yml -f docker-compose.dev.yml up
# Production  
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up
# Testing
docker-compose -f docker-compose.yml -f docker-compose.test.yml up
docker-compose.dev.yml:
version: '3.8'
services:
  backend:
    volumes:
      - ./backend:/usr/src/app
    environment:
      NODE_ENV: development
    command: npm run dev  # Hot reload
  frontend:
    volumes:
      - ./frontend:/usr/src/app
    command: npm start  # Development server
docker-compose.prod.yml:
version: '3.8'
services:
  backend:
    restart: always
    environment:
      NODE_ENV: production
  frontend:
    restart: always
  # Thêm monitoring trong production
  monitoring:
    image: prom/prometheus
    ports:
      - "9090:9090"
5.3. Health checks và dependencies
services:
  database:
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 60s
  backend:
    depends_on:
      database:
        condition: service_healthy  # Đợi DB healthy mới start
      redis:
        condition: service_started  # Chỉ cần Redis start là được
🛠️ Ví dụ cho các framework khác
6.1. Full-stack JavaScript (MEAN Stack)
version: '3.8'
services:
  # MongoDB
  mongodb:
    image: mongo:5
    environment:
      MONGO_INITDB_ROOT_USERNAME: admin
      MONGO_INITDB_ROOT_PASSWORD: password
    volumes:
      - mongo_data:/data/db
    ports:
      - "27017:27017"
  # Express.js Backend
  backend:
    build: ./backend
    environment:
      MONGODB_URI: mongodb://admin:password@mongodb:27017/myapp?authSource=admin
      NODE_ENV: production
    depends_on:
      - mongodb
    ports:
      - "5000:5000"
  # Angular Frontend
  frontend:
    build: ./frontend
    ports:
      - "4200:4200"
    depends_on:
      - backend
volumes:
  mongo_data:
6.2. Spring Boot + MySQL + Redis
version: '3.8'
services:
  # MySQL Database
  mysql:
    image: mysql:8
    environment:
      MYSQL_ROOT_PASSWORD: rootpassword
      MYSQL_DATABASE: springapp
      MYSQL_USER: springuser
      MYSQL_PASSWORD: springpass
    volumes:
      - mysql_data:/var/lib/mysql
    ports:
      - "3306:3306"
  # Redis Cache
  redis:
    image: redis:6-alpine
    ports:
      - "6379:6379"
  # Spring Boot App
  app:
    build: .
    ports:
      - "8080:8080"
    environment:
      SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/springapp
      SPRING_DATASOURCE_USERNAME: springuser
      SPRING_DATASOURCE_PASSWORD: springpass
      SPRING_REDIS_HOST: redis
      SPRING_REDIS_PORT: 6379
    depends_on:
      - mysql
      - redis
volumes:
  mysql_data:
📚 Best Practices - "Bí kíp thành công"
7.1. Tổ chức code và config
# Cấu trúc thư mục recommended
my-project/
├── docker-compose.yml          # Main compose file
├── docker-compose.dev.yml      # Development overrides
├── docker-compose.prod.yml     # Production overrides
├── .env                        # Environment variables
├── .env.example               # Template cho team members
├── .dockerignore              # Ignore files khi build
├── services/
│   ├── frontend/
│   │   ├── Dockerfile
│   │   └── nginx.conf
│   ├── backend/
│   │   └── Dockerfile
│   └── database/
│       └── init.sql
└── volumes/                   # Local volumes (gitignore)
    ├── postgres/
    └── redis/
7.2. Security best practices
# ✅ Tốt
services:
  database:
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password
    # Không expose port ra ngoài trừ khi cần thiết
    # ports:
    #   - "5432:5432"  # Bỏ dòng này!
secrets:
  db_password:
    file: ./secrets/db_password.txt
# ❌ Không tốt
services:
  database:
    environment:
      POSTGRES_PASSWORD: hardcoded_password  # Nguy hiểm!
    ports:
      - "5432:5432"  # Expose DB ra internet!
7.3. Performance tips
services:
  app:
    # Sử dụng init để handle signals đúng cách
    init: true
    
    # Giới hạn resources
    deploy:
      resources:
        limits:
          cpus: '0.50'
          memory: 512M
        reservations:
          cpus: '0.25'
          memory: 256M
    
    # Logging configuration
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"
7.4. Development workflow
# Script cho development workflow
#!/bin/bash
# scripts/dev.sh
echo "🚀 Starting development environment..."
# Pull latest images
docker-compose pull
# Build services
docker-compose build
# Start services
docker-compose up -d
# Show logs
docker-compose logs -f
🎊 Tổng kết
Docker Compose thực sự là một "phép màu" giúp việc quản lý multi-container application trở nên đơn giản và thú vị! 🎭
Những điều quan trọng cần nhớ:
- File docker-compose.yml là "kịch bản" điều phối tất cả
- Services là các container, networks kết nối chúng, volumes lưu trữ data
- Environment variables giúp config linh hoạt cho nhiều môi trường
- Health checks và depends_on đảm bảo services start đúng thứ tự
- Multiple compose files cho development/production environments
Hẹn gặp lại các bạn trong những phần tiếp theo! Chúc các bạn code vui vẻ và container luôn "up" thành công! 🐳✨
P/S: Có thắc mắc gì về Docker Compose thì đừng ngại liên hệ tôi nhé! Tôi sẽ cố gắng trả lời sớm nhất có thể! 😊