Docker for Developers · Part 5 — Docker Compose Fundamentals
Dựng ứng dụng nhiều container bằng compose.yaml: service, image/build, network, volume, dependency và workflow hằng ngày.
Đây là Phần 5 của chặng nền tảng đưa bạn từ “mới nghe nói về Docker” đến tự tin build, chạy và debug ứng dụng container — Docker, Docker Compose và Kubernetes. Bạn đã build image (Phần 2), lưu dữ liệu (Phần 3), và nối container trên user-defined network (Phần 4). Mỗi phần kết thúc bằng bài tập; hãy làm, đừng chỉ đọc.
Chạy stack thực tế — web + database + cache — bằng tay nghĩa là một đống flag docker run, docker network create, named volume và biến môi trường bạn phải nhớ mỗi lần. Docker Compose thay nghi lễ đó bằng một file khai báo (compose.yaml) và một lệnh: docker compose up.
Điểm quan trọng: Compose không chỉ tiết kiệm gõ lệnh; nó biến topology local thành tài liệu chạy được. Một senior đọc compose.yaml phải thấy ngay app có bao nhiêu service, service nào publish ra host, dữ liệu nào persist, service nào chỉ nội bộ, và config nào đến từ môi trường.
Mô hình compose.yaml
File Compose mô tả một project: tập service (container) dùng chung một mạng mặc định và volume đặt tên tùy chọn. Tên file mặc định hiện đại là compose.yaml (cũng chấp nhận docker-compose.yaml).
Cấp cao: services (mỗi key = một container), volumes và networks tùy chọn. Key trong service tương ứng docker run: image / build, ports, volumes, environment / env_file, depends_on (chỉ thứ tự), command.
Compose tự động tạo user-defined bridge cho project — cùng loại mạng bạn tạo tay ở Phần 4. Container gọi nhau bằng tên service (db, không phải localhost).
Dùng
docker compose(plugin v2, có dấu cách) — không phải binary cũdocker-compose(v1 có gạch ngang) trừ khi bạn bảo trì CI cũ. File Compose hiện đại không cần keyversion:— Docker bỏ qua và có thể cảnh báo.
Ranh giới thực tế: Compose rất mạnh cho local dev, demo, CI integration test và một số server nhỏ. Nó không thay thế orchestrator khi bạn cần nhiều node, self-healing mạnh, rollout có policy, scheduling tài nguyên và RBAC. Đó là lý do series đi tiếp tới Kubernetes ở Phần 9–10.
compose.yaml đầu tiên
Stack ba service — web (build: . hoặc image), Postgres + named volume, Redis (chỉ nội bộ):
services:
web:
build: . # or image: nginx:alpine for a quick smoke test
ports:
- "8080:3000"
environment:
DATABASE_URL: postgres://postgres:secret@db:5432/app
REDIS_URL: redis://redis:6379
depends_on: [db, redis]
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: secret
POSTGRES_DB: app
volumes:
- pgdata:/var/lib/postgresql/data
redis:
image: redis:7-alpine
volumes:
pgdata:
docker compose config && docker compose up -d
Kết nối db:5432 và redis:6379 bằng tên service, không phải localhost.
Quy trình hằng ngày
Các lệnh này bao phủ phần lớn Compose hằng ngày:
docker compose up # create + start (foreground)
docker compose up -d # detached
docker compose up --build # rebuild images before start (after Dockerfile edits)
docker compose down # stop + remove containers & project network
docker compose down -v # also remove named volumes declared in the file
docker compose ps # running services in this project
docker compose logs web # logs for one service
docker compose logs -f # follow all services
docker compose exec db sh # shell inside a running service (use psql, redis-cli, etc.)
docker compose build # build images without starting
docker compose pull # pull external images only
downkhông-vgiữ volume;down -vxóa volume.
build vs image
Mỗi service cần hoặc image chạy được hoặc hướng dẫn build:
| Approach | Khi nào |
|---|---|
image: postgres:16-alpine | Kéo image công bố — database, message broker, tool chính thức. |
build: . (or build: ./api) | Build từ Dockerfile trong context đó — code ứng dụng của bạn. |
build + image: myapp:dev | Build và gắn tag cố định để rebuild và docker image ls dễ theo dõi. |
services:
api:
build: { context: ., dockerfile: Dockerfile }
image: myshop-api:dev
db:
image: postgres:16-alpine
Sau khi sửa Dockerfile, chạy docker compose up --build — up thường có thể dùng lại image cache.
Service discovery & depends_on
Trên mạng project, DNS nhúng phân giải tên service → IP container — cùng quy tắc tên container trên user-defined bridge ở Phần 4:
web container db container
┌────────────────────┐ ┌────────────────────┐
│ DATABASE_URL= │ TCP :5432 │ POSTGRES listening │
│ ...@db:5432/app │ ─────────────► │ hostname "db" │
└────────────────────┘ └────────────────────┘
▲ ▲
└──────── Compose DNS: "db" ──────────┘
depends_on báo Compose container nào start trước — nó không đợi Postgres nhận kết nối hay Redis sẵn sàng:
services:
web:
depends_on:
- db
- redis
Nếu web crash vì “connection refused” lúc boot, đó là bình thường khi chưa có readiness — Phần 6 sẽ có healthcheck và depends_on với condition: service_healthy.
Ports, volume, env trong Compose
Tương đương YAML của flag bạn đã biết:
services:
web:
ports: ["8080:3000"] # -p (browser on host)
expose: ["3000"] # documents an internal port; it is not an ACL
volumes: ["./src:/app/src"] # bind mount (Part 3)
environment:
NODE_ENV: development
API_KEY: ${API_KEY} # from shell or project .env
env_file: [.env.local]
db:
volumes: [pgdata:/var/lib/postgresql/data]
volumes:
pgdata: # declare named volumes here
Chỉ publish ports cho service host cần gọi; DB thường nội bộ (db:5432 trên mạng project). expose không publish ra host, nhưng cũng không chặn service cùng network gọi các port khác mà process đang listen — nó là metadata, không phải firewall. Compose đọc .env cho ${VAR} — không commit secret (Phần 3).
Các bẫy thường gặp
depends_onkhông phải “đợi sẵn sàng” — chỉ thứ tự start. Dùng healthcheck ở Phần 6 hoặc retry trong app.- Quên
up --buildsau khi sửa Dockerfile — bạn chạy image cũ từ cache. - Sửa
compose.yamlnhưng không tạo lại —docker compose up -dđồng bộ nhiều thay đổi; không chắc:docker compose up -d --force-recreate. docker-composevsdocker compose— cài plugin Compose V2; bản v1 có gạch ngang là legacy.downgiữ volume;down -vxóa — mất dữ liệu production hay gặp ở đây.localhosttrong config app — trongweb,localhostlà chính container web, không phảidb. Dùng hostname service.- Publish mọi cổng — hiếm khi cần
- "5432:5432"trừ khi client GUI trên host cần.
Bảng tra nhanh
# lifecycle
docker compose up -d
docker compose up -d --build
docker compose down
docker compose down -v
# observe & debug
docker compose ps
docker compose logs -f web
docker compose exec db psql -U postgres -d app
docker compose config
# one-off commands on the project network
docker compose run --rm -e PGPASSWORD=secret db psql -h db -U postgres -c 'SELECT 1'
Bài tập / Exercises
Tạo thư mục compose-lab/, thêm file dưới đây, chạy mọi thứ từ thư mục đó.
1. Viết compose.yaml có web (nginx:alpine, cổng 8080:80), db (postgres:16-alpine, mật khẩu secret, named volume pgdata), và redis (redis:7-alpine, không publish cổng host).
Lời giải
# compose-lab/compose.yaml
services:
web:
image: nginx:alpine
ports:
- "8080:80"
depends_on:
- db
- redis
db:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD: secret
volumes:
- pgdata:/var/lib/postgresql/data
redis:
image: redis:7-alpine
volumes:
pgdata:2. Bật stack ở chế độ detached, liệt kê service, mở http://localhost:8080, và chứng minh db phân giải được trên mạng project bằng client psql một lần.
Lời giải
cd compose-lab
docker compose up -d
docker compose ps
curl -sI http://localhost:8080 | head -n 1
docker compose run --rm -e PGPASSWORD=secret db \
psql -h db -U postgres -c "SELECT current_database();"3. Theo log live chỉ service db, rồi Ctrl+C dừng theo dõi.
Lời giải
docker compose logs -f db
# Ctrl+C stops following; containers keep running4. exec vào web và kiểm tra hostname redis phân giải được (vd bằng wget hoặc getent).
Lời giải
docker compose exec web getent hosts redis
# prints an IP — proof DNS resolves the service name5. Tắt không -v, bật lại stack, xác nhận dữ liệu Postgres còn; rồi down -v và xác nhận named volume biến mất.
Lời giải
docker compose exec db psql -U postgres -c \
"CREATE TABLE markers (id int); INSERT INTO markers VALUES (1);"
docker compose down && docker compose up -d
docker compose exec db psql -U postgres -c "SELECT * FROM markers;" # still there
docker compose down -v
docker volume ls | grep compose-lab # pgdata goneNâng cao: Thêm build: . cho web với Dockerfile tối giản từ Phần 2, chạy docker compose up --build, sửa Dockerfile — xác nhận cần --build mới thấy thay đổi.
Lời giải
# Add build: . + a Dockerfile that writes /usr/share/nginx/html/build.txt
docker compose up -d --build && curl http://localhost:8080/build.txt
# Edit Dockerfile, then up -d alone → stale; up -d --build → freshĐiểm chính
- Compose = một
compose.yaml+docker compose upthay cho nhiều lệnhdocker runvà mạng tạo tay. serviceslà container; Compose tạo mạng project với DNS theo tên service.- Dùng
imagecho image bên thứ ba,buildcho Dockerfile của bạn; rebuild bằngup --build. depends_onsắp thứ tự khởi động, không đợi sẵn sàng — healthcheck ở Phần 6.ports,volumes,environment/env_filetương ứng-p,-v,-etừ Phần 3–4.docker compose down -vxóa named volume;downthường giữ lại.
Tiếp theo
Phần 6 — Compose nâng cao: Env, Profiles, Healthchecks & Scaling: cho depends_on đợi database healthy, tách dev/prod bằng profiles, ghi đè cấu hình bằng nhiều file Compose, và scale service stateless với --scale.