将应用程序迁移到另一台 Coolify 主机
Coolify 没有内置选项可以将应用程序从一台服务器迁移到另一台服务器。
您需要手动在新服务器上部署应用程序,并复制数据库和卷。本指南将逐步引导您完成这一过程。
注意
我们假设您已经在目标服务器上安装了 Coolify 并准备好迁移您的应用程序。
1. 了解数据持久化
使用 Coolify 时,应用程序数据存储在以下两个位置之一:

绑定挂载
- 使用绑定挂载时,主机目录或文件被映射到容器中。
- 对主机上的目录或文件所做的任何更改都会立即反映在容器内。
- 要备份数据,只需将主机目录或文件复制到新服务器,并更新应用程序配置中的绑定挂载路径。
卷挂载
- 使用卷挂载时,会创建一个 Docker 卷(通常由 Coolify 创建,但您也可以自己设置)并用于存储应用程序数据。
- 卷存储在 Docker 的卷目录中,通常位于
/var/lib/docker/volumes/<VOLUME_NAME>下 - 您不能直接复制该目录,而是 Docker 提供了一种使用临时容器进行安全备份和恢复的方法。
注意
由于绑定挂载可以通过直接复制文件来简单迁移,本指南将主要关注卷备份。
2. 备份和恢复概述
Docker 推荐的卷迁移过程如下:

- 挂载您的卷到一个临时容器中。
- 归档卷的内容到一个 tarball 文件中。
- 复制 tarball 文件从容器到您的主机,然后删除临时容器。
- 传输 tarball 文件到新服务器。
- 创建一个新的卷在目标服务器上。
- 挂载传输的 tarball 文件到一个临时容器中。
- 提取归档到新卷中。
这一系列步骤确保了一致、安全的备份和恢复。下面,我们将提供现成可用的脚本和详细说明。
注意
以下步骤包含脚本,可帮助您轻松、交互式地备份、传输和恢复卷。
它们还包括检查以确保卷和备份存在,以防止错误,如恢复到错误的卷。
3. 备份卷
SSH 连接到您的服务器,该服务器上有您的 Docker 卷。
创建一个名为
backup.sh的脚本:shtouch backup.sh && chmod +x backup.sh在编辑器中打开
backup.sh并粘贴以下内容:sh#!/bin/bash # === 输入提示 === # 提示输入 Docker 卷名称并设置变量 read -p "[ 备份代理 ] [ 输入 ] 请输入要备份的 Docker 卷名称: " VOLUME_NAME # 通知用户设置的卷名称 echo "[ 备份代理 ] [ 信息 ] 备份卷已设置为 $VOLUME_NAME" # 检查输入的卷是否存在 if ! docker volume ls --quiet | grep -q "^$VOLUME_NAME$"; then echo "[ 备份代理 ] [ 错误 ] 卷 '$VOLUME_NAME' 不存在,中止备份。" echo "[ 备份代理 ] [ 错误 ] 备份失败!" exit 1 # 如果卷不存在则退出 else echo "[ 备份代理 ] [ 信息 ] 卷 '$VOLUME_NAME' 存在,继续备份..." fi # 提示输入保存备份的目录 read -p "[ 备份代理 ] [ 输入 ] 请输入保存备份的目录(可选:按回车使用 ./volume-backup): " BACKUP_DIR # 如果未输入目录,则默认为 './volume-backup' BACKUP_DIR=${BACKUP_DIR:-./volume-backup} # 通知用户备份位置 echo "[ 备份代理 ] [ 信息 ] 备份位置已设置为 $BACKUP_DIR" # 根据卷名称设置备份文件名 BACKUP_FILE="${VOLUME_NAME}-backup.tar.gz" # 通知用户备份文件名 echo "[ 备份代理 ] [ 信息 ] 备份文件名已设置为 $BACKUP_FILE" # === 脚本开始 === # 检查备份目录是否存在 if [ -d "$BACKUP_DIR" ]; then echo "[ 备份代理 ] [ 信息 ] 目录 '$BACKUP_DIR' 已存在,跳过目录创建。" else echo "[ 备份代理 ] [ 信息 ] 目录 '$BACKUP_DIR' 不存在,正在创建目录。" # 创建备份目录,如果创建失败则退出 mkdir -p "$BACKUP_DIR" || { echo "[ 备份代理 ] [ 错误 ] 无法创建目录 '$BACKUP_DIR',中止备份。" echo "[ 备份代理 ] [ 错误 ] 备份失败!" exit 1 } fi # 执行备份操作 echo "[ 备份代理 ] [ 信息 ] 正在备份卷: $VOLUME_NAME 到 $BACKUP_DIR/$BACKUP_FILE" # 运行 Docker 容器创建备份 docker run --rm \ -v "$VOLUME_NAME":/volume \ -v "$(pwd)/$BACKUP_DIR":/backup \ busybox \ tar czf /backup/"$BACKUP_FILE" -C /volume . || { # 如果备份失败,打印错误消息并退出 echo "[ 备份代理 ] [ 错误 ] 备份过程失败,中止。" echo "[ 备份代理 ] [ 错误 ] 备份失败!" exit 1 } # 如果一切成功,通知用户 echo "[ 备份代理 ] [ 成功 ] 备份已完成!"通过运行以下命令查找卷名称:
shdocker volume ls或者从 Coolify 的持久存储页面(见下图)。

停止您的应用程序以执行干净的备份。
运行脚本:
sh./backup.sh- 当提示时,粘贴卷名称。
- 按Enter接受默认备份目录 (
./volume-backup),或输入自定义路径。
验证您现在有一个目录(例如,
volume-backup)包含<VOLUME_NAME>-backup.tar.gz文件。
4. 将备份传输到新服务器
提示
如果您已经知道如何手动传输备份文件,可以直接跳到下一步。
创建第二个名为
transfer.sh的脚本:shtouch transfer.sh && chmod +x transfer.sh在编辑器中打开
transfer.sh并粘贴以下内容:sh#!/bin/bash # =============== 配置变量 =============== SSH_PORT=22 SSH_USER="root" SSH_IP="192.168.1.222" SSH_KEY="$HOME/.ssh/local-vm" SOURCE_PATH="./volume-backup" DESTINATION_PATH="/root/backups/volume-backup" MAX_RETRIES=3 # 最大密码尝试次数 echo "[ 传输代理 ] [ 信息 ] 开始传输..." echo "[ 传输代理 ] [ 信息 ] 尝试 SSH 密钥: $SSH_KEY" echo "[ 传输代理 ] [ 信息 ] 从: $SOURCE_PATH 传输" echo "[ 传输代理 ] [ 信息 ] 传输到: $SSH_USER@$SSH_IP:$DESTINATION_PATH" # 如果 SSH 密钥文件不存在,回退到密码模式 if [ ! -f "$SSH_KEY" ]; then echo "[ 传输代理 ] [ 警告 ] 未找到 SSH 密钥 '$SSH_KEY'。回退到密码身份验证。" SSH_KEY="" fi # 如果需要基于密码的身份验证,确保安装了 Expect if [ -z "$SSH_KEY" ] && ! command -v expect >/dev/null 2>&1; then echo "[ 传输代理 ] [ 错误 ] 密码身份验证需要 expect 包,但未安装(请使用 sudo apt install expect 手动安装后再试)。中止。" exit 1 fi # --------------------------------------------- # 辅助函数:通过执行 "ssh … exit" 测试 $1(密码)是否有效 # 如果有效返回 0,如果 "拒绝权限"(或任何其他失败)返回 1 test_password_with_expect() { local PW="$1" expect -c " log_user 0 set timeout 15 spawn ssh -o StrictHostKeyChecking=no -p $SSH_PORT $SSH_USER@$SSH_IP exit expect { \"*?assword:\" { send -- \"$PW\r\" expect { \"Permission denied\" { exit 1 } eof { exit [lindex [wait] 3] } } } eof { exit [lindex [wait] 3] } } " >/dev/null 2>&1 return $? } # 最多提示 $MAX_RETRIES 次输入正确密码 get_password() { local retries=0 while [ $retries -lt $MAX_RETRIES ]; do read -s -p "[ 传输代理 ] [ 输入 ] 请输入 $SSH_USER@$SSH_IP 的 SSH 密码: " SSHPASS echo "" test_password_with_expect "$SSHPASS" if [ $? -eq 0 ]; then # 密码正确 return 0 else echo "[ 传输代理 ] [ 错误 ] 无效密码。请重试。" retries=$((retries + 1)) fi done echo "[ 传输代理 ] [ 错误 ] 已达到最大重试次数。中止。" exit 1 } # --------------------------------------------- # 步骤 0:尝试 SSH 密钥身份验证(如果设置了密钥) if [ -n "$SSH_KEY" ]; then # 使用 BatchMode 防止回退到密码提示 ssh -i "$SSH_KEY" -o BatchMode=yes -o StrictHostKeyChecking=no -p "$SSH_PORT" \ "$SSH_USER@$SSH_IP" exit >/dev/null 2>&1 RC=$? if [ $RC -eq 0 ]; then echo "[ 传输代理 ] [ 信息 ] SSH 密钥有效!" USING_KEY=true else echo "[ 传输代理 ] [ 警告 ] SSH 密钥身份验证失败。回退到密码身份验证。" SSH_KEY="" USING_KEY=false fi else USING_KEY=false fi # 如果密钥身份验证失败(或没有密钥),提示输入密码 if [ "$USING_KEY" = false ]; then get_password echo "[ 传输代理 ] [ 信息 ] 密码有效!" fi # --------------------------------------------- # 步骤 1:确保远程端存在完整的 DESTINATION_PATH。 echo "[ 传输代理 ] [ 信息 ] 确保远程目录 '$DESTINATION_PATH' 存在..." if [ -n "$SSH_KEY" ]; then ssh -i "$SSH_KEY" -o StrictHostKeyChecking=no -p "$SSH_PORT" \ "$SSH_USER@$SSH_IP" "mkdir -p $DESTINATION_PATH" >/dev/null 2>&1 MKRC=$? else expect -c " log_user 0 set timeout 5 spawn ssh -o StrictHostKeyChecking=no -p $SSH_PORT $SSH_USER@$SSH_IP mkdir -p $DESTINATION_PATH expect { \"*?assword:\" { send -- \"$SSHPASS\r\" exp_continue } eof { exit [lindex [wait] 3] } } " >/dev/null 2>&1 MKRC=$? fi if [ $MKRC -ne 0 ]; then echo "[ 传输代理 ] [ 错误 ] 无法创建远程目录。中止。" exit 1 fi # --------------------------------------------- # 步骤 2:仅将本地 "volume-backup" 的内容复制到该文件夹。 echo "[ 传输代理 ] [ 信息 ] 开始文件传输..." # 将任何 SCP 错误输出捕获到临时文件,以便在出现问题时显示 SCP_LOG="$(mktemp)" if [ -n "$SSH_KEY" ]; then # 抑制标准输出,仅捕获错误输出 scp -i "$SSH_KEY" -o StrictHostKeyChecking=no -P "$SSH_PORT" -r \ "$SOURCE_PATH"/. "$SSH_USER@$SSH_IP:$DESTINATION_PATH" > /dev/null 2> "$SCP_LOG" SCP_RC=$? else expect -c " log_user 0 set timeout -1 spawn scp -o StrictHostKeyChecking=no -P $SSH_PORT -r $SOURCE_PATH/. $SSH_USER@$SSH_IP:$DESTINATION_PATH expect { \"*?assword:\" { send -- \"$SSHPASS\r\" exp_continue } eof { exit [lindex [wait] 3] } } " 2> "$SCP_LOG" SCP_RC=$? fi if [ $SCP_RC -eq 0 ]; then echo "[ 传输代理 ] [ 成功 ] 传输已完成。" rm -f "$SCP_LOG" exit 0 else echo "[ 传输代理 ] [ 错误 ] 传输失败。" while IFS= read -r line; do echo "[ 传输代理 ] $line" done < "$SCP_LOG" rm -f "$SCP_LOG" exit 1 fi调整顶部的变量(
SSH_IP、SSH_USER、SSH_KEY、DESTINATION_PATH)以匹配您的新服务器。运行传输:
sh./transfer.sh- 如果基于密钥的身份验证成功,备份文件夹将通过 SCP 复制。
- 否则,系统会提示您输入 SSH 密码。
5. 在新服务器上恢复备份
注意
在这个示例中,我们将使用 Umami Analytics(PostgreSQL)来展示如何恢复数据库支持的应用程序。根据您自己的数据库调整路径和卷名。
在新服务器上部署您的应用程序,然后停止它,以便卷将被创建但不会被使用。
SSH 连接到新服务器并创建一个名为
restore.sh的脚本:shtouch restore.sh && chmod +x restore.sh将以下内容粘贴到
restore.sh中:sh#!/bin/bash # === 目标卷名输入 === # 提示输入要恢复到的目标 Docker 卷名 read -p "[ 恢复代理 ] [ 输入 ] 请输入要恢复到的目标 Docker 卷名: " TARGET_VOLUME # === 卷检查 === # 检查目标卷是否存在 if ! docker volume ls --quiet | grep -q "^$TARGET_VOLUME$"; then echo "[ 恢复代理 ] [ 错误 ] 卷 '$TARGET_VOLUME' 不存在。" # 询问用户是否要创建卷 read -p "[ 恢复代理 ] [ 输入 ] 您是否要创建一个名为 '$TARGET_VOLUME' 的新卷?(y/N): " create_volume if [[ "$create_volume" == "y" ]]; then echo "[ 恢复代理 ] [ 信息 ] 正在创建卷 '$TARGET_VOLUME'..." docker volume create "$TARGET_VOLUME" || { echo "[ 恢复代理 ] [ 错误 ] 无法创建卷 '$TARGET_VOLUME',中止恢复。" echo "[ 恢复代理 ] [ 错误 ] 恢复失败!" exit 1 } echo "[ 恢复代理 ] [ 信息 ] 卷 '$TARGET_VOLUME' 创建成功。" else echo "[ 恢复代理 ] [ 信息 ] 卷 '$TARGET_VOLUME' 不存在且用户选择不创建。中止恢复。" echo "[ 恢复代理 ] [ 错误 ] 恢复失败!" exit 1 fi else echo "[ 恢复代理 ] [ 信息 ] 卷 '$TARGET_VOLUME' 存在,继续..." fi # === 备份目录输入 === # 提示输入备份目录(默认:./volume-backup) read -p "[ 恢复代理 ] [ 输入 ] 请输入备份目录(默认:./volume-backup): " BACKUP_DIR BACKUP_DIR=${BACKUP_DIR:-./volume-backup} # === 备份目录检查 === # 检查备份目录是否存在 if [[ ! -d "$BACKUP_DIR" ]]; then echo "[ 恢复代理 ] [ 错误 ] 未找到备份目录: $BACKUP_DIR" echo "[ 恢复代理 ] [ 错误 ] 恢复失败!" exit 1 fi echo "[ 恢复代理 ] [ 信息 ] 找到备份目录 '$BACKUP_DIR',继续..." # === 备份文件输入 === # 提示输入备份文件名 read -p "[ 恢复代理 ] [ 输入 ] 请输入备份文件名(例如:abc123_postgresql.tar.gz): " BACKUP_FILE # === 备份文件检查 === # 检查备份文件是否存在 if [[ ! -f "$BACKUP_DIR/$BACKUP_FILE" ]]; then echo "[ 恢复代理 ] [ 错误 ] 未找到备份文件: $BACKUP_DIR/$BACKUP_FILE" echo "[ 恢复代理 ] [ 错误 ] 恢复失败!" exit 1 fi echo "[ 恢复代理 ] [ 信息 ] 找到备份文件 '$BACKUP_FILE',继续..." # === 安全确认 === echo "[ 恢复代理 ] [ 信息 ] 请确保使用 '$TARGET_VOLUME' 的容器已停止!" read -p "[ 恢复代理 ] [ 输入 ] 继续恢复?(y/N): " confirm if [[ "$confirm" != "y" ]]; then echo "[ 恢复代理 ] [ 错误 ] 恢复失败:用户取消。" exit 1 fi # === 恢复开始 === # 通知用户恢复开始 echo "[ 恢复代理 ] [ 信息 ] 正在将 $BACKUP_FILE 恢复到卷: $TARGET_VOLUME" # 运行 Docker 容器恢复备份 docker run --rm \ -v "$TARGET_VOLUME":/volume \ -v "$(pwd)/$BACKUP_DIR":/backup \ busybox \ sh -c "cd /volume && tar xzf /backup/$BACKUP_FILE" || { # 如果恢复过程失败,打印错误消息并退出 echo "[ 恢复代理 ] [ 错误 ] Docker 恢复过程失败,中止。" echo "[ 恢复代理 ] [ 错误 ] 恢复失败!" exit 1 } # 如果一切成功,通知用户 echo "[ 恢复代理 ] [ 成功 ] 恢复已完成!"运行脚本:
sh./restore.sh- 输入卷名(来自
docker volume ls命令或 Coolify 的持久存储页面)。 - 按Enter接受
./volume-backup,或输入自定义备份路径。 - 输入备份文件名(例如:
umami_postgresql-backup.tar.gz)。 - 输入
y确认您要继续。
- 输入卷名(来自
6. 启动您的应用程序
恢复完成后,转到 Coolify 的仪表板并点击部署。
您的应用程序现在应该使用迁移的数据。如果没有,或者日志显示错误,请重复恢复步骤以确保所有文件都正确复制。
注意
如果新服务器上的数据库凭据(用户名、数据库名称或密码)与旧服务器不同,请在 Coolify 的仪表板中更新它们以匹配旧服务器的凭据。

