服务器上只有几个目录时,备份看起来很简单:打包、传走、定期删旧文件。等到照片、网站附件和数据库导出越来越多,就会遇到另一个问题:每次都传整份数据太费时间,只保留最新文件又无法找回误删前的内容。Duplicati 适合把这些已经整理好的文件交给一个有界面的备份任务,按计划上传到另一处存储,并保留多个时间点。
先把备份范围写清楚。网站上传目录可以作为文件来源;正在运行的 PostgreSQL 数据目录不能直接照搬为可用数据库备份。数据库先用自身工具导出,Duplicati 再备份导出结果。这样恢复时得到的是有明确用途的文件,而不是一堆看起来完整、实际打不开的数据页。
三种密码管的是三件事
Duplicati 的后台登录密码用于保护管理界面,SETTINGS_ENCRYPTION_KEY 用于保护本地保存的敏感设置,备份任务中的加密口令用于解密远端备份。它们不能互相替代。登录后台成功,并不意味着手里已经有恢复所需的加密口令。
把任务加密口令和目标存储的访问凭据保存在服务器之外。例如放进自己的密码管理器,同时留一份受保护的离线记录。如果两者只写在这台 VPS 的磁盘里,服务器损坏时,备份文件即使还在远端,也可能没有办法使用。

图中将文件来源、远端备份和恢复目录分开。恢复演练写入新目录,避免把当前文件覆盖成旧版本;这也是首次测试时最值得坚持的一步。
容器只看得到你挂进去的文件
下面使用 LinuxServer 维护的 Duplicati 镜像,适用于已有 Docker Compose 的 Linux VPS。先查看负责读写数据的宿主用户 UID 和 GID,示例使用 1000。若实际值不同,修改环境变量和目录权限,别直接给整个网站目录开放写权限。
mkdir -p ~/services/duplicati
cd ~/services/duplicati
mkdir -p config restore-test
chmod 700 config restore-test
id
umask 077
printf 'SETTINGS_ENCRYPTION_KEY=%s\n' "$(openssl rand -hex 32)" > .env
printf 'DUPLICATI__WEBSERVICE_PASSWORD=%s\n' "$(openssl rand -hex 24)" >> .env
chmod 600 .env
假定要备份的附件和数据库导出已经统一放在 /srv/backup-source。这一目录需要先存在,并让运行容器的 UID 可以读取。保存以下内容为 compose.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:ro 让备份程序读取来源,恢复测试则写入 /restore-test。不要在任务里填宿主机的 /srv/backup-source,那是容器外的路径;后台看到的来源是 /source。示例中的 latest 便于初次安装,长期运行时先确认当前版本,再固定已验证的镜像标签或摘要,避免自动升级同时改变备份工具和任务行为。
docker compose config --quiet
docker compose up -d
docker compose logs --tail=80 duplicati
用 SSH 打开私人后台,不需要把 8200 端口放到公网:
ssh -L 18200:127.0.0.1:8200 user@your-server
在本机打开 http://127.0.0.1:18200,使用 .env 中的后台密码登录。首次配置后,记录当前镜像和本地设置密钥。以后重建容器仍使用同一个 config 目录和密钥,不要为了“换个更随机的密码”随意重生成设置密钥。
第一项任务只备份一小份可核对的数据
先选几份文本、图片和一个数据库导出,别直接把整台服务器交给任务。给备份取一个能识别来源的名字,例如“站点附件与数据库导出”;设置备份加密口令;选择独立的远端存储目录。S3 兼容存储、SFTP 等目标的参数不同,按后台对应表单填写,并实际点击连接测试。
同一个远端目录不要同时交给两个独立 Duplicati 实例写入。任务搬家时,先停止旧机器的定时任务,再由新机器接管。两边同时工作,会让本地索引与远端文件的状态互相干扰,后续排查也很难知道哪边做了修改。
选择 /source 中的小范围目录,然后检查排除规则。缓存、临时上传、已知可重新生成的缩略图可以酌情排除;网站原始附件、配置文件和数据库导出不能只凭文件名猜用途后删除。排除一项就写下原因,恢复测试时也检查该规则没有误伤。

官方文档中的这个页面区分“新建备份”和“从文件导入配置”。新任务从左侧入口开始;自己的存储地址、文件范围和加密设置需要重新填写。
首轮任务结束后查看结果详情,不只看绿色完成标记。留意没有读取到的文件、权限错误、超时和远端写入失败。一个任务可以完成,同时报告部分文件未能备份;这类提示应该在下次计划执行前处理。
每天一次还是每小时一次,取决于能接受丢多少数据
频率应由恢复目标决定。一天只更新几份文档,每天一次通常已经能覆盖实际变化;持续产生订单、留言或上传的站点,需要先确定数据库导出频率,再安排文件备份。备份每小时运行一次,数据库却每天才导出一次,并不能得到小时级的数据库恢复点。
也要看来源文件的生成过程。导出程序先写临时文件,成功后再改名为最终文件,能够减少备份程序读到半份导出的机会。具体数据库导出命令跟随自己的数据库版本和认证方式,不把命令中的密码写进公开日志。
保留规则同样需要计算容量。保存很多历史版本可以帮助找回误删文件,也会占用远端空间。先观察一段时间内的变化量,再选择每日、每周、每月保留策略;不要把短期压缩效果推算成长期固定成本。任务失败时及时通知,否则只会得到一个越来越旧的恢复点。
现在就恢复,而不是等磁盘坏掉
第一次测试可准备一个有明确内容的文本文件,运行备份后修改它,再运行第二次。进入恢复页面,选第一个时间点,把旧版本恢复到 /restore-test,对照内容。随后恢复一张图片和数据库导出,确认它们能被正常读取。
find restore-test -maxdepth 3 -type f
sha256sum restore-test/example.txt
命令里的文件名换成实际恢复出来的文件。恢复出来的路径可能保留来源目录层级,先用 find 确认位置,再计算哈希。需要核对权限时,也比较文件所属用户和模式;跨机器恢复不一定保留原机器的用户与组含义。
再做一次更接近事故的演练:使用独立测试实例,仅凭远端位置、访问凭据和备份口令恢复文件。官方恢复流程允许没有原本本地数据库时从远端读取必要信息,但需要额外下载和处理,速度与原实例恢复可能不同。演练的价值是确认密码、目标地址和恢复步骤都找得到,不是跑出一个漂亮的耗时。
本地配置也要有恢复路径
远端备份文件保存业务数据,config 则包含任务设置和本地数据库。后者丢失不一定意味着所有远端备份不可恢复,但重建任务会更费时间。导出受保护的任务配置,并将 .env、Compose 文件及必要的操作说明单独保管;含有凭据的配置不要作为公开附件分享。
停止容器后可以对本地配置做一份一致的副本:
docker compose stop duplicati
mkdir -p local-config-backups
tar -czf "local-config-backups/duplicati-config-$(date +%F-%H%M%S).tar.gz" config .env compose.yaml
docker compose start duplicati
把这份副本转移到服务器之外,并限制访问权限。它不能替代远端数据备份,也不应该放回同一个待备份来源目录不断递归保存。每次修改目标存储、加密或保留规则后,重新导出设置并恢复一个文件;能够完成这一步,才算这项备份任务真的可用。