资讯动态

腾讯云Ubuntu 24.04上用Docker部署PostgreSQL实战与避坑指南

发布时间:2026/9/16 8:39:28 来源:尧图企业网站定制
前阵子帮客户在一台腾讯云 Ubuntu 24.04 服务器上用 Docker 部署了一套 PostgreSQL整个过程踩了几个坑也积累了一些值得记录的细节。这几天正好有空把完整的部署过程和思考整理出来给同样想在云服务器上用容器跑数据库的朋友做一个参考。这篇文章不是什么高深的技术分享就是一次真实的项目落地记录。我会从为什么选这套方案讲起到每一步的具体操作再到我实际遇到的问题和排查过程尽量把细节都交代清楚。不管你之前有没有用 Docker 部署过数据库只要照着操作基本都能把 PostgreSQL 跑起来。如果你已经在用裸机安装的 PostgreSQL想切换到 Docker 方案这篇文章同样有参考价值。1. 本次部署的整体思路与方案选型1.1 为什么用 Docker 跑 PostgreSQL先说结论如果是一个中小型项目的数据库用 Docker 部署 PostgreSQL 要比直接在宿主机上安装更省心尤其是在环境隔离和后期迁移这两个方面。我之前在不少服务器上直接 apt install postgresql 装过数据库当时觉得挺方便。但用过一阵子就会遇到几个头疼的问题一是 PostgreSQL 的版本和系统源绑定Ubuntu 24.04 默认源里的 PostgreSQL 版本可能不是你想要的那个二是卸载或升级的时候配置文件和数据文件的迁移路径比较分散容易遗漏三是同一台机器上如果跑多个项目每个项目需要的 PostgreSQL 版本不一样直接装会互相干扰。Docker 把这些问题都解决了。PostgreSQL 官方镜像里包含了数据库本体、依赖库和默认配置启动就是一个完整可用的实例。换版本就是换个镜像标签的事想清理干净也就 docker stop 加 docker rm 两条命令。而且容器之间的网络和文件系统都是隔离的另一个项目如果想用 MySQL 8.0那就在旁边再跑一个 MySQL 容器互不干扰。不过也必须说清楚Docker 跑数据库不是没有代价。最大的争议点在于性能容器相比宿主机直接运行确实多了一层虚拟化开销但对绝大多数业务场景来说这个损耗微乎其微远没有网上说的那么夸张。我在同一台机器上做过简单对比容器内外的 PostgreSQL 在并发查询场景下性能差距基本在 5% 以内普通项目根本感知不到。1.2 PostgreSQL 版本选型和目录规划版本选型上我这次选的是 PostgreSQL 16。目前 PostgreSQL 17 已经发布了但 16 作为长期稳定版本经过大半年的社区验证生态兼容性更好各种第三方工具和监控插件的支持也比较成熟。如果你没有特殊需求PostgreSQL 16 是目前最稳妥的选择。之前在知乎上看到有人问 PostgreSQL 15 和 18 的区别说实话 18 还没正式发布生产环境不要追新版本除非你真的需要某个只有新版本才有的特性。再说目录规划。我习惯把容器相关的文件统一放在 /data 下方便管理。这次的项目我建了这样的结构/data └── postgres/ └── pgdata # PostgreSQL 数据目录这样一个目录对应一个服务的所有持久化文件以后做备份、迁移直接打包这个目录就完事了思路特别清晰。很多初学者容易犯的错就是把数据文件随便挂到 /root 或者 /home 下面后期数据量大了再整理就很痛苦。2. 腾讯云环境准备与 Docker 安装2.1 服务器初始化与安全组配置腾讯云服务器的初始化这里就不多说了重点强调两个容易忽略的坑。第一个是安全组。腾讯云的安全组相当于云服务器的第一道防火墙默认情况下只放行了 22 端口SSH和 80、443 端口。你如果直接在服务器上启动了 PostgreSQL 并监听 5432外部是根本连不上来的因为流量在到达服务器之前就被安全组拦掉了。所以部署之前一定要先去腾讯云控制台找到对应实例的安全组添加入站规则放行 TCP 5432 端口。这里我给一个建议如果你的数据库只给特定的应用服务器访问那就把来源 IP 限制成那台服务器的内网 IP 或公网 IP不要直接设成 0.0.0.0/0。数据库端口暴露到公网每天都会被扫描器问候无数次安全风险太高了。第二个是系统源。腾讯云服务器尤其是轻量应用服务器预装的 Ubuntu 系统软件源默认是腾讯云内网镜像源速度通常没问题但个别情况下会出现源同步延迟导致 apt update 的时候报 404 错误。如果遇到这种情况可以临时切换到官方源或者阿里源执行 sed 命令替换 sources.list 里的地址再 apt update 一次就能解决。2.2 Docker 安装与镜像加速配置Ubuntu 24.04 上安装 Docker 有两种常见方式一种是直接用系统源里的 docker.io 包另一种是用 Docker 官方提供的 apt 源。我强烈建议用后者因为系统源里的 Docker 版本通常滞后而且后续 docker compose 插件的版本管理也比较麻烦。Docker 官方源的安装步骤是这样的# 更新软件包索引 sudo apt update # 安装依赖包 sudo apt install -y ca-certificates curl gnupg # 添加 Docker 官方 GPG 密钥 sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod ar /etc/apt/keyrings/docker.gpg # 添加 Docker apt 仓库 echo \ deb [arch$(dpkg --print-architecture) signed-by/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ $(. /etc/os-release echo $VERSION_CODENAME) stable | \ sudo tee /etc/apt/sources.list.d/docker.list /dev/null # 安装 Docker sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin这里有个细节Ubuntu 24.04 的代号是 noble所以上面命令里的 $(. /etc/os-release echo $VERSION_CODENAME) 会自动解析成 noble不需要手写。安装完成后用 docker --version 验证一下。如果一切正常你会看到类似 Docker version XX.X.X 的输出。接下来是镜像加速。国内直连 Docker Hub 的速度实在感人尤其是拉取 postgres 这种几百 MB 的镜像有时候能卡到怀疑人生。腾讯云的加速器地址是https://mirror.ccs.tencentyun.com。配置方式很简单sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json -EOF { registry-mirrors: [https://mirror.ccs.tencentyun.com] } EOF sudo systemctl daemon-reload sudo systemctl restart docker配置完成后docker info 里会多出一行 Registry Mirrors 信息说明加速器已经生效。另外多说一句镜像加速这种东西不同的服务商在不同地区效果差别挺大如果腾讯云加速器速度不理想可以试试阿里的加速地址或者直接在 Docker 配置里写多个 mirror 地址。3. PostgreSQL 容器部署实操一步步来3.1 拉取镜像与数据目录准备镜像标签的选择有一点讲究。postgres 官方镜像的标签规则大概是这样的latest最新稳定版但不推荐写死哪天 docker pull 拉到一个大版本升级你都不知道。16主版本号拉取时会自动获取该系列最新的次要版本比如 16.x。16-bookworm具体 Debian 发行版 主版本号更可控。16-alpine基于 Alpine Linux 的精简镜像体积更小但有些扩展的编译环境不完整。我这次用 16 这个标签既有确定性又不至于太死板。拉镜像的命令很简单sudo docker pull postgres:16接着创建数据目录并设置权限sudo mkdir -p /data/postgres/pgdata # 容器内 postgres 用户 uid 是 999后面挂载目录会涉及到权限 sudo chown -R 999:999 /data/postgres/pgdata这个 chown 很多人容易漏掉。如果宿主机目录权限不对容器启动的时候 PostgreSQL 进程无法在挂载目录里写入数据直接报 Permission denied。3.2 docker run 启动容器关键参数逐个说现在到了核心步骤。我第一次部署 PostgreSQL 时docker run 命令是从网上抄的参数含义一知半解后来出了状况才回头一个个查明白。这里我建议你哪怕只是照着敲也花两分钟把每个参数搞清楚后面排查问题会省很多时间。我的完整启动命令如下sudo docker run -d \ --name postgres16 \ --restartalways \ -e TZAsia/Shanghai \ -e POSTGRES_USERmyuser \ -e POSTGRES_PASSWORDYourStrongPass \ -e POSTGRES_DBmydb \ -p 5432:5432 \ -v /data/postgres/pgdata:/var/lib/postgresql/data \ -v /etc/localtime:/etc/localtime:ro \ postgres:16逐项说明一下-d后台运行容器不占用当前终端。--name postgres16给容器取名之后 docker logs、docker exec、docker stop 都可以直接用这个名字不用去记一串容器 ID。--restartalways这个必须要有。服务器重启后Docker 服务会自动拉起这个容器省得你每次重启机器都手动 docker start。对于数据库这种需要长时间运行的服务这个参数太重要了。-e TZAsia/Shanghai设置容器内时区。PostgreSQL 的时间函数比如 now() 返回的是会话时区的时间如果不设置 TZ容器默认是 UTC跟国内差 8 个小时日志时间和查询结果时间都对不上排查问题的时候特别容易蒙。-e POSTGRES_USERmyuser初始化时创建的第一个超级用户。默认不指定的话是 postgres。-e POSTGRES_PASSWORDYourStrongPass这个用户的密码。注意这个环境变量只在数据目录为空、容器首次初始化时生效。如果数据目录已经有数据了这个变量会被忽略。-e POSTGRES_DBmydb初始化时额外创建的数据库。这个看需求不设置的话默认会有一个和 POSTGRES_USER 同名的数据库。-p 5432:5432端口映射宿主机 5432 映射到容器内 5432。左侧宿主机端口可以改比如你想跑两个 PostgreSQL 实例第二个就可以写成 -p 5433:5432。-v /data/postgres/pgdata:/var/lib/postgresql/data数据目录挂载宿主机 /data/postgres/pgdata 对应容器内的数据目录。这是整个命令里最关键的一行没有这个挂载容器一删数据全没。-v /etc/localtime:/etc/localtime:ro让容器使用宿主机的时区文件配合 TZ 环境变量确保时间正确。命令执行后立刻检查一下容器状态sudo docker ps输出里应该能看到 postgres16 容器STATUS 那一列显示 Up 状态。如果容器没起来用 docker logs postgres16 看日志这个后面第 5 部分详细讲。3.3 验证部署并配置远程连接容器启动后先进容器确认一下数据库页面能不能正常使用sudo docker exec -it postgres16 psql -U myuser -d mydbpsql 是 PostgreSQL 自带的交互式命令行工具-U 指定用户-d 指定数据库。如果能看到类似 postgres# 这样的提示符说明数据库本身已经正常工作。接着验证外部连接。我通常会在本地电脑上装一个 DBeaver 或者 Navicat用服务器的公网 IP 加上 5432 端口去连。这个步骤能测试出安全组、防火墙、PostgreSQL 监听地址三个层面是否都配置正确。这里要提一个容易踩的坑PostgreSQL 默认只监听 localhost。不过在 Docker 容器里如果你用 -p 5432:5432 做端口映射容器内的 PostgreSQL 是否监听 127.0.0.1 其实并不影响外部访问因为 Docker 的端口映射机制是在宿主机层面做的流量转发。也就是说即使容器内 PostgreSQL 只绑定了 127.0.0.1通过宿主机的 5432 端口也能访问到这是 Docker 网络模型的一个特性。当然如果你在容器里改过 postgresql.conf 的 listen_addresses那就另说了。还有认证的问题。如果你是第一次接触 PostgreSQL 的远程连接可能会遇到 FATAL: no pg_hba.conf entry for host x.x.x.x 这个错误。这是 PostgreSQL 的客户端认证配置文件 pg_hba.conf 里没有允许该 IP 访问的规则。官方镜像里默认只允许容器网络内的连接宿主机通过映射端口访问时来源 IP 在容器看来是 172.17.0.1Docker 网桥网关这个地址默认是允许的。所以用腾讯云服务器本地连容器内数据库一般没问题但如果你的应用部署在另一台机器上通过公网来连就可能触发这条规则限制。解决办法是进入容器修改 pg_hba.conf添加对应的访问规则然后重启容器。但说实话我更推荐的做法是不要把 PostgreSQL 直接暴露到公网而是让你的应用和数据库跑在同一台机器上通过 Docker 内部网络或者回环地址连接。如果应用必须远程连接至少用防火墙限制来源 IP或者干脆走 SSH 隧道。3.4 用 docker compose 固化部署docker run 命令虽然直观但要复现部署的时候就显得不够优雅了。如果你的服务器上要跑多个容器或者你想要一套可以版本控制的部署配置docker compose 是更好的选择。我这次部署顺手把 compose 文件也写了一份放在 /data/postgres/docker-compose.ymlservices: postgres: image: postgres:16 container_name: postgres16 restart: always environment: TZ: Asia/Shanghai POSTGRES_USER: myuser POSTGRES_PASSWORD: YourStrongPass POSTGRES_DB: mydb ports: - 5432:5432 volumes: - /data/postgres/pgdata:/var/lib/postgresql/data - /etc/localtime:/etc/localtime:ro注意看compose 文件里的 environment 用的是无下标格式TZ 而不是 - TZ...这是 compose 规范里推荐的写法。其实 - TZAsia/Shanghai 这种写法也兼容但既然是新项目尽量按新规范来。然后在 /data/postgres 目录下执行sudo docker compose up -d看到容器状态 Up 之后整个部署就跟 docker run 版本完全等价了。以后想启动就是 docker compose start想停就是 docker compose stop想升级版本就改一下 image 的标签然后 docker compose up -d 重新拉起Docker 会自动重建容器并复用原来的数据卷数据不会丢。4. 数据备份、恢复与日常维护4.1 容器内数据文件备份 vs 逻辑备份数据库部署好只是开始真正考验运维功底的是数据备份。我见过不少同学把数据库跑起来就完事了直到某天误操作删了表才追悔莫及。PostgreSQL 的备份方式大体分两类物理备份直接拷贝数据目录 /var/lib/postgresql/data 里的文件。这种方式速度快适合整机迁移或者灾备但要求数据目录处于一致性状态最好配合 pg_basebackup 或者停库操作来做。逻辑备份用 pg_dump 把数据导出成 SQL 文件或者自定义格式的归档文件。这种方式灵活可以备份指定库、指定表恢复的时候也可以选择性地恢复。缺点是数据量大的时候备份和恢复都比较慢。我平时最常用的组合是用 pg_dump 做每日定时逻辑备份作为误删除恢复的主要手段数据目录的磁盘快照如果有的话或 rsync 到另一台机器做容灾。两种备份方式各司其职逻辑备份负责日常恢复物理备份负责极端情况下的整体恢复。4.2 定时备份脚本实战下面是我在服务器上实际使用的备份脚本逻辑很简单但很实用。脚本放在 /data/postgres/backup.sh内容如下#!/bin/bash # PostgreSQL Docker 容器每日备份脚本 BACKUP_DIR/data/postgres/backups BACKUP_KEEP_DAYS14 CONTAINER_NAMEpostgres16 DB_USERmyuser DB_NAMEmydb DATE$(date %Y%m%d_%H%M%S) # 创建备份目录 mkdir -p ${BACKUP_DIR} # 使用 pg_dump 导出数据 docker exec ${CONTAINER_NAME} pg_dump -U ${DB_USER} -d ${DB_NAME} -F c -f /tmp/${DB_NAME}_${DATE}.dump # 从容器内拷贝到宿主机 docker cp ${CONTAINER_NAME}:/tmp/${DB_NAME}_${DATE}.dump ${BACKUP_DIR}/ # 清理容器内外临时文件 docker exec ${CONTAINER_NAME} rm -f /tmp/${DB_NAME}_${DATE}.dump # 删除超过保留天数的旧备份 find ${BACKUP_DIR} -name *.dump -mtime ${BACKUP_KEEP_DAYS} -delete echo Backup completed: ${BACKUP_DIR}/${DB_NAME}_${DATE}.dump解释一下里面的关键点pg_dump -F c 表示输出为自定义归档格式这个格式体积小而且支持 pg_restore 的选择性恢复。为什么先把 dump 文件导出到容器 /tmp 再 docker cp 出来因为容器内可能没有安装和宿主机共享的存储工具直接输出到 stdout 再重定向也可以但文件大的时候二进制数据可能被终端干扰所以用 docker cp 最稳妥。备份保留 14 天基本能覆盖日常的回滚需求也不会无限积压占用磁盘空间。给脚本加上执行权限然后通过 crontab 定时执行chmod x /data/postgres/backup.sh crontab -e在 crontab 文件里加一行30 2 * * * /data/postgres/backup.sh /data/postgres/backup.log 21意思是每天凌晨 2:30 执行一次备份日志输出到 backup.log。凌晨备份的好处是业务低峰期数据库压力小备份一致性也更可靠。4.3 演练一次数据恢复备份是为了恢复所以恢复流程必须亲自演练过别等到出事了才手忙脚乱。下面演示从刚才的备份文件恢复数据的过程。假设我们的 mydb 库被误删了两张表恢复步骤如下# 进入备份文件所在目录 cd /data/postgres/backups # 查看备份文件中包含的对象 sudo docker exec -i postgres16 pg_restore -U myuser -d mydb --list /tmp/latest.dump | head -50因为备份文件在宿主机上容器内访问不到有两条路一是先 docker cp 进容器二是直接把备份文件通过 stdin 传给 pg_restore。我习惯用 stdin 的方式sudo docker exec -i postgres16 pg_restore -U myuser -d mydb --clean --if-exists mydb_20250101_023000.dump参数说明--clean在恢复前先尝试删除目标数据库中的同名对象防止冲突。--if-exists配合 --clean 使用删除对象时加上 IF EXISTS 条件避免因为对象不存在而报错中断。-d mydb指定恢复到哪个数据库。注意目标数据库必须存在不存在的话先 createdb 或者用 psql 创建一个。恢复过程中pg_restore 会把每条 SQL 的执行结果输出到终端。如果某个对象恢复失败它不会中断整个恢复流程而是继续执行后面的对象最后在结尾汇总错误。所以我一般恢复完了还会通过查询几条关键数据来确认结果。这个小节想表达的核心是定期恢复演练真的很有必要。我见过不止一次备份脚本跑得好好的但恢复时才发现备份文件损坏或者不全的场景。每个月至少做一次恢复演练把备份和恢复当作同等重要的事情对待。5. 常见问题速查与避坑指南5.1 启动类问题容器无法启动是最常见的故障我把它列在第一位。不同原因对应的现象和排查方法完全不同这里整理成一个速查表。现象可能原因排查与解决docker ps 里没有 postgres16 容器启动命令执行失败docker logs postgres16 查看具体报错日志报 chmod: changing permissions of /var/lib/postgresql/data: Operation not permitted宿主机数据目录权限不对确认 /data/postgres/pgdata 的属主是 999:999执行 chown -R 999:999日志报 could not create directory ... Permission denied同上通常是挂载目录权限问题同上或考虑用命名卷代替绑定挂载日志报 FATAL: data directory /var/lib/postgresql/data has invalid permissions数据目录权限过于宽松PostgreSQL 要求数据目录权限不大于 0700执行 chmod 700 后重试日志报 The database cluster is initialized with locale en_US.utf8但系统没这个 locale系统缺 locale在宿主机上执行 locale-gen en_US.UTF-8 后重启容器第一次部署 PostgreSQL 容器的人最容易栽在权限问题上。PostgreSQL 是个安全要求极高的数据库对数据目录权限的检查非常严格目录权限必须是 700属主必须是要运行数据库的用户。官方镜像里的 postgres 用户 uid 是 999So 在宿主机上 chown 999:999 是最直接的解法。另一个容易忽略的点是 locale。如果你在启动容器时通过 POSTGRES_INITDB_ARGS 指定了 --localezh_CN.UTF-8 之类的参数而容器内或者系统镜像里没有对应的 locale 数据初始化就会失败。这个我在测试环境里踩过一次后来干脆不折腾直接用默认的 en_US.utf8数据库里存中文完全没问题只要客户端连接时设置正确的 client_encoding 就行。5.2 连接与权限类问题数据库起来了但连不上这种问题排在第二位。核心方向有三个端口、监听地址、认证。端口层面先确认容器端口映射状态sudo docker port postgres16正常输出应该是 5432/tcp - 0.0.0.0:5432。如果这个命令没有输出说明容器没有配置端口映射需要重新创建容器。接着验证宿主机本地能不能连sudo docker exec -it postgres16 psql -U myuser -d mydb -c SELECT version();如果这一步成功说明数据库本身没问题问题出在网络链路。这时检查腾讯云安全组是否放行了 5432 入站端口以及宿主机防火墙如果启用了 ufw 或 firewalld是否拦截了端口。认证层面常见的报错是Password authentication failed for user myuser密码不对。注意 POSTGRES_PASSWORD 只在初始化时生效如果你想改密码得用 ALTER USER 命令或者再设置一个 POSTGRES_PASSWORD 环境变量然后重新初始化数据目录会丢数据不推荐。no pg_hba.conf entry for host x.x.x.x客户端 IP 不在 pg_hba.conf 的允许列表里。官方镜像默认的 pg_hba.conf 对容器网络内的所有 IP 使用 scram-sha-256 认证如果外部 IP 访问不了可以进入容器修改配置sudo docker exec -it postgres16 bash echo host all all 0.0.0.0/0 scram-sha-256 /var/lib/postgresql/data/pg_hba.conf改完重载配置即可不用重启容器sudo docker exec -it postgres16 psql -U myuser -d mydb -c SELECT pg_reload_conf();不过我再啰嗦一句生产环境不要图省事把 pg_hba.conf 设成 0.0.0.0/0这是将数据库完全暴露给公网的行为等于把家门钥匙放在门口脚垫下面。5.3 数据与性能类问题最后说几个数据与性能方面的坑这些是数据库跑了一段时间之后才会暴露出来的。数据目录占用膨胀。PostgreSQL 的 MVCC 机制决定了删除大量数据后磁盘空间不会立刻还给操作系统而是在数据文件里留下死元组。如果长期只增删不清理数据目录会越来越大。解决办法是定期执行 VACUUM FULL。在容器里操作sudo docker exec -it postgres16 psql -U myuser -d mydb -c VACUUM FULL ANALYZE;注意 VACUUM FULL 会锁表业务高峰期不要执行建议放在凌晨维护窗口配合备份脚本一起做。连接数打满。默认 max_connections 是 100如果应用连接池配置不当很容易把连接数吃满新连接直接报 sorry, too many clients already。这时需要调大 max_connections。但这里有个连带关系max_connections 调大共享缓冲区 shared_buffers 也要随之调整不然 PostgreSQL 的共享内存可能不足。容器里改配置的方式是挂载自定义的 postgresql.conf或者在启动时用 -c 参数sudo docker run ... \ -c max_connections200 \ -c shared_buffers512MB \ postgres:16或者更规范一点把配置文件放到宿主机挂载到容器内的 /etc/postgresql/postgresql.conf然后启动参数里指定 config_file。这个做法适合对 PostgreSQL 参数有精细要求的场景。备份文件越堆越多导致磁盘满。这个问题在 4.2 的脚本里已经通过 find mtime 处理了但如果你是自己手动跑备份一定要记得定期清理。磁盘 100% 会导致 PostgreSQL 无法写入 WAL 日志整个数据库直接进入只读模式甚至宕机这个后果比丢失几个备份文件严重得多。最后再分享一点个人心得这次部署 PostgreSQL 用 Docker 方案整体感受是上手快结构清晰迁移方便但也要求你对 Docker 的数据卷机制和 PostgreSQL 的目录权限有基本的了解。整套方案我已经在好几台服务器上跑了大半年中间经历了两次服务器重启、一次数据恢复演练都很稳。如果你之前完全没接触过 Docker我建议在正式部署前先在一台临时机器上把流程完整走一遍尤其是 docker run 和 docker compose 两个方式都试一次感受一下容器和宿主机的文件交互方式。这样真正在腾讯云上操作的时候你会更从容一些。如果后续想在这个基础上做更完整的方案可以考虑配置 PostgreSQL 的日志轮转、接入 Prometheus Grafana 监控或者搞一套主从复制做高可用。Docker 的生态决定了这些扩展都有成熟的镜像和方案可以借鉴。希望这篇记录能帮你少踩几个坑。

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

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

免费获取报价