恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
使用Docker部署PostgreSQL:从镜像选择到数据持久化与日常运维
首页
资讯中心
/
使用Docker部署PostgreSQL:从镜像选择到数据持久化与日常运维
使用Docker部署PostgreSQL:从镜像选择到数据持久化与日常运维
发布时间:2026/10/9 12:48:46
1. 从本机安装到 Docker我为什么最终选了这条路线如果你在本地开发环境里装过 PostgreSQL大概都经历过这套流程去官网下载安装包、下一步下一步、设置超级用户密码、处理 PATH 环境变量然后在系统服务里找到它、点启动。到了 Linux 服务器上还要 initdb 初始化数据目录、手动配置 pg_hba.conf 和 postgresql.conf、想办法设置开机自启。这套东西第一次跑通可能要折腾一两个小时而且换了机器之后又得重新来一遍。后来我接触了 Docker就再也没在本机直接装过 PostgreSQL至少在开发和大部分生产场景里没有。用 Docker 部署 PostgreSQL本质上是把数据库本身和它运行所需的环境操作系统依赖、配置文件、数据目录、端口监听、启动命令全部打包成镜像一键拉下来就能跑。这么做最直接的好处有三个环境隔离。宿主机上是 CentOS 也好、macOS 也好、Windows 也好容器里的 PostgreSQL 永远是在它自己的运行时环境里不受宿主系统库版本、内核模块、权限策略影响。可复现。一个 docker run 命令或者一份 docker-compose.yml不管放到谁的机器上跑出来的都是同一个数据库服务。团队协作的时候这比“按文档一步步装”靠谱得多。清理干净。不想用了docker stop docker rm再删掉卷服务器上干干净净不残留一堆配置文件和服务残留。标题里写的是“使用 Docker 部署 postgresql”这篇文章我会从环境准备、镜像选择、核心命令、数据持久化、日常维护、常见排错一直到 docker-compose 规范化部署完整过一遍这条路线。适合刚开始用 Docker、想把 PostgreSQL 快速跑起来的读者也适合已经在单机部署过、但还没理清持久化和备份思路的人。2. 环境准备先把 Docker 跑稳再说数据库的事2.1 不同平台装 Docker 的差异很多人在“部署 PostgreSQL”之前其实卡在了 Docker 本身没装好。这几年 Docker Desktop 已经很成熟了Windows 和 macOS 用户直接装 Docker Desktop 是最省心的路径它自带图形界面能直接管理容器、镜像、卷默认配置了 WSL2 或 Hyper-V 后端Windows和虚拟化框架macOS日常开发完全够用。Linux 服务器这边稍微讲究一点。Ubuntu/Debian 系可以直接sudo apt update sudo apt install docker.io docker-compose-pluginCentOS/RHEL 系则是sudo yum install -y yum-utils sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo yum install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin但我在生产服务器上更推荐使用 Docker 官方安装脚本或者直接从官方仓库装 docker-ce 系列因为发行版自带的 docker.io 版本通常偏老有些新特性如 BuildKit、Compose v2 支持不完整。装完之后先把服务启起来sudo systemctl enable --now docker然后验证一下sudo docker run hello-world能打印出 Hello from Docker! 说明整个链路没问题可以进入下一步了。2.2 最常遇到的 permission denied 问题装好 Docker 后Linux 用户几乎都会遇到一个报错permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock这是很多热搜词里都在问的问题原因是当前用户不在 docker 用户组里。Docker 客户端需要访问 /var/run/docker.sock 这个 socket 文件才能和守护进程通信而默认情况下这个 socket 属于 root 组。解法很简单sudo usermod -aG docker $USER然后注销重新登录或者执行 newgrp docker 让组权限立即生效再跑一遍 docker ps 检查。这里要提醒一句把用户加入 docker 组等于给了这个用户等同于 root 的权限因为在容器里可以挂载宿主机目录、执行任意命令。个人开发机无所谓但在多人共用的服务器上要慎重不要随便给非信任账户加这个组。2.3 网络与镜像下载的准备工作PostgreSQL 镜像在 Docker Hub 上官方仓库名就是 postgres。如果你所在网络访问 Docker Hub 慢可以给 Docker 配置镜像加速器在 /etc/docker/daemon.json 里这样写{ registry-mirrors: [https://your-mirror-address] }改完重启 Dockersudo systemctl restart docker这一步不是必须的但准备一下能明显减少后面拉镜像时等待的时间。镜像本身不大postgres:16 完整版大概 400MB 左右alpine 变体更小但网络通畅的话差别不大建议直接用完整版。3. 镜像选择PostgreSQL 版本别盲目追新3.1 版本号到底有什么区别Docker Hub 上 postgres 仓库有很长的 tag 列表比如 16、16-bookworm、16-alpine、15、14、13 等。这里很多人会纠结到底该选哪个我直接说结论第一次上手选postgres:16就对了。版本号 16 指的是 PostgreSQL 主版本号也是官方目前写这篇内容时的主流稳定版本。主版本决定了数据库内核功能、默认配置以及未来升级路径。从 15 到 16官方优化了并行查询、逻辑复制、vacuum 等方向性能和可用性都有提升而 14、13 属于上一代功能也没问题但新项目没必要选旧版。关于latest这个 tag我的建议是开发环境随便用生产环境尽量别用。latest 会跟随官方推送漂移今天拉的是 16过几个月可能变成 17 甚至 18。这会导致一个非常尴尬的问题你的数据和初始化脚本在 16 下测试没问题等镜像更新了容器重建时数据目录格式可能不兼容迁移成本一下子就上来了。3.2 官方镜像 vs 带扩展的镜像postgres:16是官方自带的通用版本包含标准的 postgresql 服务端、客户端工具链也包含 contrib 模块。但如果你需要 PostGIS地理空间扩展、TimescaleDB时序数据库扩展这类功能就要用对应的衍生镜像了常见的有postgis/postgis:16-3.4、timescale/timescaledb:latest-pg16这种。选择标准并不复杂你先想清楚业务需不需要这些扩展。普通 Web 应用、日志存储、业务系统官方镜像就够了。涉及到地理位置计算、轨迹存储再考虑 PostGIS 镜像做 IoT 数据、指标监控长期存储可以考虑 TimescaleDB 镜像。镜像里带了扩展意味着你不需要自己在容器里编译安装省掉一大批麻烦。3.3 我的选型建议如果你仍然不确定我建议走这条最稳的路径场景推荐镜像理由本地开发 / 学习postgres:16功能全、文档多、踩坑有据可查个人项目生产postgres:16主版本稳定官方持续维护需要 PostGISpostgis/postgis:16-3.4内置常用地理扩展极端资源受限环境postgres:16-alpine镜像更小但底层是 musl libc个别场景可能有兼容差异Alpine 版本我之前用过一段时间小型实验项目没出现问题。但生产环境我还是选了完整版不是因为它不行而是省得哪天真遇到 glibc 兼容问题还要临时换镜像折腾数据。4. 第一次 docker run把核心命令拆到参数级别4.1 一条命令跑起 PostgreSQL 容器环境准备好、镜像选好了下面这条命令就是整个部署的核心docker run -d \ --name postgres \ -e POSTGRES_PASSWORDStrongPassw0rd \ -e POSTGRES_USERappuser \ -e POSTGRES_DBappdb \ -p 5432:5432 \ -v pgdata:/var/lib/postgresql/data \ --restart unless-stopped \ postgres:16拆开看每个参数-d后台运行容器这是部署到服务器上的常用方式不会占住终端。--name postgres容器名称后续 docker stop、docker logs、docker exec 都靠这个名字引用。-e POSTGRES_PASSWORDStrongPassw0rd设置超级用户 postgres 的密码必须设置。官方镜像里如果不指定这个环境变量默认会允许本地 trust 认证即无密码登录这是非常危险的状态。-e POSTGRES_USERappuser额外创建一个普通超级用户方便业务连接。-e POSTGRES_DBappdb同时创建一个默认数据库 appdb。初始化时会以 appuser 为属主自动建好。-p 5432:5432把容器内的 5432 端口映射到宿主机 5432。这样宿主机外部就能通过服务器的 IP 访问了。如果只想本机访问可以改成-p 127.0.0.1:5432:5432。-v pgdata:/var/lib/postgresql/data数据持久化下面单独讲。--restart unless-stopped容器异常退出时自动重启适合部署场景。4.2 为什么必须指定 POSTGRES_PASSWORD这是新手最容易忽略的点。官方 PostgreSQL 镜像有一个默认机制如果不设置 POSTGRES_PASSWORD也没有设置 POSTGRES_HOST_AUTH_METHOD那么容器会启动一个只信任本地连接的数据库并且 postgres 超级用户没有密码。这意味着虽然你从外部连不进来但只要进了这台机器就能随意操作数据库。风险评估一下任何一台暴露到公网、或者机房内网多台机器共用的服务器都不应该出现无密码的数据库实例。设置环境变量的同时我也建议你在初始化和应用配置里避免使用弱密码比如 123456、root 这种数据库这东西最容易出事的不是技术漏洞而是默认配置。4.3 数据卷的三种挂载方式把数据存到容器可写层是绝对禁止的。容器一旦被删除可写层连同数据一起消失到时候哭都来不及。PostgreSQL 官方镜像是把数据目录放在 /var/lib/postgresql/data 的镜像也默认声明了这个目录为数据卷。实际使用中我们一般会显式挂载有三种方式挂载方式示例说明匿名卷docker run -v /var/lib/postgresql/data ...由 Docker 管理路径简单但不直观容器删除后卷还在适合临时实验命名卷docker run -v pgdata:/var/lib/postgresql/data ...推荐docker volume ls 能看见迁移时方便绑定挂载docker run -v /data/postgres:/var/lib/postgresql/data ...宿主机路径直接映射适合需要手动备份或查看数据文件的场景我最推荐的是命名卷对普通开发者来说最省心。Docker 会帮你管理权限、路径和生命周期。如果你是那种习惯直接去数据目录翻文件的 DBA可以用绑定挂载但要注意宿主机目录的属主必须给 PostgreSQL 用户容器内 uid 999否则启动时会报权限错误这个坑下面第六节单独说。4.4 容器启动后的验证容器跑起来后先看状态docker ps再确认日志docker logs postgres看到这一行就说明初始化成功database system is ready to accept connections然后进去用 psql 验证一下docker exec -it postgres psql -U appuser -d appdb能进入 psql 提示符输入\l看到数据库列表整个部署就完成了。连接测试还可以从宿主机直接装 psql 来试或者用任何支持 PostgreSQL 协议的客户端DBeaver、Navicat、DataGrip 都行连接参数填服务器 IP、端口 5432、用户名 appuser、密码你设置的密码。5. 数据持久化、备份和初始化投产前必须搞定的事5.1 时区、编码和应用默认设置PostgreSQL 官方镜像默认时区是 UTC如果你在中国时区数据库里的时间会比本地时间慢 8 小时。最简单的方式是在启动参数里加一个环境变量-e TZAsia/Shanghai另外官方镜像默认编码是 UTF8这对绝大多数现代应用都够了。如果你需要指定 locale 或者 pickle 规则可以用 POSTGRES_INITDB_ARGS-e POSTGRES_INITDB_ARGS--encodingUTF8 --localeen_US.UTF-8 --lc-collateC说实话非特殊情况别改 locale默认 UTF8 已经满足中文和英文场景。很多人遇到中文乱码问题往往不是数据库编码问题而是客户端连接设置了错误的 client_encoding或者建表时指定了不合适的编码。在 PostgreSQL 里数据库、客户端、表、字段的编码要一致才不容易出问题。5.2 初始化 SQL 怎么自动执行部署数据库时常常需要自动创建表结构、初始数据、扩展。官方镜像提供了一个很优雅的机制挂载/docker-entrypoint-initdb.d/目录容器首次启动初始化数据库时会自动执行这个目录下的.sh、.sql、.sql.gz文件按文件名顺序执行。示例在宿主机准备 init.sqlCREATE EXTENSION IF NOT EXISTS pgcrypto; CREATE TABLE IF NOT EXISTS app_users ( id SERIAL PRIMARY KEY, username VARCHAR(64) UNIQUE NOT NULL, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); INSERT INTO app_users (username) VALUES (admin) ON CONFLICT DO NOTHING;启动时挂载进去docker run -d \ --name postgres \ -e POSTGRES_PASSWORDStrongPassw0rd \ -e POSTGRES_USERappuser \ -e POSTGRES_DBappdb \ -p 5432:5432 \ -v pgdata:/var/lib/postgresql/data \ -v $PWD/init.sql:/docker-entrypoint-initdb.d/init.sql:ro \ --restart unless-stopped \ postgres:16注意这个机制只在数据目录为空、容器首次初始化时生效如果数据卷已经有数据了文件不会再次执行。需要改变表结构时请通过 psql 手动执行或引入迁移工具比如 Flyway、Alembic、Golang Migrate。5.3 备份与恢复务必在容器外操作容器化部署的备份方式和我之前单机部署时有点不一样。单机可以直接用文件系统工具备份整个数据目录但在 Docker 环境下我更推荐用 PostgreSQL 自带逻辑备份工具 pg_dump 和 pg_restore因为它们不关心底层存储是不是卷只要容器还能起来就能备份。全库备份docker exec -it postgres pg_dump -U appuser -d appdb -Fc -f /tmp/appdb.dump docker cp postgres:/tmp/appdb.dump ./appdb.dump恢复cat appdb.dump | docker exec -i postgres pg_restore -U appuser -d appdb --clean --if-exists多说一句这个 -Fc 参数是自定义压缩格式配合 pg_restore 可以按表恢复比纯 SQL 导出灵活。生产环境如果数据量大可以考虑物理备份工具pg_basebackup 或者卷快照逻辑备份适合日常归档和跨版本迁移。5.4 数据文件本身的迁移技巧如果你要从一台旧服务器迁到新服务器最省事的方式是直接把整个数据卷带走。命名卷的情况下可以用 Docker 的卷迁移方式docker run --rm -v pgdata:/from -v $(pwd):/to alpine tar czf /to/pgdata.tar.gz -C /from .然后到新服务器上恢复docker run --rm -v pgdata:/to -v $(pwd):/from alpine tar xzf /from/pgdata.tar.gz -C /to再把容器启动起来。要注意的是跨主版本迁移比如从 15 迁到 16不建议直接带数据目录因为系统表格式可能不兼容。稳妥的方式是旧库 pg_dump、新库 pg_restore或者用官方自带的 pg_upgrade 工具。这个坑我踩过直接换了镜像 tag 挂旧卷启动时看到 “incompatible” 的 FATAL 报错最后老老实实导数据。6. 日常维护与排错那些最常见的 permission 和端口问题6.1 连接不上 Docker daemon 的问题前面说了用户组的问题实际工作中还有一种场景docker 命令能执行但容器起不来报 permission denied这种时候要分清楚是 daemon 权限还是容器内权限。如果是docker: permission denied while trying to connect to the Docker daemon socket是用户组问题用 2.2 的步骤解决。如果容器启动日志里出现permission denied比如/var/lib/postgresql/data目录无法写入那是宿主机挂载目录权限问题。后一种情况典型的日志是FATAL: data directory /var/lib/postgresql/data has invalid permissions这是绑定挂载时最常见的坑。宿主机目录默认属主是 rootuid 0而容器内 postgres 用户 uid 是 999两者对不上进程自然写不了数据。解决办法sudo mkdir -p /data/postgres sudo chown -R 999:999 /data/postgres999 是官方 postgres 镜像里 postgres 用户的 uid这是写死的不是随便选的数字。你用 ls -n 看一下就明白了。生产环境里我用绑定挂载不多基本都用命名卷就是为了避开这类权限问题但一旦用了必须注意属主。6.2 端口被占用与容器启动失败启动容器时报docker: Error response from daemon: driver failed programming external connectivity on endpoint postgres: Error starting userland proxy: listen tcp4 0.0.0.0:5432: bind: address already in use说明宿主机 5432 端口已经被其他进程占用了。常见原因之前单机版 PostgreSQL 还在运行或者装了别的服务占用 5432。排查方式ss -ltnp | grep 5432要么停掉旧服务要么换个宿主端口映射比如-p 5433:5432。如果在服务器上排查时没有权限看进程名可以用 lsof -i :5432 类似的命令实在不行换端口是最快的方案。6.3 容器起不来时先看日志容器化部署遇到任何异常第一件事永远是看日志docker logs --tail 100 postgres我见过不少人容器起不来先把容器删了再创建结果又遇到同样的报错来回折腾好几轮。其实日志里一般写得明明白白如果看到FATAL: password authentication failed for user appuser说明连接时用户或密码错了。如果看到FATAL: database appdb does not exist说明初始化时 POSTGRES_DB 没设置或设置错了。如果看到PANIC: could not open file pg_log/...大概率是数据目录权限或磁盘满了。先看日志再动手能省掉大量无意义的排错时间。这也是我平时处理任何容器问题时的第一习惯。6.4 容器重启策略和资源控制部署环境里我建议给容器加上--restart unless-stopped或--restart always。区别在于unless-stopped容器异常退出时自动重启但手动 stop 后不会自动重启。always只要 Docker daemon 没停容器退出就会重启手动 stop 后重启 Docker 服务时也会启动。生产环境我个人偏向unless-stopped因为你可以手动停容器做维护而不用担心下一秒被自动拉起。但如果你在配合编排系统管理这个策略可能不太重要Compose 或 K8s 有自己的策略这个看团队情况。资源限制也值得关注。数据库是吃内存的如果宿主机上还有别的容器不给 PostgreSQL 限制内存很可能把整台机器 OOM。典型配置--memory2g --cpus2对应到 docker-compose 里就是deploy: resources: limits: memory: 2g cpus: 2还有一点是关于 PG 参数。容器里改 shared_buffers 等参数可以挂载自定义 postgresql.conf或者在启动时用-c shared_buffers256MB传参但别盲目把宿主机内存全分配给 PG留下系统和其他服务所需的内存更稳妥。7. 从本地验证到团队化用 docker-compose 把配置固化7.1 一份标准 compose 文件长什么样单条 docker run 适合快速验证但如果你想把配置固化下来、提交到代码库、让团队所有人一键部署docker-compose 是下一步。我的一份标准配置大概是这样的services: postgres: image: postgres:16 container_name: postgres environment: POSTGRES_USER: appuser POSTGRES_PASSWORD: StrongPassw0rd POSTGRES_DB: appdb TZ: Asia/Shanghai ports: - 5432:5432 volumes: - pgdata:/var/lib/postgresql/data - ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro healthcheck: test: [CMD-SHELL, pg_isready -U appuser -d appdb] interval: 10s timeout: 5s retries: 5 restart: unless-stopped volumes: pgdata:启动方式非常简单docker compose up -d停掉并保留数据docker compose down彻底清掉容器和数据卷docker compose down -v。这条命令要慎用-v 会连数据卷一起删一旦执行数据就没了。我在本地经常用但生产环境几乎不碰。7.2 健康检查为什么值得写compose 文件里我特意加了 healthcheck 部分这段配置会周期性在容器内执行 pg_isready 命令判断数据库是否就绪。这对编排依赖很有用。比如后端服务要等数据库就绪再启动compose 里可以用 depends_on 配合 conditionservices: app: image: myapp:latest depends_on: postgres: condition: service_healthy没有健康检查的话depends_on 只能保证容器启动了但数据库未必已经接受连接应用启动时可能会报连接失败然后立即退出。有了健康检查编排系统会等数据库真正 ready 再启动下游服务。7.3 更完整的部署形态网络、端口和明文密码处理最后说两个规范化部署的细节。第一网络模式。默认情况下 compose 会创建一个自定义 bridge 网络同一个 compose 文件里的服务可以通过服务名互相访问。比如应用容器里数据库地址直接写postgres:5432就行不用关心 IP。如果你通过network_mode: host把数据库暴露到宿主机网络那就要小心端口冲突和访问控制问题。个人项目图省事没问题团队项目建议保持默认的自定义网络。第二环境变量管理。现在很多团队会用.env文件配合 composeservices: postgres: environment: POSTGRES_USER: ${POSTGRES_USER:-appuser} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?err}在项目根目录放.envPOSTGRES_USERappuser POSTGRES_PASSWORDStrongPassw0rd POSTGRES_DBappdb这样密码不会混在 compose 文件里提交到 Git。如果你用 Kubernetes 或 Docker Swarm还可以用 Secret 机制托管密码这里就不展开了但这个思路是一致的敏感信息要和配置文件分离。7.4 这段路线的下一步扩展到这里用 Docker 部署 PostgreSQL 的基本链路已经完整了装 Docker、选镜像、跑容器、管数据、排错、规范化。往上走的话大概有这么几个自然延伸的方向用 PGAdmin 或 DBeaver 做图形化管理适合不习惯命令行的同事。用 pgvector 镜像做向量检索配合大语言模型应用做知识库这也是最近很火的玩法。用 Patroni etcd 搭 PostgreSQL 高可用集群这是生产级多机部署的话题。把当前这套 compose 文件嵌入 CI/CD 流水线自动化测试环境里跑集成测试。这些方向我在实际项目中都接触过后面有机会再分别写开。最后分享一个小技巧吧。我习惯给所有 PostgreSQL 容器统一使用命名卷并在卷名前加项目前缀比如myapp_pgdata这样一台机器上即使跑多个项目的数据库也不会混淆。每次重建容器前我会先docker exec手动做一次 pg_dump再把旧的容器 stop 掉最后用同一份 compose 配置拉起新容器。这套流程走了很多次目前没有一次因为操作失误丢过数据。数据库这个东西稳定和可恢复永远比花哨的功能更重要。