Nên chạy database trong Docker hay trên host?
Chạy database trong Docker trên VPS hoàn toàn an toàn nếu bạn quản lý tốt volume, backup và giới hạn bộ nhớ. Bài viết phân tích rủi ro thực tế khi vận hành container production.
Nên chạy database trong Docker hay trên host?
Hãy chạy database trong Docker. Đối với một stack ứng dụng trên một VPS, việc sử dụng PostgreSQL, MySQL, MongoDB hoặc Redis dưới dạng container là lựa chọn sản xuất bình thường, và những tranh cãi xung quanh vấn đề này thường đi sai hướng. Một container thực chất là một tiến trình Linux được bao bọc bởi namespaces và cgroups, không phải là một máy ảo, vì vậy không có hypervisor nào nằm giữa database và ổ cứng. Với bind mount hoặc local named volume, các thao tác đọc ghi sẽ diễn ra trực tiếp trên filesystem của host, giống hệt như khi bạn cài đặt qua trình quản lý gói.
Chi phí thực sự nằm ở khâu vận hành. Có 4 yếu tố quyết định việc thiết lập này là ổn định hay là một thảm họa: nơi lưu trữ dữ liệu, quyền sở hữu thư mục đó, quy trình nâng cấp phiên bản chính (major version) diễn ra như thế nào, và bạn đã từng khôi phục bản backup bao giờ chưa. Nếu làm đúng những điều này, container chỉ là một chi tiết kỹ thuật. Nếu làm sai, container sẽ là thứ bạn đổ lỗi.
Đây là quyết định tương tự cho mọi database trên server. Các ví dụ dưới đây sử dụng PostgreSQL, MySQL, MongoDB và Redis, và những khác biệt đặc thù của từng sản phẩm sẽ được lưu ý khi cần thiết.
Container thực sự thay đổi những gì
Không phải đường dẫn lưu trữ, miễn là bạn mount một volume. Vẫn dùng chung kernel, chung page cache, chung filesystem.
Có một bẫy hiệu năng thực sự, đó là trường hợp bạn không mount gì cả. Nếu không có volume, thư mục dữ liệu sẽ nằm trong lớp writable của container, đây là một overlay filesystem xếp chồng lên image. Việc ghi dữ liệu ở đây chậm hơn và toàn bộ lớp này sẽ bị xóa khi container bị gỡ bỏ. Đó là lý do tại sao xảy ra tình trạng "sáng nay database của tôi trống trơn".
Những thứ thực sự thay đổi:
- Vòng đời.
docker compose downsẽ hủy container. Bất cứ thứ gì không nằm trong volume đều sẽ mất theo. - Phiên bản. Image tag chính là phiên bản. Không có
apt upgradenào bên trong một container database có thể tồn tại sau lệnhdocker compose pulltiếp theo. - Quản lý bộ nhớ. Giới hạn cgroup là một bức tường cứng do kernel thực thi, và database không hề biết về sự tồn tại của nó.
- Người dùng. Tiến trình chạy dưới một numeric user id bên trong container, thứ có thể không sở hữu bất kỳ quyền gì trên host của bạn.
Nơi lưu trữ dữ liệu quyết định mọi thứ
Có hai lựa chọn tốt và một sai lầm phổ biến.
- Named volume:
pgdata:/var/lib/postgresql/data. Docker tạo thư mục tại/var/lib/docker/volumes/<project>_pgdata/_data, và entrypoint của image sẽ thiết lập quyền sở hữu trong lần chạy đầu tiên. Đây là lựa chọn mặc định. - Bind mount:
/srv/appname/pg:/var/lib/postgresql/data. Bạn tự chọn đường dẫn, vì vậy bạn phải tự xử lý vấn đề quyền truy cập. - Không mount gì cả. Xem ở trên. Dữ liệu nằm trong container.
Toàn bộ sự đánh đổi là một chủ đề riêng, và so sánh bind mount với named volume đã đề cập đến nó. Đối với database, tóm tắt là: hãy dùng named volume trừ khi bạn có lý do cụ thể để biết đường dẫn trên host, và nếu bạn dùng bind mount, hãy đặt nó ở một nơi ổn định như /srv/appname/pg thay vì bên trong thư mục dự án nơi git clean có thể truy cập tới.
Một giới hạn cứng: không đặt thư mục dữ liệu database trên NFS (network file system) hoặc bất kỳ mount mạng nào mà bạn chưa kiểm tra hành vi khóa và fsync của nó. Các database giả định rằng một lệnh fsync thành công nghĩa là các byte đã nằm trên bộ nhớ ổn định. Khi giả định đó sai, bạn sẽ gặp lỗi hỏng dữ liệu xuất hiện sau vài tuần.
Ghim tên volume trước khi volume bị mất
Compose đặt tên cho volume là <project>_<volume>, và tên dự án mặc định sẽ lấy theo tên thư mục. Do đó, định danh của volume phụ thuộc vào tên thư mục, một thứ mà người dùng thường thay đổi mà không suy nghĩ kỹ.
Di chuyển /srv/app sang /srv/app-old, hoặc đổi tên khóa pgdata trong file compose, thì lần docker compose up -d tiếp theo sẽ tạo ra một volume mới hoàn toàn và trống rỗng. Postgres khởi tạo một cluster mới vào đó. Container vẫn ở trạng thái healthy, ứng dụng khởi chạy, và mọi bảng dữ liệu đều biến mất. Tin tốt là volume cũ vẫn còn nằm trên đĩa dưới tên cũ.
docker volume ls
docker volume inspect app_pgdataHãy ghim tên lại để tránh tình trạng này. Thiết lập tên dự án và tên volume một cách tường minh:
name: myapp
services:
db:
image: postgres:17
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
name: myapp_pgdataNếu đã có một volume cũ chứa dữ liệu của bạn, hãy copy dữ liệu đó sang sau khi đã dừng database:
docker compose stop db
docker run --rm -v app_pgdata:/from -v myapp_pgdata:/to alpine sh -c 'cp -a /from/. /to/'
docker compose start dbNếu bạn copy khi database đang chạy, bạn sẽ nhận được một bản sao bị lỗi (torn copy) của các file đang được ghi. Hãy dừng database trước.
Ai là chủ sở hữu của thư mục dữ liệu
Các image chính thức của Postgres, MySQL và MongoDB chạy server dưới một user id không đặc quyền, thường là 999. Khi container khởi động dưới quyền root, entrypoint sẽ thay đổi quyền sở hữu thư mục dữ liệu sang user đó rồi mới hạ quyền. Đó là lý do tại sao một bind mount trống thường hoạt động ngay trong lần chạy đầu tiên.
Mọi thứ sẽ lỗi ngay khi bạn thiết lập user: trong file compose, vì lúc này entrypoint không còn đủ quyền để thực hiện thay đổi gì cả. Postgres thông báo lỗi trực tiếp như sau:
initdb: error: could not change permissions of directory "/var/lib/postgresql/data": Operation not permittedMột thư mục dữ liệu tồn tại với mode sai sẽ đưa ra thông báo khác, và lỗi này cần được nhận diện vì cách sửa là chmod, không phải chown:
FATAL: data directory "/var/lib/postgresql/data" has invalid permissions
DETAIL: Permissions should be u=rwx (0700) or u=rwx,g=rx (0750).MongoDB trên một bind mount thuộc sở hữu của root sẽ thất bại tại file lock:
Unable to create/open the lock file: /data/db/mongod.lock (Permission denied). Ensure the user executing mongod is the owner of the lock file and has the appropriate permissions.Cách sửa là dùng chown thư mục trên host về đúng id dạng số, không phải tên user:
sudo chown -R 999:999 /srv/appname/pg
sudo chmod 700 /srv/appname/pg
ls -ldn /srv/appname/pgls -ldn in ra các con số thay vì tên, và nó sẽ hiển thị 999 999. Tài khoản có tên postgres trên host của bạn và tài khoản có tên postgres bên trong image không liên quan đến nhau: kernel so sánh các con số, còn tên được tra cứu riêng biệt ở mỗi phía. cách PUID và PGID ánh xạ user trên host vào container giải thích kỹ về cơ chế ánh xạ này. Trong môi trường rootless Docker hoặc user namespace remapping, các con số này sẽ thay đổi, vì vậy hãy đọc id từ container đang chạy thay vì mặc định là 999.
Named volume giúp loại bỏ hoàn toàn vấn đề trong phần này ở lần chạy đầu tiên, vì Docker tự tạo một thư mục trống và entrypoint sẽ nắm quyền sở hữu nó.
Nâng cấp: nâng cấp gói so với thay đổi image tag
Trên host, apt upgrade giúp bạn chuyển sang phiên bản minor. Bản phân phối của bạn sẽ không tự động nhảy phiên bản major của database, và khi bạn chọn nâng cấp, cả hai bộ binary đều có thể được cài đặt cùng lúc, đây chính xác là điều pg_upgrade cần.
Trong container, tag chính là phiên bản, vì vậy nâng cấp chỉ là sửa một dòng. Điều này làm cho các bản nâng cấp minor trở nên đơn giản và nâng cấp major trở thành một quy trình.
Thay đổi postgres:16 thành postgres:17, chạy docker compose up -d, và container sẽ thoát ngay lập tức:
PostgreSQL Database directory appears to contain a database; Skipping initialization
FATAL: database files are incompatible with server
DETAIL: The data directory was initialized by PostgreSQL version 16, which is not compatible with this version 17.Không có gì bị hỏng. Các binary mới từ chối đọc cấu trúc catalog cũ trên đĩa, vốn thay đổi giữa các phiên bản major. Đặt lại tag thành postgres:16 và nó sẽ khởi động lại. Khả năng rollback đó là lợi thế nâng cấp thực sự duy nhất mà container mang lại cho bạn.
Quy trình được hỗ trợ là dump và restore. PostgreSQL ưu tiên việc dump được thực hiện bởi client mới hơn, vì vậy hãy chạy nó từ image mới nhắm vào server cũ vẫn đang chạy trên compose network:
docker run --rm --network myapp_default -e PGPASSWORD="$POSTGRES_PASSWORD" \
postgres:17 pg_dumpall -h db -U postgres > /srv/backups/all.sql
ls -lh /srv/backups/all.sql
tail -n 2 /srv/backups/all.sqlFile này tối thiểu phải có dung lượng vài chục kilobyte và kết thúc bằng dòng PostgreSQL database cluster dump complete. Một file chỉ vài trăm byte nghĩa là quá trình dump đã thất bại và bạn sắp xóa volume mà không được gì cả. Chỉ thực hiện sau khi đã kiểm tra:
docker compose down
docker volume rm myapp_pgdata
# edit the compose file: image: postgres:17
docker compose up -d db
docker compose exec -T db psql -U postgres -f /dev/stdin < /srv/backups/all.sqlCác engine khác có cách hoạt động khác:
- MySQL 8 tự nâng cấp data dictionary của chính nó khi khởi động, vì vậy việc thay đổi tag minor thường chỉ cần restart. Hãy đọc release notes trước khi nhảy giữa các series phiên bản, và luôn dump dữ liệu trước trong mọi trường hợp.
- MariaDB yêu cầu chạy
mariadb-upgradesau khi server khởi động trên phiên bản mới. - MongoDB phải được nâng cấp từng phiên bản major một, và sau mỗi bước bạn phải thiết lập feature compatibility version trước khi tiếp tục. Bỏ qua một phiên bản sẽ khiến
mongodtừ chối khởi động và ghi logUPGRADE PROBLEMvới nội dung chỉ địnhfeatureCompatibilityVersion. Từ MongoDB 7.0 trở đi, lệnh này cần một flag xác nhận rõ ràng:db.adminCommand({ setFeatureCompatibilityVersion: "8.0", confirm: true }). - Redis tải các file snapshot cũ một cách bình thường nhưng không tải được file mới hơn, vì vậy nâng cấp chỉ là restart còn hạ cấp có thể thất bại khi tải dữ liệu.
Quy tắc chung: container giúp việc hạ cấp trở nên dễ dàng nhưng không làm cho việc nâng cấp dễ hơn chút nào.
Tại sao container cơ sở dữ liệu của tôi thoát với mã 137?
Vì trình OOM (out of memory) killer của kernel đã kết thúc nó. 137 là 128 cộng với tín hiệu 9.
docker compose ps
docker inspect myapp-db-1 | grep -i oomkilled
journalctl -k | tail -n 20docker compose ps hiển thị Exited (137), dòng inspect đọc là "OOMKilled": true, và log của kernel chứa một mục tương ứng:
Memory cgroup out of memory: Killed process 4711 (postgres) total-vm:2170416kBĐây là cơ chế hoạt động, và nó gây bất ngờ cho nhiều người. PostgreSQL và MySQL định kích thước bộ đệm dựa trên tổng bộ nhớ mà host báo cáo. Giới hạn cgroup không làm thay đổi con số đó đối với chúng. Trên một host 16 GB với giới hạn 2 GB, cơ sở dữ liệu lập kế hoạch như thể nó có 16 GB, và cgroup sẽ kết thúc nó từ rất lâu trước khi bản thân host chịu bất kỳ áp lực nào. Vì vậy, chỉ riêng giới hạn bộ nhớ là không đủ. Bạn cũng phải cho cơ sở dữ liệu biết nó thực sự có bao nhiêu tài nguyên:
- PostgreSQL: thiết lập
shared_buffers, và chú ý đếnwork_mem.work_memđược cấp phát cho mỗi thao tác sắp xếp trên mỗi kết nối, vì vậy một giá trị hào phóng nhân với năm mươi kết nối thường là nguyên nhân khiến container chết khi chịu tải thay vì lúc khởi động. - MySQL và MariaDB: thiết lập
innodb_buffer_pool_size, mặc định là 128M. Hãy tắtinnodb_dedicated_servertrong container, vì toàn bộ công việc của nó là tự định kích thước dựa trên bộ nhớ máy được phát hiện. - MongoDB: thiết lập kích thước bộ nhớ đệm WiredTiger một cách rõ ràng thay vì để nó tự đoán từ bộ nhớ host.
- Redis:
maxmemorymặc định là không giới hạn, vì vậy Redis sẽ tăng trưởng cho đến khi cgroup ngăn nó lại. Hãy thiết lậpmaxmemoryở mức an toàn dưới giới hạn của container và chọn mộtmaxmemory-policyphù hợp.
Postgres cũng báo cáo sự kiện này từ phía nó, và cặp dòng này là những gì bạn sẽ tìm thấy trong log:
LOG: server process (PID 123) was terminated by signal 9: Killed
LOG: terminating any other active sessions due to crash of another server processMột backend bị kết thúc sẽ buộc mọi backend khác phải khởi động lại, vì bộ nhớ chia sẻ (shared memory) lúc này có thể đã không nhất quán. Đó là một cơn bão kết nối cho ứng dụng của bạn, không phải là một sự kiện êm ả. thiết lập giới hạn bộ nhớ trong Docker Compose bao gồm cú pháp và sự khác biệt giữa mem_limit và dạng deploy.resources.
Không có vấn đề nào trong số này biến mất trên host. Nó chỉ dịch chuyển mà thôi. Nếu không có cgroup, cơ sở dữ liệu sẽ cạnh tranh với mọi thứ khác trên máy, và OOM killer của host sẽ chọn nạn nhân dựa trên điểm số, có thể là cả sshd. Một giới hạn khiến cơ sở dữ liệu bị kết thúc một cách có thể dự đoán được vẫn dễ vận hành hơn là tình trạng OOM của host làm bạn bị khóa khỏi hệ thống.
Sao lưu: dump bên trong, lưu trữ bên ngoài
Đừng sao lưu database đang chạy bằng cách copy trực tiếp thư mục dữ liệu. Việc copy ở cấp độ file khi server đang ghi dữ liệu sẽ tạo ra một bản sao bị lỗi (torn copy), và bạn chỉ phát hiện ra điều đó khi cần restore.
Có hai phương pháp chuẩn: dùng công cụ của chính database để dump khi nó đang chạy rồi sao lưu file dump đó, hoặc dừng container và copy volume ở trạng thái tĩnh (cold backup).
docker compose exec -T db pg_dump -U postgres -Fc appdb > /srv/backups/appdb.dump
docker compose exec -T db mysqldump -u root -p"$MYSQL_ROOT_PASSWORD" --single-transaction --all-databases > /srv/backups/mysql.sql
docker compose exec -T db mongodump --archive --gzip --db appdb > /srv/backups/appdb.archive.gz
docker compose exec -T redis redis-cli BGSAVE-T rất quan trọng. Nếu thiếu nó, docker compose exec có thể gắn terminal vào lệnh, và lớp terminal sẽ tự thêm các ký tự xuống dòng (carriage return) vào luồng dữ liệu đầu ra. Kết quả là file dump văn bản sẽ bị lỗi khi restore, còn file dump nhị phân thì bị hỏng hoàn toàn. Quá trình sao lưu vẫn báo thành công, nhưng bạn sẽ gặp lỗi nghiêm trọng sau đó một tháng.
--single-transaction cung cấp cho mysqldump một snapshot nhất quán của các bảng InnoDB mà không cần khóa toàn bộ server.
Các lệnh này chỉ tạo ra một file duy nhất. Chúng không phải là một hệ thống sao lưu hoàn chỉnh: không có chính sách lưu trữ (retention), không có bản sao ngoài máy chủ, và không có bước kiểm tra tính toàn vẹn. Hãy chuyển thư mục dump cho một công cụ thực hiện đủ ba yêu cầu trên, đó là lý do tại sao cần sao lưu restic từ VPS. Hãy sao lưu /srv/backups, đừng sao lưu /var/lib/docker/volumes.
Sau đó, hãy thực hiện restore, vì một bản sao lưu chưa từng được restore thì không được coi là bản sao lưu:
docker compose exec -T db createdb -U postgres restore_test
docker compose exec -T db pg_restore -U postgres -d restore_test < /srv/backups/appdb.dump
docker compose exec -T db psql -U postgres -d restore_test -c '\dt'\dt sẽ liệt kê các bảng trong ứng dụng của bạn. Nếu kết quả trả về trống hoặc báo Did not find any relations., nghĩa là file dump không chứa dữ liệu như bạn nghĩ. Hãy xóa restore_test sau khi hoàn tất.
Lệnh xóa sạch mọi thứ
docker compose down -v.
Lệnh down đơn thuần sẽ xóa các container và network. Lệnh -v còn xóa mọi volume được đặt tên đã khai báo trong file compose đó, cùng với mọi anonymous volume gắn với các container này. Hệ thống không hiện prompt xác nhận và không có tính năng hoàn tác. Đây là cách phổ biến nhất khiến database tự host bị xóa sạch, thường xảy ra khi đang xử lý sự cố không liên quan vì làm theo hướng dẫn trên diễn đàn.
Bốn cách sau giúp giảm thiểu phạm vi ảnh hưởng:
- Khai báo volume của database là
external: true. Compose sẽ không xóa volume mà nó không quản lý, vì vậy-vkhông thể tác động đến volume đó. Bạn chỉ cần tạo volume một lần bằngdocker volume create myapp_pgdata. - Sử dụng
docker compose stopvàdocker compose startcho các lần khởi động lại định kỳ. sự khác biệt giữa down và stop trong Compose giải thích chi tiết những gì mỗi lệnh sẽ xóa. - Lưu trữ các bản dump tại một đường dẫn trên host nằm ngoài mọi volume do compose quản lý.
- Không bao giờ dán
-vtừ các câu trả lời xử lý sự cố vào một stack chứa dữ liệu quan trọng.
Không public cổng database
Dòng này đưa database của bạn lên public Internet:
ports:
- "5432:5432"Nó bind vào mọi interface. Docker public một cổng bằng cách ghi đè đích đến của gói tin trước khi các quy tắc input của firewall kịp xử lý, và các quy tắc của ufw nằm trong chuỗi input, vì vậy ufw deny 5432 hoàn toàn không có tác dụng. tại sao các cổng Docker public lại bypass ufw giải thích quá trình duyệt chuỗi này.
Một ứng dụng trong cùng compose project kết nối tới database thông qua tên service trên mạng compose, nên nó không cần public cổng. Hãy xóa block đó đi. Nếu bạn muốn một client trên host kết nối, hãy bind vào loopback:
ports:
- "127.0.0.1:5432:5432"Kiểm tra xem cái gì đang thực sự lắng nghe:
sudo ss -ltnp | grep 5432127.0.0.1:5432 là trạng thái bạn muốn. 0.0.0.0:5432 nghĩa là bất kỳ ai cũng có thể thử mật khẩu của bạn.
Triển khai ở đâu
Một ứng dụng trên một VPS. Dùng container. Sử dụng một named volume với tên cố định, không publish cổng, đặt giới hạn bộ nhớ (memory limit) khớp với cấu hình database, và thực hiện dump hàng đêm ra một đường dẫn trên host để restic thu thập. Bắt đầu từ cài đặt Docker sạch trên VPS và giữ toàn bộ stack trong một file compose mà bạn commit. Lợi ích rất rõ ràng: phiên bản database trở thành một dòng có thể review được trong git.
Một host chạy nhiều dịch vụ. Dùng container, mỗi ứng dụng một database riêng, không dùng chung một server cho tất cả. Dùng chung server sẽ buộc mọi ứng dụng phải theo cùng một lịch trình nâng cấp, và một truy vấn lỗi có thể làm sập toàn bộ hệ thống. Hãy đặt giới hạn bộ nhớ cho từng container để một truy vấn xấu chỉ ảnh hưởng đến ứng dụng tạo ra nó. Chạy nhiều instance Postgres nhỏ sẽ tốn thêm một chút dung lượng đĩa nhưng giảm bớt đáng kể công sức phối hợp.
Database là sản phẩm chính. Hãy chạy nó trực tiếp trên host từ kho lưu trữ gói của nhà cung cấp, hoặc trả phí cho dịch vụ managed. pg_upgrade cần cả hai phiên bản chính của binary được cài đặt cùng lúc, điều mà các gói phần mềm cung cấp còn image đơn phiên bản thì không. Replication và phục hồi tại một thời điểm (point in time recovery) với lưu trữ WAL (write ahead log) sẽ dễ dàng hơn khi database làm chủ máy chủ và ổ đĩa của nó. Hãy chọn giải pháp an toàn cho hệ thống mà bạn không muốn bị đánh thức lúc 03:00 sáng.
Ứng dụng có quy mô nhỏ. Hãy cân nhắc không dùng server database nào cả. Một ứng dụng web chỉ có một writer trên một VPS thường hoạt động tốt hơn với SQLite trong môi trường production trên VPS, nơi việc backup chỉ là copy một file và lộ trình nâng cấp chỉ là cập nhật phiên bản thư viện.
FAQ
Chạy database production trong Docker có an toàn không?
Có, đối với stack ứng dụng trên một máy chủ đơn lẻ. Container thực chất là một tiến trình Linux được bao bọc bởi namespaces và cgroups, nên khi mount volume, database sẽ ghi dữ liệu trực tiếp vào filesystem của host giống như khi bạn cài đặt từ package. Các rủi ro chủ yếu nằm ở khâu vận hành thay vì hiệu năng: volume không được ghim tên (pin), bind mount bị sai user id, hoặc quy trình restore chưa từng được kiểm thử, và docker compose down -v. Chỉ cần xử lý tốt bốn yếu tố này, container sẽ hoạt động ổn định. Hãy chuyển sang cài đặt trực tiếp trên host khi database là workload chính và bạn cần pg_upgrade, replication hoặc tính năng point in time recovery.
Tôi nên dùng bind mount hay named volume cho dữ liệu database?
Hãy dùng named volume trừ khi bạn có lý do cụ thể cần biết đường dẫn trên host. Docker sẽ tự tạo thư mục và entrypoint của image sẽ thiết lập quyền sở hữu ngay lần khởi chạy đầu tiên, vì vậy vấn đề phân quyền sẽ không xảy ra. Hãy ghim volume bằng một name: cụ thể hoặc đánh dấu nó là external: true, nếu không, việc đổi tên thư mục dự án sẽ vô tình tạo ra một volume mới trống rỗng và làm mất dữ liệu database. Bind mount vẫn ổn nếu bạn dùng lệnh chown trên thư mục host về đúng numeric user id mà image sử dụng (thường là 999 cho các image chính thức của Postgres, MySQL và MongoDB). Hãy xác minh bằng ls -ldn, vì ls -l chỉ hiển thị tên user trên host của bạn cho con số đó, và tên này không có ý nghĩa bên trong container.
Lệnh docker compose down -v sẽ xóa những gì?
Nó xóa các container và network giống như lệnh down thông thường, và -v sẽ xóa thêm mọi named volume được khai báo trong file compose đó cùng với mọi anonymous volume gắn với các container này. Điều đó bao gồm cả database. Không có thông báo xác nhận và không thể khôi phục. Các volume được đánh dấu external: true sẽ không bị xóa, đây là lý do chính để đánh dấu volume database là external. Để khởi động lại thông thường, hãy dùng docker compose stop và docker compose start.
Làm thế nào để nâng cấp PostgreSQL lên phiên bản major mới trong Docker?
Hãy dump và restore. Việc thay đổi postgres:16 thành postgres:17 rồi khởi động lại sẽ gây ra FATAL: database files are incompatible with server với dòng log DETAIL liệt kê cả hai phiên bản, vì các binary mới không thể đọc được cấu trúc catalog cũ. Dữ liệu không bị hỏng: chỉ cần đổi lại tag cũ là container sẽ chạy bình thường. Hãy thực hiện pg_dumpall bằng client của phiên bản mới nhắm vào container cũ đang chạy, xác nhận file kết thúc bằng PostgreSQL database cluster dump complete, sau đó khởi chạy tag mới trên một volume trống và load file dump vào. Các bản nâng cấp minor trong cùng một phiên bản major chỉ cần pull image mới và khởi động lại.
Tại sao container database của tôi thoát với mã 137?
137 là 128 cộng với signal 9, nghĩa là có tiến trình nào đó đã kill tiến trình của bạn ngay lập tức. Hãy chạy docker inspect <container> | grep -i oomkilled; giá trị true cho thấy container đã chạm ngưỡng giới hạn bộ nhớ cgroup. Nguyên nhân thường gặp là PostgreSQL và MySQL đọc tổng bộ nhớ từ host thay vì giới hạn của container, nên chúng lập kế hoạch cho 16 GB trong khi chỉ có 2 GB. Hãy thiết lập shared_buffers và work_mem, hoặc innodb_buffer_pool_size, để khớp với giới hạn bạn đã cấp cho container. Kiểm tra journalctl -k để tìm dòng Memory cgroup out of memory tương ứng nhằm xác nhận tiến trình nào đã bị kernel chọn để kill.