•8 min read

Docker Compose cho Phát triển Nội bộ: Hướng dẫn Thiết lập Hoàn chỉnh

Docker Compose cho Phát triển Nội bộ: Hướng dẫn Thiết lập Hoàn chỉnh

Các ứng dụng web hiện đại hiếm khi chỉ là một dịch vụ duy nhất. Một stack điển hình vào năm 2026 bao gồm một API Node.js hoặc Python, một cơ sở dữ liệu PostgreSQL, một bộ nhớ đệm Redis, một worker Celery hoặc BullMQ, và có thể là một kho lưu trữ tương thích S3 cục bộ như MinIO. Việc quản lý các dependency này theo cách thủ công — cài đặt các phiên bản Postgres cụ thể, cấu hình cổng, chuyển đổi giữa các tab terminal — là công thức dẫn đến sự trôi dạt môi trường và lỗi kinh điển "nó chạy được trên máy của tôi".

Docker Compose giải quyết vấn đề này bằng cách thể hiện toàn bộ hạ tầng cục bộ của bạn dưới dạng một tệp YAML khai báo duy nhất. Vào năm 2026, Docker Compose v2 được tích hợp sẵn trong Docker Desktop và Docker Engine, tích hợp BuildKit một cách tự nhiên, và hỗ trợ các cấu hình GPU, kiểm tra sức khỏe (healthchecks), và bí mật (secrets) — làm cho nó có khả năng sản xuất cho các quy trình làm việc cục bộ.

Audio Briefing
0:00 / 0:00
Interactive Dev Tool
100% Client-Side & Private

Docker Compose to Kubernetes Converter

Generate Deployments, Services, ConfigMaps & Ingress

Convert multi-service Docker Compose v2/v3 YAML into production-grade Kubernetes manifests with custom replicas, health probes, resource limits, and PVC storage.

K8s DeploymentsConfigMapsPVC StorageIngress YAML

Tại sao docker compose up của bạn nên phản ánh môi trường sản xuất

Mục tiêu không chỉ là sự tiện lợi. Một tệp Compose phản ánh môi trường sản xuất sẽ bắt được các lỗi cụ thể của môi trường trước khi chúng đến giai đoạn staging. Các điểm tương đồng cụ thể cần được thực thi:

  • Cùng phiên bản PostgreSQL, cùng mã hóa (UTF8), cùng ngôn ngữ (en_US.UTF-8)
  • Cùng phiên bản Redis và chính sách loại bỏ (eviction policy) (volatile-lru cho các bộ nhớ đệm phiên)
  • Cùng tên biến môi trường (lấy từ .env, không bao giờ mã hóa cứng)
  • Cùng cấu trúc mạng (các dịch vụ tự tìm thấy nhau bằng tên dịch vụ, không phải localhost)
  • Cùng quyền người dùng (container API chạy dưới quyền non-root, phản ánh runAsNonRoot của K8s)
Advertisement

Bước 1: Tệp docker-compose.yml

Đây là một tệp Compose phản ánh môi trường sản xuất cho một API Node.js với PostgreSQL, Redis và một worker nền:

# docker-compose.yml (Docker Compose v2 — no "version:" key needed)
name: myapp

services:
  api:
    build:
      context: ./api
      target: development
      cache_from:
        - type=local,src=/tmp/.buildx-cache
    ports:
      - "3000:3000"
    volumes:
      - ./api:/usr/src/app
      - node_modules:/usr/src/app/node_modules  # Prevent host node_modules leak
    env_file:
      - .env
    environment:
      NODE_ENV: development
      DATABASE_URL: postgres://app:${POSTGRES_PASSWORD}@db:5432/myappdb
      REDIS_URL: redis://redis:6379/0
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    command: npm run dev
    user: "1001:1001"   # Match your host UID to avoid root-owned volume files

  worker:
    build:
      context: ./api
      target: development
    volumes:
      - ./api:/usr/src/app
      - node_modules:/usr/src/app/node_modules
    env_file:
      - .env
    environment:
      NODE_ENV: development
      DATABASE_URL: postgres://app:${POSTGRES_PASSWORD}@db:5432/myappdb
      REDIS_URL: redis://redis:6379/0
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    command: npm run worker
    user: "1001:1001"

  db:
    image: postgres:16-alpine
    restart: unless-stopped
    ports:
      - "5432:5432"
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: myappdb
      POSTGRES_INITDB_ARGS: "--encoding=UTF8 --locale=en_US.UTF-8"
    volumes:
      - pgdata:/var/lib/postgresql/data
      - ./db/init.sql:/docker-entrypoint-initdb.d/10-init.sql:ro
      - ./db/seed.sql:/docker-entrypoint-initdb.d/20-seed.sql:ro
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d myappdb"]
      interval: 5s
      timeout: 5s
      retries: 10
      start_period: 10s

  redis:
    image: redis:7-alpine
    restart: unless-stopped
    ports:
      - "6379:6379"
    command: >
      redis-server
      --maxmemory 256mb
      --maxmemory-policy volatile-lru
      --save ""
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 5

  minio:
    image: minio/minio:latest
    ports:
      - "9000:9000"
      - "9001:9001"   # MinIO Console UI
    environment:
      MINIO_ROOT_USER: ${MINIO_USER}
      MINIO_ROOT_PASSWORD: ${MINIO_PASSWORD}
    command: server /data --console-address ":9001"
    volumes:
      - minio_data:/data
    healthcheck:
      test: ["CMD", "mc", "ready", "local"]
      interval: 10s
      timeout: 5s
      retries: 3

volumes:
  pgdata:
  node_modules:
  minio_data:

Tại sao depends_on với condition: service_healthy lại quan trọng

depends_on: [db] một cách ngây thơ chỉ đợi container khởi động, chứ không đợi PostgreSQL chấp nhận kết nối. Truy vấn cơ sở dữ liệu đầu tiên của API của bạn sẽ gặp lỗi "connection refused" và bị crash khi khởi động. Sử dụng condition: service_healthy với một healthcheck phù hợp đảm bảo Postgres thực sự sẵn sàng trước khi API khởi động.

Mẹo về Volume node_modules

Volume có tên node_modules:/usr/src/app/node_modules ngăn chặn node_modules của máy chủ (được biên dịch cho macOS hoặc Windows) ghi đè lên các binary được biên dịch cho Linux của container. Nếu không có điều này, các addon gốc như bcrypt hoặc sharp sẽ không hoạt động bên trong container.

Bước 2: Dockerfile đa giai đoạn với BuildKit Cache Mounts

BuildKit cache mounts tăng tốc đáng kể quá trình rebuild bằng cách duy trì bộ nhớ đệm npm qua các lần build:

# api/Dockerfile
FROM node:20-alpine AS base
WORKDIR /usr/src/app
RUN addgroup -g 1001 appgroup && adduser -u 1001 -G appgroup -s /bin/sh -D appuser

# Development stage — includes devDependencies, live reload
FROM base AS development
COPY package*.json ./
# BuildKit cache mount: npm cache persists across `docker compose build` runs
RUN --mount=type=cache,target=/root/.npm \
    npm install
COPY . .
# CMD is overridden by docker-compose.yml

# Build stage — compiles TypeScript, runs tests
FROM base AS build
COPY package*.json ./
RUN --mount=type=cache,target=/root/.npm \
    npm ci
COPY . .
RUN npm run build && npm run test:ci

# Production runtime — minimal image, no devDependencies
FROM node:20-alpine AS production
WORKDIR /usr/src/app
RUN addgroup -g 1001 appgroup && adduser -u 1001 -G appgroup -s /bin/sh -D appuser
COPY package*.json ./
RUN --mount=type=cache,target=/root/.npm \
    npm ci --omit=dev
COPY --from=build /usr/src/app/dist ./dist
USER appuser
EXPOSE 3000
CMD ["node", "dist/main.js"]

Chỉ thị --mount=type=cache là một tính năng của BuildKit. Lần build đầu tiên tải xuống các dependency; các lần build tiếp theo đọc từ lớp cache, cắt giảm thời gian build từ vài phút xuống còn vài giây cho hầu hết các thay đổi lặp đi lặp lại.

Bước 3: Quản lý bí mật môi trường

Không bao giờ commit thông tin xác thực vào kiểm soát phiên bản. Sử dụng .env với một .env.example đã được commit:

# .env.example — committed to git
POSTGRES_PASSWORD=change_me_in_env
MINIO_USER=minioadmin
MINIO_PASSWORD=change_me_in_env
REPORTER_SECRET=change_me_in_env
# .env — gitignored, copied from .env.example
POSTGRES_PASSWORD=dev_local_secret_32chars
MINIO_USER=minioadmin
MINIO_PASSWORD=dev_local_minio_secret
REPORTER_SECRET=dev_reporter_secret

Đối với các bí mật giống như sản xuất trong thử nghiệm cục bộ, Docker Compose Secrets cung cấp một giải pháp thay thế an toàn hơn bằng cách gắn các bí mật dưới dạng tệp thay vì biến môi trường:

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

services:
  api:
    secrets:
      - db_password
    environment:
      DATABASE_PASSWORD_FILE: /run/secrets/db_password
Advertisement

Bước 4: Các lệnh quy trình làm việc hàng ngày

# Start the entire stack (foreground — see all logs)
docker compose up

# Start detached, tail only API logs
docker compose up -d && docker compose logs -f api

# Rebuild only the api image after Dockerfile changes
docker compose up --build api

# Run a one-off command inside the running api container
docker compose exec api npm run migrate:latest

# Run a new ephemeral container (doesn't affect the running api)
docker compose run --rm api npm run db:seed

# Stop containers, preserve volumes
docker compose down

# Nuclear reset: stop, remove containers, networks, AND volumes
docker compose down -v

# View resource usage
docker compose stats

Mạng và Khám phá dịch vụ

Docker Compose tạo một mạng riêng có tên myapp_default (có tiền tố là giá trị name:). Mọi dịch vụ đều có thể truy cập tại http://<service-name>:<internal-port>:

Truy cập từMục tiêuURL
container apiPostgreSQLpostgres://app:…@db:5432/myappdb
container apiRedisredis://redis:6379/0
container apiMinIO APIhttp://minio:9000
Trình duyệt của bạnAPIhttp://localhost:3000
Trình duyệt của bạnMinIO Consolehttp://localhost:9001

Ánh xạ cổng (ports:) hiển thị các cổng của container ra máy chủ của bạn. Nếu không có ánh xạ, một dịch vụ có thể truy cập được từ các container khác nhưng không thể truy cập được từ trình duyệt của bạn — hữu ích cho việc cô lập các dịch vụ nội bộ.

Hồ sơ cho các dịch vụ tùy chọn

Sử dụng hồ sơ để khởi động các dịch vụ tùy chọn (như bộ thu thập dấu vết Jaeger) chỉ khi cần:

services:
  jaeger:
    image: jaegertracing/all-in-one:latest
    ports:
      - "16686:16686"
      - "4317:4317"
    profiles: ["observability"]
# Start with optional observability stack
docker compose --profile observability up

# Start without (default)
docker compose up

Kết luận

Một docker-compose.yml được xây dựng tốt sẽ loại bỏ sự trôi dạt môi trường, giảm thời gian onboarding từ hàng giờ xuống còn vài phút, và cung cấp cho pipeline CI của bạn cùng một môi trường xác định như máy của mọi nhà phát triển. Khoản đầu tư là một giờ thiết lập; lợi nhuận là hàng tháng tiết kiệm thời gian gỡ lỗi cho toàn bộ nhóm của bạn.

Bạn cũng có thể thích

Share this article:

Stay Updated

Get the latest posts delivered straight to your inbox.

Free Developer Utilities

Free In-Browser Developer Tools

Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.

Explore Tools
Advertisement