Ubuntu 24.04上用Docker Compose部署PostgreSQL 16实战指南
2026/9/16 4:29:22 网站建设 项目流程

上个月帮朋友的一个内部项目做数据库部署,场景很典型:一台腾讯云上的Ubuntu 24.04服务器,需要用PostgreSQL存业务数据。本来想直接apt install postgresql省事,但考虑到后面可能要复制环境、升级版本、甚至换服务器,最后还是决定用Docker容器化部署。整套流程从系统初始化到参数调优、从踩坑到收尾,大概是半天时间。这篇文章把过程完整记录下来,包括每一步命令、参数选择的理由,以及几个会让人卡半天的坑,希望能给准备做同样部署的同学一个可靠的参考。

1. 项目概述:容器化部署PostgreSQL的核心思路

1.1 为什么不直接用apt装PostgreSQL

很多人会觉得奇怪:Ubuntu的apt源里明明有postgresql包,一条命令就能装好,为什么非要绕一圈用Docker?我最初也这么想,但实际对比下来,容器化的优势恰恰体现在后期运维上。

第一个好处是环境隔离。PostgreSQL依赖的glibc、openssl等库版本不会和系统其他软件互相干扰,Ubuntu 24.04自带的PostgreSQL版本通常偏旧,而Docker官方镜像可以精确锁定你想要的版本。第二个好处是迁移便捷。裸机安装的数据库换台机器要重新配置数据目录、日志、权限,容器化环境下只需要把数据卷和compose文件拷过去,另一台机器上docker compose up -d就完成了迁移。

当然容器化也有劣势,比如性能上会有轻微损耗,以及排障时多了一层容器隔离。但我在2C4G的机器上实测,日常读写几乎感知不到差异。对绝大多数中小业务来说,容器化带来的部署便利远远大于那点损耗,这也是我最终选Docker的原因。

1.2 适合哪些场景以及云服务器怎么选

这套方案最适合的是:业务初期、没有专职DBA、需要快速上线且可能频繁调整环境的团队。比如开发测试环境、中小型Web应用的后端数据库、数据分析项目的存储层。如果你的业务已经达到千万级日活,数据库需要精细化调优,建议直接上云数据库或者找专业DBA,那不是我这里讨论的场景。

选择PostgreSQL而不是MySQL,是因为业务里有不少复杂查询和JSON字段,PostgreSQL在这方面的能力更成熟。服务器配置方面,我用的是腾讯云2核4G的标准型实例,系统镜像选择Ubuntu 24.04 LTS,系统盘50G,额外挂了一块50G的数据盘。2C4G对中小业务够用,但如果你读写频繁,建议直接上4核8G。磁盘多说一句,PostgreSQL的数据文件对磁盘IO非常敏感,有条件的话数据盘选SSD。这次偷懒挂的是普通云硬盘,后面导入数据时明显感觉到性能瓶颈。

2. Ubuntu 24.04环境初始化与Docker安装

2.1 登录服务器后的基础设置

拿到腾讯云服务器后,第一件事是SSH登录。如果你习惯用Windows,可以直接用系统自带的ssh命令,或者Termius、Tabby这类终端工具。登录之后,我习惯先把系统更新一遍,这个步骤在Ubuntu 24.04上是必须的:

sudo apt update && sudo apt upgrade -y

更新完顺手设置一下时区,让系统时间对齐北京时间,后面排查日志的时候你会感谢这个操作:

sudo timedatectl set-timezone Asia/Shanghai

还有个容易被忽略的坑:Ubuntu 24.04默认可能启用了UFW(Uncomplicated Firewall),如果开着防火墙但没放行端口,后面Docker映射出来的端口外部访问很可能被挡掉。建议先把SSH端口放行,之后再按需放行其他端口:

sudo ufw allow 22/tcp sudo ufw enable

这一步看个人偏好,如果你习惯在腾讯云安全组里管理端口,UFW也可以不启用。但你必须清楚端口策略到底在哪一层生效,否则排查连不上数据库的问题会非常痛苦。

2.2 用官方源安装Docker

Ubuntu的apt源里也带docker.io,但版本比较旧,而且不包含docker compose插件,所以强烈建议用Docker官方仓库安装。在Ubuntu 24.04上安装Docker Engine的步骤如下:

# 安装依赖工具 sudo apt install -y ca-certificates curl gnupg # 创建keyrings目录并导入Docker官方GPG密钥 sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo tee /etc/apt/keyrings/docker.asc # 添加Docker官方apt源,注意noble对应Ubuntu 24.04 echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \ noble stable" | sudo tee /etc/apt/sources.list.d/docker.list # 更新索引并安装 sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

这里有个新手容易踩的坑:Ubuntu 24.04的代号是noble,源里写错成jammy(22.04)也能执行,但装出来的可能是旧版本甚至找不到包。装完后检查一下源文件确认无误。安装完成后,把当前用户加入docker组,这样后面执行docker命令不需要每次sudo,日常操作体验提升非常明显:

sudo usermod -aG docker $USER newgrp docker

之后用docker version验证客户端和服务端版本,两边都能正常输出就说明装好了。

2.3 配置腾讯云内网镜像加速器

Docker Hub在国内拉取镜像经常慢到怀疑人生,甚至直接超时。腾讯云服务器有一个免费的内网镜像加速地址,配置到Docker的daemon.json里,拉镜像速度会有质的飞跃。创建/etc/docker/daemon.json

{ "registry-mirrors": ["https://mirror.ccs.tencentyun.com"] }

保存后重启Docker:

sudo systemctl daemon-reload sudo systemctl restart docker

顺手把Docker开机自启打开,不然服务器重启后容器不会自动拉起来:

sudo systemctl enable docker

这里多说一句:镜像加速地址最好用你所在云厂商内网的,如果你用的是其他云厂商的机器或者自建机房,直接填这个地址不一定快,得用各自云厂商的加速方案。另外,安装包里docker-compose-plugin这个包很重要,后面我们用的docker compose命令就靠它。

3. 用Docker Compose编排PostgreSQL容器

3.1 PostgreSQL镜像版本怎么选

Docker Hub上的官方PostgreSQL镜像主要有两个tag系列:postgres:16postgres:16-alpine。alpine版本基于Alpine Linux构建,镜像体积小很多,内存占用也低,对部署来说更轻量。但要注意,alpine版本用的是musl libc而不是glibc,个别扩展可能编译不了。我这次选的是postgres:16-alpine,因为业务没有用到需要编译的自定义扩展,选alpine可以把镜像从几百MB压缩到几十MB,而且实测运行稳定。

至于为什么选16而不是最新的17:PostgreSQL 16在2023年9月发布,到2024年已经经过多个小版本迭代,稳定性和生态兼容性都更好。17虽然更新,但对中小项目来说没必要追求前沿,稳定第一。如果你有特殊需求,比如必须用某个新特性,那另说。

3.2 编写docker-compose.yaml

在服务器上规划一个项目目录,比如/opt/postgres,然后在里面放docker-compose.yaml。这是我最常用的一个基础版本:

services: postgres: image: postgres:16-alpine container_name: postgres16 restart: always environment: POSTGRES_USER: appuser POSTGRES_PASSWORD: 'YourStrongPassword' POSTGRES_DB: appdb TZ: Asia/Shanghai ports: - "5432:5432" volumes: - ./data:/var/lib/postgresql/data - ./backup:/backup command: - "postgres" - "-c" - "shared_buffers=512MB" - "-c" - "effective_cache_size=1536MB" - "-c" - "max_connections=200" - "-c" - "log_min_duration_statement=1000" - "-c" - "log_timezone=Asia/Shanghai"

几个关键点的说明:

POSTGRES_USERPOSTGRES_PASSWORD是容器首次启动时初始化数据库用的环境变量,会创建一个超级用户和对应的数据库。POSTGRES_DB设置默认数据库名。密码一定要用强密码,这里只是示例,实际部署建议用至少20位的随机字符串。

volumes里挂载了两个目录:./data是PostgreSQL的数据目录,必须挂载,否则容器一删数据就全没了;./backup是我习惯预留的备份目录,方便在宿主机上直接操作备份文件。

command部分传给postgres的是运行时参数,这些参数会把镜像默认配置覆盖掉。示例里给的shared_buffers、effective_cache_size等值针对2C4G的机器,具体怎么算我在第5节详细讲。

3.3 数据持久化的底层逻辑

很多第一次用Docker跑有状态服务的人都会踩一个大坑:容器启动时正常读写,一切看起来非常美好,结果某天手一抖执行了docker rm,或者升级镜像时没注意,数据目录直接跟着容器一起没了。PostgreSQL容器化的关键就是数据持久化,数据一定不能写在容器内部的可写层。

Docker挂载数据卷有两种主流方式:bind mount(./data:/var/lib/postgresql/data)和named volume(pgdata:/var/lib/postgresql/data)。bind mount的好处是数据直接存在宿主机指定目录,你随时可以ls查看,备份也方便;named volume由Docker管理,路径更隐蔽,但在宿主机上直接操作文件不方便。我习惯用bind mount,对数据库这种需要经常备份、排查的场景更友好。

挂载目录的权限也值得提一下:PostgreSQL镜像里的postgres用户UID是999,如果你发现容器启动后报权限错误,多半是宿主机目录权限不对。最简单的做法是:

sudo chown -R 999:999 /opt/postgres/data

当然,如果目录是新建的空目录,让容器自己初始化时创建也可以,但后续在宿主机上操作文件时还是要留意UID。

4. 启动容器与校验连接

4.1 启动与日志检查

编排文件写完后,第一次启动建议用up -d后台运行,再配合日志观察状态:

cd /opt/postgres docker compose up -d docker compose logs -f

logs -f看到类似下面的输出,说明数据库初始化成功:

LOG: database system is ready to accept connections

如果启动失败,日志里通常会有明确原因,常见的有:端口被占用、挂载目录权限不对、密码环境变量不符合规范(PostgreSQL要求密码不能为空等)。排查顺序一般是从下往上逐行看日志,先看最后几行,再追到最初报错的位置。

数据目录挂载这一点再说一遍:如果./data目录已经存在且里面有数据,PostgreSQL容器会直接复用;如果目录为空,容器会执行初始化流程,然后创建超级用户和默认数据库,用户名密码就是上面POSTGRES_USERPOSTGRES_PASSWORD里配的。这里有个隐藏细节:如果数据目录已经初始化过了,你再改POSTGRES_USERPOSTGRES_PASSWORD这些环境变量是不会生效的,因为初始化只发生在第一次启动。这经常让一些同学困惑:数据目录还在,但密码怎么改都不对。

4.2 在容器内验证psql连接

容器正常起来后,先用容器内的psql验证本地连接:

docker exec -it postgres16 psql -U appuser -d appdb

能进入psql命令行就说明数据库本身没问题。顺手做几个最基本的检查:

SELECT version(); SHOW shared_buffers; SHOW max_connections;

查看一下参数是否生效。如果你在宿主机上装了postgresql-client,也可以直接从宿主机连接:

sudo apt install -y postgresql-client psql -h 127.0.0.1 -U appuser -d appdb

4.3 远程连接与云安全组配置

本地连接没问题,很多人下一步就直接用Navicat、DBeaver等客户端从自己电脑上连。这时候最容易出问题。腾讯云服务器的端口放行有两层:一层是系统内部防火墙(如果有开UFW),另一层是云平台的安全组。两个地方都得放行5432端口,只放一个都不行。我这次就吃了这个亏:UFW放行了但安全组只开了22端口,结果从公网怎么都连不上,排查了半天才发现问题。

腾讯云控制台操作路径是:云服务器实例详情 -> 安全组 -> 配置规则 -> 入站规则 -> 添加规则,协议选TCP,端口填5432,来源按需填你的公网IP或者0.0.0.0/0(不推荐全开)。安全组规则修改后立即生效,不需要重启机器。

需要提醒的是:如果数据库只给同一台机器上的应用用,那根本不需要把5432端口暴露到公网,只用容器网络访问就行。把数据库暴露到公网是风险最高的操作之一,密码一旦泄露,被暴力破解就是分分钟的事。如果业务确实需要远程访问,建议来源填你的办公网IP而不是0.0.0.0/0,再加一层防火墙白名单。

5. PostgreSQL关键参数调优

5.1 内存参数怎么算

PostgreSQL安装完默认参数非常保守,不太适合直接上生产,这也是很多同学部署完发现性能不行的原因。调优的核心参数是shared_bufferseffective_cache_sizeshared_buffers是PostgreSQL自己的共享缓存池,业界通用建议设置为总内存的25%左右;effective_cache_size是给查询计划器估算操作系统文件缓存用的,一般设置为总内存的50%~75%。

以我这台2核4G的机器为例:

  • shared_buffers:4G x 25% = 1G,但考虑到操作系统本身也要内存,而且Docker里可能不止跑PostgreSQL一个容器,我实际设置的是512MB,留出一部分余量。如果你确定这台机器只跑数据库,可以大胆设到1G。
  • effective_cache_size:4G x 50% = 2G,我设置的是1536MB,同样偏保守。
  • max_connections:默认100,我改到200,但如果应用是连接池模式,几十个连接其实就够了,连接数太高反而会占用更多共享内存。
  • work_mem:这个参数决定单个排序或哈希操作能用的内存,默认4MB。注意它是“每次操作”分配的,不是按会话分配的,设置太高会导致内存爆掉。我一般保持默认,除非确认某些查询频繁出现磁盘排序。

修改这些参数,除了在compose文件的command里传,也可以进容器改postgresql.conf,但容器一旦重建配置就丢了,所以推荐在compose里统一管理配置,这也是容器化的优势。参数生效后,可以用SHOW命令确认。

5.2 慢SQL日志与定位

日常运维中,慢SQL是最容易拖垮数据库的元凶。定位慢SQL的通用做法是开启慢查询日志。在compose的command里加一个参数:

- "-c" - "log_min_duration_statement=1000"

log_min_duration_statement=1000表示执行时间超过1000毫秒(1秒)的语句会被记录到日志。之后查看日志:

docker compose logs postgres | grep "duration"

或者直接进容器查看日志文件。日志里会看到类似这样的内容:

LOG: duration: 2345.123 ms statement: SELECT * FROM orders WHERE user_id = 123;

这时候就可以分析这条SQL是否缺少索引、是否做了全表扫描等。慢SQL日志是定位性能瓶颈的第一手资料,建议从部署第一天就开启,反正日志量也不大。

5.3 时区、编码这些容易忽略的细节

PostgreSQL因为时区问题导致日志时间和业务时间不一致,这种问题通常不会立刻暴露,但等你查日志的时候才发现很坑。Docker官方PostgreSQL镜像默认时区是UTC,和北京时间相差8小时,所以我在compose里设置了TZ: Asia/Shanghai环境变量和log_timezone=Asia/Shanghai参数。这样容器内的时间、日志时间和宿主机时间就能保持一致,排查问题不用再手动换算时区。

编码方面,Docker官方镜像初始化数据库时会用UTF-8,一般不需要额外处理。但如果你是从旧版本迁移过来的数据库,数据里的编码格式可能不一致,迁移验证时一定要检查特殊字符是否正常。

6. 实战中的常见问题与排查心得

6.1 外部连不上数据库,按什么顺序排查

这个问题我遇到过多次,而且每次卡住的人都容易走弯路。如果你在服务器本机可以连接,但从外部客户端连不上,按下面顺序排查:

第一步,检查端口是否监听。在服务器上执行ss -lntp | grep 5432,如果只有127.0.0.1:5432而没有0.0.0.0:5432,说明PostgreSQL只监听了回环地址。Docker的ports映射默认会监听0.0.0.0,但如果手工指定过127.0.0.1:5432:5432,外部当然连不上。

第二步,检查云安全组。登录腾讯云控制台,确认入站规则放行了TCP 5432端口。

第三步,检查系统防火墙。如果你启用了UFW,执行sudo ufw status,确认5432端口已放行。

第四步,在外部机器上测试网络连通性。比如在你本地电脑执行telnet 服务器公网IP 5432,如果连不通,说明还有网络层问题;如果通了但提示认证失败,就进入下一个问题——认证。

6.2 密码认证失败与特殊字符

用psql连接时出现password authentication failed for user "appuser",先检查密码是不是真错了。这里有个很隐蔽的场景:数据目录已经初始化过,你修改compose里的POSTGRES_PASSWORD不会生效,必须执行docker compose down然后删掉数据目录重新初始化,或者用psql里的ALTER USER命令改密码。

另一个常见问题是特殊字符被shell转义了。密码里如果包含$@!等特殊字符,用双引号包起来最稳妥,比如:

docker exec -it postgres16 psql "host=127.0.0.1 user=appuser password='p@ss$word' dbname=appdb"

或者用.pgpass文件。总之认证问题先分清是密码错了还是认证方式不对,再针对性处理。

6.3 容器重启后数据没了怎么办

数据丢失通常是因为没挂数据卷,或者挂载路径写错了。有时候手误把宿主机目录挂载到了容器里的/tmp,或者多打了一个字符,容器自然不知道数据该写到哪。遇到这种情况首先停止容器,不要继续写入,然后尝试数据恢复工具。但说实话,容器内可写层一旦丢数据,恢复成功率很低。

最靠谱的办法是预防:在compose文件里永远写清楚数据卷映射,并且部署完成后马上验证一次。验证方法很粗暴:往数据库里写一条测试数据,然后docker compose downdocker compose up -d,查一下数据还在不在。这一分钟的操作能避免后面好几个小时的痛苦。

6.4 性能突然变差的排查思路

容器化数据库性能变差,先别急着怪Docker。我的排查顺序是:先看宿主机资源(topfree -h),再看容器资源占用(docker stats),最后看数据库慢日志。很多时候是磁盘IO跑满了,或者shared_buffers设小了导致频繁读写磁盘。

PostgreSQL的pg_stat_statements视图可以看到最耗时SQL语句,部署时建议加上这个扩展,实际操作:

docker exec -it postgres16 psql -U appuser -d appdb -c "CREATE EXTENSION IF NOT EXISTS pg_stat_statements;"

然后查询前几条最耗时的语句:

SELECT query, calls, total_exec_time, mean_exec_time FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 5;

这个视图的信息量非常大,很多“数据库突然变卡”的问题靠它都能排除掉大部分原因。

7. 备份、恢复与后续拓展

7.1 定时备份方案

数据库部署好之后,备份是不能省的。最基础的备份方式是pg_dump逻辑备份,它可以对某个库做一致性导出。我习惯把备份命令写成一个脚本,再用cron定时执行。脚本大概长这样:

#!/bin/bash BACKUP_DIR=/opt/postgres/backup DATE=$(date +%Y%m%d_%H%M%S) docker exec postgres16 pg_dump -U appuser -d appdb -F c -f /backup/appdb_$DATE.dump find $BACKUP_DIR -name "*.dump" -mtime +7 -delete

这里-F c表示自定义格式,/backup是容器内挂载的备份目录,对应宿主机上的/opt/postgres/backupfind命令会自动清理7天前的备份文件,避免磁盘被塞满。把脚本放到/etc/cron.daily/backup_postgres并赋予执行权限,就能每天自动备份了。

恢复的时候,用pg_restore即可:

docker exec -i postgres16 pg_restore -U appuser -d appdb --clean /backup/appdb_xxxx.dump

如果数据量不大,直接用SQL格式的备份文件配合psql恢复也可以。备份这件事,宁可多做不要少做,我自己就经历过一次数据库文件损坏后靠备份恢复的紧急情况,当时真是庆幸有自动备份。

7.2 升级与迁移

容器化的好处在版本升级时体现得很明显。假设要把PostgreSQL从16升级到17,大致流程是:先pg_dump导出数据,然后改compose文件里的镜像版本,重新docker compose up启动新容器,最后导入数据。这个过程和裸机升级相比,省去了很多环境层面的麻烦。

但如果数据量很大,pg_dumppg_restore的时间会很长,这时也可以考虑用pg_upgrade,不过那是在容器内完成的复杂操作,中小项目直接用逻辑备份迁移就够了。

7.3 将容器化思维扩展到更多基础设施

PostgreSQL容器化部署稳定跑起来之后,你会发现这套思维可以复制到几乎所有中间件:Redis、MySQL、MongoDB、Nginx、RabbitMQ,都能通过类似的docker-compose编排跑起来。以后的服务器运维会越来越像“声明式管理”:写好配置文件,一次部署,多处复用。这也是我觉得Docker很值得投入学习的地方——它不只是把某个软件跑起来那么简单,更是一种把基础设施代码化的思维方式。

回到开头说的那个项目,这套PostgreSQL容器化方案上线到现在运行了一个多月,除了中间一次磁盘告警之外没有出过问题。真要说心得,最重要的是别在第一次部署时追求所谓最优参数,先把服务跑通,后面慢慢调。另一个是数据安全永远靠备份兜底,没有备份的数据库就像没系安全带的驾驶。这套方案已经在帮我的项目稳定扛业务了,希望分享出来的这些细节,也能让你在自己的服务器上少折腾几个来回。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询