Coolify logoCoolify

将应用迁移到另一个 Coolify 主机

Coolify 没有内置的从一个服务器迁移应用到另一个服务器的选项。

您必须在新服务器上手动部署应用,并复制数据库和卷。本指南将逐步引导您完成该过程。

注意

我们假设您已经在目标服务器上安装了 Coolify,并准备迁移应用。

1. 理解数据持久化

使用 Coolify 时,应用数据位于以下两个位置之一:

Migrate Apps Different Host 的截图

绑定挂载 (Bind mounts)

  • 使用绑定挂载时,主机目录或文件会被映射到容器中。
  • 对主机上的目录或文件所做的任何更改都会立即反映在容器内部。
  • 要备份数据,只需将主机目录或文件复制到新服务器,并更新应用配置中的绑定挂载路径即可。

卷挂载 (Volume mounts)

  • 使用卷挂载时,会创建一个 Docker 卷(Coolify 通常会创建该卷,但您也可以自行设置),并用于存储应用数据。
  • 该卷存储在 Docker 的卷目录中,通常位于 /var/lib/docker/volumes/<VOLUME_NAME> 下。
  • 您不能直接复制该目录,相反,Docker 提供了一个使用临时容器进行安全备份和恢复的方法。

注意

由于通过直接复制文件来迁移绑定挂载非常简单,本指南将主要关注卷备份。

2. 备份和恢复概述

Docker 推荐的卷迁移流程如下所示:

Migrate Apps Different Host 的截图
  1. 挂载您的卷到临时容器中。
  2. 归档卷的内容为 tarball 文件。
  3. 复制 tarball 文件从容器到您的主机,然后删除临时容器。
  4. 传输 tarball 文件到新服务器。
  5. 创建目标服务器上的新卷。
  6. 挂载传输过来的 tarball 文件到临时容器中。
  7. 解压归档文件到新卷中。

这一系列步骤确保了备份和恢复的一致性和安全性。下面,我们将提供现成的脚本和详细说明。


注意

以下步骤包括脚本,可帮助您轻松、交互式地备份、传输和恢复卷。

它们还包含检查机制,以确保卷和备份存在,防止诸如恢复到错误卷之类的错误。

3. 备份卷

  1. SSH 登录到拥有 Docker 卷的服务器。

  2. 创建一个名为 backup.sh 的脚本:

    touch backup.sh && chmod +x backup.sh
  3. 在编辑器中打开 backup.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 "[ 备份代理 ] [ 成功 ] 备份完成!"
  4. 通过运行以下命令找到卷名

    docker volume ls

    或者从 Coolify 的 持久化存储 (Persistent Storage) 页面找到(见下文)。

    Migrate Apps Different Host 的截图
  5. 停止您的应用以进行干净备份。

  6. 运行脚本:

    ./backup.sh
    • 当被提示时,粘贴卷名。
    • 回车 接受默认备份目录 (./volume-backup),或输入自定义路径。
  7. 验证您现在拥有一个包含 <VOLUME_NAME>-backup.tar.gz 的目录(例如 volume-backup)。


4. 将备份传输到新服务器

提示

如果您已经知道如何手动传输备份文件,请随时跳到下一步。

  1. 创建第二个名为 transfer.sh 的脚本:

    touch transfer.sh && chmod +x transfer.sh
  2. 在编辑器中打开 transfer.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
    
    # ---------------------------------------------
    # 辅助函数:测试 $1(密码)是否有效,通过执行 “ssh … exit”
    # 如果 OK 返回 0,如果 “Permission denied”(或任何其他失败)返回 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
  3. 调整顶部的变量 (SSH_IP, SSH_USER, SSH_KEY, DESTINATION_PATH) 以匹配您的新服务器。

  4. 运行传输:

    ./transfer.sh
    • 如果基于密钥的认证成功,备份文件夹将通过 SCP 复制。
    • 否则,系统将提示您输入 SSH 密码。

5. 在新服务器上恢复备份

注意

在此示例中,我们将使用 Umami 分析(PostgreSQL)来展示如何恢复基于数据库的应用。请根据您的数据库调整路径和卷名。

  1. 在 Coolify 上部署您的应用到新服务器,然后停止它,以便创建卷但不会被使用。

  2. SSH 登录到新服务器并创建一个名为 restore.sh 的脚本:

    touch restore.sh && chmod +x restore.sh
  3. 粘贴以下内容到 restore.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 "[ 恢复代理 ] [ 成功 ] 恢复完成!"
  4. 运行脚本:

    ./restore.sh
    • 输入卷名(来自 docker volume ls 命令或 Coolify 的持久化存储页面)。
    • 回车 接受 ./volume-backup,或输入自定义备份路径。
    • 输入备份文件名(例如,umami_postgresql-backup.tar.gz)。
    • 输入 y 确认您希望继续。

6. 启动您的应用

恢复完成后,转到 Coolify 仪表板并点击 部署 (Deploy)

您的应用现在应该使用迁移后的数据。如果未使用,或者日志显示错误,请重复恢复步骤以确保所有文件都正确复制。

注意

如果新服务器上的数据库凭据(用户名、数据库名或密码)与旧服务器不同,请在 Coolify 仪表板中更新它们以匹配旧服务器的设置。

Migrate Apps Different Host 的截图

On this page