跳到主要内容

Duplicati 部署教程:加密备份、远端存储与文件恢复

使用 Docker 部署 Duplicati,设置备份范围、加密口令和远端目标,恢复旧版本文件。

Duplicati 适合备份什么

网站附件、配置和数据库导出文件,可以交给 Duplicati 定时备份到 SFTP 或对象存储,并保留多个时间点。正在写入的数据库先用数据库工具导出,再备份导出文件。

准备目录和登录密码

本篇使用 LinuxServer 镜像。先用 id 查看宿主用户的 UID、GID,下面 Compose 的 PUID、PGID 示例为 1000,需要按实际用户修改。

下面整段在 Bash 终端执行,只用于第一次创建部署目录。先确认家目录与 services 路径可信、不经过符号链接,且不允许其他账号写入。目录已存在时会停止;已有实例应进入原部署目录维护,不要重跑初始化。

bash;使用前请核对本文前提。
bash <<'BASH' || exit 1
set -euo pipefail
umask 077
parent="$HOME/services"
project="$parent/duplicati"
test -d "$HOME"
test ! -L "$HOME"
if [ -e "$parent" ] || [ -L "$parent" ]; then
  test -d "$parent"
  test ! -L "$parent"
  test -O "$parent"
else
  mkdir -m 700 "$parent"
fi
if [ -e "$project" ] || [ -L "$project" ]; then
  printf '%s\n' '部署目录已存在,停止初始化;保留原 .env、密钥和 config。' >&2
  exit 1
fi
mkdir -m 700 "$project"
cd -- "$project"
mkdir -m 700 config restore-test
id
settings_key="$(openssl rand -hex 32)"
web_password="$(openssl rand -hex 24)"
[[ "$settings_key" =~ ^[0-9a-f]{64}$ ]] || exit 1
[[ "$web_password" =~ ^[0-9a-f]{48}$ ]] || exit 1
(set -o noclobber; printf 'SETTINGS_ENCRYPTION_KEY=%s\nDUPLICATI__WEBSERVICE_PASSWORD=%s\n' "$settings_key" "$web_password" > .env)
BASH
cd -- "$HOME/services/duplicati" || exit 1

后台密码用于登录管理界面;SETTINGS_ENCRYPTION_KEY 用于加密本地敏感设置;备份任务的加密口令用于解密远端备份。三者分别保存,备份口令与存储凭据另外留在服务器之外。

初始化整段成功后,再保存下文的 Compose 文件并启动;若中途报错,先检查本次新目录,不要继续启动容器。沿用原 config 时,必须保留与它配套的 .env 和 SETTINGS_ENCRYPTION_KEY,不能重新随机生成后覆盖;密钥丢失会使原来加密保存的字段无法读取。

启动容器并访问后台

先建立 /srv/backup-source,并让指定 UID 能读取;把需要备份的附件与数据库导出放到这个目录。保存 compose.yaml:

yaml;使用前请核对本文前提。
services:
  duplicati:
    image: lscr.io/linuxserver/duplicati:latest
    env_file:
      - .env
    environment:
      PUID: "1000"
      PGID: "1000"
      TZ: Asia/Shanghai
    ports:
      - "127.0.0.1:8200:8200"
    volumes:
      - ./config:/config
      - /srv/backup-source:/source:ro
      - ./restore-test:/restore-test
    restart: unless-stopped

后台看到的来源路径是 /source,恢复目录是 /restore-test。来源只读挂载,恢复文件写入独立目录。

sh;使用前请核对本文前提。
docker compose config --quiet
docker compose up -d
docker compose logs --tail=80 duplicati
sh;使用前请核对本文前提。
ssh -L 18200:127.0.0.1:8200 user@your-server

在本机打开 http://127.0.0.1:18200,使用 .env 中的后台密码登录。8200 只绑定服务器回环地址,不需要开放公网端口。

添加远端备份任务

新建备份任务,填写名称、独立加密口令和远端存储。SFTP、S3 兼容存储分别按表单设置地址与访问凭据,点击连接测试后选择 /source 中的目录。

先运行一份小范围备份,查看结果中的未读文件、权限错误和上传失败。数据库导出先写临时文件,成功后再改成正式文件;备份计划安排在导出完成之后。

同一个远端目录由一套 Duplicati 任务维护。迁移时先停掉旧实例的计划任务,再由新实例接管。保留每日、每周和每月历史时,按文件变化量与目标容量调整。

恢复一个旧版本文件

备份一个内容明确的文本文件,修改后再次备份。进入恢复页面,选第一个时间点,将文件恢复到 /restore-test,再检查内容。

sh;使用前请核对本文前提。
find restore-test -maxdepth 3 -type f
sha256sum restore-test/example.txt

example.txt 换成实际文件名;先用 find 找到恢复后的层级。再恢复图片和数据库导出,数据库导入到独立实例检查。原实例丢失时,可以凭远端目标、凭据和加密口令,从远端重建恢复信息。

原机已经不可用时,先找齐备份目标的完整地址、访问凭据和备份加密口令。只有后台登录密码、SETTINGS_ENCRYPTION_KEY 或本地数据库,都不能代替备份口令。若备份做了加密却丢失口令,就无法解密取回文件。

新机沿用前面的目录与回环访问方式,但在启动前把 Compose 的 image 改成原实例记录的版本或镜像摘要,不直接用 latest 替代原版本。把 /config 挂到一个新的空目录,恢复目录也单独建立。新实例的后台密码和设置加密密钥可以重新生成,备份加密口令仍用原来的。旧实例若还在运行,先停止它的计划任务,不让两套任务同时维护同一备份目标。

进入恢复页面,选择“直接从备份文件恢复”(Direct restore from backup files),重新填写原来的存储类型、目标地址与访问凭据,测试连接后输入备份加密口令。Duplicati 会从目标读取文件列表和索引,建立本次恢复需要的局部数据库;没有原来的 config,也可以走这条路径。它不会因此恢复原计划任务、通知设置或全部本地管理数据。

文件列表出现后,先在版本下拉框中选择要恢复的准确时间。默认显示最新版本,误删文件可能只存在于更早的快照中。选中需要的完整目录,将目标改成新的空目录;不要直接覆盖正在运行的站点,也不要把恢复目录放回同一个备份仓库。给恢复文件和临时下载、索引、缓存都留出空间,摘要若报告错误或警告,先查明原因再接回应用。

Duplicati 默认不恢复原权限,因为新机器的用户和组可能不同。需要保留权限时,先核对原 UID、GID 与目标账号的对应关系,再启用相应选项并检查结果;不要只凭用户名相同就认为属主一致,也不要用全目录放宽权限代替排查。

文件哈希相符后,再验证数据库导出。若备份的是 SQLite 的 SQL 导出,可在已安装 sqlite3 的环境中导入一份新的检查库;下面的 database.sql 路径要按 find 的实际结果修改,不能指向应用正在使用的数据库。

sh;使用前请核对本文前提。
set -eu
sql_file=restore-test/database.sql
test -f "$sql_file"
test ! -e restore-check.sqlite
sqlite3 restore-check.sqlite < "$sql_file"
sqlite3 restore-check.sqlite 'PRAGMA integrity_check;'

完整性检查通过后,还要查看关键表和业务记录。PostgreSQL、MySQL 等导出应使用对应工具导入独立测试实例;取回一个 SQL 文件不等于数据库或应用已经恢复。确认数据后,再安排应用切换与新的备份计划。

这条“无原配置恢复文件”的路径已用 LinuxServer Duplicati 2.4.0.1(v2.4.0.1_stable_2026-10-07-ls313)做过 ARM64 本地命令行验证:从加密仓库副本、空客户端配置恢复两个历史版本,逐项比对文件内容和权限,并将两份 SQLite 导出分别导入新库检查。这里验证的是本地文件仓库的文件恢复;后台界面、SFTP、S3 与计划任务迁移仍须在各自环境中核验。

保存配置与迁移任务

本地 config 保存任务设置与数据库。停止容器后归档 config、.env 与 Compose 文件:

在原 Compose 目录执行,先结束正在运行的备份任务,并确认没有另一处维护在同时操作容器。下面的片段只处理一个原本正在运行的 Duplicati 容器。停止失败时不打包;打包或读取校验失败时返回非零,并保留独占目录里的临时文件。停止命令发出后,退出时只尝试一次启动原容器,启动失败仍返回非零。

sh;使用前请核对本文前提。
(
  set +e
  umask 077
  container_id=$(docker compose ps --all --quiet duplicati) || exit 1
  case "$container_id" in
    ''|*[!0-9a-f]*) printf '%s\n' '未找到唯一的 Duplicati 容器,停止操作。' >&2; exit 1 ;;
  esac
  [ "${#container_id}" -eq 64 ] || exit 1
  running=$(docker inspect --format '{{.State.Running}}' "$container_id") || exit 1
  [ "$running" = true ] || {
    printf '%s\n' '此片段只用于原本正在运行的实例;未启动已停止的实例。' >&2
    exit 1
  }

  mkdir -p local-config-backups || exit 1
  archive_dir=$(mktemp -d local-config-backups/duplicati-config.XXXXXX) || exit 1
  partial="$archive_dir/config.tar.gz.partial"
  archive="$archive_dir/config.tar.gz"
  restart_needed=0
  finish() {
    result=$?
    trap - 0 HUP INT TERM
    if [ "$restart_needed" -eq 1 ]; then
      restart_needed=0
      if docker start "$container_id" >/dev/null; then
        running=$(docker inspect --format '{{.State.Running}}' "$container_id") || running=unknown
        [ "$running" = true ] || result=1
      else
        result=1
      fi
    fi
    if [ "$result" -eq 0 ]; then
      printf '配置归档已通过读取校验,原容器已重新运行:%s\n' "$archive"
    else
      printf '操作未完成;检查原容器状态,保留本次目录供排查:%s\n' "$archive_dir" >&2
    fi
    exit "$result"
  }
  trap finish 0
  trap 'exit 129' HUP
  trap 'exit 130' INT
  trap 'exit 143' TERM

  restart_needed=1
  docker compose stop duplicati || exit 1
  running=$(docker inspect --format '{{.State.Running}}' "$container_id") || exit 1
  [ "$running" = false ] || exit 1
  tar -czf "$partial" config .env compose.yaml || exit 1
  gzip -t "$partial" || exit 1
  tar -tzf "$partial" >/dev/null || exit 1
  mv "$partial" "$archive" || exit 1
)

归档通过 gzip 完整性检查和 tar 目录读取后,才从 .partial 改成正式文件名。这个检查不等于数据恢复演练;配置归档仍含 .env 等敏感信息,要按前文方式另存安全副本。强制结束 Shell、主机断电等情况下,退出处理可能无法执行,需要另行检查容器状态。

将归档转移到服务器之外并限制访问。重建时沿用原 config 和设置加密密钥;升级前保存镜像版本及配置,更新后检查下一次计划任务的运行结果。

能正常访问后台时,也可以额外导出任务配置,省去重建时重新填写存储地址和选项。导出文件若另行加密,其密码不一定与备份加密口令相同,两者要分别记录。导出配置不能代替远端备份文件;只想抢救文件时,也不必先把计划任务重新启动。