Post

Docker Volumes & Mounts: Giải quyết bài toán dữ liệu trong container

Docker Volumes & Mounts: Giải quyết bài toán dữ liệu trong container

Docker Volumes & Mounts: Giải quyết bài toán dữ liệu trong container


1. Vấn đề là gì?

Khi bạn chạy một ứng dụng trong Docker container, có một điều quan trọng cần hiểu: container hoàn toàn cô lập với máy host.

Điều đó có nghĩa là:

1
2
3
Máy host                  Container
─────────────────         ─────────────────
/home/user/my-app   ≠     /app

Hãy tưởng tượng bạn đang phát triển một ứng dụng Node.js. Bạn sửa file index.js trên máy, nhưng container vẫn đang chạy phiên bản — vì nó không biết gì về thay đổi trên máy bạn cả.

Tệ hơn nữa, khi container bị xóa hoặc restart, toàn bộ dữ liệu bên trong biến mất hoàn toàn. Điều này gây ra 2 vấn đề lớn:

Vấn đề 1: Development workflow chậm

1
Sửa code → Build lại image → Restart container → Test

Mỗi lần sửa một dòng code, bạn phải lặp lại toàn bộ quy trình trên. Không ai muốn làm vậy.

Vấn đề 2: Mất dữ liệu

1
2
3
4
5
Container chạy MySQL → Lưu 1000 records
    ↓
docker rm container
    ↓
Toàn bộ 1000 records biến mất 

Database, file upload, logs — tất cả đều mất khi container bị xóa.


Docker Volumes & Mounts sinh ra để giải quyết đúng 2 vấn đề này: kết nối filesystem của host với container, và persist dữ liệu vượt qua vòng đời của container.


2. Các loại Volume trong Docker

Docker có 3 loại volume, mỗi loại phục vụ một mục đích khác nhau.


Loại 1: Bind Mount

Cú pháp:

1
2
3
4
volumes:
  - /đường/dẫn/host:/đường/dẫn/container
  # hoặc dùng relative path
  - ./src:/app/src

Cơ chế:

1
2
3
4
Máy host ./src  ←── sync 2 chiều, real-time ──→  Container /app/src

Sửa file trên host  → Container thấy ngay 
Sửa file trong container → Host thấy ngay 

Ví dụ thực tế — Hot reload cho Node.js:

1
2
3
4
5
6
services:
  app:
    build: .
    volumes:
      - ./src:/app/src    # mount source code
    command: npm run dev  # nodemon watch

Mỗi lần bạn nhấn Ctrl+S, nodemon trong container phát hiện file thay đổi và tự restart. Không cần build lại image.

Khi nào dùng Bind Mount:

  • Source code trong môi trường development
  • Config file (nginx.conf, settings.php…)
  • File cần chia sẻ real-time giữa host và container

Loại 2: Named Volume

Cú pháp:

1
2
3
4
5
6
7
8
services:
  db:
    image: mysql
    volumes:
      - db_data:/var/lib/mysql   # tên_volume:container_path

volumes:
  db_data:    # khai báo ở cuối file

Cơ chế:

1
2
3
4
5
6
7
Docker managed storage (/var/lib/docker/volumes/db_data)
         ↕
Container /var/lib/mysql

Container bị xóa → Data vẫn còn 
docker-compose down → Data vẫn còn 
docker-compose down -v → Data mới bị xóa 

Docker tự quản lý nơi lưu trữ trên máy host — bạn không cần biết path cụ thể là đâu.

Ví dụ thực tế — Persist database:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
services:
  mysql:
    image: mysql:8
    environment:
      MYSQL_ROOT_PASSWORD: secret
    volumes:
      - mysql_data:/var/lib/mysql

  redis:
    image: redis:alpine
    volumes:
      - redis_data:/data

volumes:
  mysql_data:
  redis_data:

Dù bạn restart container bao nhiêu lần, data vẫn an toàn.

Khi nào dùng Named Volume:

  • Database (MySQL, PostgreSQL, MongoDB…)
  • Cache data (Redis)
  • Bất kỳ dữ liệu nào cần persist qua vòng đời container

Loại 3: Anonymous Volume (Trick đặc biệt)

Cú pháp:

1
2
volumes:
  - /app/node_modules    # chỉ có container path, không có host path

Cơ chế:

1
2
3
4
5
6
7
8
Bind Mount đang sync ./code → /app

Nếu không có anonymous volume:
  host ./node_modules (Mac/Windows) → ghi đè /app/node_modules (Linux) 
  → Lỗi do binary không tương thích

Với anonymous volume:
  /app/node_modules được Docker bảo vệ, KHÔNG bị sync từ host 

Ví dụ thực tế — Node.js project:

1
2
3
volumes:
  - ./src:/app/src          # sync source code 
  - /app/node_modules       # bảo vệ node_modules của container 

Đây là pattern bắt buộc với Node.js để tránh conflict giữa node_modules trên Mac/Windows với Linux container.

Khi nào dùng Anonymous Volume:

  • Bảo vệ node_modules, vendor, .venv trong container khỏi bị ghi đè
  • Giữ lại build artifacts trong container

Tóm tắt 3 loại

LoạiCú phápDocker quản lý?Persist?Dùng cho
Bind Mount./host:/containerBạn quản lýTrên hostSource code, config
Named Volumename:/containerDocker quản lýTrong DockerDatabase, persistent data
Anonymous Volume/containerDocker quản lýTạm thờiBảo vệ folder container

3. Best Practices

Chỉ mount những gì cần thiết

1
2
3
4
5
6
7
#  Không nên — mount toàn bộ project
volumes:
  - .:/app

#  Nên — chỉ mount folder source code
volumes:
  - ./src:/app/src

Mount toàn bộ project kéo theo cả .git, node_modules, .env vào container — gây chậm và có thể gây lỗi.


Luôn dùng Anonymous Volume cho dependencies

1
2
3
4
5
6
7
8
9
#  Pattern chuẩn cho Node.js
volumes:
  - ./src:/app/src
  - /app/node_modules    # không bao giờ bỏ dòng này

#  Pattern chuẩn cho PHP
volumes:
  - ./src:/app/src
  - /app/vendor

Phân biệt rõ môi trường dev và production

1
2
3
4
5
6
7
8
9
10
11
12
13
# docker-compose.yml (production)
services:
  app:
    build: .
    # Không có volumes — code đã được COPY vào image lúc build

# docker-compose.dev.yml (development)
services:
  app:
    build: .
    volumes:
      - ./src:/app/src      # chỉ mount khi dev
    command: npm run dev

Chạy dev:

1
docker-compose -f docker-compose.yml -f docker-compose.dev.yml up

Dùng Named Volume thay vì Bind Mount cho database

1
2
3
4
5
6
7
8
9
10
#  Không nên
volumes:
  - ./mysql-data:/var/lib/mysql    # dễ bị conflict permissions

#  Nên
volumes:
  - mysql_data:/var/lib/mysql      # Docker tự quản lý, an toàn hơn

volumes:
  mysql_data:

Thêm :ro (read-only) khi container không cần ghi

1
2
3
4
volumes:
  - ./config/nginx.conf:/etc/nginx/nginx.conf:ro     # nginx chỉ đọc config
  - ./custom:/app/modules/custom:ro                  # PHP modules chỉ đọc
  - ./docker-config/settings.php:/app/settings.php:ro

Tránh container vô tình ghi đè source code hoặc config quan trọng.


Đặt tên volume có ý nghĩa

1
2
3
4
5
6
7
8
9
10
#  Không rõ ràng
volumes:
  data1:
  data2:

#  Rõ ràng
volumes:
  mysql_data:
  redis_data:
  uploads_storage:

Backup Named Volume trước khi xóa

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Backup
docker run --rm \
  -v mysql_data:/data \
  -v $(pwd):/backup \
  alpine tar czf /backup/mysql_backup.tar.gz /data

# Xóa volume
docker volume rm mysql_data

# Restore
docker run --rm \
  -v mysql_data:/data \
  -v $(pwd):/backup \
  alpine tar xzf /backup/mysql_backup.tar.gz -C /

Tổng kết

1
2
3
4
Cần hot-reload code khi dev?        → Bind Mount  ./src:/app/src
Cần persist database?               → Named Volume mysql_data:/var/lib/mysql
Cần bảo vệ node_modules?           → Anonymous Volume /app/node_modules
Cần ngăn container ghi vào file?   → Thêm :ro vào cuối
This post is licensed under CC BY 4.0 by the author.