用 Docker Compose 部署一个站点:数据保存、更新与恢复怎么做

用单机 Web 应用和 PostgreSQL 串起 Docker Compose 部署:固定镜像、验证持久化、配置网络与日志,再完成备份、更新和恢复演练。

把应用部署到土耳其|BRNCHOST · 土耳其 VDS
云服务器,积分可续期|雨云 · 国内外节点 · 积分兑换权益
低价年付,大流量 VPS|RackNerd · SSD 存储 · 1Gbps 端口
香港轻量,搭个小站|晚安云 · 香港云服务器
香港 VPS,大带宽可选|野草云 · BGP 直连
大陆访问,精品线路|搬瓦工 · CN2 GIA / CTGNet 套餐
资料归档,交给 AI 整理|WorkBuddy · 本地文件处理
建站起步,先看应用镜像|腾讯云 · 轻量应用服务器
CN2 GIA,中国方向优化|DMIT · Premium 网络
双 ISP 原生住宅 IP|丽萨主机 · 美国 9929 精品线路
高频 CPU,多地部署|Evoxt · 云服务器 · 每周异地备份
京东云轻量云主机:129元/年,新人专享,限购1台

一个站点能在容器里打开,并不代表下次更新还能保住数据。最容易出问题的时刻,往往是第一次换镜像、重建数据库容器,或者发现磁盘快满而开始清理。平时藏在容器里的手工修改、没有记录的环境变量,都可能在这时暴露出来。

下面以单台 Linux 服务器上的 Web 应用和 PostgreSQL 为例,把部署过程串起来。macOS 和 Windows 上也能练习 Compose,但本机 Docker Desktop 的网络、文件共享和虚拟机环境,与 Linux 服务器并不完全相同。示例配置需要结合自己的应用调整;文中会区分可以直接检查的数据库实验和需要自行实现的应用接口。

部署前先留下三份文件

第一份是镜像标识,说明这次运行的程序是什么。第二份是 Compose 配置,说明容器如何启动。第三份是数据与备份说明,记录数据库和上传文件存在哪里。密钥单独管理,不应随着镜像或公开代码分发。

这几份文件能回答一次迁移中最实际的问题:原服务器不能登录时,能否在另一台机器恢复服务?如果答案依赖“进去容器后再改一下”,那项修改就应该回到镜像构建或部署配置中。

安装 Docker 时,按官方安装文档选择对应系统。装完先检查客户端能否连接服务端,再确认 Compose 插件可用。

docker version
docker compose version

访问 Docker daemon 的权限需要认真分配。能控制普通 Docker daemon 的用户,通常也能通过挂载宿主目录等方式取得很高权限。不要把加入 docker 组当作普通的命令补全设置;多人机器可进一步评估 Rootless 模式。

准备练习环境时,可以先用一台与生产分开的机器,把安装、备份和恢复做完整。雨云的服务器配置入口在下面,选规格时同时核对磁盘空间、可用端口和备份方式;本文的部署步骤不依赖特定厂商。

高防 / BGP,按需选线路|雨云云服务器 · 依地区与套餐提供

镜像版本应该能在下次找到

镜像标签便于阅读,但标签可以被重新指向另一份内容。团队需要可复现发布时,应记录镜像 digest,并让上线记录能关联到源代码版本和构建结果。不要只留下一个 latest,等回退时才问它昨天指向哪里。Docker 构建建议解释了固定基础镜像和更新依赖的取舍。

固定 digest 也不意味着以后不用升级。它让变更发生得更明确:新基础镜像经过测试,再更新引用。若几年不更新,固定下来的也可能是一份带已知漏洞的旧环境。

自己的应用镜像应包含运行所需的产物。以 Java 为例,下面假定构建流程已经生成 app.jar,运行镜像只负责启动它:

FROM eclipse-temurin:21-jre
WORKDIR /app
RUN useradd --system --uid 10001 appuser
COPY app.jar /app/app.jar
USER appuser
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app/app.jar"]

这段代码没有替你完成编译和测试,基础镜像也需要在正式发布时固定。若使用多阶段构建,可以在第一阶段编译,最后阶段只复制产物。无论哪种方式,都不要将本地 .env、私钥、数据库转储和不必要的目录带进构建上下文。

.dockerignore 要与 Dockerfile 配合。如果构建依赖本地 app.jar,就不能一边忽略所有 JAR,一边期待 COPY 成功。构建失败先看实际上下文,而不是不停清缓存。

先独立验证数据库持久化

容器可写层跟随容器,删除容器就会失去这一层的数据。命名卷则独立存在,可以重新挂载到新容器。卷仍在同一台机器上,宿主磁盘损坏或误删都可能影响它,因此它不能替代备份。Docker 卷文档说明了生命周期和挂载行为。

下面使用 PostgreSQL 18 系列官方镜像。18 起默认数据目录布局有变化,卷应挂到 /var/lib/postgresql;不要将旧教程中的路径机械搬过来。镜像的版本说明见 PostgreSQL 官方镜像文档。

name: compose-learning
services:
  db:
    image: postgres:18
    environment:
      POSTGRES_DB: app
      POSTGRES_USER: app
      POSTGRES_PASSWORD: ${DB_PASSWORD:?请设置 DB_PASSWORD}
    volumes:
      - db-data:/var/lib/postgresql
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d app"]
      interval: 5s
      timeout: 3s
      retries: 10
    restart: unless-stopped
volumes:
  db-data:

把它保存为 compose.yaml,在本地受保护的 .env 中设置用于实验的随机密码,不要提交到仓库。这里的 POSTGRES_USER 用于初始化数据库超级用户;正式应用应另建权限受限的业务账号,不把初始化账号直接交给 Web 进程。

docker compose config --quiet
docker compose up -d db
docker compose ps

容器出现 running 后,还要等待健康检查并确认连接。首次初始化与复用已有卷是不同过程:修改初始化环境变量,不会自动修改已有数据库中的密码或用户。需要改密码时,通过数据库自己的管理流程操作。

练习时可以在数据库建立一张小表,插入一条标记记录,再仅重建数据库容器,确认记录仍能读到。整个实验使用独立项目名和卷,避免触碰已有服务。不要为了“重来一次”在生产项目中随手执行带 -v 的清理命令。

本次在独立的本地实验项目中,用 Compose 5.4.0 和 PostgreSQL 18.6 完成了建表写入、强制重建容器、逻辑转储及恢复到新数据库的验证,标记记录在重建和恢复后均能读到。该结果只覆盖这一数据库实验,不包含后文应用镜像或真实业务负载。

给应用接上数据库

Compose 同一网络中的服务可以通过服务名发现彼此。应用连接数据库应使用 db:5432;容器里的 localhost 指向自己。将宿主机上的连接字符串原样复制进容器,是初次部署时常见的错误。

以下是向前面的配置添加应用服务的示意。APP_IMAGE、业务数据库账号和健康端点均需由自己的项目提供,不能直接把占位值用于生产。

  app:
    image: ${APP_IMAGE:?请设置应用镜像}
    environment:
      DATABASE_HOST: db
      DATABASE_PORT: "5432"
    ports:
      - "127.0.0.1:8080:8080"
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped
    stop_grace_period: 30s
    mem_limit: 1g
    cpus: 1.0
    logging:
      driver: json-file
      options:
        max-size: "20m"
        max-file: "5"

depends_on 的健康条件帮助处理初次启动顺序,但数据库运行中断开以后,应用仍需连接重试、超时与错误处理。Compose 不会持续替每个业务请求确认依赖可用。启动顺序文档有更完整的行为说明。

数据库健康检查也不是应用验收。pg_isready 可以判断服务器是否接收连接,却不证明业务账号密码正确、迁移已完成或所有表都存在。应用上线检查应该包含一条有代表性的读操作;写入检查则使用专门测试数据,并安排清理。

应用自身的健康检查要调用镜像里确实存在的工具。原样复制一条 curl 命令,而运行镜像没有安装 curl,会把健康检查变成持续失败。应选择项目提供的探测方式,并区分进程存活和能否承接业务。

端口从容器到公网有几层

应用需要监听容器内可被网络访问的地址,宿主端口映射再将流量转给它。上例绑定宿主机 127.0.0.1:8080,适合由同机反向代理接入;数据库没有发布端口,只供内部网络访问。

EXPOSE 8080 只是镜像中的说明,并不会自动把端口开放到公网。ports 不写宿主绑定地址时,默认发布范围可能比预期大。具体行为与安全注意事项见 Docker 端口发布文档。

如果 Caddy 或 Nginx 也运行在另一个容器里,它的 127.0.0.1 同样指向自己。可以让代理与应用加入适当的共享网络,通过应用服务名访问,而不是照抄宿主代理的地址。

验证时从里向外检查:应用是否启动,容器网络能否连通,宿主入口是否响应,反向代理是否转发,最后再看公网与 DNS。还要从外部机器验证端口暴露情况,不能只看本机防火墙界面就认定数据库没有对外开放。

停止进程与限制资源

容器主进程结束,容器就退出。启动脚本如果把服务放到后台,自己立即退出,容器也可能随之结束。Dockerfile 使用 JSON 形式的入口命令,或者包装脚本最后使用 exec,有助于让应用接收停止信号。

收到停止信号后,应用需要停止接收新任务,给正在处理的请求留出时间,再关闭资源。Compose 的 stop_grace_period 是等待预算,超过后仍可能强制结束。一个只会忽略信号的程序,不会因为设置了三十秒就自动具备优雅停机。

重启策略处理的是进程退出后的动作。普通单机 Docker 不会仅因健康状态变为 unhealthy 就按 restart 策略重启它。健康检查失败应该进入监控和排查流程,不能把它当成已经配置好的自动修复系统。容器运行文档列出了运行与资源选项。

内存限制应给运行时、线程栈和本地分配留空间。Java 堆上限不能直接等于容器内存上限。遇到退出时先看退出码和 OOM 标记,再检查实际占用;反复提高限额可能掩盖泄漏,也可能将问题转移到整台主机。

日志和备份都需要容量预算

日志写到标准输出后,仍会由运行时保存或转发,不是凭空消失。上例为 json-file 配了大小和份数限制。Docker 也提供其他驱动,选择时看检索、丢失容忍度和集中采集要求。日志配置文档提醒默认值变化不会自动应用到已有容器。

数据库备份使用数据库理解的一致性机制。直接在运行中压缩数据目录,不应被当作通用的可靠备份方法。对本例数据库,可在权限允许的环境使用逻辑转储:

docker compose exec -T db pg_dump -U app -d app -Fc > app.dump
docker compose exec -T db pg_restore --list < app.dump

检查目录能证明文件可被识别,却不能代替恢复。下一步应该在一个新建的测试数据库中执行恢复,并查询关键记录。不要把带清理选项的恢复命令直接指向正在服务的生产库。

pg_dump 不负责备份整个集群的所有角色等全局对象,也不包含应用上传目录。需要恢复什么,就分别列入清单。客户端版本与服务端版本也要符合兼容要求,详见 pg_dump 文档。

备份完成后记录文件大小、时间、校验摘要和恢复测试结果,并将副本放到独立故障范围内。同一块磁盘上的另一个目录,无法帮助你应对整盘损坏。备份中可能包含用户信息,存放权限和访问凭据同样需要管理。

更新时先想好数据库还能不能回退

更新应用镜像前,保留上一版的镜像标识和部署配置,完成备份,再拉取已测试的新版本。通常无需先把整个 Compose 项目停掉,可以让 up 按配置重建发生变化的服务。

docker compose pull app
docker compose up -d --no-deps app
docker compose ps
docker compose logs --tail 100 app

--no-deps 适用于确认依赖无需跟随改变的情况。如果新应用依赖数据库迁移或新服务,应该按这次发布的依赖顺序处理。不要把这条命令当作不需要阅读变更说明的快捷键。

单副本容器重建可能有短暂中断,Compose 本身不保证零停机。需要连续服务时,可以安排多实例与代理切换,但要先确认会话、任务消费和迁移支持并行版本。仅同时启动两份程序,可能导致定时任务重复执行。

回退镜像之前尤其要检查数据结构。新版本已经删除旧字段时,旧程序未必还能运行。较容易回退的迁移通常先增加兼容结构,再逐步切换读写,最后清理旧结构。是否能够回退,应在测试环境验证,不能等上线失败时才决定。

把一次恢复演练当作部署验收

最后安排一个明确的演练:创建独立测试项目,使用保存的镜像、配置和备份恢复服务,再验证登录、核心查询、上传文件和后台任务。记录完成所需时间,以及过程中哪些信息还需要临时找人询问。

这能直接暴露遗漏:镜像只在旧机器本地、上传目录没备份、密钥没有恢复途径、DNS 切换后回调仍指向旧地址。修复这些遗漏后,部署说明才足以交给另一个人使用。

单机 Compose 很适合这类规模可控的站点,但主机故障仍可能影响整套服务。选择更复杂的编排平台之前,先确认眼前需要的是跨主机调度,还是一份能真正恢复的备份。对于已经能满足负载的站点,把恢复流程做实通常是更迫切的一步。