刚处理完一台腾讯云 Ubuntu 24.04 服务器上的 PostgreSQL 部署,顺手帮朋友救了一次“裸装翻车”现场。他那台机器照着一篇旧教程敲apt install postgresql,结果装的是 Ubuntu 自带的 PostgreSQL 16,数据目录在/var/lib/postgresql/16/main,配置文件散落一地,远程连接又卡在pg_hba.conf上,折腾到半夜还没好。我接手后直接换了个思路:全部推倒,用 Docker 部署。今天这篇就把整个流程拆开揉碎写清楚,从选型、安装环境、编写 Compose 文件到日常备份和踩坑排查,一次性讲完。
我先说结论:腾讯云 Ubuntu 24.04 加 Docker 加 PostgreSQL 这套组合,非常适合个人项目、中小团队内部服务,甚至是准备上生产的业务库。你不需要懂太多底层编译,不需要操心系统包版本混乱,只要把目录映射、端口、认证和备份管好,一样能跑得比裸装还稳。
1. 为什么我推荐容器化部署,而不是直接 apt 装 PostgreSQL
1.1 裸装 PostgreSQL 的三个痛点
很多人拿到一台云服务器,第一反应是apt install postgresql。这条路在 Ubuntu 24.04 上确实能装,但不代表省心。我遇到过的典型问题有三个。
版本跟系统绑定。Ubuntu 24.04 默认软件源里带的 PostgreSQL 可能是 16,如果你需要 17,或者某些兼容性更好的分支版本,就得额外添加 PGDG 源,加完源还要处理优先级,稍不留神就把系统自带的包搞坏。
数据目录和配置位置太分散。裸装的 PostgreSQL,数据文件在/var/lib/postgresql/16/main,配置文件在/etc/postgresql/16/main,日志又在别的地方。备份要记一堆路径,迁移要手动搬目录,想换台机器重建环境时特别痛苦。
权限和用户管理不够透明。postgres系统用户、pg_hba.conf的认证规则、Unix Socket 权限,这些对新手来说都是坑。我之前见过有人为了方便,直接改成trust认证,等于把数据库裸奔在公网上,风险非常大。
1.2 Docker 部署把复杂度关进“盒子里”
容器化之后,PostgreSQL 对宿主机的侵入降到了最低。
依赖隔离。PostgreSQL 运行在独立的文件系统里,不需要在宿主机上装一堆依赖,也不会污染 Ubuntu 的系统环境。卸载的时候直接删容器和镜像,宿主机几乎没有残留。
数据目录可控。容器内的数据目录是固定的,比如官方postgres:16镜像默认使用/var/lib/postgresql/data。我们通过-v或 Compose 的volumes把它映射到宿主机某个目录,比如/data/postgres/data。以后要备份、迁移,只需要操作这个宿主机目录,逻辑清晰很多。
版本切换简单。想升级 PostgreSQL 大版本,不再需要pg_upgrade那套复杂流程。我可以先备份数据,再换一个镜像 Tag,调整参数后重新起容器。虽然不能保证数据文件完全无缝升级,但环境切换的成本比裸装小太多了。
1.3 为什么数据库适配容器化,这件事已经成熟
早些年有人说数据库不适合容器,因为状态数据不好管理。但如今官方 PostgreSQL 镜像已经原生支持 Docker 的初始化机制、健康检查和持久化卷,只要把数据目录正确挂载出来,容器重启、宿主机重启,数据都不会丢。
我常把容器里的 PostgreSQL 比作一台“移动版数据库主机”。你不需要关心硬件细节,不用操心系统服务怎么注册,只要定义好端口和存储,它就是一个可以被随时停止、恢复、迁移的服务。腾讯云的轻量服务器或 CVM 上跑这套尤其顺,因为底层网络和磁盘 I/O 都很稳定。
2. 腾讯云环境准备与 Docker 安装
2.1 服务器选择和安全组配置
我这次用的是腾讯云标准型 CVM,系统镜像选的 Ubuntu 24.04 LTS,配置是 2 核 4G。对一个小型业务库来说,这个规格完全够用。如果你只是跑个人项目,1 核 2G 也能凑合,但建议把内存加到 4G,避免 PostgreSQL 在并发稍高时被 OOM 杀掉。
拿到服务器后,第一步不是装 Docker,而是先把安全组和防火墙想清楚。腾讯云 CVM 默认安全组一般只开放 22、80、443 等端口,PostgreSQL 默认端口是 5432,如果你想从本地 Navicat、DBeaver 远程连接,必须在腾讯云控制台的安全组里放行 5432。
如果你用的是腾讯云轻量应用服务器,入口不在“安全组”,而是在“防火墙”页面加规则。两边配置原理一样,都是允许来源 IP 访问目标端口。这里有个建议:来源不要填0.0.0.0/0,只填你办公楼或家里的公网 IP,能少很多扫描爆破。
2.2 在 Ubuntu 24.04 上安装 Docker 的正确姿势
很多 Ubuntu 安装教程会让你直接apt install docker.io,我一般不推荐这么干。Ubuntu 源里的 docker.io 版本更新慢,而且默认不带 Compose 插件,后面用docker compose时还得额外装,比较别扭。
我更推荐直接用 Docker 官方源的 docker-ce。安装过程如下:
# 更新系统基础包 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 a+r /etc/apt/keyrings/docker.gpg # 添加 Docker 软件源 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安装完成后,加上当前用户进 docker 组,省得每次敲命令都要 sudo:
sudo usermod -aG docker $USER # 重新登录服务器后生效然后验证一下:
docker --version docker compose version能看到版本号就说明环境没问题。注意一点,这里用的docker-compose-plugin提供的是docker compose子命令,中间有空格,不是老版本的docker-compose。我后文提到的所有命令都基于新版写法。
2.3 镜像加速与拉取策略
国内服务器直接拉 Docker Hub 镜像有时候会比较慢。Docker 官方提供了配置 registry mirror 的机制,你可以在/etc/docker/daemon.json里配置镜像加速地址。
基本格式是这样:
{ "registry-mirrors": ["https://你的加速地址"] }配置完执行sudo systemctl restart docker生效。镜像加速地址变化快,这里我就不写具体的了,装完 Docker 后搜一下“Docker 镜像加速”通常能找到可用的。如果你访问 Docker Hub 速度还行,其实这一步可以跳过。
顺带说一句,别用docker pull拉取时中断就反复重试,PostgreSQL 官方镜像分层很多,断点续传不总是有效。实在不行,可以在另一台网络环境好的机器上提前拉好再导出导入,这是后话。
3. 用 Docker Compose 部署 PostgreSQL 实例
3.1 设计目录结构
我习惯把状态数据和应用配置分开。在宿主机上,我规划了这样的目录结构:
/data/postgres/ ├── data # PostgreSQL 数据文件 └── backup # 数据库备份文件创建目录并处理权限:
sudo mkdir -p /data/postgres/{data,backup}为什么要把backup目录放在宿主机而不是容器里?因为容器本身是易失的,备份文件如果在容器内,容器一删就全没了。映射到宿主机后,我还能直接把备份文件同步到腾讯云 COS 或者其它对象存储。
关于数据目录权限,官方postgres:16镜像内部是用postgres用户跑的,UID 是 999。如果你直接把宿主机空目录挂载进去,容器启动时会因为权限不足报错。稳妥的做法是提前归好属主:
sudo chown -R 999:999 /data/postgres/data3.2 编写 docker-compose.yml
我模拟了一份可以直接上线的 Compose 配置:
services: postgres: image: postgres:16 container_name: postgres restart: always environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: "替换成强密码" POSTGRES_DB: appdb TZ: Asia/Shanghai PGTZ: Asia/Shanghai ports: - "5432:5432" volumes: - /data/postgres/data:/var/lib/postgresql/data - /data/postgres/backup:/backup command: - "postgres" - "-c" - "shared_buffers=256MB" healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 10s timeout: 5s retries: 5几个关键点我展开讲一下。
POSTGRES_PASSWORD:这个环境变量只在首次初始化数据目录时生效。如果数据目录已经存在且初始化过,再改这个值不会重置密码。很多人不知道这一点,后面我会再提。
POSTGRES_DB:首次启动时,官方镜像会自动创建一个名为appdb的数据库。如果你不设置,默认只会创建postgres库。
TZ和PGTZ:设置时区为上海。这样数据库的now()返回的是北京时间,日志时间也正常。很多教程不会提这两个变量,结果部署完发现数据库时间和服务器差 8 小时,排查半天。
command里的shared_buffers=256MB:这是 PostgreSQL 核心内存参数。对 4G 内存的机器,256MB 起步比较稳。如果你服务器内存只有 1G,建议改成 128MB,给操作系统和文件缓存留足空间。
healthcheck:让 Docker 周期性执行pg_isready,方便我们通过容器状态判断数据库是否真正就绪,而不是只看进程有没有起来。
3.3 启动容器并验证
配置写好后,执行:
cd /data/postgres docker compose up -d首次启动会从 Docker Hub 拉取postgres:16镜像,时间取决于网络状况。拉完后,容器会自动初始化数据目录、创建超级用户和默认数据库。
查看容器状态:
docker ps正常会看到postgres容器状态是Up,健康检查列是healthy。如果启动失败,用下面命令看日志:
docker logs -f postgres日志里如果出现chmod: changing permissions of '/var/lib/postgresql/data': Operation not permitted这类权限错误,大概率就是宿主机目录属主没设对,回到chown -R 999:999那一步重新处理。
3.4 初始化业务账号和数据库
容器起来后,第一步进入容器内执行 psql:
docker exec -it postgres psql -U postgres此时你会进入 PostgreSQL 的命令行。现在开始创建业务账号和数据库:
-- 创建一个业务用户,密码换成实际使用的强密码 CREATE USER appuser WITH PASSWORD '替换成强密码'; -- 创建业务库,并指定属主 CREATE DATABASE appdb OWNER appuser; -- 切换到 appdb \c appdb -- 给 appuser 授予 schema 权限 GRANT ALL ON SCHEMA public TO appuser;这里有个细节:PostgreSQL 在 15 版本之后,默认不再允许普通用户随意在publicschema 下建表。如果你用appuser连接appdb后想直接建表,必须像上面那样显式授权。这是很多从 MySQL 转过来的朋友最容易踩的坑。
如果你是远程用 DBeaver 或 Navicat 连接,需要把认证方式也确认清楚。官方镜像默认在pg_hba.conf里使用scram-sha-256认证,而不是 MySQL 那种caching_sha2_password。只要你设置了POSTGRES_PASSWORD,客户端用密码登录即可。
4. 部署后的使用、备份与恢复
4.1 最常用的 psql 命令
进入容器后,我经常用这些命令快速检查数据库状态:
# 进入容器 docker exec -it postgres bash # 切换用户并进入 psql su - postgres -c psql # 或者直接用容器环境变量里的用户 psql -U postgres在 psql 里,下面这些指令是最常用的:
-- 查看所有数据库 \l -- 切换数据库 \c appdb -- 查看当前库所有表 \dt -- 查看某个表结构 \d table_name -- 查看所有角色/用户 \du -- 查看当前连接数 SELECT count(*) FROM pg_stat_activity; -- 查看 PostgreSQL 版本 SELECT version();如果你不习惯命令行,也可以从本地用图形化工具连接。前提是腾讯云安全组已经放行 5432,并且服务器上没有额外的 iptables 规则拦截。
4.2 备份数据库,强烈建议从第一天就开始
我见过太多次“数据库崩了才发现从没备份”的事故。容器部署 PostgreSQL 让备份命令简化到了几行。
使用pg_dump做单库逻辑备份:
docker exec -t postgres pg_dump -U postgres -d appdb -F c -f /backup/appdb_$(date +%F_%H%M).dump解释一下参数:
-F c:自定义压缩格式,体积小,便于后面pg_restore恢复。-f /backup/...:备份文件写到容器内/backup路径,对应宿主机/data/postgres/backup。
备份完,宿主机上可以直接看到文件:
ls -lh /data/postgres/backup/恢复也同样简单:
docker exec -i postgres pg_restore -U postgres -d appdb --clean --if-exists /backup/appdb_xxx.dump--clean表示在导入前先清除已存在的同名对象,适合覆盖恢复。如果你是从零恢复到一个空库,可以去掉--clean。
4.3 定期备份落脚本和计划任务
手动备份不是长久之计。我一般在宿主机上写一个备份脚本/data/postgres/backup.sh:
#!/bin/bash DATE=$(date +%F_%H%M) docker exec -t postgres pg_dump -U postgres -d appdb -F c -f /backup/appdb_${DATE}.dump # 只保留最近 7 天备份 find /data/postgres/backup -name "appdb_*.dump" -mtime +7 -delete然后加执行权限:
sudo chmod +x /data/postgres/backup.sh再配置 crontab:
crontab -e添加一行,每天凌晨 2 点执行:
0 2 * * * /data/postgres/backup.sh >> /var/log/postgres_backup.log 2>&1这里把标准输出和错误都重定向到日志,方便以后排查备份是否成功。如果你担心磁盘占用,可以把备份定期上传腾讯云 COS,或者做异地同步。
4.4 更新镜像和切换 PostgreSQL 版本
Docker 部署还有一个隐藏优势:换版本相对可控。升级小版本,比如从 16.3 升到 16.4,只需要:
# 先备份 docker exec -t postgres pg_dump -U postgres -d appdb -F c -f /backup/appdb_before_update.dump # 拉取新镜像 docker pull postgres:16.4 # 停止旧容器 cd /data/postgres docker compose down # 修改镜像版本号,然后启动 docker compose up -d大版本升级,比如从 16 升到 17,不能直接复用数据目录。官方 PostgreSQL 的大版本升级需要执行pg_upgrade,在容器里的操作更复杂。一般情况下我会选择“逻辑迁移”,也就是先用pg_dump导出旧库,再在新版本的容器里pg_restore导入。这样虽然慢一点,但风险低、可回滚。真要做大版本升级,我建议先在测试环境跑一遍流程,别拿线上库直接试。
5. 常见问题与排查技巧实录
5.1 远程连接不上,到底卡在哪一层
这是被问得最多的问题。现象很统一:本地 Navicat 连不上腾讯云上的 PostgreSQL,提示超时或者拒绝连接。
排查顺序很重要,我一般按这四步走:
第一,看 PostgreSQL 是否在监听所有网卡。容器里默认监听0.0.0.0,你可以在宿主机执行:
docker exec -it postgres bash -c "cat /var/lib/postgresql/data/postgresql.conf | grep listen_addresses"如果值是localhost,当然连不上。但官方镜像默认配置往往是注释状态,实际监听0.0.0.0。大多数情况不是这里的问题。
第二,看腾讯云安全组。登录腾讯云控制台,找到实例所属安全组,确认入站规则里有没有TCP:5432。没有就添加上,并限定来源 IP。
第三,看宿主机防火墙。Ubuntu 24.04 默认可能没有启用 ufw,但如果你开了,检查一下:
sudo ufw status如果Status: active,执行:
sudo ufw allow 5432/tcp第四,看容器端口映射是否正常:
docker port postgres应该输出5432/tcp -> 0.0.0.0:5432。如果端口没映射出来,回到 Compose 文件里检查ports配置。
5.2 容器重启后数据“消失”了
这个问题的场景往往是:我用docker compose down再up -d,发现之前建的库全没了。
原因是数据目录没有正确挂载。如果你直接docker run postgres:16,没有写-v /data/postgres/data:/var/lib/postgresql/data,容器一旦被删除,数据就随着容器匿名卷一起没了。即便写了挂载,也要确认路径是否正确。
检查方法:
docker inspect postgres | grep -A 5 Mounts看输出的Source是否是/data/postgres/data,Destination是否是/var/lib/postgresql/data。如果这两个值没问题,数据就不会丢。
另外提醒一句:docker compose down默认不会删除命名卷,但如果你用了匿名卷,在down -v时会把卷删干净。所以要么坚持用宿主机目录挂载,要么给卷起名字,写清楚是postgres_data。我自己更偏爱宿主机目录挂载,因为出问题后可以直接拿文件系统工具来排查。
5.3 PostgreSQL 内存占用过高,容器被 OOM 杀掉
腾讯云小规格机器上,PostgreSQL 跑着跑着就没响应,看docker logs发现被系统杀了。这种情况常见于shared_buffers设置过大,加上操作系统本身可用内存不足。
我之前在一台 2G 内存的服务器上,把shared_buffers调到 1G,结果一跑并发查询,内存就爆了。后来改成 256MB,同时给系统留出 swap,整个就稳了。
如果你要调参数,编辑 Compose 文件里的command段,加上更多-c参数:
command: - "postgres" - "-c" - "shared_buffers=256MB" - "-c" - "effective_cache_size=768MB" - "-c" - "work_mem=16MB"改完执行:
docker compose up -dPostgreSQL 是重启后才生效参数。这里要说明,effective_cache_size不是真的分配内存,只是给查询规划器一个“系统大概有多少缓存可用”的参考值,设大了也不会被 OOM,但会影响执行计划选择。
5.4 时区差了 8 小时,日志和查询时间全不对
容器默认使用 UTC 时区,所以数据库里的now()会和北京时间差 8 小时。第一次部署时我忘记配TZ,业务方反馈数据时间不对,排查了很久。
解决办法是启动容器前就在 Compose 环境变量里加:
environment: TZ: Asia/Shanghai PGTZ: Asia/ShanghaiPGTZ会直接设置 PostgreSQL 的timezone参数。改完重启容器后,在 psql 里执行:
SHOW timezone;确认输出是Asia/Shanghai,时间就正常了。如果你已经启动过的容器没配这个变量,可以在宿主机上修改 Compose 文件后重建容器。这里要注意,重建容器不会删数据,只要挂载卷和初始化目录没变。
5.5 镜像拉取慢、中文乱码和编码问题
镜像拉取慢的问题我前面提过,可以配镜像加速。还有一个容易忽略的点是数据库编码。PostgreSQL 官方镜像默认使用UTF8作为编码,这对中文存储没问题。但如果你是通过CREATE DATABASE appdb创建的库,建议确认一下:
\l appdb看Collate和Ctype是否为C.UTF-8或en_US.UTF-8。只要编码是 UTF8,中文字符基本不会乱码。实际遇到乱码,多数是客户端连接时字符集设置不对,确认客户端连接字符串里加了characterEncoding=UTF8即可。
如果你需要自定义排序规则,可以在CREATE DATABASE时指定:
CREATE DATABASE appdb WITH ENCODING 'UTF8' LC_COLLATE 'C.UTF-8' LC_CTYPE 'C.UTF-8' TEMPLATE template0;模板要用template0,因为template1的排序规则可能被默认设置限制住了。这些都是我在实际项目里踩过之后才记住的细节。
5.6 我额外想提醒的小事
容器日志如果一直输出password authentication failed,先看是不是密码被改了但客户端还存着旧密码。PostgreSQL 不会因为 Docker 容器重启就重置密码,除非你删了数据目录重新初始化。重新初始化是一个非常危险的操作,会把所有数据清空,千万小心。
另外,尽量别用latest标签固定镜像版本。生产环境必须指定一个明确的版本号,比如postgres:16.4。这样以后镜像更新、团队协作时,大家拿到的是同一个版本,不会出现某天latest变了导致行为不一致的情况。
容器时间长了日志文件会变大。PostgreSQL 的日志在容器内,如果没做清理,容器层会越来越大。建议在 Compose 里不要直接暴露日志驱动配置,但可以在日常维护时定期docker logs --tail 100 postgres看一眼,或者用docker system prune清理无用镜像和构建缓存。当然,最重要的还是数据目录千万别被 Docker 的自动清理误伤,所以一切手滑前先备份。
6. 一点个人体会和部署建议
这套“腾讯云 Ubuntu 24.04 + Docker + PostgreSQL”的组合,我从测试环境一路用到生产环境,最大的感受就是:数据库本身不复杂,复杂的是环境不一致带来的问题。容器化之后,团队里任何人拿到的 PostgreSQL 环境都应该一模一样,不会再出现“我本机能跑,服务器上就报错”的尴尬。
如果只让我挑一条最重要的建议,我会说:从一开始就把数据和配置分开,数据目录挂载到宿主机,备份计划提前配好。你不用追求一步到位把所有参数都调得多么极致,但数据安全和可恢复性是底线。
最后再分享一个小技巧:每次更改 PostgreSQL 的配置或升级版本前,我都会先手动执行一次备份,把 dump 文件推到一个独立的地方,比如腾讯云 COS 或者另一台机器。这样即使 Compose 文件写错、镜像拉取失败、甚至不小心清了容器,手里永远有一份能恢复的筹码。数据库这种东西,平时感觉不到它的存在,丢一次数据就知道后悔了。希望这篇记录能让你少走一些我走过的弯路。