资讯动态

Ubuntu云服务器上用Docker部署PostgreSQL:持久化、安全与备份实战

发布时间:2026/9/16 3:46:49 来源:尧图企业网站定制
上周我在腾讯云新开了一台 Ubuntu 24.04 服务器准备把一个后端项目的数据库环境完整迁移过去。选型时没有犹豫直接决定用 Docker 部署 PostgreSQL。整个流程从控制台安全组配置、系统初始化、Docker 安装写到 docker-compose.yml 落地、参数调优和备份恢复踩了几个很典型的坑也整理了一套可以直接复用的方案。这篇文章就是完整的操作记录包含每个步骤背后的选型逻辑、参数依据以及实际运维中会遇到的问题排查思路。适合需要在云服务器上快速部署 PostgreSQL 的开发者、运维同学也适合刚接触容器化数据库、想搞清楚“为什么这么配置”的人。1. 动手前的方案设计容器化部署的三个关键选择部署一件基础设施如果上来就敲命令后面大概率要返工。我在动手前先把几个关键选择定了后面所有操作都围绕这些决定展开。1.1 为什么选择 Docker 而不是裸机安装 PostgreSQL如果直接apt install postgresqlUbuntu 软件源里的 PostgreSQL 版本通常不是最新而且版本升级、多实例管理、环境迁移都非常折腾。裸机安装最大的问题不是装不上而是“环境不可复现”。今天在一台服务器上配好的参数、扩展、数据目录结构换一台机器就要从头再来中间漏掉任何一个细节都会埋雷。Docker 的价值不是“跑起来”而是把整个运行环境固化成镜像。同一份镜像在开发机、测试机、生产机上跑出来的行为完全一致。升级数据库大版本时我可以先拉一个新版本镜像做一次备份恢复演练确认无误后再切换回滚也只是秒级的事情。对于我这种需要同时维护多个项目的场景容器化的隔离性也很有价值不同项目可以用不同版本的 PostgreSQL互不干扰。不过也得说实话容器化不是万能的。如果你有极高并发、对 IO 延迟极其敏感的 OLTP 业务或者需要依赖特殊的操作系统内核特性裸机部署仍然有它的优势。数据库本身在容器里的性能损耗很小但网络模式、存储驱动选不对会影响稳定性。这个后文会说。1.2 PostgreSQL 版本与镜像选型Alpine 还是 Debian 基础镜像当前 PostgreSQL 的稳定版本线是 16 和 1716 经过长时间生产验证17 引入了不少性能优化。我选的是postgres:16-alpine原因有两个一是 16 的生态兼容性最好各种扩展、监控工具基本都跟进了二是 Alpine 镜像体积只有标准版的三分之一左右在云服务器上拉取更快占用的磁盘也更少。但 Alpine 不是没有代价。它基于 musl libc而标准版基于 glibc。虽然 PostgreSQL 的数据文件格式与 libc 版本关系不大但如果你将来要把数据目录迁移到 glibc 环境的实例上最好先在测试环境验证一次恢复流程。如果你不需要纠结体积或者对 musl 有顾虑直接用标准版postgres:16-bookworm完全没问题功能上没有差异只是镜像大了几百 MB。这里特别提醒一个细节PostgreSQL 官方镜像的PGDATA路径在不同大版本之间发生过变化。16 和 17 的默认数据目录是/var/lib/postgresql/data但从 18 开始变成了/var/lib/postgresql/18/docker。如果你照着网上老教程写挂载路径版本一换可能就会遇到数据目录初始化异常。我用的 16 版本路径是/var/lib/postgresql/data这点要看清楚。1.3 数据持久化数据卷必须落在宿主机容器是“用完即走”的如果 PostgreSQL 的数据存在容器内部容器一删数据全部消失。所以数据持久化是整个部署方案里最重要的一环没有任何讨价还价的余地。持久化有命名卷named volume和绑定挂载bind mount两种方式。命名卷由 Docker 管理路径在/var/lib/docker/volumes/下优点是免去了宿主机目录权限的烦恼绑定挂载则把宿主机的一个目录直接映射进容器比如./data:/var/lib/postgresql/data数据文件放在你规定的路径下备份、查看文件、迁移都比较直观。我选择的是绑定挂载把数据目录放在/opt/postgres/data下。这么做的考虑很实际云服务器上要定期做快照备份绑定挂载让我可以针对这个目录做精细化备份策略也能直接通过du看到数据库占了多少磁盘空间。代价是需要手动处理目录权限问题这个我在第 5 节会详细讲。2. 腾讯云环境准备安全组、系统更新与 Docker 安装第一次用腾讯云的同学最容易在控制台层面卡住明明服务器起来了SSH 就是连不上。这部分我按实际操作的顺序来写把容易忽略的细节都标出来。2.1 安全组与登录名的准备工作腾讯云的 CVM 云服务器网络访问控制叫“安全组”如果你用的是轻量应用服务器对应的入口叫“防火墙”。两者本质一样都是控制哪些端口能对外放行。我踩过的第一个坑就是安全组没放行 22 端口导致 SSH 连不上。理论上腾讯云创建实例时会让你指定安全组但如果选了默认全封禁的策略服务器创建成功后你根本登不进去。所以第一步先到控制台确认入站规则至少要有 22 端口来源可以限定为你自己的公网 IP更安全以及后面可能要用到的端口。关于 5432 端口我的建议是不要在安全组里放行 5432 到 0.0.0.0/0。数据库端口直接暴露公网等于把数据库摆在公网上任人扫描暴力破解只是时间问题。如果你确实需要从本地连接数据库先通过 SSH 隧道或者只在安全组里放行你自己的办公网 IP。具体做法在第 4 节详述。Ubuntu 24.04 镜像默认的 SSH 登录用户是ubuntu不是 root有 sudo 权限。用密码登录还是密钥登录看你创建实例时的选择。我个人建议用密钥登录然后把密码登录禁用这是最基础的加固手段。2.2 Ubuntu 24.04 安装 Docker 的两种方式系统登录后先做一次基础更新sudo apt update sudo apt upgrade -y腾讯云 Ubuntu 默认的 apt 源已经指向内网镜像速度很快这一步不用折腾。Docker 安装我推荐通过 apt 仓库完成。虽然官方脚本curl -fsSL https://get.docker.com | sudo sh也能用但国内服务器直连 docker 官方源偶尔会卡不如直接用国内镜像源稳妥。如果使用清华的 docker-ce 源命令如下sudo apt install -y ca-certificates curl gnupg sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/ubuntu/gpg | sudo gpg --dearmor --yes -o /etc/apt/keyrings/docker.gpg sudo chmod ar /etc/apt/keyrings/docker.gpg echo deb [arch$(dpkg --print-architecture) signed-by/etc/apt/keyrings/docker.gpg] https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/ubuntu $(. /etc/os-release echo $VERSION_CODENAME) stable | sudo tee /etc/apt/sources.list.d/docker.list /dev/null sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin完成后设置开机自启并验证sudo systemctl enable --now docker sudo docker --version sudo docker compose version一个常见的坑旧教程里用的是docker-compose带横杠v1现在官方插件是docker compose不带横杠v2。如果上面安装的docker-compose-plugin装好了直接执行docker compose就行。个别系统里可能还残留着 v1 的docker-compose命令核心命令基本兼容但新项目建议统一用 v2。2.3 Docker Hub 镜像拉取慢的常规解法安装好 Docker 之后拉取官方镜像可能会遇到速度不理想的情况这是国内云服务器普遍会碰到的网络环境问题。解决方案是配置镜像加速器修改/etc/docker/daemon.json{ registry-mirrors: [ https://docker.m.daocloud.io ] }如果你用的是腾讯云也可以在控制台打开“容器镜像服务”里面会生成一个专属的镜像加速地址填进来即可。修改完重启 Dockersudo systemctl restart docker先拉个镜像试试sudo docker pull postgres:16-alpine如果这一步能顺利拉完后面部署就顺畅多了。有一点要说清楚公共加速器毕竟是第三方服务如果项目对供应链安全要求很高可以考虑自己搭一个镜像缓存或者直接从可信渠道导入镜像包。2.4 把本地配置文件上传到服务器后面我们会用到docker-compose.yml和.env文件。你可以在服务器上用nano直接编辑但我更习惯在本地用编辑器写好后上传格式不容易出错。用scp一条命令搞定scp docker-compose.yml .env ubuntu服务器公网IP:/opt/postgres/如果你用的是 Windows也可以用 WinSCP 或者直接用 VS Code 的 Remote-SSH 插件拖拽上传更直观。上传后记得检查文件权限尤其.env里面有数据库密码建议执行chmod 600 /opt/postgres/.env这样只有 root 和当前用户才能读取。3. 部署实施docker-compose.yml 编写与启动验证部署方式上我不太建议直接docker run一个长长的命令因为参数一多阅读和后期维护都麻烦。用 compose 文件把配置固定下来下次重建环境只需要一条命令。3.1 目录规划与 .env 环境变量文件在服务器上创建项目目录sudo mkdir -p /opt/postgres/{data,init,backups}目录结构如下/opt/postgres/data数据库数据文件绑定挂载进容器/opt/postgres/init首次初始化时执行的 SQL 或 shell 脚本/opt/postgres/backups后续定时备份文件存放位置在/opt/postgres/.env里写入数据库超级用户的密码POSTGRES_PASSWORD你的高强度密码compose 文件里通过${POSTGRES_PASSWORD}引用这个变量。这样做的好处是密码不直接出现在 compose 文件里避免哪天把 yml 文件发出去时密码也跟着泄露。3.2 完整 compose 文件与逐段解释/opt/postgres/docker-compose.yml的内容如下services: postgres: image: postgres:16-alpine container_name: postgres16 restart: unless-stopped environment: POSTGRES_USER: road_admin POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: road_db TZ: Asia/Shanghai PGTZ: Asia/Shanghai POSTGRES_INITDB_ARGS: -E UTF8 --data-checksums ports: - 127.0.0.1:5432:5432 volumes: - ./data:/var/lib/postgresql/data - ./init:/docker-entrypoint-initdb.d:ro command: - postgres - -c - shared_buffers256MB - -c - max_connections200 - -c - effective_cache_size768MB - -c - maintenance_work_mem128MB healthcheck: test: [CMD-SHELL, pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB] interval: 10s timeout: 5s retries: 5 logging: driver: json-file options: max-size: 10m max-file: 3逐个解读关键配置container_name: postgres16固定容器名后面用docker exec -it postgres16 psql ...很方便。restart: unless-stopped容器异常退出或服务器重启后自动拉起云服务器上非常实用。POSTGRES_USER/POSTGRES_PASSWORD/POSTGRES_DB首次初始化时创建用户、密码和默认数据库。官方镜像只在数据目录为空时执行初始化如果后面你想改用户名密码不会自动生效。POSTGRES_INITDB_ARGS: -E UTF8 --data-checksums指定字符集 UTF8并启用数据块校验。后者会带来轻微性能开销但能提前发现数据损坏对于数据库这种场景值得开启。ports: 127.0.0.1:5432:5432只在本机回环地址上映射端口外网无法直接访问。这是刻意的安全设计后文详述。command里的-c参数相当于在启动时追加postgresql.conf配置项不需要手动改配置文件。healthcheck用pg_isready做健康检查compose 里能用docker compose ps直接看到容器是否 healthy。logging限制单个日志文件 10MB、最多 3 个文件防止容器日志无限增长把磁盘撑爆。3.3 启动容器并跑通第一个查询在/opt/postgres目录下执行sudo docker compose config这个命令会检查 compose 文件语法并展示最终生效的配置确认无误后启动sudo docker compose up -d sudo docker compose ps首次启动会先创建数据目录和数据库可以从日志里看到初始化过程sudo docker compose logs -f postgres看到database system is ready to accept connections说明启动成功。然后进入容器执行查询sudo docker exec -it postgres16 psql -U road_admin -d road_db -c SELECT version();能正常输出 PostgreSQL 版本信息部署就算基本完成了。再验证一下本机端口状态ss -tlnp | grep 5432应该看到127.0.0.1:5432而不是0.0.0.0:5432。这一步确认了数据库只在本机可达。3.4 初始化脚本首次启动自动建库建表官方镜像在初始化数据目录时会按文件名顺序执行/docker-entrypoint-initdb.d目录下的.sql、.sql.gz或.sh文件。如果你有建表、建扩展、初始化基础数据的脚本只需要放到/opt/postgres/init/下比如-- 01_init.sql CREATE EXTENSION IF NOT EXISTS uuid-ossp; CREATE TABLE IF NOT EXISTS app_user ( id bigserial PRIMARY KEY, name varchar(64) NOT NULL, email varchar(128) UNIQUE, created_at timestamptz NOT NULL DEFAULT now() );这个功能很实用适合把部署和建表表结构合并成一步。但必须注意脚本只在数据目录为空、首次初始化时执行一次。如果容器已经启动过之后再往 init 目录里放 SQL不会自动执行只能手动进容器执行或者把数据目录清空重建。所以如果你打算用 init 脚本管理表结构要在第一次启动前就放好。4. PostgreSQL 配置与安全加固容器能跑起来只是第一步把参数调对、把安全做好才算真正上线。4.1 时区、字符集与排序规则PostgreSQL 对时区和字符集非常敏感最好在初始化阶段就确定。compose 里设置了TZAsia/Shanghai和PGTZAsia/Shanghai确保容器系统和数据库会话都使用北京时间。同时初始化参数指定了 UTF8 编码避免中文乱码问题。如果你已经启动了一个数据库才发现时区或字符集不对此时改起来比较麻烦。字符集在初始化后不能直接改需要重新初始化或者迁移数据。所以这里给的建议是如果数据量不大尽早重建如果已经跑了真实业务数据一定要做备份后用相同参数重建再恢复数据。连接数据库后可以查看当前会话的时区和编码SHOW timezone; SHOW server_encoding; SHOW client_encoding;只要server_encoding是UTF8一般中文场景就没有问题。4.2 内存与连接参数这样调PostgreSQL 的性能参数很多但云服务器上最先要关注的是这几个参数说明建议值shared_buffers共享缓冲区物理内存的 25% 左右effective_cache_size操作系统缓存估算物理内存的 50%75%max_connections最大连接数按业务量调整不宜过大maintenance_work_mem维护操作内存64MB256MBwork_mem单次排序/哈希内存默认 4MB谨慎调大以一台 2C4G 的云服务器为例我设置的参数是shared_buffers256MB、effective_cache_size768MB、max_connections200、maintenance_work_mem128MB。这个组合不会吃满内存也能满足中低并发业务。注意max_connections并不等于可以随便调大每个连接都会占用内存。连接数从 100 调到 1000内存占用可能增加好几 GB小规格服务器很容易被拖垮。建议先用默认值压测后发现连接不够再逐步调整。修改参数有两种方式一是像我在 compose 的command里用-c参数适合少量参数二是进容器直接改postgresql.conf然后重启。用 compose 管理的好处是参数变更会被记录在版本控制里环境重建后能自动复现。4.3 远程访问别把 5432 直接暴露到公网这是我认为最重要的一节。很多人在部署完数据库后喜欢在 compose 里写5432:5432然后在安全组里放行 5432 端口这样本地就能直接连数据库了。省事是真的省事危险也是真的危险。公网上扫描数据库端口的脚本非常多端口一旦暴露你的服务器马上就会收到大量的连接尝试。即使用了一个强密码也经不住持续的暴力破解。PostgreSQL 的日志里会很快刷出各种认证失败记录。我采用的方案是端口只绑定到127.0.0.1外网完全无法直接访问。如果你要从本地操作数据库用 SSH 隧道转发ssh -L 5432:127.0.0.1:5432 ubuntu服务器公网IP -N这条命令把本地 5432 端口和服务器 5432 端口通过 SSH 隧道打通。隧道建立后本地工具连接127.0.0.1:5432就能访问服务器上的数据库。数据全程走 SSH 加密通道比直接暴露 5432 安全得多。如果业务上多个服务器之间需要互连建议用腾讯云 VPC 内网地址连接配合安全组只放行内网来源的 5432也不要暴露公网。4.4 日志、健康检查与资源限制容器化部署最容易被忽视的是日志管理。默认情况下 Docker 的 json-file 日志驱动是无限制增长的如果数据库长时间运行日志文件可能占用大量磁盘空间。我在 compose 里做了上限配置日志单文件 10MB、最多保留 3 个避免日志写满磁盘。healthcheck配置让 Docker 可以客观判断数据库是否“活着”。docker compose ps输出里的 STATUS 列会显示healthy或unhealthy这对于后续接入监控告警很有用。如果你有 Prometheus 监控体系可以再挂一个prometheuscommunity/postgres-exporter容器采集数据库指标。这部分我已经部署好了单独写一篇来展开。5. 常见问题排查实录与运维脚本最后这部分是实战中踩过的坑和我现在每天在用的运维套路直接抄作业就行。5.1 连不上数据库的三层排查法数据库连不上大多数时候不是 PostgreSQL 本身的问题而是网络链路中某一层没有打通。我总结了一个三层排查顺序排查层级操作常见问题应用/客户端确认连接地址、端口、用户名密码是否写对写错密码、端口写错云平台安全组控制台确认 5432 入站规则是否放行安全组忘记放行服务器本地查看端口监听地址、UFW 防火墙状态端口绑定 127.0.0.1、UFW 未放行、Docker 未映射端口先在本机执行docker compose ps看容器是否运行。再ss -tlnp | grep 5432确认端口监听状态。然后从外部 telnet 一下公网 IP 的 5432如果通说明安全组和防火墙都放行了如果不通逐步往上查。一个很容易踩坑的点Ubuntu 24.04 默认可能启用了 UFW 防火墙。如果安全组放行了端口但系统防火墙没有放行外部一样连不上。查看方式sudo ufw status如果Status: active就需要放行对应端口或者直接关掉 UFW在云平台上安全组已经承担了防火墙职责可以视情况关闭。5.2 密码认证失败与数据目录权限问题容器启动失败时第一件事永远是看日志sudo docker logs postgres16我自己遇到过的典型问题有两个。第一个是宿主机数据目录权限不对。因为容器内的 postgres 用户 UID 是 999如果宿主机/opt/postgres/data目录的属主不是 999容器初始化时就会报权限错误。解决办法sudo chown -R 999:999 /opt/postgres/data第二个是认证失败。很多人改了POSTGRES_PASSWORD环境变量却用旧的密码连接自然报password authentication failed。要注意官方镜像的规则如果数据目录已经初始化过再改环境变量不会生效。此时要更新密码需要进容器执行sudo docker exec -it postgres16 psql -U road_admin -d postgres -c ALTER USER road_admin WITH PASSWORD 新密码;还有pg_hba.conf不要随便改。官方镜像默认的认证配置已经比较合理如果你手动覆盖了它导致所有连接都被拒绝恢复方式是把镜像里自带的配置找回来或者直接重建容器。5.3 每日备份一条 cron 命令搞定备份这事没有监控告警兜底很容易忘了。我用最简单的 cron 方案每天凌晨把业务库导出到宿主机备份目录保留最近 7 天。创建脚本/opt/postgres/backup.sh#!/bin/bash TIMESTAMP$(date %F) docker exec -t postgres16 pg_dump -U road_admin -d road_db -F c -f /tmp/road_db_${TIMESTAMP}.dump docker cp postgres16:/tmp/road_db_${TIMESTAMP}.dump /opt/postgres/backups/ docker exec postgres16 rm -f /tmp/road_db_${TIMESTAMP}.dump find /opt/postgres/backups -type f -mtime 7 -delete给脚本加执行权限然后写入 crontabchmod x /opt/postgres/backup.sh crontab -e加入一行0 3 * * * /opt/postgres/backup.sh /opt/postgres/backup.log 21每天凌晨 3 点执行一次全量备份。恢复时用sudo docker exec -i postgres16 pg_restore -U road_admin -d road_db --clean --if-exists /opt/postgres/backups/xxx.dump这里最关键的建议是备份脚本写完之后务必做一次真正的恢复演练。把备份文件恢复到一台临时实例上确认数据可以正常读取。备份不可恢复等于没有备份这句话是很多生产事故换来的教训。5.4 结构同步工具让测试库和生产库保持一致部署环境多了以后dev、staging、prod 三套数据库的表结构经常出现漂移开发改了表结构忘了同步到生产或者不同环境执行了不同版本的 SQL。单纯靠人肉对比效率太低。我常用一个叫migra的工具Python 写的专门用来对比两个 PostgreSQL 数据库的结构差异。安装很简单pip install migra使用方式migra postgresql://user:passhost1:5432/db1 postgresql://user:passhost2:5432/db2 --unsafe它会把两个库的表、字段、索引、约束差异以 SQL 形式输出你可以直接把这些 SQL 拿到目标库执行。我用它来核对 staging 和生产环境的表结构是否一致每次发布前跑一遍能省下大量排查问题的时间。写在最后整套部署流程下来最深的体会是数据库在 Docker 里跑并不难难的是把持久化、安全、备份这三件事真正想清楚。很多问题的根源不是命令写错而是动手之前没有做方案设计。最后再分享一个小技巧如果你在用docker compose管理数据库建议把docker-compose.yml、.env.example、初始化脚本放在同一个 Git 仓库里。.env文件本身不要提交但可以提交一个.env.example模板里面写清楚需要填哪些变量。这样即使换人接手一条docker compose up -d就能把环境完整拉起来这才是容器化部署真正值钱的地方。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价