下面給你一套「可離線、最小停機」的遷移步驟,從 A(rootless)搬到 B(rootful)。思路是:停 A → 打包數據卷 → 傳到 B → 還原 → 用同版本鏡像啟動 → 驗證。整套操作不依賴公網,只用你已有的離線鏡像 tar 包即可。
1) 在服務器 A 上準備與備份
假設你的 docker-compose.yml
在 /opt/bookstack/
,并且里面的相對目錄 ./data/app
與 ./data/db
就在這個目錄下。
# 進入 compose 目錄
cd /opt/bookstack# 停止容器(確保數據一致)
docker compose down# ? 方法1:打包數據(含 app 與 db 兩個持久化目錄), 有權限問題
# tar -czf bookstack_backup_$(date +%F).tgz data# ? 方法2:從容器里面拷貝,繞開權限問題# 從容器里把 /config 拷到當前目錄
docker cp bookstack_db:/config ./db_copy
docker cp bookstack:/config ./app_copy# 再打包
tar -czf bookstack_data_$(date +%F).tgz app_copy db_copy# ? 可選:額外做一次數據庫邏輯備份(雙保險):
docker compose up -d bookstack_db
docker exec bookstack_db sh -c 'mysqldump -ubookstack -pbookstackpass --databases bookstackapp' > bookstackapp_dump_$(date +%F).sql
docker compose down
把 bookstack_backup_YYYY-MM-DD.tgz
(以及可選的 bookstackapp_dump_YYYY-MM-DD.sql
)拷到服務器 B,比如放到 /opt/bookstack/
。
2) 在服務器 B 上準備運行環境
(1) 放置目錄與文件
mkdir -p /opt/bookstack
cd /opt/bookstack# 拷貝 A 來的備份包
# scp/sftp/rsync 均可,這里假設已經放到了當前目錄
tar -xzf bookstack_backup_YYYY-MM-DD.tgz# 確保目錄結構如下:
# /opt/bookstack/
# ├─ docker-compose.yml (你會在下一步創建/粘貼)
# └─ data/
# ├─ app/ (BookStack 應用配置與上傳)
# └─ db/ (MariaDB 數據)
(2) 離線導入鏡像(你已經有 tar 包)
docker load -i /path/to/ghcr.io_linuxserver_bookstack-amd64.tar
docker load -i /path/to/ghcr.io_linuxserver_mariadb-amd64.tar
導入后用
docker images | grep linuxserver
看看對應 tag,確保 compose 用的image:
與本地 tag 一致(不帶 tag 默認拉latest
,離線環境建議顯式寫 tag,見下方示例)。
(3) SELinux 與權限(RHEL 關鍵點)
- RHEL 通常 SELinux=Enforcing,建議在 卷掛載后綴 加
:Z
,或對目錄執行chcon
。我推薦在 compose 的volumes
里直接用:Z
。 - linuxserver.io 鏡像用
PUID/PGID
控制容器內文件屬主。你用的是1000:1000
,那就確保宿主機目錄也是這個屬主,避免寫入報錯: - 恢復數據的腳本如下:
cd /opt/bookstack# 1) 先建目標目錄
mkdir -p data/app data/db# 2) 把 app_copy 的內容拷到 data/app
# 兼容兩種結構:app_copy/config/... 或 app_copy/直接就是內容
if [ -d app_copy/config ]; thencp -a app_copy/config/. data/app/
elsecp -a app_copy/. data/app/
fi# 3) 把 db_copy 的內容拷到 data/db
# 同樣兼容 db_copy/config/... 或 db_copy/直接就是內容
if [ -d db_copy/config ]; thencp -a db_copy/config/. data/db/
elsecp -a db_copy/. data/db/
fi# 4) 修正屬主(linuxserver 鏡像要求 1000:1000)
chown -R 1000:1000 data/app data/db# 如 RHEL/SELinux 處于 Enforcing,且你沒有在 compose 里用 :Z,
# 可以加上這行給目錄打上容器可讀寫的類型(兩者選其一即可):
# chcon -Rt svirt_sandbox_file_t data/app data/db# 5) 清理臨時目錄(可選)
rm -rf app_copy db_copy
- 最終啟動并訪問
http://<服務器B的IP>:6875
docker compose up -d
(4) 防火墻(RHEL)
如果要從外部訪問 6875/tcp
:
firewall-cmd --permanent --add-port=6875/tcp
firewall-cmd --reload
3) 在服務器 B 上的 docker-compose.yml(示例)
把下面內容保存為 /opt/bookstack/docker-compose.yml
。和你在 A 上的幾乎一致,只是:
- 更新了
APP_URL
為服務器 B 的地址(或你的域名、HTTPS); - 給兩個卷都加了
:Z
以適配 SELinux; - 可選:顯式寫鏡像 tag(把
<TAG>
換成你docker images
里看到的實際 tag;如果你確認是latest
,也可保留latest
)。
services:bookstack:image: ghcr.io/linuxserver/bookstack:<TAG>container_name: bookstackenvironment:- PUID=1000- PGID=1000- APP_URL=http://<服務器B的IP或域名>:6875- APP_KEY=base64:w+nAsQLJ1Q/EPxIx5JXXnJ/USqOG/cR21vIGvyFLIeU= # 保持不變- DB_HOST=bookstack_db- DB_PORT=3306- DB_USERNAME=bookstack- DB_PASSWORD=bookstackpass- DB_DATABASE=bookstackappvolumes:- ./data/app:/config:Zports:- "6875:80"restart: unless-stoppeddepends_on:- bookstack_dbbookstack_db:image: ghcr.io/linuxserver/mariadb:<TAG>container_name: bookstack_dbenvironment:- PUID=1000- PGID=1000- MYSQL_ROOT_PASSWORD=bookstackpass- TZ=Asia/Shanghai- MYSQL_DATABASE=bookstackapp- MYSQL_USER=bookstack- MYSQL_PASSWORD=bookstackpassvolumes:- ./data/db:/config:Zrestart: unless-stopped
說明:
- APP_KEY 一定要沿用原值,這樣原有上傳、會話等不會失效。
- 如果 B 上將來走反向代理/HTTPS,記得把
APP_URL
改成https://你的域名
,必要時添加APP_TRUSTED_PROXIES=*
。
4) 啟動與驗證
cd /opt/bookstack
docker compose up -d# 先看數據庫是否健康
docker logs -f bookstack_db# 再看應用
docker logs -f bookstack
打開瀏覽器訪問:http://<服務器B的IP>:6875
確認歷史頁面、圖片/附件、用戶、權限等都在。
5) 常見坑與排查
-
權限被拒絕(Permission denied)
- 大多是宿主目錄屬主不對或 SELinux。先
chown -R 1000:1000 /opt/bookstack/data/*
,再確保卷掛載后綴:Z
,或用chcon -Rt svirt_sandbox_file_t ...
。
- 大多是宿主目錄屬主不對或 SELinux。先
-
容器一直重啟,App 連接不上 DB
depends_on
只保證順序不保證“可用”。再等等,或看bookstack_db
日志;數據庫初始化首次啟動會花點時間。必要時重啟應用容器:docker restart bookstack
。
-
訪問 6875 不通
- 檢查
docker ps
端口映射、firewall-cmd
、以及宿主機上是否有別的服務占用 6875。
- 檢查
-
鏡像版本不匹配
- 離線導入的鏡像 tag 最好與 compose 對齊;若不清楚,運行前
docker images
看清楚 tag。
- 離線導入的鏡像 tag 最好與 compose 對齊;若不清楚,運行前
-
APP_URL 更換導致外鏈/回調異常
- 換了 IP/域名就更新
APP_URL
,否則某些重定向、文件 URL 可能不正確。
- 換了 IP/域名就更新
6) 可選:用 SQL 備份恢復(當你不想拷貝整個 db 卷時)
如果只想遷移應用卷 + 用 mysqldump
恢復:
# 在 B 上:啟動空庫
docker compose up -d bookstack_db# 導入 A 導出的 SQL
docker exec -i bookstack_db sh -c 'mysql -ubookstack -pbookstackpass bookstackapp' < bookstackapp_dump_YYYY-MM-DD.sql# 再啟動應用
docker compose up -d bookstack
一句話總結
- 把 A 的
data/app
與data/db
原樣帶到 B; - B 上用相同或兼容的鏡像、相同的
APP_KEY
與 DB 賬號; - RHEL 要注意 SELinux(:Z) 和 目錄屬主(1000:1000);
- 更新
APP_URL
,放行端口,啟動即可。