PostgreSQL 工程化部署深度解析

PostgreSQL 工程化部署深度解析

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

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


目录


一、整体架构与部署全景

1.1 部署架构图

#mermaid-svg-TgZWnjj2Z55p014y{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-TgZWnjj2Z55p014y .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-TgZWnjj2Z55p014y .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-TgZWnjj2Z55p014y .error-icon{fill:#552222;}#mermaid-svg-TgZWnjj2Z55p014y .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-TgZWnjj2Z55p014y .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-TgZWnjj2Z55p014y .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-TgZWnjj2Z55p014y .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-TgZWnjj2Z55p014y .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-TgZWnjj2Z55p014y .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-TgZWnjj2Z55p014y .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-TgZWnjj2Z55p014y .marker{fill:#333333;stroke:#333333;}#mermaid-svg-TgZWnjj2Z55p014y .marker.cross{stroke:#333333;}#mermaid-svg-TgZWnjj2Z55p014y svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-TgZWnjj2Z55p014y p{margin:0;}#mermaid-svg-TgZWnjj2Z55p014y .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-TgZWnjj2Z55p014y .cluster-label text{fill:#333;}#mermaid-svg-TgZWnjj2Z55p014y .cluster-label span{color:#333;}#mermaid-svg-TgZWnjj2Z55p014y .cluster-label span p{background-color:transparent;}#mermaid-svg-TgZWnjj2Z55p014y .label text,#mermaid-svg-TgZWnjj2Z55p014y span{fill:#333;color:#333;}#mermaid-svg-TgZWnjj2Z55p014y .node rect,#mermaid-svg-TgZWnjj2Z55p014y .node circle,#mermaid-svg-TgZWnjj2Z55p014y .node ellipse,#mermaid-svg-TgZWnjj2Z55p014y .node polygon,#mermaid-svg-TgZWnjj2Z55p014y .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-TgZWnjj2Z55p014y .rough-node .label text,#mermaid-svg-TgZWnjj2Z55p014y .node .label text,#mermaid-svg-TgZWnjj2Z55p014y .image-shape .label,#mermaid-svg-TgZWnjj2Z55p014y .icon-shape .label{text-anchor:middle;}#mermaid-svg-TgZWnjj2Z55p014y .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-TgZWnjj2Z55p014y .rough-node .label,#mermaid-svg-TgZWnjj2Z55p014y .node .label,#mermaid-svg-TgZWnjj2Z55p014y .image-shape .label,#mermaid-svg-TgZWnjj2Z55p014y .icon-shape .label{text-align:center;}#mermaid-svg-TgZWnjj2Z55p014y .node.clickable{cursor:pointer;}#mermaid-svg-TgZWnjj2Z55p014y .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-TgZWnjj2Z55p014y .arrowheadPath{fill:#333333;}#mermaid-svg-TgZWnjj2Z55p014y .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-TgZWnjj2Z55p014y .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-TgZWnjj2Z55p014y .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-TgZWnjj2Z55p014y .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-TgZWnjj2Z55p014y .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-TgZWnjj2Z55p014y .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-TgZWnjj2Z55p014y .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-TgZWnjj2Z55p014y .cluster text{fill:#333;}#mermaid-svg-TgZWnjj2Z55p014y .cluster span{color:#333;}#mermaid-svg-TgZWnjj2Z55p014y div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-TgZWnjj2Z55p014y .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-TgZWnjj2Z55p014y rect.text{fill:none;stroke-width:0;}#mermaid-svg-TgZWnjj2Z55p014y .icon-shape,#mermaid-svg-TgZWnjj2Z55p014y .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-TgZWnjj2Z55p014y .icon-shape p,#mermaid-svg-TgZWnjj2Z55p014y .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-TgZWnjj2Z55p014y .icon-shape .label rect,#mermaid-svg-TgZWnjj2Z55p014y .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-TgZWnjj2Z55p014y .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-TgZWnjj2Z55p014y .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-TgZWnjj2Z55p014y :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 远程 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 部署流程总览

#mermaid-svg-3R4JrFX45AslrMKu{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-3R4JrFX45AslrMKu .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-3R4JrFX45AslrMKu .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-3R4JrFX45AslrMKu .error-icon{fill:#552222;}#mermaid-svg-3R4JrFX45AslrMKu .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-3R4JrFX45AslrMKu .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-3R4JrFX45AslrMKu .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-3R4JrFX45AslrMKu .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-3R4JrFX45AslrMKu .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-3R4JrFX45AslrMKu .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-3R4JrFX45AslrMKu .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-3R4JrFX45AslrMKu .marker{fill:#333333;stroke:#333333;}#mermaid-svg-3R4JrFX45AslrMKu .marker.cross{stroke:#333333;}#mermaid-svg-3R4JrFX45AslrMKu svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-3R4JrFX45AslrMKu p{margin:0;}#mermaid-svg-3R4JrFX45AslrMKu .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-3R4JrFX45AslrMKu .cluster-label text{fill:#333;}#mermaid-svg-3R4JrFX45AslrMKu .cluster-label span{color:#333;}#mermaid-svg-3R4JrFX45AslrMKu .cluster-label span p{background-color:transparent;}#mermaid-svg-3R4JrFX45AslrMKu .label text,#mermaid-svg-3R4JrFX45AslrMKu span{fill:#333;color:#333;}#mermaid-svg-3R4JrFX45AslrMKu .node rect,#mermaid-svg-3R4JrFX45AslrMKu .node circle,#mermaid-svg-3R4JrFX45AslrMKu .node ellipse,#mermaid-svg-3R4JrFX45AslrMKu .node polygon,#mermaid-svg-3R4JrFX45AslrMKu .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-3R4JrFX45AslrMKu .rough-node .label text,#mermaid-svg-3R4JrFX45AslrMKu .node .label text,#mermaid-svg-3R4JrFX45AslrMKu .image-shape .label,#mermaid-svg-3R4JrFX45AslrMKu .icon-shape .label{text-anchor:middle;}#mermaid-svg-3R4JrFX45AslrMKu .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-3R4JrFX45AslrMKu .rough-node .label,#mermaid-svg-3R4JrFX45AslrMKu .node .label,#mermaid-svg-3R4JrFX45AslrMKu .image-shape .label,#mermaid-svg-3R4JrFX45AslrMKu .icon-shape .label{text-align:center;}#mermaid-svg-3R4JrFX45AslrMKu .node.clickable{cursor:pointer;}#mermaid-svg-3R4JrFX45AslrMKu .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-3R4JrFX45AslrMKu .arrowheadPath{fill:#333333;}#mermaid-svg-3R4JrFX45AslrMKu .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-3R4JrFX45AslrMKu .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-3R4JrFX45AslrMKu .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3R4JrFX45AslrMKu .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-3R4JrFX45AslrMKu .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3R4JrFX45AslrMKu .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-3R4JrFX45AslrMKu .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-3R4JrFX45AslrMKu .cluster text{fill:#333;}#mermaid-svg-3R4JrFX45AslrMKu .cluster span{color:#333;}#mermaid-svg-3R4JrFX45AslrMKu div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-3R4JrFX45AslrMKu .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-3R4JrFX45AslrMKu rect.text{fill:none;stroke-width:0;}#mermaid-svg-3R4JrFX45AslrMKu .icon-shape,#mermaid-svg-3R4JrFX45AslrMKu .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3R4JrFX45AslrMKu .icon-shape p,#mermaid-svg-3R4JrFX45AslrMKu .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-3R4JrFX45AslrMKu .icon-shape .label rect,#mermaid-svg-3R4JrFX45AslrMKu .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3R4JrFX45AslrMKu .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-3R4JrFX45AslrMKu .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-3R4JrFX45AslrMKu :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 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.04 LTS 长期支持版,内核 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 预备配置参数

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

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


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

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

3.1 验证 Docker 环境

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

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

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

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

预期输出: 绿色 active (running)

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

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

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

原理:

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

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

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

3.3 配置 Docker 镜像源

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

配置文件内容:

json 复制代码
{
  "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 配置
bash 复制代码
sudo systemctl daemon-reload

原理:

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

命令 4:重启 Docker
bash 复制代码
sudo systemctl restart docker

原理:

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

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


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

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

4.1 拉取 PostgreSQL 镜像

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

为什么不用 latest

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

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

镜像分层原理:
#mermaid-svg-4w3iyGRgGCRZ4urG{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-4w3iyGRgGCRZ4urG .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-4w3iyGRgGCRZ4urG .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-4w3iyGRgGCRZ4urG .error-icon{fill:#552222;}#mermaid-svg-4w3iyGRgGCRZ4urG .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-4w3iyGRgGCRZ4urG .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-4w3iyGRgGCRZ4urG .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-4w3iyGRgGCRZ4urG .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-4w3iyGRgGCRZ4urG .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-4w3iyGRgGCRZ4urG .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-4w3iyGRgGCRZ4urG .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-4w3iyGRgGCRZ4urG .marker{fill:#333333;stroke:#333333;}#mermaid-svg-4w3iyGRgGCRZ4urG .marker.cross{stroke:#333333;}#mermaid-svg-4w3iyGRgGCRZ4urG svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-4w3iyGRgGCRZ4urG p{margin:0;}#mermaid-svg-4w3iyGRgGCRZ4urG .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-4w3iyGRgGCRZ4urG .cluster-label text{fill:#333;}#mermaid-svg-4w3iyGRgGCRZ4urG .cluster-label span{color:#333;}#mermaid-svg-4w3iyGRgGCRZ4urG .cluster-label span p{background-color:transparent;}#mermaid-svg-4w3iyGRgGCRZ4urG .label text,#mermaid-svg-4w3iyGRgGCRZ4urG span{fill:#333;color:#333;}#mermaid-svg-4w3iyGRgGCRZ4urG .node rect,#mermaid-svg-4w3iyGRgGCRZ4urG .node circle,#mermaid-svg-4w3iyGRgGCRZ4urG .node ellipse,#mermaid-svg-4w3iyGRgGCRZ4urG .node polygon,#mermaid-svg-4w3iyGRgGCRZ4urG .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-4w3iyGRgGCRZ4urG .rough-node .label text,#mermaid-svg-4w3iyGRgGCRZ4urG .node .label text,#mermaid-svg-4w3iyGRgGCRZ4urG .image-shape .label,#mermaid-svg-4w3iyGRgGCRZ4urG .icon-shape .label{text-anchor:middle;}#mermaid-svg-4w3iyGRgGCRZ4urG .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-4w3iyGRgGCRZ4urG .rough-node .label,#mermaid-svg-4w3iyGRgGCRZ4urG .node .label,#mermaid-svg-4w3iyGRgGCRZ4urG .image-shape .label,#mermaid-svg-4w3iyGRgGCRZ4urG .icon-shape .label{text-align:center;}#mermaid-svg-4w3iyGRgGCRZ4urG .node.clickable{cursor:pointer;}#mermaid-svg-4w3iyGRgGCRZ4urG .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-4w3iyGRgGCRZ4urG .arrowheadPath{fill:#333333;}#mermaid-svg-4w3iyGRgGCRZ4urG .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-4w3iyGRgGCRZ4urG .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-4w3iyGRgGCRZ4urG .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4w3iyGRgGCRZ4urG .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-4w3iyGRgGCRZ4urG .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4w3iyGRgGCRZ4urG .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-4w3iyGRgGCRZ4urG .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-4w3iyGRgGCRZ4urG .cluster text{fill:#333;}#mermaid-svg-4w3iyGRgGCRZ4urG .cluster span{color:#333;}#mermaid-svg-4w3iyGRgGCRZ4urG div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-4w3iyGRgGCRZ4urG .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-4w3iyGRgGCRZ4urG rect.text{fill:none;stroke-width:0;}#mermaid-svg-4w3iyGRgGCRZ4urG .icon-shape,#mermaid-svg-4w3iyGRgGCRZ4urG .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4w3iyGRgGCRZ4urG .icon-shape p,#mermaid-svg-4w3iyGRgGCRZ4urG .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-4w3iyGRgGCRZ4urG .icon-shape .label rect,#mermaid-svg-4w3iyGRgGCRZ4urG .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4w3iyGRgGCRZ4urG .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-4w3iyGRgGCRZ4urG .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-4w3iyGRgGCRZ4urG :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} postgres:15.4 镜像
基础层: debian-bullseye-slim
运行时层: 安装 PostgreSQL 15.4 二进制
入口层: docker-entrypoint.sh
配置层: postgresql.conf 模板

pg_hba.conf 模板

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

4.2 准备部署目录

bash 复制代码
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 创建数据持久化目录并授权

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

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

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

核心原理:Docker 容器是「无状态、可丢弃」的------容器一删,里面的数据全部消失。
#mermaid-svg-lFZpZhNpVHqLkJms{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-lFZpZhNpVHqLkJms .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-lFZpZhNpVHqLkJms .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-lFZpZhNpVHqLkJms .error-icon{fill:#552222;}#mermaid-svg-lFZpZhNpVHqLkJms .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-lFZpZhNpVHqLkJms .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-lFZpZhNpVHqLkJms .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-lFZpZhNpVHqLkJms .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-lFZpZhNpVHqLkJms .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-lFZpZhNpVHqLkJms .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-lFZpZhNpVHqLkJms .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-lFZpZhNpVHqLkJms .marker{fill:#333333;stroke:#333333;}#mermaid-svg-lFZpZhNpVHqLkJms .marker.cross{stroke:#333333;}#mermaid-svg-lFZpZhNpVHqLkJms svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-lFZpZhNpVHqLkJms p{margin:0;}#mermaid-svg-lFZpZhNpVHqLkJms .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-lFZpZhNpVHqLkJms .cluster-label text{fill:#333;}#mermaid-svg-lFZpZhNpVHqLkJms .cluster-label span{color:#333;}#mermaid-svg-lFZpZhNpVHqLkJms .cluster-label span p{background-color:transparent;}#mermaid-svg-lFZpZhNpVHqLkJms .label text,#mermaid-svg-lFZpZhNpVHqLkJms span{fill:#333;color:#333;}#mermaid-svg-lFZpZhNpVHqLkJms .node rect,#mermaid-svg-lFZpZhNpVHqLkJms .node circle,#mermaid-svg-lFZpZhNpVHqLkJms .node ellipse,#mermaid-svg-lFZpZhNpVHqLkJms .node polygon,#mermaid-svg-lFZpZhNpVHqLkJms .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-lFZpZhNpVHqLkJms .rough-node .label text,#mermaid-svg-lFZpZhNpVHqLkJms .node .label text,#mermaid-svg-lFZpZhNpVHqLkJms .image-shape .label,#mermaid-svg-lFZpZhNpVHqLkJms .icon-shape .label{text-anchor:middle;}#mermaid-svg-lFZpZhNpVHqLkJms .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-lFZpZhNpVHqLkJms .rough-node .label,#mermaid-svg-lFZpZhNpVHqLkJms .node .label,#mermaid-svg-lFZpZhNpVHqLkJms .image-shape .label,#mermaid-svg-lFZpZhNpVHqLkJms .icon-shape .label{text-align:center;}#mermaid-svg-lFZpZhNpVHqLkJms .node.clickable{cursor:pointer;}#mermaid-svg-lFZpZhNpVHqLkJms .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-lFZpZhNpVHqLkJms .arrowheadPath{fill:#333333;}#mermaid-svg-lFZpZhNpVHqLkJms .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-lFZpZhNpVHqLkJms .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-lFZpZhNpVHqLkJms .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-lFZpZhNpVHqLkJms .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-lFZpZhNpVHqLkJms .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-lFZpZhNpVHqLkJms .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-lFZpZhNpVHqLkJms .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-lFZpZhNpVHqLkJms .cluster text{fill:#333;}#mermaid-svg-lFZpZhNpVHqLkJms .cluster span{color:#333;}#mermaid-svg-lFZpZhNpVHqLkJms div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-lFZpZhNpVHqLkJms .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-lFZpZhNpVHqLkJms rect.text{fill:none;stroke-width:0;}#mermaid-svg-lFZpZhNpVHqLkJms .icon-shape,#mermaid-svg-lFZpZhNpVHqLkJms .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-lFZpZhNpVHqLkJms .icon-shape p,#mermaid-svg-lFZpZhNpVHqLkJms .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-lFZpZhNpVHqLkJms .icon-shape .label rect,#mermaid-svg-lFZpZhNpVHqLkJms .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-lFZpZhNpVHqLkJms .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-lFZpZhNpVHqLkJms .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-lFZpZhNpVHqLkJms :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 宿主机(持久存储)
postgres_db 容器(可随时销毁)
volumes 挂载

读写都落这里
PostgreSQL 进程
/opt/postgresql/data/

所有表数据、索引、WAL日志

compose 文件中的挂载配置:

yaml 复制代码
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)

冲突场景:
#mermaid-svg-O9LG65MhxwxFNBiX{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-O9LG65MhxwxFNBiX .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-O9LG65MhxwxFNBiX .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-O9LG65MhxwxFNBiX .error-icon{fill:#552222;}#mermaid-svg-O9LG65MhxwxFNBiX .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-O9LG65MhxwxFNBiX .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-O9LG65MhxwxFNBiX .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-O9LG65MhxwxFNBiX .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-O9LG65MhxwxFNBiX .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-O9LG65MhxwxFNBiX .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-O9LG65MhxwxFNBiX .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-O9LG65MhxwxFNBiX .marker{fill:#333333;stroke:#333333;}#mermaid-svg-O9LG65MhxwxFNBiX .marker.cross{stroke:#333333;}#mermaid-svg-O9LG65MhxwxFNBiX svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-O9LG65MhxwxFNBiX p{margin:0;}#mermaid-svg-O9LG65MhxwxFNBiX .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-O9LG65MhxwxFNBiX .cluster-label text{fill:#333;}#mermaid-svg-O9LG65MhxwxFNBiX .cluster-label span{color:#333;}#mermaid-svg-O9LG65MhxwxFNBiX .cluster-label span p{background-color:transparent;}#mermaid-svg-O9LG65MhxwxFNBiX .label text,#mermaid-svg-O9LG65MhxwxFNBiX span{fill:#333;color:#333;}#mermaid-svg-O9LG65MhxwxFNBiX .node rect,#mermaid-svg-O9LG65MhxwxFNBiX .node circle,#mermaid-svg-O9LG65MhxwxFNBiX .node ellipse,#mermaid-svg-O9LG65MhxwxFNBiX .node polygon,#mermaid-svg-O9LG65MhxwxFNBiX .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-O9LG65MhxwxFNBiX .rough-node .label text,#mermaid-svg-O9LG65MhxwxFNBiX .node .label text,#mermaid-svg-O9LG65MhxwxFNBiX .image-shape .label,#mermaid-svg-O9LG65MhxwxFNBiX .icon-shape .label{text-anchor:middle;}#mermaid-svg-O9LG65MhxwxFNBiX .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-O9LG65MhxwxFNBiX .rough-node .label,#mermaid-svg-O9LG65MhxwxFNBiX .node .label,#mermaid-svg-O9LG65MhxwxFNBiX .image-shape .label,#mermaid-svg-O9LG65MhxwxFNBiX .icon-shape .label{text-align:center;}#mermaid-svg-O9LG65MhxwxFNBiX .node.clickable{cursor:pointer;}#mermaid-svg-O9LG65MhxwxFNBiX .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-O9LG65MhxwxFNBiX .arrowheadPath{fill:#333333;}#mermaid-svg-O9LG65MhxwxFNBiX .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-O9LG65MhxwxFNBiX .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-O9LG65MhxwxFNBiX .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-O9LG65MhxwxFNBiX .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-O9LG65MhxwxFNBiX .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-O9LG65MhxwxFNBiX .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-O9LG65MhxwxFNBiX .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-O9LG65MhxwxFNBiX .cluster text{fill:#333;}#mermaid-svg-O9LG65MhxwxFNBiX .cluster span{color:#333;}#mermaid-svg-O9LG65MhxwxFNBiX div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-O9LG65MhxwxFNBiX .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-O9LG65MhxwxFNBiX rect.text{fill:none;stroke-width:0;}#mermaid-svg-O9LG65MhxwxFNBiX .icon-shape,#mermaid-svg-O9LG65MhxwxFNBiX .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-O9LG65MhxwxFNBiX .icon-shape p,#mermaid-svg-O9LG65MhxwxFNBiX .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-O9LG65MhxwxFNBiX .icon-shape .label rect,#mermaid-svg-O9LG65MhxwxFNBiX .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-O9LG65MhxwxFNBiX .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-O9LG65MhxwxFNBiX .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-O9LG65MhxwxFNBiX :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 目录属主是 root:root
目录属主是 999:999
容器启动
PG 进程以 UID 999

尝试写入数据目录
权限不足

Permission denied
容器退出

Exited (1)
写入成功

数据库正常启动

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

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

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

镜像 内部用户 UID
postgres postgres 999
mysql mysql 999
redis redis 999
nginx nginx 101
elasticsearch elasticsearch 1000

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

4.4 编写 docker-compose.yml

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

yaml 复制代码
# /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 postgresdocker 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 execdocker 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:
yaml 复制代码
ports:
  - "5432:5432"
片段 含义
左边 5432 宿主机端口
右边 5432 容器内端口
"5432:5432" 将宿主机 5432 映射到容器内 5432

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

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

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

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

deploy:
yaml 复制代码
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:
yaml 复制代码
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(给数据库初始化留缓冲时间)

原理与作用:
#mermaid-svg-RnJ2fh5C1HOBT7hw{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-RnJ2fh5C1HOBT7hw .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-RnJ2fh5C1HOBT7hw .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-RnJ2fh5C1HOBT7hw .error-icon{fill:#552222;}#mermaid-svg-RnJ2fh5C1HOBT7hw .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-RnJ2fh5C1HOBT7hw .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-RnJ2fh5C1HOBT7hw .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-RnJ2fh5C1HOBT7hw .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-RnJ2fh5C1HOBT7hw .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-RnJ2fh5C1HOBT7hw .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-RnJ2fh5C1HOBT7hw .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-RnJ2fh5C1HOBT7hw .marker{fill:#333333;stroke:#333333;}#mermaid-svg-RnJ2fh5C1HOBT7hw .marker.cross{stroke:#333333;}#mermaid-svg-RnJ2fh5C1HOBT7hw svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-RnJ2fh5C1HOBT7hw p{margin:0;}#mermaid-svg-RnJ2fh5C1HOBT7hw defs #statediagram-barbEnd{fill:#333333;stroke:#333333;}#mermaid-svg-RnJ2fh5C1HOBT7hw g.stateGroup text{fill:#9370DB;stroke:none;font-size:10px;}#mermaid-svg-RnJ2fh5C1HOBT7hw g.stateGroup text{fill:#333;stroke:none;font-size:10px;}#mermaid-svg-RnJ2fh5C1HOBT7hw g.stateGroup .state-title{font-weight:bolder;fill:#131300;}#mermaid-svg-RnJ2fh5C1HOBT7hw g.stateGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-RnJ2fh5C1HOBT7hw g.stateGroup line{stroke:#333333;stroke-width:1;}#mermaid-svg-RnJ2fh5C1HOBT7hw .transition{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-RnJ2fh5C1HOBT7hw .stateGroup .composit{fill:white;border-bottom:1px;}#mermaid-svg-RnJ2fh5C1HOBT7hw .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-RnJ2fh5C1HOBT7hw .state-note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-RnJ2fh5C1HOBT7hw .state-note text{fill:black;stroke:none;font-size:10px;}#mermaid-svg-RnJ2fh5C1HOBT7hw .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-RnJ2fh5C1HOBT7hw .edgeLabel .label rect{fill:#ECECFF;opacity:0.5;}#mermaid-svg-RnJ2fh5C1HOBT7hw .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-RnJ2fh5C1HOBT7hw .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-RnJ2fh5C1HOBT7hw .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-RnJ2fh5C1HOBT7hw .edgeLabel .label text{fill:#333;}#mermaid-svg-RnJ2fh5C1HOBT7hw .label div .edgeLabel{color:#333;}#mermaid-svg-RnJ2fh5C1HOBT7hw .stateLabel text{fill:#131300;font-size:10px;font-weight:bold;}#mermaid-svg-RnJ2fh5C1HOBT7hw .node circle.state-start{fill:#333333;stroke:#333333;}#mermaid-svg-RnJ2fh5C1HOBT7hw .node .fork-join{fill:#333333;stroke:#333333;}#mermaid-svg-RnJ2fh5C1HOBT7hw .node circle.state-end{fill:#9370DB;stroke:white;stroke-width:1.5;}#mermaid-svg-RnJ2fh5C1HOBT7hw .end-state-inner{fill:white;stroke-width:1.5;}#mermaid-svg-RnJ2fh5C1HOBT7hw .node rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-RnJ2fh5C1HOBT7hw .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-RnJ2fh5C1HOBT7hw #statediagram-barbEnd{fill:#333333;}#mermaid-svg-RnJ2fh5C1HOBT7hw .statediagram-cluster rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-RnJ2fh5C1HOBT7hw .cluster-label,#mermaid-svg-RnJ2fh5C1HOBT7hw .nodeLabel{color:#131300;}#mermaid-svg-RnJ2fh5C1HOBT7hw .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-RnJ2fh5C1HOBT7hw .statediagram-state .divider{stroke:#9370DB;}#mermaid-svg-RnJ2fh5C1HOBT7hw .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-RnJ2fh5C1HOBT7hw .statediagram-cluster.statediagram-cluster .inner{fill:white;}#mermaid-svg-RnJ2fh5C1HOBT7hw .statediagram-cluster.statediagram-cluster-alt .inner{fill:#f0f0f0;}#mermaid-svg-RnJ2fh5C1HOBT7hw .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-RnJ2fh5C1HOBT7hw .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-RnJ2fh5C1HOBT7hw .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#f0f0f0;}#mermaid-svg-RnJ2fh5C1HOBT7hw .note-edge{stroke-dasharray:5;}#mermaid-svg-RnJ2fh5C1HOBT7hw .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-RnJ2fh5C1HOBT7hw .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-RnJ2fh5C1HOBT7hw .statediagram-note text{fill:black;}#mermaid-svg-RnJ2fh5C1HOBT7hw .statediagram-note .nodeLabel{color:black;}#mermaid-svg-RnJ2fh5C1HOBT7hw .statediagram .edgeLabel{color:red;}#mermaid-svg-RnJ2fh5C1HOBT7hw #dependencyStart,#mermaid-svg-RnJ2fh5C1HOBT7hw #dependencyEnd{fill:#333333;stroke:#333333;stroke-width:1;}#mermaid-svg-RnJ2fh5C1HOBT7hw .statediagramTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-RnJ2fh5C1HOBT7hw :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} docker compose up
pg_isready 返回 0
start_period 后仍失败
连续 3 次检查失败
检查恢复成功
docker compose down
starting
healthy
unhealthy

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

logging:
yaml 复制代码
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.logjson.log.1json.log.2 → 删除最旧的。

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

4.5 启动服务

bash 复制代码
# 后台启动
sudo docker compose up -d

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

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

执行流程:
#mermaid-svg-eP099nsAmAyikBbV{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-eP099nsAmAyikBbV .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-eP099nsAmAyikBbV .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-eP099nsAmAyikBbV .error-icon{fill:#552222;}#mermaid-svg-eP099nsAmAyikBbV .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-eP099nsAmAyikBbV .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-eP099nsAmAyikBbV .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-eP099nsAmAyikBbV .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-eP099nsAmAyikBbV .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-eP099nsAmAyikBbV .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-eP099nsAmAyikBbV .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-eP099nsAmAyikBbV .marker{fill:#333333;stroke:#333333;}#mermaid-svg-eP099nsAmAyikBbV .marker.cross{stroke:#333333;}#mermaid-svg-eP099nsAmAyikBbV svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-eP099nsAmAyikBbV p{margin:0;}#mermaid-svg-eP099nsAmAyikBbV .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-eP099nsAmAyikBbV .cluster-label text{fill:#333;}#mermaid-svg-eP099nsAmAyikBbV .cluster-label span{color:#333;}#mermaid-svg-eP099nsAmAyikBbV .cluster-label span p{background-color:transparent;}#mermaid-svg-eP099nsAmAyikBbV .label text,#mermaid-svg-eP099nsAmAyikBbV span{fill:#333;color:#333;}#mermaid-svg-eP099nsAmAyikBbV .node rect,#mermaid-svg-eP099nsAmAyikBbV .node circle,#mermaid-svg-eP099nsAmAyikBbV .node ellipse,#mermaid-svg-eP099nsAmAyikBbV .node polygon,#mermaid-svg-eP099nsAmAyikBbV .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-eP099nsAmAyikBbV .rough-node .label text,#mermaid-svg-eP099nsAmAyikBbV .node .label text,#mermaid-svg-eP099nsAmAyikBbV .image-shape .label,#mermaid-svg-eP099nsAmAyikBbV .icon-shape .label{text-anchor:middle;}#mermaid-svg-eP099nsAmAyikBbV .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-eP099nsAmAyikBbV .rough-node .label,#mermaid-svg-eP099nsAmAyikBbV .node .label,#mermaid-svg-eP099nsAmAyikBbV .image-shape .label,#mermaid-svg-eP099nsAmAyikBbV .icon-shape .label{text-align:center;}#mermaid-svg-eP099nsAmAyikBbV .node.clickable{cursor:pointer;}#mermaid-svg-eP099nsAmAyikBbV .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-eP099nsAmAyikBbV .arrowheadPath{fill:#333333;}#mermaid-svg-eP099nsAmAyikBbV .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-eP099nsAmAyikBbV .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-eP099nsAmAyikBbV .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-eP099nsAmAyikBbV .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-eP099nsAmAyikBbV .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-eP099nsAmAyikBbV .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-eP099nsAmAyikBbV .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-eP099nsAmAyikBbV .cluster text{fill:#333;}#mermaid-svg-eP099nsAmAyikBbV .cluster span{color:#333;}#mermaid-svg-eP099nsAmAyikBbV div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-eP099nsAmAyikBbV .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-eP099nsAmAyikBbV rect.text{fill:none;stroke-width:0;}#mermaid-svg-eP099nsAmAyikBbV .icon-shape,#mermaid-svg-eP099nsAmAyikBbV .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-eP099nsAmAyikBbV .icon-shape p,#mermaid-svg-eP099nsAmAyikBbV .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-eP099nsAmAyikBbV .icon-shape .label rect,#mermaid-svg-eP099nsAmAyikBbV .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-eP099nsAmAyikBbV .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-eP099nsAmAyikBbV .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-eP099nsAmAyikBbV :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否



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 查看服务日志
-f follow,持续跟踪新输出(类似 tail -f
postgres 服务名(compose 文件中 services: 下的 key)

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

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

4.6 验证数据库连接

bash 复制代码
sudo docker exec -it postgres_db psql -U postgres

逐参数解析:

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

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

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

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

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

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

-it 的作用:

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

进入 psql 后的命令:

命令 作用
\l list,列出所有数据库
\du 列出所有用户/角色
\q quit,退出 psql

为什么先在容器内验证:

容器内验证是「零依赖」测试------不依赖网络、IP、防火墙、pg_hba.conf。只要这一步通过,就证明数据库本身完全正常。之后任何「连不上」的问题都可以缩小到网络层或配置层,排查效率大幅提升。
#mermaid-svg-VHQPxnL13HOJqoSW{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-VHQPxnL13HOJqoSW .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-VHQPxnL13HOJqoSW .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-VHQPxnL13HOJqoSW .error-icon{fill:#552222;}#mermaid-svg-VHQPxnL13HOJqoSW .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-VHQPxnL13HOJqoSW .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-VHQPxnL13HOJqoSW .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-VHQPxnL13HOJqoSW .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-VHQPxnL13HOJqoSW .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-VHQPxnL13HOJqoSW .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-VHQPxnL13HOJqoSW .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-VHQPxnL13HOJqoSW .marker{fill:#333333;stroke:#333333;}#mermaid-svg-VHQPxnL13HOJqoSW .marker.cross{stroke:#333333;}#mermaid-svg-VHQPxnL13HOJqoSW svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-VHQPxnL13HOJqoSW p{margin:0;}#mermaid-svg-VHQPxnL13HOJqoSW .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-VHQPxnL13HOJqoSW .cluster-label text{fill:#333;}#mermaid-svg-VHQPxnL13HOJqoSW .cluster-label span{color:#333;}#mermaid-svg-VHQPxnL13HOJqoSW .cluster-label span p{background-color:transparent;}#mermaid-svg-VHQPxnL13HOJqoSW .label text,#mermaid-svg-VHQPxnL13HOJqoSW span{fill:#333;color:#333;}#mermaid-svg-VHQPxnL13HOJqoSW .node rect,#mermaid-svg-VHQPxnL13HOJqoSW .node circle,#mermaid-svg-VHQPxnL13HOJqoSW .node ellipse,#mermaid-svg-VHQPxnL13HOJqoSW .node polygon,#mermaid-svg-VHQPxnL13HOJqoSW .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-VHQPxnL13HOJqoSW .rough-node .label text,#mermaid-svg-VHQPxnL13HOJqoSW .node .label text,#mermaid-svg-VHQPxnL13HOJqoSW .image-shape .label,#mermaid-svg-VHQPxnL13HOJqoSW .icon-shape .label{text-anchor:middle;}#mermaid-svg-VHQPxnL13HOJqoSW .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-VHQPxnL13HOJqoSW .rough-node .label,#mermaid-svg-VHQPxnL13HOJqoSW .node .label,#mermaid-svg-VHQPxnL13HOJqoSW .image-shape .label,#mermaid-svg-VHQPxnL13HOJqoSW .icon-shape .label{text-align:center;}#mermaid-svg-VHQPxnL13HOJqoSW .node.clickable{cursor:pointer;}#mermaid-svg-VHQPxnL13HOJqoSW .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-VHQPxnL13HOJqoSW .arrowheadPath{fill:#333333;}#mermaid-svg-VHQPxnL13HOJqoSW .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-VHQPxnL13HOJqoSW .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-VHQPxnL13HOJqoSW .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-VHQPxnL13HOJqoSW .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-VHQPxnL13HOJqoSW .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-VHQPxnL13HOJqoSW .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-VHQPxnL13HOJqoSW .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-VHQPxnL13HOJqoSW .cluster text{fill:#333;}#mermaid-svg-VHQPxnL13HOJqoSW .cluster span{color:#333;}#mermaid-svg-VHQPxnL13HOJqoSW div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-VHQPxnL13HOJqoSW .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-VHQPxnL13HOJqoSW rect.text{fill:none;stroke-width:0;}#mermaid-svg-VHQPxnL13HOJqoSW .icon-shape,#mermaid-svg-VHQPxnL13HOJqoSW .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-VHQPxnL13HOJqoSW .icon-shape p,#mermaid-svg-VHQPxnL13HOJqoSW .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-VHQPxnL13HOJqoSW .icon-shape .label rect,#mermaid-svg-VHQPxnL13HOJqoSW .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-VHQPxnL13HOJqoSW .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-VHQPxnL13HOJqoSW .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-VHQPxnL13HOJqoSW :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 通过
失败
失败
4.6 容器内验证
数据库本身 OK
数据库有问题

检查日志/权限/镜像
4.7 远程访问配置
远程连接验证
问题在网络层或配置层

排查 pg_hba/listen/防火墙

4.7 配置远程访问

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

bash 复制代码
# 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(监听地址)
bash 复制代码
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 -i stream 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.conflisten_addresses 默认被 # 注释掉(表示用默认值 'localhost')。用 sed# 去掉并改值,是最小修改方式。

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

命令 2:修改 pg_hba.conf(访问控制白名单)
bash 复制代码
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 md5 pg_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:重启容器
bash 复制代码
sudo docker compose restart

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

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

两个配置的关系图

#mermaid-svg-wcQmeclH6z95bLJR{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-wcQmeclH6z95bLJR .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-wcQmeclH6z95bLJR .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-wcQmeclH6z95bLJR .error-icon{fill:#552222;}#mermaid-svg-wcQmeclH6z95bLJR .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-wcQmeclH6z95bLJR .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-wcQmeclH6z95bLJR .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-wcQmeclH6z95bLJR .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-wcQmeclH6z95bLJR .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-wcQmeclH6z95bLJR .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-wcQmeclH6z95bLJR .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-wcQmeclH6z95bLJR .marker{fill:#333333;stroke:#333333;}#mermaid-svg-wcQmeclH6z95bLJR .marker.cross{stroke:#333333;}#mermaid-svg-wcQmeclH6z95bLJR svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-wcQmeclH6z95bLJR p{margin:0;}#mermaid-svg-wcQmeclH6z95bLJR .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-wcQmeclH6z95bLJR .cluster-label text{fill:#333;}#mermaid-svg-wcQmeclH6z95bLJR .cluster-label span{color:#333;}#mermaid-svg-wcQmeclH6z95bLJR .cluster-label span p{background-color:transparent;}#mermaid-svg-wcQmeclH6z95bLJR .label text,#mermaid-svg-wcQmeclH6z95bLJR span{fill:#333;color:#333;}#mermaid-svg-wcQmeclH6z95bLJR .node rect,#mermaid-svg-wcQmeclH6z95bLJR .node circle,#mermaid-svg-wcQmeclH6z95bLJR .node ellipse,#mermaid-svg-wcQmeclH6z95bLJR .node polygon,#mermaid-svg-wcQmeclH6z95bLJR .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-wcQmeclH6z95bLJR .rough-node .label text,#mermaid-svg-wcQmeclH6z95bLJR .node .label text,#mermaid-svg-wcQmeclH6z95bLJR .image-shape .label,#mermaid-svg-wcQmeclH6z95bLJR .icon-shape .label{text-anchor:middle;}#mermaid-svg-wcQmeclH6z95bLJR .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-wcQmeclH6z95bLJR .rough-node .label,#mermaid-svg-wcQmeclH6z95bLJR .node .label,#mermaid-svg-wcQmeclH6z95bLJR .image-shape .label,#mermaid-svg-wcQmeclH6z95bLJR .icon-shape .label{text-align:center;}#mermaid-svg-wcQmeclH6z95bLJR .node.clickable{cursor:pointer;}#mermaid-svg-wcQmeclH6z95bLJR .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-wcQmeclH6z95bLJR .arrowheadPath{fill:#333333;}#mermaid-svg-wcQmeclH6z95bLJR .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-wcQmeclH6z95bLJR .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-wcQmeclH6z95bLJR .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-wcQmeclH6z95bLJR .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-wcQmeclH6z95bLJR .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-wcQmeclH6z95bLJR .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-wcQmeclH6z95bLJR .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-wcQmeclH6z95bLJR .cluster text{fill:#333;}#mermaid-svg-wcQmeclH6z95bLJR .cluster span{color:#333;}#mermaid-svg-wcQmeclH6z95bLJR div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-wcQmeclH6z95bLJR .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-wcQmeclH6z95bLJR rect.text{fill:none;stroke-width:0;}#mermaid-svg-wcQmeclH6z95bLJR .icon-shape,#mermaid-svg-wcQmeclH6z95bLJR .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-wcQmeclH6z95bLJR .icon-shape p,#mermaid-svg-wcQmeclH6z95bLJR .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-wcQmeclH6z95bLJR .icon-shape .label rect,#mermaid-svg-wcQmeclH6z95bLJR .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-wcQmeclH6z95bLJR .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-wcQmeclH6z95bLJR .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-wcQmeclH6z95bLJR :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 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 查看容器状态

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

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

5.2 查看实时日志

bash 复制代码
sudo docker compose logs -f postgres
片段 作用
logs 查看日志输出
-f follow,持续跟踪新输出
postgres 服务名

5.3 停止服务

bash 复制代码
sudo docker compose stop

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

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

5.4 启动服务

bash 复制代码
sudo docker compose start

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

5.5 重启服务

bash 复制代码
sudo docker compose restart

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

5.6 进入容器内的 psql

bash 复制代码
sudo docker exec -it postgres_db psql -U postgres

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

5.7 进入容器 shell

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

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

5.8 停止并删除容器

bash 复制代码
sudo docker compose down

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

什么时候用:

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

5.9 修改 compose 文件后重新生效

bash 复制代码
sudo docker compose up -d

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

stop / start / restart / down / up 的区别总览:
#mermaid-svg-JUKvZEwxoqbK1NEw{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-JUKvZEwxoqbK1NEw .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-JUKvZEwxoqbK1NEw .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-JUKvZEwxoqbK1NEw .error-icon{fill:#552222;}#mermaid-svg-JUKvZEwxoqbK1NEw .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-JUKvZEwxoqbK1NEw .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-JUKvZEwxoqbK1NEw .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-JUKvZEwxoqbK1NEw .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-JUKvZEwxoqbK1NEw .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-JUKvZEwxoqbK1NEw .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-JUKvZEwxoqbK1NEw .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-JUKvZEwxoqbK1NEw .marker{fill:#333333;stroke:#333333;}#mermaid-svg-JUKvZEwxoqbK1NEw .marker.cross{stroke:#333333;}#mermaid-svg-JUKvZEwxoqbK1NEw svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-JUKvZEwxoqbK1NEw p{margin:0;}#mermaid-svg-JUKvZEwxoqbK1NEw .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-JUKvZEwxoqbK1NEw .cluster-label text{fill:#333;}#mermaid-svg-JUKvZEwxoqbK1NEw .cluster-label span{color:#333;}#mermaid-svg-JUKvZEwxoqbK1NEw .cluster-label span p{background-color:transparent;}#mermaid-svg-JUKvZEwxoqbK1NEw .label text,#mermaid-svg-JUKvZEwxoqbK1NEw span{fill:#333;color:#333;}#mermaid-svg-JUKvZEwxoqbK1NEw .node rect,#mermaid-svg-JUKvZEwxoqbK1NEw .node circle,#mermaid-svg-JUKvZEwxoqbK1NEw .node ellipse,#mermaid-svg-JUKvZEwxoqbK1NEw .node polygon,#mermaid-svg-JUKvZEwxoqbK1NEw .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-JUKvZEwxoqbK1NEw .rough-node .label text,#mermaid-svg-JUKvZEwxoqbK1NEw .node .label text,#mermaid-svg-JUKvZEwxoqbK1NEw .image-shape .label,#mermaid-svg-JUKvZEwxoqbK1NEw .icon-shape .label{text-anchor:middle;}#mermaid-svg-JUKvZEwxoqbK1NEw .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-JUKvZEwxoqbK1NEw .rough-node .label,#mermaid-svg-JUKvZEwxoqbK1NEw .node .label,#mermaid-svg-JUKvZEwxoqbK1NEw .image-shape .label,#mermaid-svg-JUKvZEwxoqbK1NEw .icon-shape .label{text-align:center;}#mermaid-svg-JUKvZEwxoqbK1NEw .node.clickable{cursor:pointer;}#mermaid-svg-JUKvZEwxoqbK1NEw .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-JUKvZEwxoqbK1NEw .arrowheadPath{fill:#333333;}#mermaid-svg-JUKvZEwxoqbK1NEw .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-JUKvZEwxoqbK1NEw .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-JUKvZEwxoqbK1NEw .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-JUKvZEwxoqbK1NEw .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-JUKvZEwxoqbK1NEw .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-JUKvZEwxoqbK1NEw .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-JUKvZEwxoqbK1NEw .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-JUKvZEwxoqbK1NEw .cluster text{fill:#333;}#mermaid-svg-JUKvZEwxoqbK1NEw .cluster span{color:#333;}#mermaid-svg-JUKvZEwxoqbK1NEw div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-JUKvZEwxoqbK1NEw .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-JUKvZEwxoqbK1NEw rect.text{fill:none;stroke-width:0;}#mermaid-svg-JUKvZEwxoqbK1NEw .icon-shape,#mermaid-svg-JUKvZEwxoqbK1NEw .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-JUKvZEwxoqbK1NEw .icon-shape p,#mermaid-svg-JUKvZEwxoqbK1NEw .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-JUKvZEwxoqbK1NEw .icon-shape .label rect,#mermaid-svg-JUKvZEwxoqbK1NEw .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-JUKvZEwxoqbK1NEw .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-JUKvZEwxoqbK1NEw .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-JUKvZEwxoqbK1NEw :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 数据影响
stop/start/restart/down/up
数据目录 /opt/postgresql/data
容器生命周期
stop
start
restart
up -d
down
up -d
容器存在且运行
容器存在但停止
容器被删除
重新创建并启动

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

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

6.1 服务器本地验证

bash 复制代码
sudo docker exec -it postgres_db psql -U postgres

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

6.2 Windows 本地连接(Python 测试)

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

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

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

连接验证流程:
PostgreSQL 服务器 5432 Windows 客户端 PostgreSQL 服务器 5432 Windows 客户端 #mermaid-svg-aLdXUO3BrkMqE2tu{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-aLdXUO3BrkMqE2tu .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-aLdXUO3BrkMqE2tu .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-aLdXUO3BrkMqE2tu .error-icon{fill:#552222;}#mermaid-svg-aLdXUO3BrkMqE2tu .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-aLdXUO3BrkMqE2tu .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-aLdXUO3BrkMqE2tu .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-aLdXUO3BrkMqE2tu .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-aLdXUO3BrkMqE2tu .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-aLdXUO3BrkMqE2tu .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-aLdXUO3BrkMqE2tu .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-aLdXUO3BrkMqE2tu .marker{fill:#333333;stroke:#333333;}#mermaid-svg-aLdXUO3BrkMqE2tu .marker.cross{stroke:#333333;}#mermaid-svg-aLdXUO3BrkMqE2tu svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-aLdXUO3BrkMqE2tu p{margin:0;}#mermaid-svg-aLdXUO3BrkMqE2tu .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-aLdXUO3BrkMqE2tu text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-aLdXUO3BrkMqE2tu .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-aLdXUO3BrkMqE2tu .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-aLdXUO3BrkMqE2tu .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-aLdXUO3BrkMqE2tu .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-aLdXUO3BrkMqE2tu #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-aLdXUO3BrkMqE2tu .sequenceNumber{fill:white;}#mermaid-svg-aLdXUO3BrkMqE2tu #sequencenumber{fill:#333;}#mermaid-svg-aLdXUO3BrkMqE2tu #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-aLdXUO3BrkMqE2tu .messageText{fill:#333;stroke:none;}#mermaid-svg-aLdXUO3BrkMqE2tu .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-aLdXUO3BrkMqE2tu .labelText,#mermaid-svg-aLdXUO3BrkMqE2tu .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-aLdXUO3BrkMqE2tu .loopText,#mermaid-svg-aLdXUO3BrkMqE2tu .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-aLdXUO3BrkMqE2tu .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-aLdXUO3BrkMqE2tu .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-aLdXUO3BrkMqE2tu .noteText,#mermaid-svg-aLdXUO3BrkMqE2tu .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-aLdXUO3BrkMqE2tu .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-aLdXUO3BrkMqE2tu .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-aLdXUO3BrkMqE2tu .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-aLdXUO3BrkMqE2tu .actorPopupMenu{position:absolute;}#mermaid-svg-aLdXUO3BrkMqE2tu .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-aLdXUO3BrkMqE2tu .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-aLdXUO3BrkMqE2tu .actor-man circle,#mermaid-svg-aLdXUO3BrkMqE2tu line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-aLdXUO3BrkMqE2tu :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 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 ..." 关闭连接

运行脚本
bash 复制代码
python test01.py

预期输出:

复制代码
连接成功!
PostgreSQL 版本: ('PostgreSQL 15.4 ...',)

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


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

7.1 手动备份

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

逐段解析:

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

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

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

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

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

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

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

7.2 定时备份

cron 复制代码
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 恢复

恢复全量备份
bash 复制代码
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 语句恢复数据。

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

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

8.1 容器层面

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

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

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

8.2 数据库层面

sql 复制代码
-- 当前连接数
SELECT count(*) FROM pg_stat_activity;
函数/视图 作用
pg_stat_activity PostgreSQL 内置视图,记录每个连接的会话信息
count(*) 统计总连接数
sql 复制代码
-- 长事务(运行超过 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 分钟
sql 复制代码
-- 数据库大小
SELECT pg_size_pretty(pg_database_size('postgres'));
函数 作用
pg_database_size('postgres') 返回指定数据库的字节数
pg_size_pretty() 把字节数转为人类可读格式(如 45 MB

8.3 专业监控

postgres_exporter + Prometheus + Grafana 的监控架构:
#mermaid-svg-FiAyiMxqRsByOQCt{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-FiAyiMxqRsByOQCt .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-FiAyiMxqRsByOQCt .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-FiAyiMxqRsByOQCt .error-icon{fill:#552222;}#mermaid-svg-FiAyiMxqRsByOQCt .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-FiAyiMxqRsByOQCt .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-FiAyiMxqRsByOQCt .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-FiAyiMxqRsByOQCt .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-FiAyiMxqRsByOQCt .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-FiAyiMxqRsByOQCt .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-FiAyiMxqRsByOQCt .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-FiAyiMxqRsByOQCt .marker{fill:#333333;stroke:#333333;}#mermaid-svg-FiAyiMxqRsByOQCt .marker.cross{stroke:#333333;}#mermaid-svg-FiAyiMxqRsByOQCt svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-FiAyiMxqRsByOQCt p{margin:0;}#mermaid-svg-FiAyiMxqRsByOQCt .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-FiAyiMxqRsByOQCt .cluster-label text{fill:#333;}#mermaid-svg-FiAyiMxqRsByOQCt .cluster-label span{color:#333;}#mermaid-svg-FiAyiMxqRsByOQCt .cluster-label span p{background-color:transparent;}#mermaid-svg-FiAyiMxqRsByOQCt .label text,#mermaid-svg-FiAyiMxqRsByOQCt span{fill:#333;color:#333;}#mermaid-svg-FiAyiMxqRsByOQCt .node rect,#mermaid-svg-FiAyiMxqRsByOQCt .node circle,#mermaid-svg-FiAyiMxqRsByOQCt .node ellipse,#mermaid-svg-FiAyiMxqRsByOQCt .node polygon,#mermaid-svg-FiAyiMxqRsByOQCt .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-FiAyiMxqRsByOQCt .rough-node .label text,#mermaid-svg-FiAyiMxqRsByOQCt .node .label text,#mermaid-svg-FiAyiMxqRsByOQCt .image-shape .label,#mermaid-svg-FiAyiMxqRsByOQCt .icon-shape .label{text-anchor:middle;}#mermaid-svg-FiAyiMxqRsByOQCt .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-FiAyiMxqRsByOQCt .rough-node .label,#mermaid-svg-FiAyiMxqRsByOQCt .node .label,#mermaid-svg-FiAyiMxqRsByOQCt .image-shape .label,#mermaid-svg-FiAyiMxqRsByOQCt .icon-shape .label{text-align:center;}#mermaid-svg-FiAyiMxqRsByOQCt .node.clickable{cursor:pointer;}#mermaid-svg-FiAyiMxqRsByOQCt .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-FiAyiMxqRsByOQCt .arrowheadPath{fill:#333333;}#mermaid-svg-FiAyiMxqRsByOQCt .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-FiAyiMxqRsByOQCt .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-FiAyiMxqRsByOQCt .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-FiAyiMxqRsByOQCt .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-FiAyiMxqRsByOQCt .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-FiAyiMxqRsByOQCt .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-FiAyiMxqRsByOQCt .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-FiAyiMxqRsByOQCt .cluster text{fill:#333;}#mermaid-svg-FiAyiMxqRsByOQCt .cluster span{color:#333;}#mermaid-svg-FiAyiMxqRsByOQCt div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-FiAyiMxqRsByOQCt .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-FiAyiMxqRsByOQCt rect.text{fill:none;stroke-width:0;}#mermaid-svg-FiAyiMxqRsByOQCt .icon-shape,#mermaid-svg-FiAyiMxqRsByOQCt .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-FiAyiMxqRsByOQCt .icon-shape p,#mermaid-svg-FiAyiMxqRsByOQCt .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-FiAyiMxqRsByOQCt .icon-shape .label rect,#mermaid-svg-FiAyiMxqRsByOQCt .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-FiAyiMxqRsByOQCt .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-FiAyiMxqRsByOQCt .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-FiAyiMxqRsByOQCt :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 指标暴露
/metrics 端点
查询数据
阈值触发
PostgreSQL
postgres_exporter
Prometheus

定时抓取&存储
Grafana

可视化面板
告警

邮件/钉钉/飞书


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

9.1 故障对照表解析

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

排查命令:

bash 复制代码
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 后重试。

远程连接被拒绝

进入容器检查配置:

bash 复制代码
sudo docker exec -it postgres_db cat /var/lib/postgresql/data/pg_hba.conf

确认:

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

用本地连接验证密码:

bash 复制代码
sudo docker exec -it postgres_db psql -U postgres

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

修改密码:

sql 复制代码
ALTER USER postgres PASSWORD '新密码';

9.2 常用排查命令汇总

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

进入容器的 Linux shell,可执行 lscatps 等命令。

bash 复制代码
# 查看端口监听
ss -tlnp | grep 5432
片段 作用
ss socket statistics,替代已弃用的 netstat
-t TCP 连接
-l listening,仅显示监听状态
-n numeric,不解析端口名(显示 5432 而非 postgres)
-p process,显示占用端口的进程
` grep 5432` 过滤出 5432 端口
bash 复制代码
# 查看磁盘空间
df -h
片段 作用
df disk free,显示文件系统磁盘使用情况
-h human-readable,用 KB/MB/GB 显示

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

10.1 升级前必做

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

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

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

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

10.2 升级

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

bash 复制代码
sudo docker compose up -d

升级类型与风险:
#mermaid-svg-OMOWAecUFoJJ3Ovn{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-OMOWAecUFoJJ3Ovn .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-OMOWAecUFoJJ3Ovn .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-OMOWAecUFoJJ3Ovn .error-icon{fill:#552222;}#mermaid-svg-OMOWAecUFoJJ3Ovn .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-OMOWAecUFoJJ3Ovn .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-OMOWAecUFoJJ3Ovn .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-OMOWAecUFoJJ3Ovn .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-OMOWAecUFoJJ3Ovn .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-OMOWAecUFoJJ3Ovn .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-OMOWAecUFoJJ3Ovn .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-OMOWAecUFoJJ3Ovn .marker{fill:#333333;stroke:#333333;}#mermaid-svg-OMOWAecUFoJJ3Ovn .marker.cross{stroke:#333333;}#mermaid-svg-OMOWAecUFoJJ3Ovn svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-OMOWAecUFoJJ3Ovn p{margin:0;}#mermaid-svg-OMOWAecUFoJJ3Ovn .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-OMOWAecUFoJJ3Ovn .cluster-label text{fill:#333;}#mermaid-svg-OMOWAecUFoJJ3Ovn .cluster-label span{color:#333;}#mermaid-svg-OMOWAecUFoJJ3Ovn .cluster-label span p{background-color:transparent;}#mermaid-svg-OMOWAecUFoJJ3Ovn .label text,#mermaid-svg-OMOWAecUFoJJ3Ovn span{fill:#333;color:#333;}#mermaid-svg-OMOWAecUFoJJ3Ovn .node rect,#mermaid-svg-OMOWAecUFoJJ3Ovn .node circle,#mermaid-svg-OMOWAecUFoJJ3Ovn .node ellipse,#mermaid-svg-OMOWAecUFoJJ3Ovn .node polygon,#mermaid-svg-OMOWAecUFoJJ3Ovn .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-OMOWAecUFoJJ3Ovn .rough-node .label text,#mermaid-svg-OMOWAecUFoJJ3Ovn .node .label text,#mermaid-svg-OMOWAecUFoJJ3Ovn .image-shape .label,#mermaid-svg-OMOWAecUFoJJ3Ovn .icon-shape .label{text-anchor:middle;}#mermaid-svg-OMOWAecUFoJJ3Ovn .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-OMOWAecUFoJJ3Ovn .rough-node .label,#mermaid-svg-OMOWAecUFoJJ3Ovn .node .label,#mermaid-svg-OMOWAecUFoJJ3Ovn .image-shape .label,#mermaid-svg-OMOWAecUFoJJ3Ovn .icon-shape .label{text-align:center;}#mermaid-svg-OMOWAecUFoJJ3Ovn .node.clickable{cursor:pointer;}#mermaid-svg-OMOWAecUFoJJ3Ovn .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-OMOWAecUFoJJ3Ovn .arrowheadPath{fill:#333333;}#mermaid-svg-OMOWAecUFoJJ3Ovn .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-OMOWAecUFoJJ3Ovn .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-OMOWAecUFoJJ3Ovn .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-OMOWAecUFoJJ3Ovn .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-OMOWAecUFoJJ3Ovn .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-OMOWAecUFoJJ3Ovn .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-OMOWAecUFoJJ3Ovn .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-OMOWAecUFoJJ3Ovn .cluster text{fill:#333;}#mermaid-svg-OMOWAecUFoJJ3Ovn .cluster span{color:#333;}#mermaid-svg-OMOWAecUFoJJ3Ovn div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-OMOWAecUFoJJ3Ovn .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-OMOWAecUFoJJ3Ovn rect.text{fill:none;stroke-width:0;}#mermaid-svg-OMOWAecUFoJJ3Ovn .icon-shape,#mermaid-svg-OMOWAecUFoJJ3Ovn .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-OMOWAecUFoJJ3Ovn .icon-shape p,#mermaid-svg-OMOWAecUFoJJ3Ovn .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-OMOWAecUFoJJ3Ovn .icon-shape .label rect,#mermaid-svg-OMOWAecUFoJJ3Ovn .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-OMOWAecUFoJJ3Ovn .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-OMOWAecUFoJJ3Ovn .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-OMOWAecUFoJJ3Ovn :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 升级
小版本升级

如 15.4 → 15.5
大版本升级

如 15.4 → 16.2
✅ 可直接升级

数据目录兼容
⚠️ 需要 pg_upgrade

迁移数据格式
直接挂载旧目录

容器启动失败

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

10.3 回滚

bash 复制代码
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.conf 0.0.0.0/0 允许全球访问,应限制为具体网段
scram-sha-256 替代 md5 SCRAM 协议更安全,防止重放攻击
启用 SSL/TLS 防止中间人截获密码和数据
网络隔离 防火墙/安全组限制 5432 端口来源 IP
最小权限账号 应用不用 superuser,只授增删改查权限
凭证管理 密码通过环境变量/密钥管理服务注入
定期备份 + 演练恢复 有备份不等于能恢复,必须实际演练
日志审计 记录连接日志,发现异常登录
版本补丁 关注安全公告,及时升级小版本

附录 B:配置参数速查表

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

十二、全流程串联总览

#mermaid-svg-a5xNqeNKKBkpAldy{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-a5xNqeNKKBkpAldy .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-a5xNqeNKKBkpAldy .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-a5xNqeNKKBkpAldy .error-icon{fill:#552222;}#mermaid-svg-a5xNqeNKKBkpAldy .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-a5xNqeNKKBkpAldy .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-a5xNqeNKKBkpAldy .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-a5xNqeNKKBkpAldy .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-a5xNqeNKKBkpAldy .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-a5xNqeNKKBkpAldy .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-a5xNqeNKKBkpAldy .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-a5xNqeNKKBkpAldy .marker{fill:#333333;stroke:#333333;}#mermaid-svg-a5xNqeNKKBkpAldy .marker.cross{stroke:#333333;}#mermaid-svg-a5xNqeNKKBkpAldy svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-a5xNqeNKKBkpAldy p{margin:0;}#mermaid-svg-a5xNqeNKKBkpAldy .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-a5xNqeNKKBkpAldy .cluster-label text{fill:#333;}#mermaid-svg-a5xNqeNKKBkpAldy .cluster-label span{color:#333;}#mermaid-svg-a5xNqeNKKBkpAldy .cluster-label span p{background-color:transparent;}#mermaid-svg-a5xNqeNKKBkpAldy .label text,#mermaid-svg-a5xNqeNKKBkpAldy span{fill:#333;color:#333;}#mermaid-svg-a5xNqeNKKBkpAldy .node rect,#mermaid-svg-a5xNqeNKKBkpAldy .node circle,#mermaid-svg-a5xNqeNKKBkpAldy .node ellipse,#mermaid-svg-a5xNqeNKKBkpAldy .node polygon,#mermaid-svg-a5xNqeNKKBkpAldy .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-a5xNqeNKKBkpAldy .rough-node .label text,#mermaid-svg-a5xNqeNKKBkpAldy .node .label text,#mermaid-svg-a5xNqeNKKBkpAldy .image-shape .label,#mermaid-svg-a5xNqeNKKBkpAldy .icon-shape .label{text-anchor:middle;}#mermaid-svg-a5xNqeNKKBkpAldy .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-a5xNqeNKKBkpAldy .rough-node .label,#mermaid-svg-a5xNqeNKKBkpAldy .node .label,#mermaid-svg-a5xNqeNKKBkpAldy .image-shape .label,#mermaid-svg-a5xNqeNKKBkpAldy .icon-shape .label{text-align:center;}#mermaid-svg-a5xNqeNKKBkpAldy .node.clickable{cursor:pointer;}#mermaid-svg-a5xNqeNKKBkpAldy .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-a5xNqeNKKBkpAldy .arrowheadPath{fill:#333333;}#mermaid-svg-a5xNqeNKKBkpAldy .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-a5xNqeNKKBkpAldy .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-a5xNqeNKKBkpAldy .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-a5xNqeNKKBkpAldy .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-a5xNqeNKKBkpAldy .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-a5xNqeNKKBkpAldy .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-a5xNqeNKKBkpAldy .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-a5xNqeNKKBkpAldy .cluster text{fill:#333;}#mermaid-svg-a5xNqeNKKBkpAldy .cluster span{color:#333;}#mermaid-svg-a5xNqeNKKBkpAldy div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-a5xNqeNKKBkpAldy .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-a5xNqeNKKBkpAldy rect.text{fill:none;stroke-width:0;}#mermaid-svg-a5xNqeNKKBkpAldy .icon-shape,#mermaid-svg-a5xNqeNKKBkpAldy .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-a5xNqeNKKBkpAldy .icon-shape p,#mermaid-svg-a5xNqeNKKBkpAldy .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-a5xNqeNKKBkpAldy .icon-shape .label rect,#mermaid-svg-a5xNqeNKKBkpAldy .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-a5xNqeNKKBkpAldy .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-a5xNqeNKKBkpAldy .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-a5xNqeNKKBkpAldy :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 运维阶段
验证阶段
部署阶段
准备阶段
一、配置需求

确认系统/资源/参数
二、流程简述

了解整体步骤
三、验证环节

检查 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 部署方案。