jvinhit//lab

Search posts

Type to search across journal entries.

navigate open esc close

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.

8 MIN READ Updated JUL 11, 2026

Đâ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), volumesnetworks 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.yaml 3 services up network: appnet (auto-created) web build: . db postgres redis cache db redis named volume :8080 → host
One compose.yaml → many services on an auto-created network; they reach each other by service name, db persists to a named volume, web publishes a host port

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 key version: — 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:5432redis: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

down không -v giữ volume; down -v xóa volume.


build vs image

Mỗi service cần hoặc image chạy được hoặc hướng dẫn build:

ApproachKhi nào
image: postgres:16-alpineKé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:devBuild 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 --buildup 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ó healthcheckdepends_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_on khô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 --build sau khi sửa Dockerfile — bạn chạy image cũ từ cache.
  • Sửa compose.yaml nhưng không tạo lạidocker compose up -d đồng bộ nhiều thay đổi; không chắc: docker compose up -d --force-recreate.
  • docker-compose vs docker compose — cài plugin Compose V2; bản v1 có gạch ngang là legacy.
  • down giữ volume; down -v xóa — mất dữ liệu production hay gặp ở đây.
  • localhost trong config app — trong web, localhost là chính container web, không phải db. 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.yamlweb (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 running

4. 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 name

5. 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 gone

Nâ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 up thay cho nhiều lệnh docker run và mạng tạo tay.
  • services là container; Compose tạo mạng project với DNS theo tên service.
  • Dùng image cho image bên thứ ba, build cho Dockerfile của bạn; rebuild bằng up --build.
  • depends_on sắp thứ tự khởi động, không đợi sẵn sàng — healthcheck ở Phần 6.
  • ports, volumes, environment/env_file tương ứng -p, -v, -e từ Phần 3–4.
  • docker compose down -v xóa named volume; down thườ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.