Backup và nâng cấp Docker Compose stack đúng cách
Hướng dẫn backup Docker Compose đủ 4 phần: file compose, .env, volume và database dump; kiểm tra restore trước khi pull để tránh lỗi migration.
Một bản backup của Docker Compose stack phải chứa những gì
Một bản backup của Docker Compose stack phải chứa 4 thành phần riêng biệt. Nếu thiếu bất kỳ thành phần nào, ứng dụng sẽ không thể khôi phục: file compose, .env nằm bên cạnh file đó, nội dung của mọi volume và bản dump database được tạo bằng client của chính database. Sao chép các file của database trong khi container vẫn đang chạy không phải là backup. Khi nâng cấp, hãy dùng cùng danh sách này và tuân thủ thêm một quy tắc: tạo backup trước khi pull, vì schema migration được thiết kế để chạy theo chiều tiến và phần lớn project không cung cấp cách quay lui.
Toàn bộ nội dung dưới đây giả định stack đã được triển khai và docker compose ps cho thấy stack đang chạy. Các ví dụ sử dụng thư mục project tại /srv/myapp, với các service có tên app và db. Hãy thay bằng tên của bạn. Các lệnh cố ý giữ ở dạng tổng quát, vì những phần quan trọng là volume và database hoạt động theo cùng một cách, bất kể ứng dụng là gì.
Xác định chính xác stack của bạn đang lưu gì
cd /srv/myapp
docker compose ps
docker compose config --volumes
docker volume ls --filter label=com.docker.compose.project=myappdocker compose config --volumes in ra tên ngắn của các named volume được khai báo trong file. docker volume ls in ra tên thực tế của các volume đó trên disk. Hai danh sách này khác nhau vì Compose đặt tên project ở phía trước: volume được ghi là db_data trong file sẽ tồn tại dưới tên myapp_db_data. Tên project mặc định là tên thư mục. Vì vậy, đổi tên thư mục sẽ khiến stack trỏ đến một bộ volume mới và rỗng, còn các volume cũ vẫn nằm đó cùng toàn bộ dữ liệu của bạn. Mọi lệnh bên dưới đều cần tên thực tế lấy từ docker volume ls.
Bind mount không xuất hiện trong cả hai danh sách. Trong file compose, đó là các mục có host path ở bên trái dấu hai chấm, ./config:/app/config. Chúng là các thư mục thông thường trên host nên có thể dùng các công cụ thông thường để truy cập. Named volume nằm dưới /var/lib/docker/volumes/, và docker volume inspect --format '{{.Mountpoint}}' myapp_db_data in ra path chính xác của một volume. Stack của bạn dùng loại nào sẽ quyết định cách bạn sao chép nó. Bind mount và named volume trình bày đầy đủ các đánh đổi này.
Bây giờ hãy phân loại những gì bạn tìm được thành 2 nhóm. Một số volume chứa state mà không cách nào tạo lại: file đã upload, key được tạo tự động, chính database và mọi dữ liệu người dùng đã nhập vào app. Các volume khác chứa dữ liệu dẫn xuất như thumbnail và search index, là những dữ liệu app có thể tự xây dựng lại. Backup nhóm thứ hai chỉ tốn disk space và thời gian restore mà không mang lại lợi ích. Redis cache volume là ví dụ rõ nhất: mất volume này chỉ khiến request đầu tiên chậm hơn.
Sao lưu file compose và file .env
Cả hai file đều nằm cạnh nhau trên host và không nằm trong volume nào. .env chứa mật khẩu database, application secret và mọi API token, nên đây là file giúp biến một tập hợp volume trở lại thành ứng dụng hoạt động. File này cũng thường được liệt kê trong .gitignore, nên kế hoạch kiểu “cấu hình của tôi nằm trong git” lại loại trừ đúng file quan trọng nhất. Lưu secret trong file env là pattern phù hợp, đồng thời yêu cầu bạn đưa file này vào backup.
sudo install -d -m 700 -o "$USER" -g "$(id -gn)" /srv/backups/myapp
cp -a compose.yaml .env /srv/backups/myapp/
chmod 600 /srv/backups/myapp/.envHãy copy mọi file compose mà stack sử dụng, không chỉ file đầu tiên. Stack được khởi động bằng -f compose.yaml -f compose.prod.yaml cần có đủ cả hai file để khôi phục đúng cách, và cách nhiều file compose được merge quyết định giá trị nào thực sự được truyền vào container.
Có một cảnh báo liên quan trực tiếp giữa .env và các volume. Official Postgres image chỉ đọc POSTGRES_PASSWORD khi khởi tạo một data directory trống. Thay đổi giá trị đó về sau không thay đổi mật khẩu bên trong database. Nếu khôi phục volume của tháng trước cùng .env của hôm nay, app sẽ không kết nối được với FATAL: password authentication failed for user "appuser", dù kiểm tra riêng từng file đều thấy đúng. Hãy giữ .env và các volume tại cùng một thời điểm trong cùng một bản backup.
Dump database bằng client riêng
Database server liên tục ghi vào các file dữ liệu. Một tar của /var/lib/postgresql/data được tạo khi server đang chạy sẽ sao chép một số page trước khi ghi và một số page sau khi ghi, nên archive chứa dữ liệu từ nhiều thời điểm khác nhau và có thể không replay được. Công cụ dump đọc dữ liệu bên trong một transaction duy nhất, nên file chứa một trạng thái nhất quán tại một thời điểm. Đây là điểm phân biệt backup với việc sao chép file.
docker compose exec -T db sh -c \
'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' \
> /srv/backups/myapp/db-$(date +%F).dumpGiữ lại -T. Tùy chọn này tắt việc cấp phát TTY. Khi có TTY, Docker sẽ chuyển đổi output stream trước khi gửi về shell của bạn, làm hỏng binary dump. Bạn chỉ phát hiện lỗi này khi restore thất bại. Dấu nháy đơn cũng quan trọng: chúng ngăn host shell expand $POSTGRES_USER, để shell bên trong container expand biến này bằng các giá trị mà compose file đã thiết lập ở đó. -Fc ghi dump ở custom format. Format này được nén trong lúc ghi và cho phép pg_restore chọn riêng các object sau đó.
Role và password của chúng nằm ngoài từng database riêng lẻ, nên cũng cần dump các thông tin này:
docker compose exec -T db sh -c 'pg_dumpall -U "$POSTGRES_USER" --globals-only' \
> /srv/backups/myapp/globals.sqlSau đó kiểm tra file là dump chứ không phải thông báo lỗi:
ls -lh /srv/backups/myapp/
head -c 5 /srv/backups/myapp/db-$(date +%F).dumpDump ở custom format bắt đầu bằng 5 byte PGDMP. File có kích thước 0 byte hoặc bắt đầu bằng pg_dump: cho biết command đã thất bại. Shell tạo output file trước khi command chạy, nên một lần dump thất bại vẫn để lại một file có tên và timestamp trông hợp lệ. Đây là lỗi backup âm thầm phổ biến nhất.
Với MariaDB hoặc MySQL, client thay đổi nhưng cách thực hiện không đổi:
docker compose exec -T db sh -c \
'mariadb-dump -u root -p"$MARIADB_ROOT_PASSWORD" --single-transaction --databases "$MARIADB_DATABASE"' \
> /srv/backups/myapp/db-$(date +%F).sql--single-transaction tạo dump nhất quán của các bảng InnoDB mà không chặn writer. Trên MySQL image, command là mysqldump và các biến là MYSQL_ROOT_PASSWORD và MYSQL_DATABASE. Trên các MariaDB image hiện tại, mysqldump vẫn hoạt động như tên tương thích của mariadb-dump. Lưu ý rằng password truyền trên command line sẽ hiển thị trong process list của container trong suốt thời gian dump.
SQLite cần được xử lý riêng. Database là một file, nhưng các transaction gần đây vẫn có thể nằm trong một file -wal riêng bên cạnh file đó. Vì vậy, chỉ sao chép .db sẽ tạo ra database bị thiếu các lần ghi mới nhất. Nếu image có sẵn client, sqlite3 /data/app.db ".backup '/data/app-backup.db'" sẽ tạo một bản sao nhất quán trong khi app vẫn chạy. Nếu không có, hãy dừng container rồi sao chép file .db cùng với các file đi kèm -wal và -shm.
Nếu database chạy trên host thay vì bên trong stack, các command tương tự vẫn áp dụng nhưng không có tiền tố docker compose exec. Bạn nên đọc chạy database trong Docker hoặc trên host trước lần rebuild tiếp theo.
Chụp các volume
Named volume không có host path để bạn chỉnh sửa thủ công. Vì vậy, hãy mount nó vào một container tạm thời rồi tạo archive từ đó.
docker run --rm \
-v myapp_uploads:/data:ro \
-v /srv/backups/myapp:/backup \
alpine:3 tar czf /backup/uploads.tar.gz -C /data .Container hỗ trợ mount volume ở chế độ chỉ đọc tại /data và thư mục backup tại /backup, sau đó ghi archive ra phía host. --rm sẽ xóa container hỗ trợ ngay khi tar thoát. :ro rất quan trọng, vì lệnh tar viết sai khi đó không thể làm hỏng dữ liệu nguồn. -C /data . giúp restore vào đúng vị trí: nó lưu mọi path tương đối với thư mục gốc của volume. Nếu viết tar czf /backup/uploads.tar.gz /data, mọi path sẽ có thêm data/ ở đầu. Khi đó, quá trình restore sẽ tạo /data/data bên trong volume và app sẽ thấy một thư mục trống. Archive thuộc về root vì tar đã chạy dưới quyền root bên trong container. Chạy sudo chown "$USER" /srv/backups/myapp/uploads.tar.gz nếu điều đó gây vướng, và đọc cách PUID và PGID quyết định quyền sở hữu file nếu các file được restore không thể đọc được bởi app.
Chạy lệnh này một lần cho mỗi named volume. Bind mount không cần container: tar czf /srv/backups/myapp/config.tar.gz -C /srv/myapp/config . thực hiện cùng công việc trực tiếp trên host.
Quyết định theo từng volume xem app có phải dừng hay không. Nếu app liên tục ghi đè file trong volume, tar đang chạy có thể bắt gặp một file khi file đó mới được ghi dở. Với thư mục uploads, nơi file chỉ được ghi một lần rồi chỉ đọc, rủi ro này thấp. Với các trường hợp khác, hãy dừng service trong thời gian copy bằng docker compose stop app, rồi chạy docker compose start app. stop giữ nguyên các container và volume, đúng với mục đích ở đây. Bạn nên hiểu rõ sự khác nhau giữa down và stop trước khi chạy một trong hai lệnh này.
Không xem tar của database volume là database backup. Dump mới là backup. Archive của volume từ một database đã dừng hữu ích để rebuild nhanh, và chỉ có vậy.
Thứ tự thực hiện
- Sao chép các file compose và
.envvào thư mục backup. - Dump database khi nó vẫn đang chạy.
- Dừng container app nếu các volume của nó bị thay đổi trực tiếp.
- Archive từng named volume và từng thư mục bind-mount.
- Khởi động lại những gì bạn đã dừng, sau đó xác nhận bằng
docker compose ps. - Ghi lại các image tag và digest mà stack đang chạy.
- Sao chép toàn bộ thư mục backup ra khỏi server này.
Bước 7 là bước mọi người thường để lại làm sau.
Đưa bản sao ra khỏi máy
Bản backup nằm trên cùng ổ đĩa với stack chỉ bảo vệ bạn khỏi lỗi thao tác của chính mình, chứ không bảo vệ được khỏi sự cố khác. Một volume hỏng, một server bị xóa hoặc một account bị mất sẽ làm mất cả hai bản sao cùng lúc. Hãy đẩy thư mục lên storage không nằm trên VPS này theo lịch định kỳ và áp dụng retention policy. backup restic từ một VPS đã trình bày cách thiết lập repository, các retention flag và lệnh kiểm tra, nên không cần lặp lại ở đây.
restic cũng có thể đọc dump trực tiếp từ pipe. Cách này giữ database plaintext hoàn toàn ngoài disk:
docker compose exec -T db sh -c 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' \
| restic backup --stdin --stdin-filename db.dumpDù dùng công cụ nào, hãy đặt lịch chạy trong systemd timer hoặc cron job, đồng thời cấu hình job báo lỗi ở nơi bạn có thể nhìn thấy. Một backup script không có nơi ghi output có thể ngừng hoạt động suốt sáu tháng mà không ai phát hiện.
Kiểm chứng backup hoạt động bằng một buổi diễn tập restore
Một backup chưa từng được restore chỉ là giả thuyết. Quy trình dưới đây restore vào một stack thứ hai chạy song song với stack đầu tiên. Production vẫn tiếp tục phục vụ và không lệnh nào bạn nhập có thể tác động đến stack đó.
Cơ chế nằm ở project name. Compose lấy tên này từ tên thư mục rồi gắn nó vào mọi container và volume mà nó tạo. Sao chép backup vào một thư mục mới, stack được restore sẽ tự động có các volume riêng.
sudo install -d -m 700 -o "$USER" -g "$(id -gn)" /srv/myapp-restore
cd /srv/myapp-restore
cp /srv/backups/myapp/compose.yaml /srv/backups/myapp/.env .Chỉnh sửa file compose đã sao chép để published host port không xung đột với stack đang chạy: thay 8080:8080 bằng 18080:8080, hoặc thay đổi biến đặt port đó trong .env đã sao chép. Sau đó tạo các container và volume rỗng mà chưa khởi động gì:
docker compose create
docker volume ls --filter label=com.docker.compose.project=myapp-restoreLệnh thứ hai phải liệt kê cùng tên volume với production, nhưng có myapp-restore_ ở phía trước. Nạp dữ liệu vào các volume đó, chỉ khởi động database rồi load dump:
docker run --rm -v myapp-restore_uploads:/data -v /srv/backups/myapp:/backup \
alpine:3 tar xzf /backup/uploads.tar.gz -C /data
docker compose up -d db
docker compose exec -T db sh -c \
'pg_restore -U "$POSTGRES_USER" -d "$POSTGRES_DB" --clean --if-exists' \
< /srv/backups/myapp/db-2026-08-16.dump--clean --if-exists xóa từng object trước khi tạo lại, nhờ đó có thể lặp lại quá trình restore. Nếu không có tùy chọn này, lần chạy thứ hai vào database đã có các table đó sẽ dừng với pg_restore: error: could not execute query: ERROR: relation "users" already exists.
Sau đó khởi động phần còn lại và kiểm tra như một người dùng thực tế:
docker compose up -d --wait
docker compose exec -T db sh -c 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -c "\dt"'
docker compose logs --tail=50docker compose up -d --wait chờ cho đến khi mọi service báo đang chạy hoặc healthy, đồng thời trả về mã thoát khác 0 nếu có service không đạt trạng thái đó. Nhờ vậy, bước này có thể đưa vào script. Khi một service không bao giờ chuyển sang healthy, docker compose ps hiển thị trạng thái của service đó. Healthcheck của Compose giải thích cột đó đang đọc thông tin gì. Sau đó mở app trên port thay thế và đăng nhập bằng một account thực. Tạo một record rồi mở một file nằm trong volume. Hai thao tác này là bằng chứng: dump đã được restore, volume đã được restore và dữ liệu trong hai thành phần khớp nhau. Một buổi diễn tập chỉ chứng minh trang đăng nhập hiển thị được thì chưa chứng minh gì về dữ liệu.
Xóa môi trường diễn tập sau khi kiểm tra thành công:
docker compose down -vĐây là nơi duy nhất -v là flag đúng. Trong thư mục production, cùng lệnh đó sẽ xóa các volume mà bạn đang cố bảo vệ.
Cách nâng cấp một stack Compose
Đọc release notes của mọi phiên bản nằm giữa phiên bản bạn đang chạy và phiên bản muốn nâng cấp lên. Tìm các từ breaking và migration. Những project không hỗ trợ bỏ qua nhiều major version thường nêu rõ điều đó trong release notes. Một migration bị lỗi có thể chỉ báo cho bạn biết sau khi đã thay đổi một phần schema.
Ghi lại trạng thái hiện tại trước khi thay đổi:
docker compose images
docker image inspect --format '{{index .RepoDigests 0}}' postgres:16.4docker compose images liệt kê image và tag mà từng service đang dùng. Digest là giá trị duy nhất định danh chính xác một image, vì tag có thể được chuyển sang trỏ đến nơi khác bất kỳ lúc nào.
Lấy backup từ các phần trên và sao chép ra khỏi máy chủ. Hãy làm vậy cả với patch release. Những lần nâng cấp tưởng là đơn giản thường là những lần người ta không còn chuẩn bị backup.
Sau đó pin version trong file Compose, vì latest không phải là một version:
services:
db:
image: postgres:16.4Với image: postgres:latest, docker compose pull sẽ tải bất cứ thứ gì mà tag đó đang trỏ tới hôm nay. Bạn không có cách xác định chính xác image đã chạy hôm qua. Một tag đã pin biến việc nâng cấp thành một chỉnh sửa một dòng có thể đọc lại trong git diff và hoàn tác bằng thêm một chỉnh sửa nữa. Pin application image theo cách tương tự, lấy version chính xác từ release page của project.
Pull và tạo lại container:
docker compose pull
docker compose up -d --waitdocker compose up -d so sánh file với các container đang chạy và chỉ tạo lại những service có image hoặc cấu hình thay đổi. Lệnh này không chạm vào named volume, nên container mới khởi động với dữ liệu hiện có. Đó là mục đích của việc này, đồng thời cũng là rủi ro, vì lần khởi động đầu tiên của version mới thường là lúc schema migration chạy.
Theo dõi quá trình:
docker compose ps
docker compose logs -f --tail=100 appContainer bị lỗi sẽ hiển thị Exited (1) trong cột STATUS của docker compose ps. Lý do nằm trong những dòng cuối của log. Lỗi migration thường hiển thị rõ ở đó nhưng không xuất hiện ở nơi nào khác. Khi log đã ổn định, đăng nhập và dùng app trong một phút.
Nếu docker compose pull dừng với no space left on device, nguyên nhân thường là các image layer cũ. Dọn các Docker image không còn được dùng sẽ giải phóng dung lượng. Hãy dọn sau khi xác nhận việc nâng cấp hoạt động ổn định, không phải trước đó, vì các layer cũ là nền tảng để rollback nhanh.
Cách rollback khi quá trình nâng cấp gặp lỗi
Có 2 trường hợp và chi phí xử lý rất khác nhau. Nếu phiên bản mới không thay đổi schema, rollback chỉ cần 1 dòng: đặt lại tag cũ trong file compose rồi chạy docker compose up -d. Container sẽ được thay thế, các volume vẫn giữ nguyên, và code cũ có thể đọc dữ liệu mà phiên bản mới đã ghi.
Nếu phiên bản mới đã migrate schema, code cũ sẽ không đọc được schema đó nữa. Migration được viết để chạy theo chiều tiến, và hầu hết project không cung cấp script downgrade. Vì vậy, phiên bản cũ khởi động rồi fail ngay ở query đầu tiên truy vấn đến column đã bị đổi tên hoặc xóa, với lỗi dạng ERROR: column "avatar_url" does not exist. Cách khôi phục là dùng dump đã tạo trước khi pull: đặt lại tag cũ, xóa database volume, tạo lại volume rỗng, restore dump vào đó rồi khởi động. Nếu không có dump thì hoàn toàn không còn cách khôi phục. Đây là lý do phải backup trước khi pull.
Postgres major version là trường hợp nghiêm trọng nhất. Trường hợp này thường khiến người dùng bất ngờ vì lỗi xảy ra khi upgrade chứ không phải khi rollback. Định dạng dữ liệu trên đĩa thay đổi theo mỗi major release. Đổi postgres:16.4 thành postgres:17.2, chạy docker compose up -d, rồi server mới sẽ từ chối khởi động:
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.2.Image không tự chạy pg_upgrade cho bạn. Cách được hỗ trợ trong một Compose stack là dump, thay thế, restore: tạo dump khi phiên bản cũ vẫn đang chạy, docker compose down, xóa database volume, đặt tag mới, chạy docker compose create để tạo data directory mới và rỗng, khởi động database, restore dump rồi khởi động các service còn lại. Giữ dump cũ cho đến khi major version mới đã xử lý traffic thực tế trong 1 ngày. Các bản nâng cấp minor trong cùng 1 major, chẳng hạn từ 16.4 lên 16.9, không cần các bước này vì định dạng vẫn ổn định giữa các bản đó và container chỉ cần khởi động.
Snapshot VPS có phải là backup không?
Snapshot là phần bổ sung cho backup, và hai loại này có các kiểu lỗi khác nhau. Snapshot sao chép toàn bộ disk tại hypervisor, nên có thể khôi phục toàn bộ máy trong vài phút, bao gồm cả những phần bạn quên backup. Vì vậy, snapshot phù hợp với một tác vụ cụ thể: bản nâng cấp làm hỏng server và bạn muốn đưa server về trạng thái của hai mươi phút trước.
Snapshot không phù hợp cho mọi việc khác. Mức độ chi tiết chỉ là toàn bộ máy, nên để khôi phục một table đã bị xóa, bạn phải khôi phục cả server ở đâu đó rồi lấy table ra. Thời gian lưu giữ thường ngắn. Các bản sao thường nằm trong cùng provider account với server, nên nếu mất account thì server và snapshot cũng mất cùng lúc. Ngoài ra, snapshot của một máy đang chạy sẽ ghi lại database ở giữa một lần ghi. Khi khởi động lần đầu, database sẽ thực hiện crash recovery, còn mọi transaction vẫn đang xử lý sẽ bị mất.
Hãy dùng cả hai. Snapshot là nút hoàn tác cho thời điểm nâng cấp. Dump là bản sao vẫn còn nếu account bị xóa. snapshot và backup khác nhau như thế nào phân tích từng loại lỗi mà mỗi phương án thực sự xử lý được. Cùng thư mục backup đó cũng biến việc chuyển một stack sang VPS mới thành một tác vụ thường lệ thay vì phải dựng lại từ trí nhớ.
Có gì sẽ hỏng và bạn sẽ thấy gì
Cờ volumes trong lệnh down. docker compose down -v xóa các named volume được khai báo trong file, và Compose xác nhận bằng một dòng có nội dung Volume myapp_db_data Removed. Không thể hoàn tác. Lệnh docker compose down thông thường giữ nguyên các volume. Hãy nhập dạng đầy đủ, docker compose down --volumes, để cờ có tính hủy dữ liệu là một từ bạn phải tự gõ rõ ràng.
Bản dump không có chuỗi nhận dạng. pg_restore: error: did not find magic string in file header có nghĩa là file không phải archive. Nguyên nhân thường gặp là thiếu -T trên docker compose exec, vì khi có TTY được gắn vào, stream bị chuyển đổi trên đường tới shell và binary dump bị hỏng. Tạo lại dump bằng -T, rồi kiểm tra 5 byte đầu tiên bằng head -c 5.
Mật khẩu không thay đổi. FATAL: password authentication failed for user "appuser" sau khi restore có nghĩa là .env và data directory được tạo ở hai thời điểm khác nhau. Image chỉ đặt mật khẩu khi tạo data directory trống, nên sửa .env sau đó không thay đổi gì bên trong database. Hãy restore đúng .env tương ứng, hoặc đổi mật khẩu bên trong database bằng ALTER USER.
Một volume thứ hai bị trống. Docker tạo volume khi cần, nên docker run -v myapp_upload:/data khi thiếu s sẽ ghi vào một volume mới, trống và báo thành công. Sau đó docker volume ls hiển thị cả hai tên, trong đó một volume không chứa dữ liệu. Hãy sao chép tên volume từ docker volume ls thay vì nhập theo trí nhớ.
Restore nhầm vào production. Chạy các lệnh restore trong /srv/myapp thay vì /srv/myapp-restore sẽ ghi đè dữ liệu đang chạy bằng bản backup, và các lệnh này trông giống hệt nhau ở cả hai nơi. Kiểm tra pwd trước mỗi lệnh restore, và giữ drill trong thư mục riêng.
FAQ
docker compose down có xóa dữ liệu của tôi không?
Không. docker compose down xóa các container và network mặc định, nhưng không tác động đến named volume hoặc bind mount. docker compose down -v xóa các named volume được khai báo trong file, và thao tác này là vĩnh viễn. Bind mount là các thư mục trên host, nên Compose không bao giờ xóa chúng. Nếu bạn muốn dừng các service trong lúc backup nhưng giữ nguyên mọi thứ khác, hãy dùng docker compose stop.
Tôi có thể sao chép thư mục dữ liệu Postgres thay vì chạy pg_dump không?
Chỉ khi container đã dừng. Khi server đang chạy, các file của nó liên tục thay đổi, nên bản sao có thể chứa dữ liệu ở nhiều thời điểm khác nhau và không thể khôi phục nhất quán. Bản sao ở cấp file cũng gắn với một major version cụ thể của Postgres, nên không thể khởi động bằng một version khác. Hãy dừng container, archive volume, khởi động lại container, và xem bản sao này là cách rebuild nhanh chứ không phải bản backup duy nhất. Dump mới là bản sao portable và là bản bạn dùng để restore.
Làm cách nào để nâng cấp Postgres lên major version mới trong Compose?
Chỉ đổi tag là chưa đủ. Server mới sẽ từ chối khởi động với data directory cũ và ghi log The data directory was initialized by PostgreSQL version 16, which is not compatible with this version 17.2. Chạy pg_dump khi version cũ vẫn đang chạy, sau đó chạy docker compose down, xóa database volume, đặt tag mới, chạy docker compose create để tạo volume trống mới, khởi động database và restore dump vào đó. Giữ dump cũ cho đến khi version mới đã xử lý network traffic thực tế.
Nên chạy backup bao lâu một lần và giữ chúng trong bao lâu?
Hãy chọn interval dựa trên lượng công việc bạn chấp nhận phải làm lại. Chạy nightly backup phù hợp với stack cá nhân hoặc stack của nhóm nhỏ, đồng thời tạo thêm một bản backup thủ công ngay trước mỗi lần upgrade. Về retention, hãy giữ đủ lịch sử để phát hiện những hư hỏng không được nhận ra ngay. Một table bị corrupt được phát hiện vào thứ Sáu sẽ không thể khôi phục nhờ bản sao của tối thứ Năm. restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune là một chính sách khởi đầu hợp lý. Dù lịch backup thế nào, mỗi quý hãy thử restore một lần. Cho đến khi làm việc đó, bạn chưa có backup; bạn chỉ có các file.
Có phải dừng toàn bộ stack để tạo backup không?
Thông thường là không. Database dump vẫn nhất quán khi server đang chạy, nên database không cần downtime. Vấn đề chính là các volume. Nếu app chỉ thêm file, chẳng hạn thư mục uploads, archive trực tiếp thường đủ an toàn. Nếu app ghi đè file tại chỗ, hãy dừng riêng service đó trong thời gian sao chép bằng docker compose stop app, rồi khởi động lại sau khi hoàn tất. Dừng app trong khi database vẫn chạy thường là khoảng thời gian an toàn ngắn nhất mà bạn có thể bố trí.