Docker Compose & Ứng Dụng Multi-Container

Chào mừng đến Giai đoạn 2 của Lộ Trình Học Docker & Kubernetes! Giờ bạn đã hiểu Docker cơ bản — images, containers, volumes và networking — đã đến lúc tự build images với Dockerfile và điều phối ứng dụng multi-container bằng Docker Compose.
Đây là lúc Docker thực sự mạnh mẽ. Thay vì pull images có sẵn, bạn sẽ đóng gói ứng dụng của riêng mình. Thay vì chạy từng container một, bạn sẽ define toàn bộ stack trong một file duy nhất.
Bạn Sẽ Học Được
✅ Viết Dockerfile từ đầu với tất cả instructions cần thiết
✅ Chọn base image phù hợp (alpine, slim, distroless)
✅ Dùng multi-stage builds để giảm image size 70-90%
✅ Hiểu layer caching và build optimization
✅ Thành thạo Docker Compose cho multi-container orchestration
✅ Cấu hình networks, volumes và environment variables trong Compose
✅ Thiết lập development và production configurations
Thời gian: 5–7 ngày, 1–2 giờ mỗi ngày
Yêu cầu: Giai đoạn 1: Docker Cơ Bản
Phần 1: Dockerfile Cơ Bản
Dockerfile là file text chứa các instructions để build Docker image. Hãy nghĩ nó như một công thức nấu ăn — mỗi instruction thêm một layer vào image.
Dockerfile Đầu Tiên
Bắt đầu với một ứng dụng Node.js đơn giản:
// app.js
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ message: 'Hello from Docker!', time: new Date() }));
});
server.listen(3000, () => {
console.log('Server running on port 3000');
});Dockerfile tương ứng:
# Dùng Node.js làm base image
FROM node:20-alpine
# Set working directory trong container
WORKDIR /app
# Copy package files trước (để cache tốt hơn)
COPY package*.json ./
# Install dependencies
RUN npm install --production
# Copy application code
COPY . .
# Expose port 3000
EXPOSE 3000
# Khởi động ứng dụng
CMD ["node", "app.js"]Build và chạy:
# Build image
docker build -t my-node-app .
# Chạy container
docker run -d -p 3000:3000 --name my-app my-node-app
# Test
curl http://localhost:3000
# {"message":"Hello from Docker!","time":"2026-03-19T..."}Các Dockerfile Instructions Cần Biết
FROM — Base Image
Mọi Dockerfile bắt đầu bằng FROM. Nó define base image mà ứng dụng build trên đó.
# Official images
FROM node:20-alpine
FROM python:3.12-slim
FROM golang:1.22-alpine
FROM openjdk:21-slim
# Minimal base images
FROM alpine:3.19
FROM ubuntu:24.04
FROM debian:bookworm-slim
# Empty base (cho static binaries)
FROM scratchRUN — Thực Thi Lệnh
RUN thực thi commands trong quá trình build. Mỗi RUN tạo một layer mới.
# Install system packages
RUN apt-get update && apt-get install -y \
curl \
git \
&& rm -rf /var/lib/apt/lists/*
# Install Python dependencies
RUN pip install --no-cache-dir -r requirements.txt
# Tạo directories
RUN mkdir -p /app/dataBest practice: Chain commands bằng
&&và cleanup trong cùng mộtRUNinstruction để giảm layer size.
COPY vs ADD
Cả hai đều copy files vào image, nhưng hoạt động khác nhau:
# COPY — Copy file đơn giản (nên dùng)
COPY package.json ./
COPY src/ ./src/
COPY . .
# ADD — Tính năng thêm (thường tránh dùng)
ADD https://example.com/file.tar.gz /app/ # Download URLs
ADD archive.tar.gz /app/ # Tự động giải nénBest practice: Dùng
COPYtrừ khi bạn cần download URL hoặc auto-extract từADD.
WORKDIR — Thư Mục Làm Việc
Set working directory cho các instructions tiếp theo:
WORKDIR /app
# Tất cả commands sau đây chạy trong /app
COPY . .
RUN npm install
CMD ["node", "app.js"]CMD vs ENTRYPOINT
Cả hai define cái gì chạy khi container start, nhưng mục đích khác nhau:
# CMD — Default command (có thể override)
CMD ["node", "app.js"]
CMD ["python", "main.py"]
# ENTRYPOINT — Executable cố định (arguments có thể thêm vào)
ENTRYPOINT ["python", "main.py"]
CMD ["--port", "8000"] # Default argumentsKhác biệt trong thực tế:
# Với CMD ["node", "app.js"]
docker run my-app # Chạy: node app.js
docker run my-app bash # Chạy: bash (CMD bị override)
# Với ENTRYPOINT ["python", "main.py"]
docker run my-app # Chạy: python main.py --port 8000
docker run my-app --port 9000 # Chạy: python main.py --port 9000
docker run my-app bash # Chạy: python main.py bash (lỗi)Quy tắc:
- Dùng
CMDcho general-purpose images khi user có thể muốn chạy commands khác - Dùng
ENTRYPOINT+CMDkhi container luôn phải chạy một program cụ thể
ENV và ARG — Biến
# ENV — Có trong build time VÀ runtime
ENV NODE_ENV=production
ENV PORT=3000
# ARG — CHỈ có trong build time
ARG VERSION=1.0.0
ARG NODE_VERSION=20
# Dùng ARG trong FROM
ARG NODE_VERSION=20
FROM node:${NODE_VERSION}-alpine
# Dùng build args
docker build --build-arg VERSION=2.0.0 -t my-app .EXPOSE — Document Ports
EXPOSE không thực sự publish ports — nó chỉ là documentation cho biết container lắng nghe port nào:
EXPOSE 3000
EXPOSE 8080
EXPOSE 5432Bạn vẫn cần -p flag khi chạy: docker run -p 3000:3000 my-app
USER — Chạy Không Phải Root
# Tạo non-root user
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
# Chuyển sang non-root user
USER appuser
# Tất cả commands sau chạy dưới appuser
COPY --chown=appuser:appgroup . .
CMD ["node", "app.js"]Security: Luôn chạy production containers dưới non-root users.
VOLUME — Khai Báo Mount Points
VOLUME ["/data"]
VOLUME ["/var/log/app"]LABEL — Metadata
LABEL maintainer="[email protected]"
LABEL version="1.0"
LABEL description="My awesome application"Build Context và .dockerignore
Khi bạn chạy docker build ., Docker gửi toàn bộ directory (build context) đến Docker daemon. File .dockerignore loại trừ files không cần thiết:
# .dockerignore
node_modules
npm-debug.log
.git
.gitignore
.env
.env.*
Dockerfile
docker-compose.yml
README.md
.DS_Store
coverage
.nyc_output
dist
build
*.mdTại sao quan trọng:
# Không có .dockerignore — gửi mọi thứ (chậm)
Sending build context to Docker daemon 250MB
# Có .dockerignore — chỉ gửi cái cần (nhanh)
Sending build context to Docker daemon 2.5MBPhần 2: Dockerfile Best Practices
Viết Dockerfile thì dễ. Viết Dockerfile tốt cần hiểu layers, caching và security.
Chọn Base Image Đúng
Base image ảnh hưởng lớn đến image size và security:
| Base Image | Size | Use Case |
|---|---|---|
node:20 | ~1GB | Full development environment |
node:20-slim | ~200MB | Production (hầu hết dependencies) |
node:20-alpine | ~130MB | Production (lightweight) |
python:3.12 | ~1GB | Full Python environment |
python:3.12-slim | ~150MB | Production Python |
python:3.12-alpine | ~50MB | Minimal Python |
golang:1.22 | ~800MB | Go build environment |
alpine:3.19 | ~7MB | Minimal Linux |
gcr.io/distroless/static | ~2MB | Static binaries only |
Hướng dẫn:
- Development: Dùng full images để có debugging tools
- Production: Dùng variants
slimhoặcalpine - Go/Rust: Dùng
scratchhoặcdistrolesscho final images (static binaries) - Luôn luôn: Pin specific versions (không dùng
:latest)
Layer Caching và Thứ Tự Instructions
Docker cache mỗi layer. Nếu layer không thay đổi, Docker dùng lại cache. Thứ tự rất quan trọng:
# ❌ SAI — Reinstall dependencies mỗi khi code thay đổi
FROM node:20-alpine
WORKDIR /app
COPY . . # Code thay đổi invalidate layer này
RUN npm install # Phải install lại mỗi lần
CMD ["node", "app.js"]
# ✅ ĐÚNG — Dependencies được cache trừ khi package.json thay đổi
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./ # Chỉ thay đổi khi dependencies đổi
RUN npm install # Cached trừ khi package.json thay đổi
COPY . . # Code changes chỉ ảnh hưởng layer này
CMD ["node", "app.js"]Nguyên tắc: Copy files ít thay đổi trước.
- Layers xanh: Hiếm khi thay đổi (cached gần như luôn)
- Layers vàng: Thay đổi khi dependencies update
- Layers đỏ: Thay đổi với mỗi code update
Giảm Số Lượng Layers
Mỗi RUN, COPY và ADD tạo layer mới. Gộp các commands liên quan:
# ❌ SAI — 3 layers riêng biệt
RUN apt-get update
RUN apt-get install -y curl
RUN rm -rf /var/lib/apt/lists/*
# ✅ ĐÚNG — 1 layer, có cleanup
RUN apt-get update && apt-get install -y \
curl \
git \
&& rm -rf /var/lib/apt/lists/*Security Hardening
FROM node:20-alpine
# Tạo non-root user
RUN addgroup -S app && adduser -S app -G app
WORKDIR /app
# Install dependencies với root
COPY package*.json ./
RUN npm ci --production && npm cache clean --force
# Copy app files với ownership đúng
COPY --chown=app:app . .
# Chuyển sang non-root user
USER app
EXPOSE 3000
CMD ["node", "app.js"]Phần 3: Multi-Stage Builds
Multi-stage builds là game-changer. Cho phép bạn dùng một image để build và một image khác (nhỏ hơn) để chạy.
Vấn Đề
Không có multi-stage builds:
# Single-stage: gồm TẤT CẢ build tools trong final image
FROM node:20
WORKDIR /app
COPY . .
RUN npm install
RUN npm run build
# Final image: ~1GB (gồm TypeScript compiler, dev dependencies, v.v.)
CMD ["node", "dist/app.js"]Giải Pháp
# Stage 1: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Stage 2: Production
FROM node:20-alpine AS production
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY --from=builder /app/dist ./dist
USER node
EXPOSE 3000
CMD ["node", "dist/app.js"]--from=builder chỉ copy built artifacts từ stage đầu. Mọi thứ khác (source code, dev dependencies, build tools) bị loại bỏ.
Multi-Stage Build cho Python (FastAPI)
# Stage 1: Build dependencies
FROM python:3.12-slim AS builder
WORKDIR /app
# Install build dependencies
RUN apt-get update && apt-get install -y \
gcc \
&& rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
# Stage 2: Production
FROM python:3.12-slim
WORKDIR /app
# Copy installed packages từ builder
COPY --from=builder /install /usr/local
# Tạo non-root user
RUN useradd --create-home appuser
USER appuser
COPY . .
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]Multi-Stage Build cho Go
Go tỏa sáng với multi-stage builds vì Go compile thành static binary:
# Stage 1: Build
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o /server ./cmd/server
# Stage 2: Production (từ scratch — image nhỏ nhất có thể!)
FROM scratch
COPY --from=builder /server /server
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
EXPOSE 8080
ENTRYPOINT ["/server"]Kết quả: Final image chỉ vài MB thay vì ~800MB.
Multi-Stage Build cho Java (Spring Boot)
# Stage 1: Build với Maven
FROM maven:3.9-eclipse-temurin-21 AS builder
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:resolve
COPY src ./src
RUN mvn package -DskipTests
# Stage 2: Production
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
RUN addgroup -S app && adduser -S app -G app
COPY --from=builder /app/target/*.jar app.jar
RUN chown app:app app.jar
USER app
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]So Sánh Image Size
Impact của multi-stage builds:
| Ứng Dụng | Single-Stage | Multi-Stage | Giảm |
|---|---|---|---|
| Node.js API | ~1.1GB | ~180MB | 84% |
| Python FastAPI | ~950MB | ~150MB | 84% |
| Go API | ~800MB | ~8MB | 99% |
| Java Spring Boot | ~800MB | ~280MB | 65% |
Phần 4: Docker Compose Cơ Bản
Chạy docker run với đống flags dài dòng mệt lắm, nhất là khi ứng dụng có nhiều services. Docker Compose giải quyết bằng cách define và quản lý multi-container applications trong một file YAML duy nhất.
Docker Compose Là Gì?
Docker Compose là tool define và chạy multi-container Docker applications. Thay vì viết thế này:
# Tạo network
docker network create myapp
# Start database
docker run -d --name postgres \
--network myapp \
-e POSTGRES_DB=mydb \
-e POSTGRES_USER=admin \
-e POSTGRES_PASSWORD=secret \
-v pgdata:/var/lib/postgresql/data \
postgres:16-alpine
# Start Redis
docker run -d --name redis \
--network myapp \
redis:7-alpine
# Start application
docker run -d --name app \
--network myapp \
-p 3000:3000 \
-e DATABASE_URL=postgres://admin:secret@postgres:5432/mydb \
-e REDIS_URL=redis://redis:6379 \
my-appBạn viết thế này:
# docker-compose.yml
services:
app:
build: .
ports:
- "3000:3000"
environment:
DATABASE_URL: postgres://admin:secret@postgres:5432/mydb
REDIS_URL: redis://redis:6379
depends_on:
- postgres
- redis
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: mydb
POSTGRES_USER: admin
POSTGRES_PASSWORD: secret
volumes:
- pgdata:/var/lib/postgresql/data
redis:
image: redis:7-alpine
volumes:
pgdata:Và chạy mọi thứ bằng một lệnh:
docker compose up -dCấu Trúc docker-compose.yml
Compose file có các top-level keys:
services: # Container definitions (bắt buộc)
networks: # Custom networks (tùy chọn)
volumes: # Named volumes (tùy chọn)
configs: # Configuration objects (tùy chọn, Swarm mode)
secrets: # Secret objects (tùy chọn, Swarm mode)Service Configuration Chi Tiết
Mỗi service có thể cấu hình nhiều options:
services:
web:
# Image source (chọn một)
image: nginx:alpine # Dùng pre-built image
build: . # Build từ Dockerfile
build: # Build với options
context: ./app
dockerfile: Dockerfile.prod
args:
NODE_ENV: production
# Container settings
container_name: my-web # Tên container tùy chỉnh
hostname: web-server # Hostname container
restart: unless-stopped # Restart policy
# Networking
ports:
- "80:80" # host:container
- "443:443"
- "127.0.0.1:8080:8080" # Bind vào interface cụ thể
# Storage
volumes:
- ./src:/app/src # Bind mount
- app-data:/app/data # Named volume
- /app/node_modules # Anonymous volume
# Environment
environment:
NODE_ENV: production
API_KEY: ${API_KEY} # Từ host environment
env_file:
- .env # Load từ file
# Dependencies
depends_on:
- db
- redis
# Resource limits
deploy:
resources:
limits:
cpus: "0.5"
memory: 512M
reservations:
cpus: "0.25"
memory: 256M
# Health check
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:80"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
# Logging
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"Environment Variables
Docker Compose hỗ trợ nhiều cách set environment variables:
Inline trong docker-compose.yml
services:
app:
environment:
NODE_ENV: production
PORT: "3000"
DEBUG: "false"Từ .env File
# .env
POSTGRES_USER=admin
POSTGRES_PASSWORD=supersecret
POSTGRES_DB=myapp
APP_PORT=3000services:
app:
env_file:
- .env
ports:
- "${APP_PORT}:3000"
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}Variable Substitution
services:
app:
image: my-app:${TAG:-latest} # Default 'latest' nếu TAG chưa set
environment:
LOG_LEVEL: ${LOG_LEVEL:?error} # Error nếu LOG_LEVEL chưa setDepends_on và Health Checks
depends_on kiểm soát thứ tự khởi động, nhưng không đợi service sẵn sàng:
services:
app:
build: .
depends_on:
postgres:
condition: service_healthy # Đợi health check pass
redis:
condition: service_started # Chỉ đợi container start
postgres:
image: postgres:16-alpine
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 5s
retries: 5Restart Policies
services:
app:
restart: "no" # Không restart (mặc định)
restart: always # Luôn restart
restart: on-failure # Restart chỉ khi exit code khác 0
restart: unless-stopped # Restart trừ khi dừng thủ côngProduction: Dùng
unless-stoppedhoặcalwayscho critical services.
Phần 5: Compose Networking
Docker Compose tự động tạo network cho project. Tất cả services giao tiếp bằng service name làm hostname.
Default Network
# docker-compose.yml
services:
app:
build: .
ports:
- "3000:3000"
db:
image: postgres:16-alpine
redis:
image: redis:7-alpineVới setup này:
- Compose tạo network tên
<project>_default appkết nối PostgreSQL tạidb:5432appkết nối Redis tạiredis:6379- Tất cả services giao tiếp bằng service name
// Trong app container, kết nối bằng service names:
const dbUrl = 'postgres://admin:secret@db:5432/mydb'; // "db" = service name
const redisUrl = 'redis://redis:6379'; // "redis" = service nameCustom Networks
Cho setup phức tạp hơn, define custom networks để isolate nhóm services:
services:
# Frontend — chỉ nói chuyện với API
frontend:
build: ./frontend
ports:
- "80:80"
networks:
- frontend-net
# API — nói chuyện với frontend VÀ backend services
api:
build: ./api
ports:
- "3000:3000"
networks:
- frontend-net
- backend-net
# Database — chỉ API truy cập được
db:
image: postgres:16-alpine
networks:
- backend-net
# Redis — chỉ API truy cập được
redis:
image: redis:7-alpine
networks:
- backend-net
networks:
frontend-net:
driver: bridge
backend-net:
driver: bridgeContainer frontend không thể truy cập db hoặc redis trực tiếp — network isolation được enforce.
Phần 6: Compose cho Development
Docker Compose thực sự tỏa sáng trong development workflows. Setup hot reload, override files và development-specific configurations.
Volume Mounts cho Hot Reload
Mount source code như volume để thay đổi reflect ngay lập tức:
services:
app:
build: .
ports:
- "3000:3000"
volumes:
- ./src:/app/src # Mount source code
- /app/node_modules # Tránh overwrite node_modules
environment:
NODE_ENV: development
command: npm run dev # Dùng dev server với hot reloadOverride Files
Docker Compose tự động merge docker-compose.yml với docker-compose.override.yml:
docker-compose.yml (base — shared settings):
services:
app:
build: .
ports:
- "3000:3000"
depends_on:
- db
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: myapp
POSTGRES_USER: admin
POSTGRES_PASSWORD: secret
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:docker-compose.override.yml (development — tự động load):
services:
app:
build:
target: development
volumes:
- ./src:/app/src
- /app/node_modules
environment:
NODE_ENV: development
DEBUG: "true"
command: npm run dev
db:
ports:
- "5432:5432" # Expose DB port cho local toolsdocker-compose.prod.yml (production — chỉ định rõ):
services:
app:
build:
target: production
environment:
NODE_ENV: production
restart: unless-stopped
deploy:
resources:
limits:
cpus: "1.0"
memory: 1G
db:
restart: unless-stopped
# Không expose port — chỉ truy cập được trong Docker networkCách dùng:
# Development (tự động load override file)
docker compose up
# Production (chỉ định prod file)
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -dCompose Profiles
Dùng profiles để include services có điều kiện:
services:
app:
build: .
ports:
- "3000:3000"
db:
image: postgres:16-alpine
# Chỉ start khi profile "debug" active
adminer:
image: adminer
ports:
- "8080:8080"
profiles:
- debug
# Chỉ start khi profile "monitoring" active
prometheus:
image: prom/prometheus
ports:
- "9090:9090"
profiles:
- monitoring
grafana:
image: grafana/grafana
ports:
- "3001:3000"
profiles:
- monitoring# Start chỉ app + db
docker compose up -d
# Start app + db + adminer
docker compose --profile debug up -d
# Start app + db + monitoring stack
docker compose --profile monitoring up -d
# Start tất cả
docker compose --profile debug --profile monitoring up -dPhần 7: Ví Dụ Full-Stack Application
Gộp tất cả lại với một full-stack application hoàn chỉnh: React frontend + Node.js API + PostgreSQL + Redis.
Cấu Trúc Project
my-fullstack-app/
├── frontend/
│ ├── Dockerfile
│ ├── package.json
│ ├── src/
│ └── nginx.conf
├── api/
│ ├── Dockerfile
│ ├── package.json
│ └── src/
├── docker-compose.yml
├── docker-compose.override.yml
├── docker-compose.prod.yml
├── .env
└── .env.exampleAPI Dockerfile (Multi-Stage)
# api/Dockerfile
FROM node:20-alpine AS base
WORKDIR /app
COPY package*.json ./
# Development stage
FROM base AS development
RUN npm install
COPY . .
CMD ["npm", "run", "dev"]
# Build stage
FROM base AS builder
RUN npm ci
COPY . .
RUN npm run build
# Production stage
FROM node:20-alpine AS production
WORKDIR /app
RUN addgroup -S app && adduser -S app -G app
COPY package*.json ./
RUN npm ci --production
COPY --from=builder /app/dist ./dist
USER app
EXPOSE 3000
CMD ["node", "dist/app.js"]Frontend Dockerfile (Multi-Stage)
# frontend/Dockerfile
FROM node:20-alpine AS base
WORKDIR /app
COPY package*.json ./
# Development stage
FROM base AS development
RUN npm install
COPY . .
EXPOSE 5173
CMD ["npm", "run", "dev", "--", "--host"]
# Build stage
FROM base AS builder
RUN npm ci
COPY . .
RUN npm run build
# Production stage — serve bằng Nginx
FROM nginx:alpine AS production
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]Nginx Configuration cho Frontend
# frontend/nginx.conf
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
# Handle client-side routing
location / {
try_files $uri $uri/ /index.html;
}
# Proxy API requests tới backend
location /api/ {
proxy_pass http://api:3000/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}Docker Compose Files
docker-compose.yml (base):
services:
frontend:
build:
context: ./frontend
depends_on:
- api
api:
build:
context: ./api
environment:
DATABASE_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}
REDIS_URL: redis://redis:6379
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
interval: 5s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
volumes:
- redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 5s
retries: 5
volumes:
pgdata:
redis-data:docker-compose.override.yml (development):
services:
frontend:
build:
target: development
ports:
- "5173:5173"
volumes:
- ./frontend/src:/app/src
- /app/node_modules
api:
build:
target: development
ports:
- "3000:3000"
volumes:
- ./api/src:/app/src
- /app/node_modules
environment:
NODE_ENV: development
DEBUG: "true"
db:
ports:
- "5432:5432"
redis:
ports:
- "6379:6379"docker-compose.prod.yml (production):
services:
frontend:
build:
target: production
ports:
- "80:80"
restart: unless-stopped
api:
build:
target: production
environment:
NODE_ENV: production
restart: unless-stopped
db:
restart: unless-stopped
redis:
restart: unless-stopped.env file:
POSTGRES_DB=myapp
POSTGRES_USER=admin
POSTGRES_PASSWORD=changeme_in_productionChạy Full Stack
# Development — hot reload, exposed ports, debug tools
docker compose up
# Production build và chạy
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build
# Xem logs
docker compose logs -f
# Xem logs service cụ thể
docker compose logs -f api
# Dừng tất cả
docker compose down
# Dừng và xóa volumes (clean slate)
docker compose down -vPhần 8: Docker Compose Commands Cần Biết
Lifecycle Commands
# Start services (foreground)
docker compose up
# Start services (background)
docker compose up -d
# Start với rebuild
docker compose up -d --build
# Dừng services
docker compose stop
# Dừng và xóa containers, networks
docker compose down
# Dừng, xóa containers VÀ xóa volumes
docker compose down -v
# Restart service cụ thể
docker compose restart apiInspection Commands
# Liệt kê running services
docker compose ps
# Xem logs (tất cả services)
docker compose logs
# Follow logs cho service cụ thể
docker compose logs -f api
# Xem resource usage
docker compose topExecution Commands
# Chạy command trong running container
docker compose exec api sh
docker compose exec db psql -U admin myapp
# Chạy one-off command (tạo container mới)
docker compose run --rm api npm test
docker compose run --rm api npm run migrateBuild Commands
# Build tất cả images
docker compose build
# Build service cụ thể
docker compose build api
# Build không cache
docker compose build --no-cache
# Pull latest images
docker compose pullScaling Services
# Scale service lên 3 instances
docker compose up -d --scale api=3
# Lưu ý: cần bỏ fixed port mapping
# và dùng load balancer (như Nginx) phía trướcPatterns và Tips Thường Dùng
1. Wait-for-It Pattern
Đôi khi depends_on với health checks chưa đủ. Dùng wait script:
services:
api:
build: .
command: >
sh -c "
echo 'Waiting for database...' &&
while ! nc -z db 5432; do sleep 1; done &&
echo 'Database is ready!' &&
npm run migrate &&
npm start
"
depends_on:
- db2. Database Initialization
Chạy SQL scripts khi start lần đầu:
services:
db:
image: postgres:16-alpine
volumes:
- pgdata:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql # Tự chạy lần đầu3. Chia Sẻ Data Giữa Services
services:
app:
volumes:
- shared-uploads:/app/uploads
worker:
volumes:
- shared-uploads:/app/uploads
volumes:
shared-uploads:4. Dùng YAML Anchors Giảm Trùng Lặp
x-common-env: &common-env
NODE_ENV: production
LOG_LEVEL: info
TZ: UTC
services:
api:
build: ./api
environment:
<<: *common-env
PORT: "3000"
worker:
build: ./worker
environment:
<<: *common-env
CONCURRENCY: "5"5. Multi-Compose File cho Các Environments
# docker-compose.yml — base config
# docker-compose.override.yml — dev (tự động load)
# docker-compose.prod.yml — production
# docker-compose.test.yml — testing
# Chạy tests
docker compose -f docker-compose.yml -f docker-compose.test.yml run --rm api npm test
# Deploy production
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -dBài Tập Thực Hành
Bài 1: Dockerfile Challenge
Viết multi-stage Dockerfile cho ứng dụng Python Flask:
- Build stage: install dependencies từ
requirements.txt - Production stage: dùng
python:3.12-slim, chạy non-root user - Final image phải dưới 200MB
Bài 2: Compose Stack
Tạo docker-compose.yml cho WordPress stack:
- WordPress container (port 8080)
- MySQL container với persistent volume
- Dùng environment variables cho database credentials
- Thêm health checks cho cả hai services
Bài 3: Full Development Environment
Setup development environment với:
- Node.js API có hot reload
- PostgreSQL với initialization scripts
- Redis cho caching
- Adminer để quản lý database (dùng profiles)
- Tách development và production configurations
Tiếp Theo
Bài tiếp theo sẽ đi sâu vào Kubernetes Fundamentals — học cách orchestrate containers ở quy mô lớn:
- Kiến trúc Kubernetes (control plane, worker nodes)
- Pods, Deployments và Services
- ConfigMaps và Secrets
- kubectl commands cần thiết
- Setup local Kubernetes cluster
Tóm Tắt
✅ Dockerfile define cách build images — dùng FROM, COPY, RUN, CMD
✅ Luôn dùng .dockerignore để giữ build context nhỏ
✅ Sắp xếp Dockerfile instructions cho optimal layer caching
✅ Multi-stage builds giảm image size 70-99%
✅ Chạy containers dưới non-root users cho security
✅ Docker Compose define multi-container apps trong một YAML file
✅ Services giao tiếp bằng name trong Compose networks
✅ Dùng override files để tách dev và production configs
✅ Health checks + depends_on đảm bảo thứ tự startup đúng
✅ Profiles include services có điều kiện như debug tools
Series: Lộ Trình Docker & Kubernetes
Trước đó: Giai đoạn 1: Docker Cơ Bản
Tiếp theo: Giai đoạn 3: Kubernetes Cơ Bản
Có câu hỏi về Dockerfile hay Docker Compose? Hãy liên hệ hoặc để lại comment!
📬 Đăng ký nhận bản tin
Nhận những bài viết mới nhất qua email mỗi tuần. Không spam, hủy đăng ký bất cứ lúc nào.
Chúng tôi tôn trọng quyền riêng tư của bạn. Hủy đăng ký bất cứ lúc nào.
💬 Comments
Đăng nhập để bình luận
Chúng tôi sẽ không đăng bất cứ điều gì mà không có sự cho phép của bạn.