很多朋友第一次接触 PostgreSQL习惯性地去官网下载安装包一路 Next 点到底。装完才发现系统服务里多了一堆 postgres 开头的条目本机 5432 端口被占卸载之后目录、注册表和用户残留清不干净。更头疼的是项目 A 要用 PostgreSQL 12项目 B 要 16两个本机实例互相打架。我当年就是在这样的折腾里切到了 Docker 部署 PostgreSQL pgAdmin 的路线一份 docker-compose.yml把数据库实例、图形管理端、远程连接、数据持久化全部安排明白。这篇文章以标题里的场景为准从环境准备、compose 文件编写、远程连接配置到数据备份恢复完整走一遍适合第一次用 Docker 搭数据库、或者被本机安装折腾过的人。1. 为什么我建议用Docker跑PostgreSQL本机安装的三个真实痛点1.1 版本冲突与卸载残留是最大的隐形杀手PostgreSQL 官方安装包在 Windows 上默认把二进制装到C:\Program Files\PostgreSQL\16注册成系统服务数据目录放在C:\Users\xxx\AppData\PostgreSQL\16还会往 PATH 里塞几段路径。装一个还好装两个版本就开始乱了服务名都叫 postgresql-x64-16 这种端口不手动改就会撞卸载完第一个版本第二个版本的某些文件可能被共用删了之后第二个也起不来。Linux 上直接用 apt/yum 装虽然路径整齐但发行版源里的版本往往偏旧想装 PG16 还得折腾第三方源踩一遍也是时间成本。真正让人崩溃的场景是项目本地用的是 15.5同事推送的备份文件拿过来一恢复工具直接提示版本不匹配。你在服务器上 dump 出来的 SQL 带到本地可能因为新版本语法不兼容直接报错。这些折腾和数据库本身没什么关系纯粹是环境管理没做好。1.2 我机器上明明能跑环境一致性问题团队协作里最经典的句式是我这没问题啊。数据库这种服务型组件版本、插件、字符集、时区配置稍有不同行为就差很远。用 Docker 之后PostgreSQL 镜像、配置、扩展都在镜像里固化下来compose 文件一提交同事 pull 起来就是完全一样的环境。这不只是省事是让我们少接很多环境问题的误报。1.3 Docker在数据库场景下的收益边界先把话说清楚Docker 部署不等于生产级高可用。这里说的收益是在开发、测试、个人服务器、小团队内网这个层面一台机器同时跑多个 PG 版本端口映射不同即可初始化、销毁、再初始化都很便宜配合数据卷不怕丢换机器、换云服务器把 compose 文件和备份数据带过去就完成迁移。生产环境里要考虑的复制、自动故障切换、备份定时策略仍然需要额外设计。但这篇保姆级教程先把单机跑通讲明白后面的复杂度可以后面再说。2. 动手前先把运行环境确认好Docker与Compose的安装和常见翻车点2.1 WindowsDocker Desktop报virtualisation support wasnt detected怎么查Windows 上最常见的挫败感来自 Docker Desktop 启动时弹出的提示virtualisation support wasnt detected或者 virtualization support not detected。我见过很多人卡在这一步直接放弃。排查顺序先看好打开任务管理器 - 性能 - CPU右下角看虚拟化是否显示已启用。如果显示已禁用进 BIOS/UEFI 找 Intel Virtualization TechnologyVT-x或 AMD SVM Mode开启后保存重启。不同主板的按键不一样常见是 F2、Del、F10按品牌搜一下即可。如果 BIOS 已经开启但 Docker 还报错去启用或关闭 Windows 功能里勾选虚拟机平台和适用于 Linux 的 Windows 子系统WSL。Windows 11 下最好再跑一次wsl --update把 WSL2 内核更新到最新。还有一个冷门原因你的 Windows 本身跑在 VMware、VirtualBox 这类虚拟机里宿主机的 CPU 虚拟化没有嵌套传递进去Docker Desktop 一样会报这个错。这种情况得先在虚拟机软件设置里开启虚拟化 Intel VT-x/EPT之类的嵌套虚拟化选项。很多人的误解是 Docker Desktop 必须开 Hyper-V。其实 Windows 10/11 走 WSL2 后端就行关键是虚拟机平台和虚拟化这两个前提条件要同时满足。2.2 Linux优先用Docker Engine加Compose插件Linux 服务器上我建议装官方源里的 Docker Engine再配合新版 Docker Compose v2 插件也就是docker compose子命令而不是去单独下载那种带横线的 docker-compose v1 二进制。Ubuntu 大致是这几步sudo apt update sudo apt install ca-certificates curl gnupg 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 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 sudo apt update sudo apt install docker-ce docker-ce-cli containerd.io docker-compose-plugin sudo systemctl enable --now docker装完验证一下docker version docker compose version出现两个版本号就说明环境没问题。注意docker compose中间有空格是 v2 写法v1 用的是docker-compose连字符。本文所有命令都用新版写法老用户自行替换。2.3 那条docker-compose: error while loading shared libraries: libz.so.1是怎么回事很多教程让用户去 GitHub Releases 下载独立的 docker-compose 文件放到/usr/local/bin。在精简版系统或 glibc 版本比较特殊的小机器上运行时会直接给你一句docker-compose: error while loading shared libraries: libz.so.1: cannot open shared object file这意思就是系统缺少 zlib 这个基础库或者你下载的二进制和系统架构不匹配。解决方案分两种补齐依赖Ubuntu/Debian 执行apt-get install zlib1gCentOS/RHEL 执行yum install zlib更推荐的做法放弃独立二进制直接用上面说的 docker-compose-plugin 插件依赖跟 Docker Engine 一起由官方包管理解决不再有这类共享库问题。如果你坚持用独立二进制下载时务必确认架构x86_64 和 aarch64 的包混用报错方式五花八门。装完之后chmod x再放进 PATH 里。3. docker-compose.yml逐行拆解PostgreSQL和pgAdmin一套配置全拉起3.1 镜像版本选择为什么我推荐postgres:16而不是latest先定版本。官方镜像直接写postgres:16、postgres:17这样固定主版本号别用 latest。原因后面第 5 节会重点讲PostgreSQL 的数据目录是主版本强相关的你今年跑 postgres:16明天把标签改成 latest假设已经变成 17挂载同一个数据卷新容器根本起不来。固定版本是给自己留后路。至于 alpine 后缀比如postgres:16-alpine体积确实小很多但遇到扩展编译、特定 locale、时区这些场景小镜像容易缺组件。本地调试、团队内网这种场景我建议用默认的 Debian 版稳一点。pgAdmin4 就用官方镜像dpage/pgadmin4这个镜像没有太多版本纠结用 latest 即可主版本变化对使用影响小。3.2 完整compose文件先放出来下面的文件是整个部署的核心保存为 docker-compose.ymlservices: postgres: image: postgres:16 container_name: pg16 restart: unless-stopped environment: POSTGRES_USER: admin POSTGRES_PASSWORD: Str0ngPssw0rd POSTGRES_DB: appdb TZ: Asia/Shanghai PGTZ: Asia/Shanghai ports: - 5432:5432 volumes: - pg_data:/var/lib/postgresql/data healthcheck: test: [CMD-SHELL, pg_isready -U admin -d appdb] interval: 10s timeout: 5s retries: 5 pgadmin: image: dpage/pgadmin4:latest container_name: pgadmin4 restart: unless-stopped environment: PGADMIN_DEFAULT_EMAIL: adminexample.com PGADMIN_DEFAULT_PASSWORD: AdminPass123 PGADMIN_LISTEN_PORT: 80 ports: - 5050:80 volumes: - pgadmin_data:/var/lib/pgadmin depends_on: postgres: condition: service_healthy volumes: pg_data: pgadmin_data:这个文件没有一行是多余的下面逐个说清楚。3.3 环境变量背后的逻辑首次初始化之后不生效POSTGRES_USER、POSTGRES_PASSWORD、POSTGRES_DB 这三个变量只在该容器第一次初始化数据目录时生效。数据卷 pg_data 一旦生成后续无论你怎么改 compose 里的密码都不会改变数据库里已有的用户和密码。这是非常多人踩过的坑改了环境变量重启密码没变然后怀疑人生。pgAdmin 那边的 PGADMIN_DEFAULT_EMAIL 和 PGADMIN_DEFAULT_PASSWORD同样只在 pgadmin_data 卷为空时生效。忘掉了初始密码的解决办法要么去清掉 pgadmin_data 卷重新初始化要么用命令行方式重设后面第 6 节会说。健康检查这一段值得说明。depends_on 默认只保证postgres 容器起来了不保证数据库可用。我在 postgres 服务里加了pg_isready探活再让 pgadmin 的 depends_on 带上condition: service_healthy这样 pgAdmin 会等到 PostgreSQL 真正接受连接后再启动避免第一次打开界面时连不上数据库。3.4 启动后先做三件事验证、看日志、进容器在 docker-compose.yml 所在目录执行docker compose up -d docker compose ps看到两个容器状态都是 Up再看一眼日志docker logs -f pg16最后进容器验证数据库docker exec -it pg16 psql -U admin -d appdb能进入 psql 提示符基础部署就算成功。此时浏览器访问http://localhost:5050用 compose 里配置的邮箱和密码登录 pgAdmin第一件事是加服务器这个操作放到第 4 节讲因为里面藏着最容易搞混的网络概念。4. 远程连接从连不上到顺手就通分清楚是容器、防火墙还是云策略4.1 官方镜像到底允不允许远程连接很多人一想到远程连接马上去改 pg_hba.conf。官方镜像其实已经替你做了一件事初始化数据目录时生成的 pg_hba.conf 默认包含host all all all scram-sha-256这条规则意思是允许任意来源的 TCP 连接密码以 SCRAM-SHA-256 方式校验。所以容器本身让不让远程连这个层面官方镜像默认是允许的。真正决定能不能远程的是另外三层端口映射有没有暴露出去宿主机防火墙Windows 防火墙、Linux UFW/firewalld有没有放行如果是云服务器安全组入方向有没有放行 5432。这三层任何一层没放远程连不上都是正常的。4.2 三层放行实操本地、局域网、云服务器本机访问最简单compose 里已经把 5432 映射出来了连接串写localhost:5432或者127.0.0.1:5432都行。局域网访问假设你的服务器 IP 是 192.168.1.100客户端要从另一台电脑连需要在服务器上放行。Ubuntu 的 UFW 大概是sudo ufw allow from any to any port 5432 proto tcpCentOS 的 firewalld 对应是sudo firewall-cmd --permanent --add-port5432/tcp sudo firewall-cmd --reloadWindows 服务器则要去Windows 防火墙高级设置里新建入站规则放行 TCP 5432。云服务器还要多注意一层安全组。即便系统内防火墙全开、端口映射正常安全组没加规则一样无效。去云厂商控制台找到安全组添加入方向规则端口填 5432来源可以先写你公司的出口 IP 或者需要访问的内网网段别一上来就 0.0.0.0/0 全放。4.3 pgAdmin添加服务器时的两个常见错误第一个错误是把 Host name/address 填成了 localhost。当你用本机浏览器访问http://localhost:5050时pgAdmin 界面是从 pgadmin 容器里出来的它和 postgres 容器在同一个 compose 网络里。这两个容器之间通信要用服务名也就是postgres而不是 localhost。localhost 只代表 pgAdmin 容器自己指向它是连不上数据库的。第二个错误是把端口填成 5050。5050 是宿主机访问 pgAdmin 界面的入口端口不是 PostgreSQL 的端口。连接数据库那栏端口填 5432。正确填法大致是这样Host name/addresspostgres容器内用服务名或服务器 IP从外部连Port5432Maintenance databaseappdbUsernameadminPassword按你的实际配置填提示如果你希望 pgAdmin 只给自己本地用端口映射写成127.0.0.1:5050:80就不会暴露到外部网络。5. 数据持久化不是挂个目录这么简单卷的选型与备份恢复5.1 具名卷 vs 绑定挂载一张表看差别Docker 的持久化做法就两种compose 里都能写。对比项具名卷named volume绑定挂载bind mount配置示例pg_data:/var/lib/postgresql/data./pgdata:/var/lib/postgresql/data数据位置Docker 管理/var/lib/docker/volumes 下你指定的宿主机目录直观可见备份方式docker cp 或临时容器导出直接拷贝目录权限风险低Docker 自动处理高宿主机目录所有权不对会启动失败适用场景正式部署、多机迁移快速调试、想直接看见数据文件我推荐默认用具名卷最省心。绑定挂载最大的陷阱是权限容器里的 postgres 进程以 postgres 用户运行UID 是 999如果宿主机上的 ./pgdata 目录属于 root 或其他用户容器初始化数据目录时会直接报data directory has wrong ownership之类错误。解决方法是提前chown -R 999:999 ./pgdata但每次换机器都要记得处理不如具名卷自动搞定。5.2 备份与恢复别等磁盘挂了才想起这里教最实用的 pg_dump 逻辑备份适合 PostgreSQL 之间迁移、版本升级、日常快照。备份到宿主机当前目录一条命令docker exec pg16 pg_dump -U admin -d appdb appdb_$(date %Y%m%d).sql如果数据库比较大更推荐自定义格式方便之后恢复成不同库名docker exec pg16 pg_dump -U admin -d appdb -F c -f /tmp/appdb.dump docker cp pg16:/tmp/appdb.dump .恢复同样简单cat appdb_20250601.sql | docker exec -i pg16 psql -U admin -d appdb或者先建好目标库再用 pgAdmin 的备份/恢复界面操作逻辑是一样的。定期备份的话可以写个宿主机上的 crontab0 2 * * * docker exec pg16 pg_dump -U admin -d appdb /backup/appdb_$(date \%Y\%m\%d).sql注意 crontab 里 % 需要转义。备份文件要有保留策略别让磁盘被无限增长的历史备份塞满。5.3 升级主版本时不要直接换镜像标签PostgreSQL 对数据目录主版本非常敏感。postgres:16 初始化的卷直接给 postgres:17 容器挂载启动日志会明确告诉你版本不兼容。这时候一定不要强行 override正确流程是备份pg_dump 导出当前数据库停服务docker compose down修改镜像版本号并删掉旧数据卷或者换一个新的卷名docker compose up -d重新初始化恢复备份数据验证关键表、索引、外键。数据量巨大时不建议用 pg_dump 全量恢复可以考虑 pg_upgrade但那需要同时挂载新旧版本的数据目录来做操作复杂度高很多我建议先在测试环境演练一遍再动生产数据。6. 部署期间最容易踩的6个坑日志和现象对照6.1 镜像拉取慢 / 拉不下来这是国内服务器最常见的痛。解决思路是给 Docker 配置 registry mirror也就是镜像加速源。编辑/etc/docker/daemon.json{ registry-mirrors: [ https://docker.mirrors.ustc.edu.cn, https://hub-mirror.c.163.com ] }然后重启 Dockersudo systemctl restart docker。注意不同镜像源的可用性会随时间变化可以找一个当前能用的。如果是在局域网里自建了镜像仓库也可以把地址填进去。拉取失败时先重试配置好镜像源再重新 pull。6.2 端口被占用起不来最常见的端口冲突就是 5432 被本机已安装的 PostgreSQL 占了。检查方式ss -lntp | grep 5432如果确实是本机 PG 在跑二选一停掉本机服务或者把 compose 里的端口映射改成5433:5432。pgAdmin 的 5050 端口同理被占就改成5051:80。改完端口别忘了在 pgAdmin 连接时填对应的宿主机端口。6.3 容器起来了又退出 / 反复重启先看日志日志会告诉你 90% 的原因docker logs pg16 --tail 100常见的原因有几种挂载目录权限不对日志里出现 permission denied 或 wrong ownership磁盘满了日志里出现 No space left on device端口冲突docker compose ps会显示 Exited (1)。如果容器处于重启中状态用docker inspect pg16看 ExitCode 和 OOMKilled 字段能确认是不是内存不足被杀。6.4 pgAdmin界面都进不去先检查 5050 端口映射有没有生效curl http://localhost:5050看返回。如果界面能打开但登录密码不对多半是第一次启动时卷已经初始化过了后来你再改 PGADMIN_DEFAULT_PASSWORD 不生效。实在不行就备份好 pgadmin_data 里的用户配置删掉 pgadmin_data 卷重新初始化。6.5 中文数据乱码 / 排序不理想官方镜像默认数据库编码就是 UTF8存中文不会乱码。真正需要关注的是 locale如果应用的排序、大小写规则依赖特定 locale可以在第一次初始化时传入参数environment: POSTGRES_INITDB_ARGS: --localeen_US.UTF-8注意这个参数只在数据目录首次初始化时生效初始化完就不能改了。6.6 改了密码连不上环境和数据库初始化一次性绑定之后改 compose 环境变量没用。想要真正改密码进容器执行 SQLdocker exec -it pg16 psql -U admin -d appdb ALTER USER admin WITH PASSWORD NewPass123;或者用 pgAdmin 图形界面在对象树里找到登录角色右键改密码。改完记得更新所有用到旧密码的连接配置否则你会看到密码验证失败刷屏。最后再分享几个我自己一直用的习惯。我在 compose 文件同级目录放一个 backup.sh里面把 pg_dump 输出放到带日期的文件并做 7 天轮转卷的命名会带项目前缀比如 app_pg_data方便同时维护多套环境时一眼认出来。这套部署方案我反复用在不同项目里稳定性足够高。前面这些坑我基本都踩过一遍写出来就是希望你能一次性把 PostgreSQL pgAdmin 跑顺把时间留给真正的业务逻辑。