PostgreSQL 工程化部署深度解析

📖 本文档定位:本文是 PostgreSQL数据库部署安装流程.md 的配套解析文档。部署流程文档回答「怎么做」,本文档回答「为什么这么做、每条命令的原理是什么、不这么做会怎样」。

每一条命令、每一个配置字段都会被逐行拆解,力求不遗漏、不遗留。


目录


一、整体架构与部署全景

1.1 部署架构图

远程 Windows 客户端

Ubuntu 22.04 宿主机

Docker Engine

/opt/postgresql/data

TCP 5432
md5认证

数据读写

docker compose up -d 读取

volumes 挂载

/opt/postgresql/compose

docker-compose.yml
部署配置文件

PG数据文件
postgresql.conf
pg_hba.conf
PG_VERSION

postgres_db 容器

test01.py
psycopg2

1.2 部署流程总览

1.配置需求

2.流程简述

3.验证环节
Docker/镜像源

4.具体流程
compose部署

5.常用管理命令

6.验证连接
本地+Python

7.备份与恢复

8.监控与告警

9.故障排查

10.升级与回滚

1.3 核心设计理念

设计理念在本部署中的体现
声明式配置用 docker-compose.yml 描述「要什么」,而不是用 docker run 描述「怎么做」
数据与计算分离数据落宿主机 /opt/postgresql/data,容器可随时销毁重建,数据不丢
最小权限容器内以非 root 用户(UID 999)运行 PostgreSQL
可观测性配置 healthcheck + logging,容器假活可被发现,日志不会撑满磁盘
可恢复性提供 pg_dumpall 备份 + 定时任务 + 恢复命令的完整闭环

二、第一章「配置需求」解析

2.1 系统要求

- 一台装了 Docker 的服务器
- 该服务器能与本地 win 电脑 ping 通
- 操作系统:Ubuntu 22.04(推荐)

逐条解析:

要求原理
装了 Docker整个部署基于容器化,Docker Engine 是前提。Docker Compose 是 Docker 的子命令(docker compose),V2 已内置,无需单独安装
能 ping 通后续第六章要用 Python 从 Windows 远程连接 PostgreSQL。如果网络不通,所有远程验证都会失败。这一条把网络问题前置排除
Ubuntu 22.04LTS 长期支持版,内核 5.15+,对 Docker 和 cgroup v2 支持完善。非强制,CentOS/Rocky 也可,但命令细节(包管理器、防火墙命令)会不同

2.2 资源需求

- PostgreSQL 15.4 镜像(Docker官方仓库)
- 国内的能用来拉取镜像的docker镜像源

为什么需要国内镜像源:

Docker Hub 官方仓库服务器在海外,国内直连拉取速度极慢甚至超时。需要配置 daemon.json 中的 registry-mirrors 指向国内加速节点(如 DaoCloud、1ms.run 等),第三章会详细讲配置方法。

为什么选 PostgreSQL 15.4:

  • PostgreSQL 15 是稳定的大版本(2022 年 10 月发布)
  • .4 是该大版本的小版本补丁,修复了已知安全与稳定性问题
  • 指定精确版本号(而非 latest)是工程化最佳实践:避免未来 latest 指向新大版本导致不兼容

2.3 预备配置参数

参数值解析
端口5432PostgreSQL 官方默认端口,IANA 注册的标准端口
用户名postgresPostgreSQL 镜像约定的超级用户名,镜像初始化时自动创建
密码postgres教程示例值。生产环境必须替换为强密码
默认数据库postgres镜像初始化时自动创建的默认库,POSTGRES_DB 环境变量控制

⚠️ 安全提醒:postgres/postgres 是公开的示例凭证。生产环境中,任何人知道你的 IP 就能用这个密码登录你的数据库。务必修改。


三、第三章「验证环节」逐条解析

本章的作用是前置排障:在正式部署前,先确认 Docker 环境和镜像拉取能力正常,避免部署到一半才发现环境问题。

3.1 验证 Docker 环境

命令 1:检查 Docker 是否安装
sudo docker --version
片段作用
sudo以 root 权限执行。Docker 守护进程监听 /var/run/docker.sock,默认只有 root 和 docker 组用户可访问
dockerDocker CLI 客户端
--version打印 Docker 版本号并退出(不连接守护进程)

预期输出: Docker version 24.x.x, build xxxxxxx

如果失败: 说明 Docker 未安装,需先执行 curl -fsSL https://get.docker.com | sh 安装。

命令 2:检查 Docker 服务是否运行
sudo systemctl status docker
片段作用
systemctlSystemd 服务管理工具(Ubuntu 22.04 默认使用 systemd)
status查看服务运行状态
docker服务名

预期输出: 绿色 active (running)

如果显示 inactive: 执行 sudo systemctl start docker 启动。

3.2 验证镜像源是否能进行拉取镜像

sudo docker pull hello-world
片段作用
docker pull从 registry 拉取镜像
hello-worldDocker 官方提供的最小测试镜像(约 13KB),仅打印一段欢迎信息

原理:

用最小镜像测试网络通路,而不是直接拉 PostgreSQL(约 400MB)。如果 hello-world 都拉不下来,说明镜像源有问题,拉大镜像必然超时。

如果失败(超时/报错):

需要配置国内镜像源加速,见下方步骤。

3.3 配置 Docker 镜像源

命令 1:创建配置目录
sudo mkdir -p /etc/docker
片段作用
mkdir -p-p 表示递归创建,目录已存在时不报错(幂等操作)
/etc/dockerDocker 约定的配置目录,daemon.json 必须放在这里
命令 2:编辑配置文件
sudo vim /etc/docker/daemon.json
片段作用
vimVim 文本编辑器
/etc/docker/daemon.jsonDocker 守护进程的配置文件

配置文件内容:

{
  "registry-mirrors": [
    "https://docker.m.daocloud.io",
    "https://docker.1ms.run",
    "https://docker.anyhub.us.kg"
  ]
}
字段作用
registry-mirrors镜像加速地址列表。Docker 拉取镜像时会依次尝试列表中的地址,第一个成功即停止

原理:

Docker 默认从 registry-1.docker.io 拉取镜像。配置 registry-mirrors 后,Docker 会优先通过镜像加速节点拉取,加速节点本质是 Docker Hub 的反向代理/缓存。

命令 3:重新加载 systemd 配置
sudo systemctl daemon-reload

原理:

daemon-reload 让 systemd 重新读取所有 service 文件。修改了 Docker 的配置后需要这一步,让 systemd 感知到 Docker 服务配置的变化。

命令 4:重启 Docker
sudo systemctl restart docker

原理:

Docker 守护进程在启动时才会读取 daemon.json。修改配置文件后必须重启守护进程才能生效。

重启后: 重新执行 sudo docker pull hello-world 验证镜像源是否生效。


四、第四章「具体流程」逐条深度解析

本章是整个部署的核心。从拉取镜像到配置远程访问,每一步都关系到部署能否成功。

4.1 拉取 PostgreSQL 镜像

sudo docker pull postgres:15.4
片段作用
docker pull从 registry 拉取镜像到本地
postgres镜像名(官方 PostgreSQL 镜像)
:15.4版本标签。精确指定大版本 15 + 小版本 4

为什么不用 latest:

latest 是一个浮动的指针——今天指向 15.4,明天可能指向 16.0。生产环境用 latest 会导致:

  • 不可复现:不同时间拉取的镜像版本不同
  • 意外升级:新大版本可能不兼容旧数据格式
  • 无法回滚:不知道之前用的是什么版本

镜像分层原理:

postgres:15.4 镜像

基础层: debian-bullseye-slim

运行时层: 安装 PostgreSQL 15.4 二进制

入口层: docker-entrypoint.sh

配置层: postgresql.conf 模板
pg_hba.conf 模板

镜像内的 docker-entrypoint.sh 是关键——它在容器首次启动时执行 initdb 初始化数据目录,并根据环境变量(POSTGRES_USER、POSTGRES_PASSWORD 等)自动创建用户和数据库。

4.2 准备部署目录

sudo mkdir -p /opt/postgresql/compose
cd /opt/postgresql/compose

逐行解析:

命令作用
sudo mkdir -p /opt/postgresql/compose在 /opt/ 下创建 postgresql/compose 目录。-p 递归创建父目录,已存在不报错
cd /opt/postgresql/compose切换工作目录到 compose 目录

为什么用 /opt/ 目录:

Linux 文件系统层次标准(FHS)规定:

  • /opt/ —— 第三方独立软件/大型应用的安装目录
  • /var/ —— 系统运行时可变数据(日志、缓存等)
  • /usr/ —— 系统自带软件包

PostgreSQL 作为独立的第三方生产数据库,放在 /opt/ 语义最合适,便于备份、迁移、识别。

为什么需要专门的 compose 目录:

Docker Compose 的工作方式是:在哪个目录执行 docker compose 命令,就以当前目录的 docker-compose.yml 为部署描述文件。如果没有固定目录:

  • 每次执行命令都要带 -f /path/to/docker-compose.yml,容易写错路径
  • 文档和脚本无法统一
  • 团队协作时找不到配置文件

4.3 创建数据持久化目录并授权

# 创建数据目录
sudo mkdir -p /opt/postgresql/data
# 授权给容器内的 postgres 用户(UID/GID 均为 999)
sudo chown -R 999:999 /opt/postgresql/data

这两行命令分别解决两个完全不同的问题:

① mkdir —— 数据持久化(容器与数据分离)

核心原理:Docker 容器是「无状态、可丢弃」的——容器一删,里面的数据全部消失。

宿主机(持久存储)

postgres_db 容器(可随时销毁)

volumes 挂载
读写都落这里

PostgreSQL 进程

/opt/postgresql/data/
所有表数据、索引、WAL日志

compose 文件中的挂载配置:

volumes:
  - /opt/postgresql/data:/var/lib/postgresql/data
路径含义
/opt/postgresql/data(冒号左)宿主机路径
/var/lib/postgresql/data(冒号右)容器内 PostgreSQL 的标准数据目录(PGDATA)

效果: 容器内 PG 进程读写的数据,实际上落在宿主机的 /opt/postgresql/data 下。即使容器被 docker compose down 删除,数据依然完整保留。重新 docker compose up -d 挂载同一目录即可恢复。

② chown —— 解决容器内外 UID 不一致问题

核心原理:容器内外的「用户身份」是隔离的,但文件权限是共享的。

角色UID:GID说明
宿主机 sudo mkdir 创建的目录0:0(root)sudo 意味着以 root 执行,创建的目录属主是 root
容器内 PostgreSQL 进程999:999官方镜像安全最佳实践:不以 root 运行,用专用用户 postgres(UID 999)

冲突场景:

目录属主是 root:root

目录属主是 999:999

容器启动

PG 进程以 UID 999
尝试写入数据目录

权限不足
Permission denied

容器退出
Exited (1)

写入成功
数据库正常启动

chown -R 999:999 把目录属主改为 999,使容器内 postgres 用户有写权限。

参数作用
chownchange owner,修改文件/目录属主
-R递归处理,包括所有子目录和文件
999:999UID:GID。这里 999 不是随便写的,是官方 postgres 镜像内部 postgres 用户的固定 UID

常见官方镜像的固定 UID 速查:

镜像内部用户UID
postgrespostgres999
mysqlmysql999
redisredis999
nginxnginx101
elasticsearchelasticsearch1000

不执行 chown 会怎样: 容器启动后立即退出,日志报 initdb: error: could not access directory "/var/lib/postgresql/data": Permission denied。这是新手最常踩的坑。

4.4 编写 docker-compose.yml

这是整个部署的「单一事实源」(Single Source of Truth)。所有运行时参数都声明在这个文件里。

# /opt/postgresql/compose/docker-compose.yml
services:
  postgres:
    image: postgres:15.4
    container_name: postgres_db
    restart: unless-stopped
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: postgres
      TZ: Asia/Shanghai
    ports:
      - "5432:5432"
    volumes:
      - /opt/postgresql/data:/var/lib/postgresql/data
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 2G
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 30s
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

逐字段深度解析:

services:

Compose 文件的顶级键。下面定义所有要启动的服务。本例只有一个 postgres 服务。

postgres:

服务名(自定义)。后续 docker compose logs postgres、docker compose exec postgres bash 都用这个名字引用服务。

image: postgres:15.4
字段作用
image指定使用哪个镜像
postgres:15.4镜像名:标签。如果本地没有,Compose 会自动拉取
container_name: postgres_db
字段作用
container_name固定容器名

为什么固定名字: 不指定时 Docker 会自动生成 compose目录名-服务名-序号 格式的名字(如 compose-postgres-1),难以记忆和引用。固定为 postgres_db 后,所有 docker exec、docker logs 命令都可以直接用这个名字。

restart: unless-stopped
值行为
no容器退出后不重启(默认)
always总是重启,即使手动 docker stop 也会在 Docker 重启时被拉起
unless-stopped宕机/异常退出自动重启;但手动 docker stop 后不会被拉起
on-failure仅在非零退出码时重启

选 unless-stopped 的原因: 既保证数据库崩溃后自动恢复,又不会在你主动维护(手动 stop)时被意外拉起。

environment:

环境变量注入到容器内。PostgreSQL 官方镜像约定了一系列 POSTGRES_* 环境变量,在首次启动(数据目录为空)时由 docker-entrypoint.sh 读取并执行初始化:

变量作用
POSTGRES_USER: postgres创建超级用户,用户名为 postgres
POSTGRES_PASSWORD: postgres设置该用户的密码
POSTGRES_DB: postgres自动创建名为 postgres 的默认数据库
TZ: Asia/Shanghai设置容器时区,影响 PostgreSQL 的时间函数(now()、current_timestamp)

⚠️ 重要:这些环境变量仅在数据目录为空时生效。如果数据目录已有数据(非首次启动),修改这些变量不会改变已有的用户/密码/库。

ports:
ports:
  - "5432:5432"
片段含义
左边 5432宿主机端口
右边 5432容器内端口
"5432:5432"将宿主机 5432 映射到容器内 5432

原理: 容器有独立的网络命名空间,外部无法直接访问容器内端口。ports 在宿主机上创建一个端口转发规则,把宿主机的 5432 流量转发到容器内的 5432。

变体: 如改为 "127.0.0.1:5432:5432",则只有宿主机本机能连,远程无法连接。适合「仅本机应用访问数据库」的场景。

volumes:
volumes:
  - /opt/postgresql/data:/var/lib/postgresql/data
片段含义
左边宿主机路径(绑定挂载,bind mount)
右边容器内路径
:分隔符

已在 4.3 详解,此处不重复。

deploy:
deploy:
  resources:
    limits:
      cpus: '2.0'
      memory: 2G
字段作用
limits.cpus: '2.0'限制容器最多使用 2 个 CPU 核心
limits.memory: 2G限制容器最多使用 2GB 内存

原理: 通过 Linux cgroup(控制组)机制限制容器的资源使用上限。防止数据库因异常查询(如全表扫描)吃光宿主机内存,导致其他服务被 OOM Killer 杀掉。

为什么是 2G: PostgreSQL 的 shared_buffers 默认 128MB,加上连接池、排序、临时表等,2G 对于中小规模应用足够。生产环境需根据实际数据量和并发量调整。

healthcheck:
healthcheck:
  test: ["CMD-SHELL", "pg_isready -U postgres"]
  interval: 30s
  timeout: 5s
  retries: 3
  start_period: 30s
字段作用
test健康检查命令。pg_isready 是 PostgreSQL 自带的就绪检测工具,检查服务器是否可接受连接
interval: 30s每 30 秒检查一次
timeout: 5s单次检查超时时间。超过 5 秒未响应视为失败
retries: 3连续 3 次失败后标记为 unhealthy
start_period: 30s启动后 30 秒内的失败不计入 retries(给数据库初始化留缓冲时间)

原理与作用:

docker compose up

pg_isready 返回 0

start_period 后仍失败

连续 3 次检查失败

检查恢复成功

docker compose down

starting

healthy

unhealthy

没有 healthcheck 会怎样: Docker 只能判断进程是否存活(Up),但无法判断数据库是否真正可用。进程活着但数据库还在做 crash recovery 时,连接会被拒绝——这就是「假活」。healthcheck 解决了这个问题。

logging:
logging:
  driver: json-file
  options:
    max-size: "10m"
    max-file: "3"
字段作用
driver: json-file日志以 JSON 格式写入文件(Docker 默认驱动)
max-size: "10m"单个日志文件最大 10MB
max-file: "3"最多保留 3 个日志文件

原理: Docker 默认会无限增长容器日志(存在 /var/lib/docker/containers/<id>/<id>-json.log),不做轮转最终会撑满磁盘。配置后,日志达到 10MB 会自动轮转:json.log → json.log.1 → json.log.2 → 删除最旧的。

总日志上限: 10MB × 3 = 30MB,不会失控。

4.5 启动服务

# 后台启动
sudo docker compose up -d

# 查看状态(等待片刻后 STATUS 应显示 healthy)
sudo docker compose ps

# 查看实时日志
sudo docker compose logs -f postgres
命令 1:docker compose up -d
片段作用
docker composeCompose V2 语法(作为 Docker 的子命令)
up根据当前目录的 docker-compose.yml 创建并启动容器
-ddetach,后台运行,不占用当前终端

执行流程:

否

是

是

否

docker compose up -d

镜像是否在本地?

自动 docker pull

创建容器

挂载 volumes

设置端口映射

启动容器进程

数据目录是否为空?

执行 initdb 初始化
创建用户/库/配置文件

跳过初始化
直接启动 PostgreSQL

PostgreSQL 开始接受连接

幂等性: docker compose up -d 是幂等的——重复执行不会创建多个容器。如果配置没变,它什么也不做;如果修改了 compose 文件,它会重建受影响的容器。

命令 2:docker compose ps
片段作用
ps列出当前 compose 项目中所有服务的运行状态

重点看 STATUS 列:

状态值含义是否可继续
Up容器已启动,但 healthcheck 尚未完成或未配置等待
Up (healthy)健康检查通过,数据库可接受连接✅ 可继续
Up (unhealthy)健康检查连续失败❌ 需排查
Exited (1)启动失败(常见原因:权限问题、端口占用)❌ 需排查
Restarting反复重启中❌ 需排查
命令 3:docker compose logs -f postgres
片段作用
logs查看服务日志
-ffollow,持续跟踪新输出(类似 tail -f)
postgres服务名(compose 文件中 services: 下的 key)

首次启动的正常日志关键行:

database system was shut down at ...      # 正常
database system is ready to accept connections   # ← 这行出现说明启动成功

4.6 验证数据库连接

sudo docker exec -it postgres_db psql -U postgres

逐参数解析:

片段作用
sudosuperuser do,临时以 root 权限执行这一条命令。普通用户访问 Docker 守护进程的 socket 文件需要 root 权限
docker exec在运行中的容器内执行一条命令(区别于 docker run 启动新容器)
-i--interactive,保持标准输入(STDIN)打开,让 psql 能接收键盘输入
-t--tty,分配伪终端(pseudo-TTY),让交互体验类似 SSH
postgres_db目标容器名(来自 compose 的 container_name)
psqlPostgreSQL 命令行客户端
-U postgresuser,以 postgres 这个用户身份登录数据库

💡 关于 sudo:已经进入 root 还需要加吗?

不需要。 文档中统一写了 sudo 是为了兼容「以普通用户登录服务器」的情况。实际上:

场景提示符是否需要 sudo
以普通用户登录(如 aw@ubuntu:~$)$需要,执行 root 权限操作时必须加 sudo
已通过 sudo -i 或 su - 切到 root(如 root@ubuntu:~#)#不需要,你已经是 root,加了等于没加

判断方法:看终端提示符末尾——$ 是普通用户(要加 sudo),# 是 root(不用加)。

本文档所有命令前的 sudo,如果你已经切到 root,直接去掉即可。

-it 的作用:

-i 和 -t 是「进容器做交互操作」的标配组合。不加的话,psql 启动后立刻退出(因为没有终端、没有输入源),根本无法交互。加上后才能看到 psql 的交互提示符 postgres=# 并输入 SQL。

进入 psql 后的命令:

命令作用
\llist,列出所有数据库
\du列出所有用户/角色
\qquit,退出 psql

为什么先在容器内验证:

容器内验证是「零依赖」测试——不依赖网络、IP、防火墙、pg_hba.conf。只要这一步通过,就证明数据库本身完全正常。之后任何「连不上」的问题都可以缩小到网络层或配置层,排查效率大幅提升。

通过

失败

失败

4.6 容器内验证

数据库本身 OK

数据库有问题
检查日志/权限/镜像

4.7 远程访问配置

远程连接验证

问题在网络层或配置层
排查 pg_hba/listen/防火墙

4.7 配置远程访问

PostgreSQL 出于安全考虑,默认只允许本机连接。要让远程客户端连进来,必须改两个配置。

# 1) 让 PostgreSQL 监听所有网卡
sudo docker exec -it postgres_db bash -c "sed -i \"s/^#listen_addresses = 'localhost'/listen_addresses = '*'/\" /var/lib/postgresql/data/postgresql.conf"

# 2) 放行远程访问(示例:放行 192.168.1.0/24 网段)
sudo docker exec -it postgres_db bash -c "echo \"host all all 192.168.1.0/24 md5\" >> /var/lib/postgresql/data/pg_hba.conf"

# 3) 重启容器使配置生效
sudo docker compose restart
命令 1:修改 listen_addresses(监听地址)
sudo docker exec -it postgres_db bash -c \
  "sed -i \"s/^#listen_addresses = 'localhost'/listen_addresses = '*'/\" /var/lib/postgresql/data/postgresql.conf"

拆解:

片段作用
docker exec -it postgres_db bash -c "..."在容器内执行 bash 命令
sed -istream editor,-i 表示 in-place 直接修改文件
s/旧文本/新文本/替换操作
^#listen_addresses = 'localhost'匹配以 # 开头的注释行(PostgreSQL 默认把 listen_addresses 注释掉)
listen_addresses = '*'替换为监听所有网卡

listen_addresses 的作用:

PostgreSQL 进程监听 TCP 套接字,listen_addresses 决定它绑定在哪些网络接口(IP)上:

值含义效果
'localhost'仅绑定 127.0.0.1只有容器内进程能连
'*'绑定所有可用 IP外部网络可达
'192.168.1.10'只绑定指定 IP精细控制

为什么是注释行: postgresql.conf 中 listen_addresses 默认被 # 注释掉(表示用默认值 'localhost')。用 sed 把 # 去掉并改值,是最小修改方式。

为什么不用环境变量改: PostgreSQL 镜像的 POSTGRES_* 环境变量只控制初始化的用户/密码/库,不控制监听地址。监听地址必须改配置文件。

命令 2:修改 pg_hba.conf(访问控制白名单)
sudo docker exec -it postgres_db bash -c \
  "echo \"host all all 192.168.1.0/24 md5\" >> /var/lib/postgresql/data/pg_hba.conf"

拆解:

片段作用
echo "..." >>追加一行到文件末尾
>>追加(不覆盖原内容)
host all all 192.168.1.0/24 md5pg_hba.conf 规则行

规则行各字段含义:

host  all  all  192.168.1.0/24  md5
 │     │    │   │               │
 │     │    │   │               └─ 认证方式
 │     │    │   └───────────────── 来源 IP 段
 │     │    └───────────────────── 数据库用户(all = 所有用户)
 │     └────────────────────────── 目标数据库(all = 所有库)
 └──────────────────────────────── 连接类型(host = TCP/IP 远程)
认证方式含义
md5密码认证(MD5 加密传输)
scram-sha-256更安全的密码认证(SCRAM 协议,推荐生产使用)
trust无需密码直接通过(极度危险)
peer仅限本机,操作系统用户与数据库用户一致时通过

为什么用 >> 追加而不是覆盖: pg_hba.conf 已有 localhost 的规则,覆盖会丢失本机连接能力。追加一行新规则在文件末尾,PostgreSQL 自上而下匹配,新规则优先生效。

⚠️ 注意:echo >> 是非幂等操作——每次执行都会追加一行。多次执行会产生重复行。工程化更好的做法是用 compose 挂载自定义 pg_hba.conf 文件,但教程为简化流程使用了 echo 追加。

命令 3:重启容器
sudo docker compose restart

原理: postgresql.conf 和 pg_hba.conf 不支持热加载,必须重启 PostgreSQL 进程才能重新读取。docker compose restart 等价于「先 stop 再 start」——容器内的 PG 进程重启,但容器本身和数据不动。

重启后数据会丢吗: 不会。重启只是重启进程,数据目录里的 PG_VERSION 等标识文件还在,PG 不会重新 initdb。这正是 4.3 数据持久化的价值。

两个配置的关系图

1. TCP 连接 5432

2. 连接到达 PG

3. 匹配规则

是

否

md5

trust

密码正确

密码错误

远程客户端
192.168.1.100

listen_addresses = '*'
PostgreSQL 监听所有网卡

pg_hba.conf

来源 IP 在白名单中?

4. 认证方式

拒绝连接

要求输入密码

直接通过


五、第五章「常用管理命令」逐条解析

以下命令均在 /opt/postgresql/compose/ 目录下执行。

5.1 查看容器状态

sudo docker compose ps
片段作用
compose ps列出当前 compose 项目中所有服务的容器状态

输出重点: NAME、STATUS(含 healthcheck 结果)、PORTS。

5.2 查看实时日志

sudo docker compose logs -f postgres
片段作用
logs查看日志输出
-ffollow,持续跟踪新输出
postgres服务名

5.3 停止服务

sudo docker compose stop

作用: 停止容器进程,但不删除容器。容器状态变为 Exited,数据完整保留。

与 down 的区别: stop 只是停下,down 会停止并删除容器(数据卷保留)。

5.4 启动服务

sudo docker compose start

作用: 启动已停止的容器。与 stop 配对使用。

5.5 重启服务

sudo docker compose restart

作用: 先 stop 再 start。用于修改配置文件后使其生效。

5.6 进入容器内的 psql

sudo docker exec -it postgres_db psql -U postgres

已在 4.6 详解,此处不重复。

5.7 进入容器 shell

sudo docker exec -it postgres_db bash
片段作用
bash在容器内启动 bash shell

与 5.6 的区别: 5.6 直接进入 psql(数据库客户端),5.7 进入容器的操作系统 shell,可以执行任意 Linux 命令(ls、cat、ps 等),用于深度排查。

5.8 停止并删除容器

sudo docker compose down

作用: 停止并删除容器、网络。数据目录 /opt/postgresql/data 保留,不会丢数据。

什么时候用:

  • 升级镜像版本前
  • 完全重建容器
  • 清理环境

5.9 修改 compose 文件后重新生效

sudo docker compose up -d

作用: Compose 会对比当前运行状态与 compose 文件的差异,只重建有变化的部分。如果配置没变,什么也不做。

stop / start / restart / down / up 的区别总览:

数据影响

stop/start/restart/down/up

数据目录 /opt/postgresql/data

容器生命周期

stop

start

restart

up -d

down

up -d

容器存在且运行

容器存在但停止

容器被删除

重新创建并启动

命令容器数据
stop停止,不删除保留
start启动已停止的保留
restart重启进程保留
down停止并删除保留(在宿主机目录)
up -d创建并启动保留

六、第六章「验证连接」逐条解析

6.1 服务器本地验证

sudo docker exec -it postgres_db psql -U postgres

已在 4.6 详解。 这一步是「容器内零依赖验证」,确认数据库本身可用。

6.2 Windows 本地连接(Python 测试)

安装依赖
pip install psycopg2-binary
片段作用
pipPython 包管理器
install安装包
psycopg2-binaryPostgreSQL 的 Python 驱动(预编译版本,无需编译 C 扩展)

为什么用 psycopg2-binary 而非 psycopg2: psycopg2 需要在安装时编译 C 代码,依赖 pg_config 和编译器。psycopg2-binary 提供预编译的 wheel 包,直接安装即可,适合开发和快速验证。

Python 测试脚本
# test01.py

import psycopg2

try:
    conn = psycopg2.connect(
        host="192.168.1.128",
        port="5432",
        database="postgres",
        user="postgres",
        password="postgres"
    )
    print("连接成功!")
    cursor = conn.cursor()
    cursor.execute("SELECT version();")
    print("PostgreSQL 版本:", cursor.fetchone())
    cursor.close()
    conn.close()
except Exception as e:
    print("连接失败:", e)

逐行解析:

行代码作用
1# test01.py文件注释(脚本名)
3import psycopg2导入 PostgreSQL 驱动
5try:开始异常捕获块。网络连接可能失败,必须用 try-except
6conn = psycopg2.connect(...)建立到 PostgreSQL 的 TCP 连接
7host="192.168.1.128"服务器 IP。⚠️ 需替换为你的实际 IP
8port="5432"端口,与 compose 中 ports 映射一致
9database="postgres"连接的数据库名
10user="postgres"用户名
11password="postgres"密码
13print("连接成功!")连接成功提示
14cursor = conn.cursor()创建游标对象,用于执行 SQL
15cursor.execute("SELECT version();")执行 SQL 查询,获取 PostgreSQL 版本
16print(... cursor.fetchone())fetchone() 获取第一行结果并打印
17cursor.close()关闭游标,释放资源
18conn.close()关闭连接,释放资源
19-20except Exception as e:捕获所有异常
20print("连接失败:", e)打印错误信息

连接验证流程:

PostgreSQL 服务器 5432 Windows 客户端 PostgreSQL 服务器 5432 Windows 客户端 TCP SYN (192.168.1.128:5432) TCP SYN-ACK TCP ACK (连接建立) 启动协议 (user=postgres, database=postgres) 检查 pg_hba.conf AuthenticationRequest (md5) 密码 (postgres) AuthenticationOk SELECT version() "PostgreSQL 15.4 ..." 关闭连接
运行脚本
python test01.py

预期输出:

连接成功!
PostgreSQL 版本: ('PostgreSQL 15.4 ...',)

⚠️ 注意:请将代码中的 192.168.1.128 替换为您自己的服务器 IP 地址。


七、第七章「备份与恢复」逐条解析

7.1 手动备份

备份单个数据库
sudo docker exec postgres_db pg_dump -U postgres postgres > backup_$(date +%F).sql

逐段解析:

片段作用
docker exec postgres_db在容器内执行命令(注意没有 -it,因为这是非交互式管道操作)
pg_dumpPostgreSQL 逻辑备份工具,将数据库导出为 SQL 文本
-U postgres以 postgres 用户执行
postgres(最后的参数)要备份的数据库名
> backup_$(date +%F).sql将输出重定向到文件。$(date +%F) 生成当前日期如 2026-06-26

为什么不加 -it: -i 和 -t 是为交互式终端设计的。这里用 > 重定向输出到文件,不需要终端,去掉 -it 更干净。

pg_dump 的原理: 逻辑备份——遍历数据库的所有表、索引、函数等对象,生成等价的 CREATE TABLE、INSERT 等 SQL 语句。恢复时就是重新执行这些 SQL。

备份所有数据库与角色
sudo docker exec postgres_db pg_dumpall -U postgres > full_backup_$(date +%F).sql
差异说明
pg_dumpall备份所有数据库 + 角色(用户/组)+ 表空间定义
pg_dump只备份单个数据库

为什么推荐 pg_dumpall: pg_dump 不备份角色信息(用户和密码)。如果你有多个数据库和自定义用户,用 pg_dumpall 才能完整恢复。

备份并压缩
sudo docker exec postgres_db pg_dumpall -U postgres | gzip > full_backup_$(date +%F).sql.gz
片段作用
``管道符,把前一个命令的输出作为后一个命令的输入
gzipGNU Zip 压缩工具
> .sql.gz压缩后写入 .gz 文件

原理: pg_dumpall 输出的 SQL 文本有大量重复模式(如 INSERT INTO),gzip 压缩率通常可达 70%-90%。100MB 的 SQL 文件压缩后约 10-30MB。

7.2 定时备份

30 2 * * * docker exec postgres_db pg_dumpall -U postgres | gzip > /opt/postgresql/backup/full_$(date +%F).sql.gz && find /opt/postgresql/backup -name "full_*.sql.gz" -mtime +14 -delete

cron 表达式解析:

30  2  *  *  *
 │   │  │  │  │
 │   │  │  │  └─ 星期 (0-7, 0和7都是周日)
 │   │  │  └──── 月份 (1-12)
 │   │  └─────── 日期 (1-31)
 │   └────────── 小时 (0-23)
 └────────────── 分钟 (0-59)

→ 每天 02:30 执行

命令链解析:

片段作用
docker exec postgres_db pg_dumpall -U postgres全量备份
| gzip > .../full_$(date +%F).sql.gz压缩写入文件
&&前一条成功才执行下一条
find ... -name "full_*.sql.gz"查找备份目录中所有备份文件
-mtime +14修改时间超过 14 天的
-delete删除匹配的文件

整体效果: 每天凌晨 2:30 全量备份,保留最近 14 天,超过 14 天的自动删除。

7.3 恢复

恢复全量备份
gunzip -c full_backup_2026-06-26.sql.gz | sudo docker exec -i postgres_db psql -U postgres
片段作用
gunzip -c解压 gzip 文件,-c 输出到标准输出(不修改原文件)
``管道,把解压后的 SQL 送给 psql 执行
docker exec -i-i 保持标准输入开启(接收管道输入),不加 -t(无终端)
psql -U postgres执行 SQL

原理: 解压备份文件 → 管道传输 → psql 逐行执行 SQL 语句恢复数据。

恢复单库
cat backup_2026-06-26.sql | sudo docker exec -i postgres_db psql -U postgres -d postgres
差异说明
cat直接读取未压缩的 SQL 文件
-d postgres指定恢复到哪个数据库

八、第八章「监控与告警」逐条解析

8.1 容器层面

实时资源占用
sudo docker stats postgres_db
片段作用
stats实时显示容器的 CPU、内存、网络、磁盘 IO 使用情况
postgres_db容器名

输出类似 top 命令,实时刷新。

健康状态
sudo docker inspect --format='{{.State.Health.Status}}' postgres_db
片段作用
inspect查看容器的详细元数据(JSON 格式)
--format='{{.State.Health.Status}}'Go 模板语法,只提取健康状态字段
输出healthy / unhealthy / starting / 空(未配置 healthcheck)

8.2 数据库层面

-- 当前连接数
SELECT count(*) FROM pg_stat_activity;
函数/视图作用
pg_stat_activityPostgreSQL 内置视图,记录每个连接的会话信息
count(*)统计总连接数
-- 长事务(运行超过 5 分钟)
SELECT pid, now() - query_start AS duration, query
FROM pg_stat_activity
WHERE state = 'active' AND now() - query_start > interval '5 minutes';
片段作用
pid进程 ID
now() - query_start AS duration当前时间减去查询开始时间 = 已运行时长
state = 'active'正在执行中的查询(非空闲)
> interval '5 minutes'运行超过 5 分钟
-- 数据库大小
SELECT pg_size_pretty(pg_database_size('postgres'));
函数作用
pg_database_size('postgres')返回指定数据库的字节数
pg_size_pretty()把字节数转为人类可读格式(如 45 MB)

8.3 专业监控

postgres_exporter + Prometheus + Grafana 的监控架构:

指标暴露

/metrics 端点

查询数据

阈值触发

PostgreSQL

postgres_exporter

Prometheus
定时抓取&存储

Grafana
可视化面板

告警
邮件/钉钉/飞书


九、第九章「故障排查」逐条解析

9.1 故障对照表解析

现象可能原因解决方案
容器启动后立即退出数据目录权限不对 / 端口被占见下方详解
docker pull 超时镜像源失效见下方详解
远程连接被拒绝pg_hba.conf 未放行 / 未监听见下方详解
password authentication failed密码错误 / 用户名错误见下方详解
数据目录初始化失败目录非空且不是 PG 数据目录见下方详解
容器健康检查 unhealthy数据库未就绪或资源不足见下方详解
磁盘写满日志/备份未轮转见下方详解
容器启动后立即退出

排查命令:

sudo docker compose logs postgres

查看日志中的错误信息。常见原因:

  1. 数据目录权限不对:日志报 Permission denied → 重新执行 sudo chown -R 999:999 /opt/postgresql/data
  2. 端口被占:日志报 address already in use → 执行 ss -tlnp | grep 5432 查看占用进程,停止它或改用其他端口
docker pull 超时

镜像源失效。修改 /etc/docker/daemon.json 更换可用镜像源,sudo systemctl restart docker 后重试。

远程连接被拒绝

进入容器检查配置:

sudo docker exec -it postgres_db cat /var/lib/postgresql/data/pg_hba.conf

确认:

  1. pg_hba.conf 中有放行远程 IP 的规则
  2. postgresql.conf 中 listen_addresses = '*'
  3. 修改后执行了 sudo docker compose restart
password authentication failed

用本地连接验证密码:

sudo docker exec -it postgres_db psql -U postgres

如果本地能连、远程不能连,说明是 pg_hba.conf 的认证方式问题。如果本地也不能连,说明密码确实错了。

修改密码:

ALTER USER postgres PASSWORD '新密码';

9.2 常用排查命令汇总

# 看容器日志(最近 200 行)
sudo docker compose logs --tail 200 postgres
片段作用
--tail 200只显示最后 200 行(避免输出过多)
postgres服务名
# 进入容器排查
sudo docker exec -it postgres_db bash

进入容器的 Linux shell,可执行 ls、cat、ps 等命令。

# 查看端口监听
ss -tlnp | grep 5432
片段作用
sssocket statistics,替代已弃用的 netstat
-tTCP 连接
-llistening,仅显示监听状态
-nnumeric,不解析端口名(显示 5432 而非 postgres)
-pprocess,显示占用端口的进程
`grep 5432`过滤出 5432 端口
# 查看磁盘空间
df -h
片段作用
dfdisk free,显示文件系统磁盘使用情况
-hhuman-readable,用 KB/MB/GB 显示

十、第十章「升级与回滚」逐条解析

10.1 升级前必做

# 1) 完整备份(务必!)
sudo docker exec postgres_db pg_dumpall -U postgres > pre_upgrade_$(date +%F).sql

为什么必须备份: 升级有风险——新版本可能不兼容旧数据格式,升级失败后需要回滚到备份。

# 2) 停止并删除旧容器(数据目录保留)
cd /opt/postgresql/compose
sudo docker compose down

为什么先 down: 避免新旧容器同时挂载同一数据目录导致数据损坏。

10.2 升级

修改 docker-compose.yml 中的 image: postgres:15.4 为新版本,然后:

sudo docker compose up -d

升级类型与风险:

升级

小版本升级
如 15.4 → 15.5

大版本升级
如 15.4 → 16.2

✅ 可直接升级
数据目录兼容

⚠️ 需要 pg_upgrade
迁移数据格式

直接挂载旧目录
容器启动失败

⚠️ 跨大版本升级(如 15 → 16)不能直接挂载旧数据目录,必须使用 pg_upgrade 迁移数据,否则容器会启动失败。小版本升级(15.4 → 15.5)可直接升级。

10.3 回滚

cd /opt/postgresql/compose
# 1) 将 docker-compose.yml 的 image 改回旧版本(如 postgres:15.4)
# 2) 重新启动(数据目录未被新版本改写时可直接回退)
sudo docker compose up -d

# 若数据目录已被改写,则从 pre_upgrade_*.sql 恢复
gunzip -c pre_upgrade_2026-06-26.sql | sudo docker exec -i postgres_db psql -U postgres

回滚的两种情况:

情况回滚方式
新版本未改写数据目录改回旧 image → docker compose up -d 直接回退
新版本已改写数据目录无法直接回退 → 从 pre_upgrade_*.sql 备份恢复

十一、附录解析

附录 A:安全加固清单

加固项原理
修改默认密码postgres/postgres 是公开凭证,任何人知道 IP 就能登录
收紧 pg_hba.conf0.0.0.0/0 允许全球访问,应限制为具体网段
scram-sha-256 替代 md5SCRAM 协议更安全,防止重放攻击
启用 SSL/TLS防止中间人截获密码和数据
网络隔离防火墙/安全组限制 5432 端口来源 IP
最小权限账号应用不用 superuser,只授增删改查权限
凭证管理密码通过环境变量/密钥管理服务注入
定期备份 + 演练恢复有备份不等于能恢复,必须实际演练
日志审计记录连接日志,发现异常登录
版本补丁关注安全公告,及时升级小版本

附录 B:配置参数速查表

类别参数值说明
连接端口5432PostgreSQL 默认端口
连接用户名postgres教程默认,生产请修改
连接密码postgres教程默认,生产必须修改
连接默认库postgres镜像自动创建
持久化数据目录(宿主机)/opt/postgresql/data绑定挂载路径
持久化数据目录(容器内)/var/lib/postgresql/dataPG 标准 PGDATA 路径
持久化数据卷属主999:999容器内 postgres 用户的 UID:GID
部署compose 目录/opt/postgresql/composedocker-compose.yml 所在目录
镜像版本postgres:15.4官方镜像
容器名称postgres_db统一命名
重启策略unless-stopped宕机自动拉起,手动停止不拉起
时区TZAsia/Shanghaicompose 中设置

十二、全流程串联总览

运维阶段

验证阶段

部署阶段

准备阶段

一、配置需求
确认系统/资源/参数

二、流程简述
了解整体步骤

三、验证环节
检查 Docker + 配置镜像源

4.1 拉取镜像
docker pull postgres:15.4

4.2 准备部署目录
mkdir /opt/postgresql/compose

4.3 创建数据目录+授权
mkdir + chown 999:999

4.4 编写 docker-compose.yml
声明式配置

4.5 启动服务
docker compose up -d

4.6 容器内验证
docker exec psql

4.7 配置远程访问
改 listen_addresses + pg_hba + restart

五、常用管理命令
ps/logs/stop/start/exec

6.1 本地验证
容器内 psql

6.2 Python 远程连接
psycopg2 test01.py

七、备份与恢复
pg_dumpall + crontab + 恢复

八、监控与告警
docker stats + pg_stat_activity

九、故障排查
日志/端口/权限/磁盘

十、升级与回滚
备份→改版本→回滚

各步骤核心作用与不做会怎样

步骤解决的核心问题不做会怎样
4.1 拉取镜像获取 PostgreSQL 运行时docker compose up 时才拉取,首次启动变慢
4.2 准备部署目录给 compose 文件一个标准位置每次执行命令都得带 -f 路径
4.3 数据目录+授权持久化 + 解决 UID 不一致容器一删数据全丢 / 容器直接起不来
4.4 写 compose.yml声明式配置,可复现可版本化散落命令,难维护、难复现
4.5 启动服务把描述变成运行的容器没有运行实例
4.6 容器内验证验证数据库自身可用不知道是 DB 坏还是网络坏
4.7 远程访问配置解决 PG 默认只允许本机连远程客户端全部连不上
7. 备份与恢复数据安全兜底数据丢失无法恢复
8. 监控与告警及时发现故障问题发生后才知道
9. 故障排查快速定位问题出问题后无从下手
10. 升级与回滚安全升级 + 出问题能退回升级失败后数据丢失

📝 结语:工程化部署的核心不是「能跑起来」,而是「可维护、可复现、可恢复、可观测」。docker-compose.yml 把部署配置文件化,数据目录持久化保证数据安全,healthcheck 提供可观测性,备份恢复提供兜底能力——这些共同构成了一个生产级的 PostgreSQL 部署方案。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐