Cách phát triển module Odoo trong Docker mà tôi từng dùng suốt các bài đầu loạt này: sửa file trên máy, docker cp vào container, rồi -u module, rồi restart. Lặp mãi rất mệt. Có cách gọn hơn nhiều: bind-mount thư mục addons từ máy host vào container — sửa file trên máy, container thấy ngay, khỏi copy. Ghép với --dev=all, vòng lặp code trở nên mượt mà. Đây là cách dựng một docker-compose.yml cho dev.

Hai kiểu volume: bind-mount vs named volume

Đây là mấu chốt cả bài. Docker có hai kiểu gắn thư mục vào container:

  • Named volume (odoo-data:/var/lib/odoo): Docker tự quản, dữ liệu bền, nhưng bạn không sửa trực tiếp được từ máy host một cách tiện lợi. Hợp cho filestore, database.
  • Bind-mount (./addons:/mnt/extra-addons): trỏ thẳng một thư mục trên máy host vào container. Sửa file ở host là container thấy ngay lập tức. Đây chính là thứ ta cần cho code.

docker-compose.yml cho dev Odoo mount addons nóng: khối services có db là postgres 16 với biến môi trường POSTGRES_USER odoo POSTGRES_PASSWORD odoo POSTGRES_DB postgres và volume db-data gắn vào var lib postgresql data; service odoo dùng image odoo 19, depends_on db, mở cổng 8069 http và 8072 websocket, phần volumes gồm ba dòng: chấm gạch chéo addons gắn vào mnt extra-addons là bind-mount code trên máy bạn, chấm gạch chéo config gắn vào etc odoo là odoo.conf ở host, và named volume odoo-data gắn vào var lib odoo giữ filestore; command chạy odoo với dev bằng all để auto-reload và đọc XML từ file; cuối khai hai named volume db-data và odoo-data; phần dưới nhấn khác biệt then chốt là bind-mount host thì sửa trên máy bạn container thấy ngay, còn named volume thì Docker quản dữ liệu bền không cần soi tay

Hình 1: docker-compose.yml cho dev. Dòng vàng ./addons:/mnt/extra-addons là bind-mount — thư mục addons/ trên máy bạn xuất hiện trong container tại /mnt/extra-addons (đúng addons_path). command: odoo --dev=all để auto-reload. Còn odoo-data là named volume cho filestore — dữ liệu bền, khỏi soi tay.

Bind-mount hoạt động thật

Để bạn thấy đây không phải lý thuyết, tôi bind-mount một thư mục host chứa module quan_ca_phe vào container, rồi sửa file trên host và đọc lại trong container:

Bằng chứng bind-mount hoạt động thật: chạy docker run với volume bind thư mục dev-addons trên host vào mnt extra-addons chế độ chỉ đọc, lệnh ls liệt kê thư mục quan_ca_phe trong container thấy đủ các file init.py manifest.py models security views đúng như trên host; sau đó ghi một dòng đánh dấu sua-tren-host-151840 vào file models init.py trên host, rồi chạy lại container đọc dòng cuối của chính file đó và nhận đúng dòng sua-tren-host-151840 khớp hoàn toàn, chứng minh sửa trên host phản ánh ngay trong container không cần docker cp

Hình 2: Bằng chứng thật. Container thấy đủ file của module ngay qua bind-mount. Rồi tôi ghi dòng đánh dấu # sua-tren-host-151840 vào file trên host, và container đọc lại được đúng dòng đó — không hề docker cp. Sửa ở host, container thấy tức thì. Ghép với --dev=all: sửa .py là Odoo tự reload, sửa .xml là F5 thấy.

Vài lưu ý dựng compose dev

  • Mở cả cổng 8072 (websocket/longpolling): thiếu nó thì thông báo realtime và một số widget không chạy.
  • odoo.conf cũng nên bind-mount (./config:/etc/odoo) để chỉnh tham số mà khỏi vào container.
  • Quyền file: trên Linux, UID trong container (thường là odoo) phải đọc được thư mục host. Lỗi quyền làm module "vô hình" như đã nói ở bài addons_path.
  • Đừng bind-mount /var/lib/odoo (filestore) ra host — để named volume cho Docker quản, tránh rối quyền và rác.

Vòng lặp dev hoàn chỉnh

# 1. Khởi động stack
docker compose up -d

# 2. Cài module lần đầu (đọc từ thư mục bind-mount)
docker compose exec odoo odoo -d dev -i quan_ca_phe --stop-after-init

# 3. Sửa code trên máy bạn bằng IDE quen thuộc
#    - sửa .py  → --dev=all tự reload
#    - sửa .xml → F5 thấy ngay (nhờ tính năng 'xml' của dev)
#    - đổi schema (thêm trường) → chỉ khi đó mới cần -u:
docker compose exec odoo odoo -d dev -u quan_ca_phe --stop-after-init

So với docker cp + restart mỗi lần, đây là trời với vực. Đổi trường/model mới cần -u; còn sửa logic Python hay giao diện XML thì gần như tức thì.

Khác gì compose production?

  • Dev bind-mount code để sửa nóng; production đóng code vào image (COPY trong Dockerfile) để bất biến, tái lập được.
  • Dev bật --dev=all; production tuyệt đối không.
  • Dev một worker (--workers=0) cho dễ debug; production nhiều worker.

Nhớ ba ý

  1. Bind-mount (./addons:/mnt/extra-addons) cho container thấy code trên máy bạn tức thì — tôi đã chứng minh sửa host, container đọc đúng dòng đó, khỏi docker cp.
  2. Ghép với command: odoo --dev=all: sửa .py tự reload, sửa .xml F5 thấy; chỉ đổi schema mới cần -u.
  3. Dùng named volume cho filestore (/var/lib/odoo) và database — bền, khỏi soi tay; nhớ mở cả cổng 8072 cho websocket.

Xong chương môi trường & công cụ (15 bài), ta bước vào lõi lập trình Odoo: ORM. Phần sau mở màn bằng khái niệm nền tảng nhất — recordset: vì sao trong Odoo mọi thứ là tập bản ghi chứ không phải object đơn lẻ.