Tóm tắt: Có 2 cách đưa code vào container khi dev: bind mount (volumes: ./app:/srv/app) hoặc docker compose watch với khối develop.watch (action sync, sync+restart, rebuild). Debug Python bằng debugpy mở cổng 5678 trong container rồi attach từ VS Code. Stack nhiều service xem Docker Compose – pattern hay dùng.
Luồng hoạt động
flowchart LR A["Sửa code trên máy"] --> B{"Cách đưa code vào"} B -->|"bind mount"| C["Container thấy file ngay"] B -->|"compose watch"| D["sync / sync+restart / rebuild"] C --> E["App reload"] D --> E E --> F["debugpy :5678"] F --> G["VS Code attach"]
Cấu hình
Chọn bind mount hay watch
| Tiêu chí | Bind mount | compose watch |
|---|---|---|
| Cấu hình | volumes: - ./app:/srv/app |
develop.watch trong compose |
Đổi dependency (pyproject.toml) |
Phải build lại tay | action: rebuild tự làm |
Thư mục sinh ra (.venv, node_modules) |
Dễ đè lên thư mục trong image | Chỉ sync path khai báo, có ignore |
| Quyền file (UID/GID) | Hay lệch giữa host và container | File sync vào container |
| Phù hợp | Code thay đổi liên tục, app tự reload | Cần phân biệt sửa code / sửa config / đổi dependency |
Compose cho dev
name: netapp
services:
api:
build: .
command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
ports:
- "127.0.0.1:8000:8000"
environment:
PYTHONUNBUFFERED: "1"
develop:
watch:
- action: sync
path: ./app
target: /srv/app
ignore:
- __pycache__/
- action: sync+restart
path: ./config.yaml
target: /srv/config.yaml
- action: rebuild
path: ./pyproject.tomldocker compose watch # build, up, rồi theo dõi file
docker compose up --watch # tương đương, kèm log của service
docker compose up -d --build && docker compose watch --no-up # tách up và watchÝ nghĩa các action:
sync: copy file thay đổi vàotargettrong container, app tự reload (vduvicorn --reload).sync+restart: copy rồi restart container, dùng cho file config chỉ đọc lúc khởi động.rebuild: build lại image và recreate container, dùng khi đổi dependency hoặc Dockerfile.pathtương đối so với thư mục chứa compose file;ignorenhận pattern kiểu gitignore.
Bind mount (cách đơn giản)
services:
api:
volumes:
- ./app:/srv/app
command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --reloadChỉ mount thư mục code, không mount cả thư mục project (tránh đè .venv hoặc node_modules đã cài trong image). compose.override.yaml được docker compose up tự đọc, xem phần override ở Docker Compose – pattern hay dùng.
Debug Python bằng debugpy
Thêm debugpy vào dev dependency, rồi dùng file override riêng cho debug:
services:
api:
command: >
python -m debugpy --listen 0.0.0.0:5678
-m uvicorn app.main:app --host 0.0.0.0 --port 8000
ports:
- "127.0.0.1:5678:5678"docker compose -f compose.yaml -f compose.debug.yaml up -d{
"version": "0.2.0",
"configurations": [
{
"name": "Attach api (compose)",
"type": "debugpy",
"request": "attach",
"connect": { "host": "localhost", "port": 5678 },
"pathMappings": [
{ "localRoot": "${workspaceFolder}/app", "remoteRoot": "/srv/app" }
]
}
]
}- Thêm
--wait-for-clientvào lệnhdebugpynếu cần dừng ngay từ lúc khởi động cho tới khi debugger attach. - Bỏ
--reloadkhi debug: reloader của uvicorn chạy app ở tiến trình con, debugger hay mất kết nối. - Chỉ publish cổng debug về
127.0.0.1: debugpy cho phép thực thi code tuỳ ý, không được mở ra mạng. pathMappingssai thì breakpoint hiện xám (unverified), vì VS Code không khớp file local với file trong container.
Lệnh
| Mục đích | Lệnh |
|---|---|
| Xem file compose sau merge | docker compose -f compose.yaml -f compose.debug.yaml config |
| Vào shell service đang chạy | docker compose exec api sh |
| Chạy lệnh với user khác | docker compose exec -u root api sh |
| Chạy test một lần | docker compose run --rm api pytest -q |
| Chạy một lần nhưng publish cổng của service | docker compose run --rm --service-ports api sh |
| Log theo dõi | docker compose logs -f --tail 100 api |
| Build lại không cache | docker compose build --no-cache api |
| Dọn container và network | docker compose down |
Lỗi thường gặp
| Triệu chứng | Nguyên nhân | Cách xử lý |
|---|---|---|
| Sửa file mà app không đổi | App không có reload, hoặc path watch/mount sai | Thêm --reload (dev) hoặc dùng sync+restart; kiểm tra path/target |
watch báo không có gì để theo dõi |
Service thiếu khối develop.watch |
Khai báo develop.watch cho service cần sync |
.venv/node_modules trong image bị biến mất |
Bind mount cả project đè lên thư mục trong image | Chỉ mount thư mục code, hoặc dùng watch + ignore |
| File tạo trong container thuộc root trên host | Container chạy root, ghi vào bind mount | Chạy container bằng user: trùng UID host, hoặc dùng watch |
| Breakpoint không dừng | pathMappings sai hoặc app chạy ở tiến trình con do reload |
Sửa mapping, bỏ --reload khi debug |
Connection refused khi attach cổng 5678 |
Chưa publish cổng hoặc debugpy chưa chạy | Dùng compose.debug.yaml, kiểm tra docker compose ps và log |
| Đổi dependency mà container vẫn thiếu package | Image chưa build lại | action: rebuild hoặc docker compose up -d --build |