Docker Compose mất file upload sau khi restart container — fix bằng volume mount


Docker Compose mất file upload sau khi restart container — fix bằng volume mount

Dự án chạy ổn định cả tuần, khách hàng upload file đính kèm vào ticket bình thường. Thế rồi đến lúc deploy phiên bản mới — docker compose down && docker compose up -d --build — mọi file đính kèm cũ đều trả về 404. Database vẫn còn đủ metadata: filename, stored_path, file size. Nhưng khi backend đọc file từ đường dẫn đó thì không còn gì trên đĩa nữa.

Đây là một trong những lỗi “tân binh Docker” kinh điển nhất, và nó rất dễ bỏ qua vì chỉ xuất hiện sau lần restart đầu tiên.

Triệu chứng

  • File upload hoạt động bình thường ngay sau khi upload.
  • Sau docker compose down/up, deploy mới, hoặc server reboot: toàn bộ file upload biến mất.
  • API trả về 404 File not found on disk hoặc tương tự.
  • Database vẫn còn bản ghi đầy đủ — chỉ file vật lý là không còn.
  • Lỗi không xuất hiện trong môi trường local nếu bạn ít khi restart container.

Nguyên nhân

Docker container có ephemeral filesystem — tức là filesystem riêng, tồn tại trong thời gian sống của container. Khi container bị xóa (dù chỉ để rebuild), mọi thứ bên trong container filesystem đều mất theo.

Nếu backend ghi file upload vào /app/files/ bên trong container mà không có volume mount, thực chất bạn đang ghi vào lớp writable layer của container — lớp này biến mất ngay khi docker compose down.

Container filesystem (ephemeral):
  /app/
    main.py
    files/          ← ghi vào đây = mất khi restart
      upload_123.pdf
      avatar_456.jpg

Database không bị mất vì thường đã được cấu hình volume ngay từ đầu (ví dụ helpdesk_db_data:/var/lib/mysql). File storage hay bị quên vì ít ai nghĩ đến lúc khởi tạo project.

Cách tái hiện

# docker-compose.yml — SAI, không có volume cho files
services:
  backend:
    build: ./backend
    ports:
      - "8001:8001"
    # Không khai báo volumes → files ghi vào container filesystem
    # → mất hoàn toàn sau docker compose down

Upload một file, kiểm tra thấy hoạt động. Chạy docker compose restart backend. Gọi lại endpoint lấy file — 404.

Cách sửa

Thêm volumes vào tất cả service có đọc/ghi file:

services:
  backend:
    build: ./backend
    ports:
      - "8001:8001"
    volumes:
      - ./files:/app/files   # host:container

  celery_worker:
    build: ./backend
    command: celery -A app.tasks worker --loglevel=info
    volumes:
      - ./files:/app/files   # worker xử lý file cũng phải mount

Cách hoạt động: ./files là thư mục trên host machine (máy chủ thật), được mount vào /app/files bên trong container. Khi backend ghi file vào /app/files/upload_123.pdf, thực ra Docker ghi lên ./files/upload_123.pdf trên host — tồn tại vĩnh viễn, không phụ thuộc vào vòng đời container.

Docker tự tạo thư mục ./files/ trên host nếu chưa có.

Kiểm tra sau khi sửa

# 1. Apply config mới
docker compose down
docker compose up -d

# 2. Upload một file test qua API hoặc UI

# 3. Kiểm tra file có trên HOST không (ngoài container)
ls ./files/
# → phải thấy file vừa upload

# 4. Restart container
docker compose restart backend

# 5. Gọi lại endpoint lấy file — phải vẫn còn
curl http://localhost:8001/api/attachments/123
# → 200 OK, không còn 404

Nếu bước 3 thấy file trên host, bạn đã fix đúng. File tồn tại độc lập với container.

Lưu ý thêm

File cũ đã mất không khôi phục được. Volume mount chỉ có tác dụng từ lần deploy tiếp theo trở đi. File upload trước khi thêm volume đã nằm trong layer container đã bị xóa — không còn cách nào lấy lại.

Đường dẫn tương đối. ./files là tương đối so với vị trí file docker-compose.yml. Nếu chạy docker compose từ thư mục khác, đường dẫn sẽ sai. Dùng đường dẫn tuyệt đối nếu có thể gây nhầm lẫn:

volumes:
  - /home/deploy/myapp/files:/app/files

Production cloud nên dùng object storage. Volume mount giải quyết vấn đề trước mắt, nhưng với môi trường production có nhiều server hoặc autoscaling, filesystem local sẽ không đồng bộ giữa các instance. Giải pháp lâu dài là lưu file lên S3, Cloudflare R2, hoặc MinIO — tránh hoàn toàn vấn đề ephemeral filesystem và còn được CDN, backup tự động kèm theo.


Tóm lại: container filesystem là tạm thời theo thiết kế — bất kỳ dữ liệu nào cần tồn tại qua các lần restart đều phải được mount ra ngoài bằng volume. Database thường được nhớ đến, file storage thì hay bị bỏ qua cho đến khi khách hàng báo mất file. Thêm volumes vào docker-compose.yml ngay từ đầu dự án, trước khi có dữ liệu thật.