简化开发:Docker Compose 快速部署 PostgreSQL
在当今瞬息万变的软件开发领域,效率、可移植性和环境一致性已成为决定项目成败的关键因素。无论是小型创业公司还是大型企业,开发者们都在寻求能够加速开发流程、减少配置摩擦并确保应用程序在任何环境中都能可靠运行的解决方案。容器化技术,尤其是 Docker,正是为了应对这些挑战而生。它将应用程序及其所有依赖项打包在一个独立的、可移植的单元中,从而消除了“在我机器上能跑”的问题。
然而,当应用程序变得更加复杂,涉及多个服务(例如,前端界面、后端API、数据库、缓存等)时,仅仅使用 Docker 来管理单个容器就显得力不不足了。这时,Docker Compose 便闪亮登场,它是一个用于定义和运行多容器 Docker 应用程序的工具。通过一个简单的 YAML 文件,开发者可以声明式地配置所有服务,包括它们的网络、卷、环境变量等,然后一键启动、停止和管理整个应用栈。
本文将深入探讨如何利用 Docker Compose 这一强大工具,快速、高效且一致地部署 PostgreSQL 数据库。我们将从基础概念讲起,逐步深入到高级配置、最佳实践,甚至涵盖一些生产环境下的考量,旨在为读者提供一个全面的指南,助您在开发旅程中如虎添翼。
第一章:背景与动机——为何选择 Docker Compose 与 PostgreSQL?
在深入技术细节之前,我们首先要理解为什么这种组合是如此具有吸引力。
1.1 传统数据库部署的挑战
在 Docker 和容器化技术普及之前,部署和管理数据库(如 PostgreSQL)通常伴随着一系列挑战:
- 环境差异性: 开发、测试和生产环境的操作系统、库版本、配置参数可能各不相同,导致“我的机器上能跑,你的机器上不能”的问题。
 - 依赖冲突: 在同一台机器上运行多个项目时,可能需要不同版本的数据库或相关依赖,容易造成冲突。
 - 安装与配置复杂: 手动安装 PostgreSQL 涉及下载软件包、配置数据目录、用户权限、网络监听等一系列步骤,耗时且容易出错。
 - 清理与重置困难: 当需要快速重置数据库到初始状态,或者在不同分支间切换时,手动清理数据和配置往往很麻烦。
 - 可移植性差: 将一个已配置好的数据库环境从一台机器迁移到另一台机器,或者分享给团队成员,通常需要重复上述繁琐的安装配置过程。
 - 资源隔离不足: 在同一台服务器上直接安装多个数据库实例,可能会导致资源争抢,且管理复杂。
 
这些挑战无疑会拖慢开发速度,增加维护成本,并引入潜在的错误。
1.2 Docker 的容器化优势
Docker 通过引入容器技术,有效地解决了上述大部分问题:
- 隔离性: 每个容器都是一个独立的运行环境,拥有自己的文件系统、进程空间、网络接口等。这意味着不同版本的 PostgreSQL 可以安全地在同一台宿主机上运行,互不干扰。
 - 可移植性: 一旦一个 Docker 镜像被构建,它就可以在任何支持 Docker 的环境中运行,无论是本地开发机、测试服务器还是生产云平台,都能保持一致的行为。
 - 环境一致性: 开发者、测试人员和运维人员都可以使用相同的 Docker 镜像,确保所有环境都基于相同的代码和配置。
 - 快速启动与停止: 容器可以在几秒钟内启动或停止,大大加速了开发和测试周期。
 - 资源效率: 容器共享宿主机的操作系统内核,比虚拟机更轻量级,启动更快,占用资源更少。
 - 版本控制: Docker 镜像可以通过标签进行版本控制,方便回溯和管理。
 
1.3 Docker Compose:多容器编排的利器
Docker 解决了单个服务的容器化问题,但当应用程序由多个相互依赖的服务组成时,手动管理这些容器的启动顺序、网络连接和配置就变得繁琐起来。Docker Compose 应运而生,它提供了一种声明式的方法来定义和运行多容器 Docker 应用程序:
- 声明式配置: 通过 
docker-compose.yml文件,开发者可以清晰地定义应用程序的所有服务、它们之间的网络关系、卷挂载、环境变量等。 - 一键式管理: 仅需一个命令 (
docker compose up),Compose 就能根据配置文件自动创建、启动和链接所有服务,极大地简化了复杂应用的部署过程。 - 服务发现: Compose 会为所有服务创建一个内部网络,服务之间可以通过服务名(即 
docker-compose.yml中定义的服务名称)相互访问,无需关心 IP 地址。 - 环境管理: 可以轻松地为不同的环境(开发、测试)创建不同的 Compose 文件或使用扩展文件。
 - 数据持久化: Compose 提供了简单的方式来定义和管理数据卷,确保数据库等有状态服务的数据能够持久保存。
 
1.4 PostgreSQL:稳定可靠的关系型数据库
最后,为什么选择 PostgreSQL?
- 强大与成熟: PostgreSQL 是一个功能强大、高度可扩展且成熟的对象关系型数据库系统,拥有超过30年的发展历史。
 - 功能丰富: 它支持 SQL 标准,并提供了许多高级功能,如事务、复杂查询、JSONB 数据类型、全文搜索、地理空间数据支持 (PostGIS) 等。
 - 可靠性与数据完整性: PostgreSQL 以其 ACID 兼容性和数据完整性保障而闻名。
 - 开源与社区活跃: 作为一个开源项目,PostgreSQL 拥有庞大而活跃的社区支持,提供了丰富的文档和工具。
 - 广泛应用: 从 Web 应用到数据仓库,PostgreSQL 在各种应用场景中都表现出色。
 
综上所述,使用 Docker Compose 部署 PostgreSQL,意味着我们能够结合容器化的灵活性、多服务编排的便捷性以及 PostgreSQL 本身卓越的性能和稳定性,从而极大地简化开发流程,提升整体效率。
第二章:核心实践——使用 Docker Compose 部署 PostgreSQL
本章将引导您完成使用 Docker Compose 部署 PostgreSQL 的具体步骤。
2.1 前提条件
在开始之前,请确保您的系统已安装 Docker 和 Docker Compose。如果您使用的是 Docker Desktop (适用于 Windows 或 macOS),则 Docker Compose 已内置其中。对于 Linux 用户,您可能需要单独安装 Docker Engine 和 Docker Compose 插件。
您可以通过运行以下命令来验证安装:
bash
docker --version
docker compose version
2.2 基础 docker-compose.yml 文件构建
docker-compose.yml 文件是 Docker Compose 的核心。它是一个 YAML 格式的文件,用于定义您的应用程序的服务。让我们从一个最简单的 PostgreSQL 配置开始。
创建一个名为 docker-compose.yml 的文件:
“`yaml
version: ‘3.8’ # 定义 Compose 文件格式版本
services:
db: # 定义一个名为 ‘db’ 的服务,这是我们PostgreSQL容器的逻辑名称
image: postgres:14 # 指定使用的PostgreSQL镜像和版本。推荐使用特定版本而非’latest’以确保稳定性
container_name: my-postgres-container # 为容器指定一个易于识别的名称
environment: # 设置PostgreSQL容器的环境变量
POSTGRES_DB: mydatabase # 数据库名称
POSTGRES_USER: user # 数据库用户
POSTGRES_PASSWORD: password # 数据库密码
ports:
– “5432:5432” # 端口映射:将宿主机的5432端口映射到容器的5432端口
volumes:
– pgdata:/var/lib/postgresql/data # 数据卷:将宿主机的pgdata命名卷挂载到容器的PostgreSQL数据目录
restart: unless-stopped # 容器重启策略:除非手动停止,否则始终重启
volumes:
pgdata: # 定义一个名为 ‘pgdata’ 的命名卷
“`
配置详解:
version: '3.8':指定 Docker Compose 文件格式的版本。不同版本支持不同的功能和语法。services:此部分包含应用程序中所有服务的定义。db:这是我们定义的 PostgreSQL 服务的名称。在同一个 Compose 网络中,其他服务可以通过这个名称访问 PostgreSQL。image: postgres:14:指定要使用的 Docker 镜像。postgres是官方 PostgreSQL 镜像的名称,14是其版本标签。推荐使用特定的版本(如14或15.2)而不是latest,以避免因latest标签更新导致的环境不一致。container_name: my-postgres-container:为运行的容器提供一个自定义名称。这有助于您在docker ps命令的输出中快速识别容器。environment:此部分用于设置容器内部的环境变量。对于 PostgreSQL 官方镜像,有一些特定的环境变量可用于初始化数据库:POSTGRES_DB:指定数据库的名称。POSTGRES_USER:指定数据库超级用户的名称。POSTGRES_PASSWORD:指定数据库超级用户的密码。- 您还可以设置 
PGDATA来改变数据存储路径,但通常默认的/var/lib/postgresql/data即可。 
ports: - "5432:5432":这是端口映射。它将宿主机的5432端口(左侧)映射到容器内部的5432端口(右侧)。这样,您就可以从宿主机或其他网络中的客户端通过localhost:5432连接到容器内的 PostgreSQL 数据库。volumes: - pgdata:/var/lib/postgresql/data:这是数据持久化的关键。pgdata:这是在volumes顶层部分定义的命名卷的名称。Docker 会自动管理这个卷,数据将存储在宿主机 Docker 的数据目录中。/var/lib/postgresql/data:这是 PostgreSQL 容器内部存储其数据文件的默认路径。- 通过这种方式,即使容器被删除,数据库数据也会被保存在 
pgdata卷中,确保数据不会丢失。 
restart: unless-stopped:定义容器的重启策略。unless-stopped意味着除非手动停止容器,否则如果容器因任何原因退出,Docker 都会自动重启它。这有助于提高服务的可用性。volumes: pgdata::在volumes的顶层定义命名卷pgdata。当docker compose up命令第一次运行时,如果pgdata卷不存在,Docker 会自动创建它。
2.3 部署与管理
有了 docker-compose.yml 文件后,部署 PostgreSQL 就变得非常简单。
- 
启动服务:
在docker-compose.yml文件所在的目录中打开终端,运行以下命令:bash
docker compose up -dup:根据docker-compose.yml文件创建并启动所有服务。-d:表示“detached”模式,即在后台运行容器,并返回终端控制。
首次运行时,Docker Compose 会下载
postgres:14镜像(如果本地没有),然后创建并启动db服务。您会看到类似以下输出:[+] Running 2/2
✔ Network compose_default Created
✔ Volume "compose_pgdata" Created
✔ Container my-postgres-container Started - 
检查服务状态:
要查看容器是否正在运行,可以使用:bash
docker compose ps您将看到
my-postgres-container的状态,通常是running。 - 
查看日志:
要检查 PostgreSQL 容器的日志输出,以确保它已正确启动并正在监听连接:“`bash
docker compose logs db或者查看所有服务的日志
docker compose logs
“`您应该会看到 PostgreSQL 启动的详细信息,包括监听端口和数据库初始化消息。
 - 
连接到数据库:
现在,PostgreSQL 容器已在运行,您可以从宿主机使用任何 PostgreSQL 客户端连接到它。例如,使用psql命令行工具:“`bash
如果您的宿主机安装了psql
psql -h localhost -p 5432 -U user -d mydatabase
或者,从容器内部连接 (如果需要调试)
docker compose exec db psql -U user mydatabase
“`当提示输入密码时,输入您在
docker-compose.yml中定义的password。连接成功后,您就可以执行 SQL 查询了。 - 
停止与删除服务:
当您完成开发或需要清理环境时,可以停止并删除 Compose 定义的所有服务、网络和卷。“`bash
停止并删除容器和网络,但保留数据卷 (pgdata)
docker compose down
停止并删除容器、网络和数据卷 (彻底清理)
docker compose down -v
“`down:停止并删除docker-compose.yml中定义的服务和网络。-v:同时删除所有匿名卷和命名卷(在此例中是pgdata)。请谨慎使用此选项,因为它会删除您的数据库数据。
 
2.4 数据持久化详解
数据持久化是部署数据库的核心要素。Docker 容器默认是无状态的,容器被删除后,其内部的所有数据都会丢失。为了防止数据丢失,我们必须将数据库数据存储在容器外部。Docker 提供了两种主要机制:命名卷(Named Volumes)和绑定挂载(Bind Mounts)。
在上面的例子中,我们使用了命名卷 pgdata。
- 
命名卷(Named Volumes):
- 定义: 在 
docker-compose.yml文件的顶层volumes部分定义。 - 管理: 由 Docker 引擎管理,通常存储在宿主机的 
/var/lib/docker/volumes/目录下(Linux)。 - 优势:
- 更易管理和备份: Docker 负责创建、管理和存储位置,用户无需关心具体路径。
 - 跨平台兼容: 在不同操作系统上表现一致。
 - 更安全: 与容器的文件系统隔离,不容易被误删。
 
 - 使用场景: 大多数情况下的推荐方式,尤其是需要数据持久化而无需直接访问宿主机文件系统的场景。
 
 - 定义: 在 
 - 
绑定挂载(Bind Mounts):
- 定义: 直接将宿主机文件系统上的一个目录挂载到容器内部的指定路径。
 - 管理: 用户需要手动创建和管理宿主机上的目录。
 - 示例:
“`yaml
volumes:- ./pg_data:/var/lib/postgresql/data # 将当前目录下的pg_data文件夹挂载到容器内
“` 
 - ./pg_data:/var/lib/postgresql/data # 将当前目录下的pg_data文件夹挂载到容器内
 - 优势:
- 宿主机直接访问: 可以直接从宿主机的文件系统访问和修改容器内部挂载的数据。
 - 灵活: 适用于配置文件、本地开发调试等需要快速迭代的场景。
 
 - 使用场景:
- 希望直接在宿主机上编辑容器内部的配置文件。
 - 本地开发,快速将本地代码挂载到容器中进行测试。
 - 不推荐用于生产环境下的数据库数据存储,因为它将宿主机的文件系统结构暴露给容器,且管理不如命名卷方便。
 
 
 
对于 PostgreSQL 这种需要严格数据持久化的服务,命名卷通常是更优的选择。
第三章:进阶配置与最佳实践
在掌握了基本部署后,我们可以进一步优化配置,使其更健壮、更安全。
3.1 使用 .env 文件管理环境变量
将数据库密码等敏感信息直接写在 docker-compose.yml 文件中并不安全,尤其是当这个文件需要提交到版本控制系统时。最佳实践是使用 .env 文件来管理环境变量。
- 
在
docker-compose.yml同级目录下创建.env文件:POSTGRES_DB=mydatabase
POSTGRES_USER=user
POSTGRES_PASSWORD=secure_password_123!
PG_PORT=5432 - 
修改
docker-compose.yml文件,使用变量引用:“`yaml
version: ‘3.8’services:
db:
image: postgres:14
container_name: my-postgres-container
environment:
POSTGRES_DB: ${POSTGRES_DB} # 引用.env文件中的变量
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
ports:
– “${PG_PORT}:5432” # 端口也可以变量化
volumes:
– pgdata:/var/lib/postgresql/data
restart: unless-stoppedvolumes:
pgdata:
“` 
Docker Compose 会自动加载 .env 文件中的变量,并将其替换到 docker-compose.yml 中。请确保将 .env 文件添加到您的 .gitignore 中,以防止敏感信息被意外提交。
3.2 自定义网络
默认情况下,Docker Compose 会为您的所有服务创建一个默认的桥接网络。但在某些情况下,您可能需要自定义网络,以提供更好的隔离性或特定的网络配置。
- 
修改
docker-compose.yml,定义一个自定义网络:“`yaml
version: ‘3.8’services:
db:
image: postgres:14
container_name: my-postgres-container
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
ports:
– “${PG_PORT}:5432”
volumes:
– pgdata:/var/lib/postgresql/data
restart: unless-stopped
networks: # 将db服务连接到自定义网络
– my_app_networkvolumes:
pgdata:networks:
my_app_network: # 定义一个名为my_app_network的自定义网络
driver: bridge
“` 
现在 db 服务将连接到 my_app_network。如果您有其他服务(例如一个 Web 应用程序),您可以将它们也连接到同一个 my_app_network,然后它们就可以通过服务名(例如 db)相互通信。
3.3 连接 Web 应用程序到 PostgreSQL
这是多容器应用中最常见的场景。假设您有一个 Node.js 或 Python 的 Web 应用程序服务:
“`yaml
version: ‘3.8’
services:
web:
build: . # 假设您的Web应用程序Dockerfile在当前目录
container_name: my-web-app
ports:
– “8080:8080”
environment:
DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:${PG_PORT}/${POSTGRES_DB} # 使用服务名”db”来连接
depends_on:
– db # 确保db服务在web服务之前启动
networks:
– my_app_network
db:
image: postgres:14
container_name: my-postgres-container
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
ports:
– “${PG_PORT}:5432”
volumes:
– pgdata:/var/lib/postgresql/data
restart: unless-stopped
networks:
– my_app_network
volumes:
pgdata:
networks:
my_app_network:
driver: bridge
“`
关键点:
depends_on: - db:这个指令告诉 Docker Compose,web服务依赖于db服务。Compose 会在启动web服务之前先启动db服务。请注意,depends_on仅确保服务启动顺序,不保证数据库完全就绪。 对于生产环境,您可能需要结合健康检查。DATABASE_URL:Web 应用程序可以通过db作为主机名来连接 PostgreSQL,因为它们在同一个my_app_network中。端口仍然是容器内部的 5432。
3.4 数据库初始化脚本
在开发或测试环境中,您可能希望在 PostgreSQL 容器首次启动时自动执行一些 SQL 脚本,例如创建表、插入初始数据等。PostgreSQL 官方 Docker 镜像支持通过挂载初始化脚本来实现这一点。
- 在 
docker-compose.yml同级目录下创建init-scripts目录。 - 
在该目录下创建
init.sql文件:“`sql
— init.sql
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL
);INSERT INTO users (name, email) VALUES
(‘Alice’, ‘[email protected]’),
(‘Bob’, ‘[email protected]’)
ON CONFLICT (email) DO NOTHING;
“` - 
修改
docker-compose.yml,将init-scripts目录绑定挂载到容器的/docker-entrypoint-initdb.d目录:“`yaml
version: ‘3.8’services:
db:
image: postgres:14
container_name: my-postgres-container
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
ports:
– “${PG_PORT}:5432”
volumes:
– pgdata:/var/lib/postgresql/data
– ./init-scripts:/docker-entrypoint-initdb.d # 挂载初始化脚本目录
restart: unless-stopped
networks:
– my_app_networkvolumes:
pgdata:networks:
my_app_network:
driver: bridge
“` 
当容器首次启动时(即 pgdata 卷为空时),PostgreSQL 镜像会自动执行 /docker-entrypoint-initdb.d 目录下的所有 *.sql、*.sh 和 *.sql.gz 文件。这对于设置初始模式和数据非常有用。
3.5 健康检查 (Health Check)
depends_on 只能保证服务启动顺序,无法保证服务完全就绪。例如,PostgreSQL 容器可能已经启动,但数据库服务尚未完全初始化,无法接受连接。这时,Web 应用如果立即尝试连接,可能会失败。健康检查可以解决这个问题。
在 docker-compose.yml 中为 db 服务添加健康检查:
“`yaml
version: ‘3.8’
services:
web:
# … (与上面相同)
healthcheck: # 为web服务添加健康检查依赖
test: [“CMD-SHELL”, “curl -f http://localhost:8080/health || exit 1”] # 假设web服务有一个健康检查接口
interval: 30s
timeout: 10s
retries: 5
depends_on:
db:
condition: service_healthy # 只有当db服务健康时才启动web服务
db:
image: postgres:14
container_name: my-postgres-container
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
ports:
– “${PG_PORT}:5432”
volumes:
– pgdata:/var/lib/postgresql/data
– ./init-scripts:/docker-entrypoint-initdb.d
restart: unless-stopped
networks:
– my_app_network
healthcheck: # 为db服务添加健康检查
test: [“CMD-SHELL”, “pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}”] # 使用PostgreSQL自带的pg_isready工具
interval: 5s # 每5秒检查一次
timeout: 5s # 检查超时时间
retries: 5 # 失败重试次数
start_period: 10s # 在开始检查前等待10秒,给容器启动时间
volumes:
pgdata:
networks:
my_app_network:
driver: bridge
“`
healthcheck:定义容器的健康检查。test:执行健康检查的命令。对于 PostgreSQL,pg_isready是一个很好的工具,它会检查数据库是否接受连接。interval:两次健康检查之间的时间间隔。timeout:健康检查命令的超时时间。retries:在将容器标记为不健康之前,健康检查失败的连续次数。start_period:容器启动后,健康检查开始前的宽限期。在此期间,失败的健康检查不会计入重试次数。
depends_on: db: condition: service_healthy:现在web服务将等待db服务被 Docker 标记为healthy之后才启动。这显著提升了多服务应用的健壮性。
第四章:生产环境考量与高级主题
虽然 Docker Compose 主要用于开发和测试环境,但了解一些生产环境的考量和高级功能仍然很有价值。
4.1 备份与恢复
数据备份是任何数据库部署的基石。
备份:
您可以从正在运行的 PostgreSQL 容器中执行 pg_dump 命令来备份数据库。
“`bash
备份整个数据库到宿主机
docker compose exec db pg_dump -U user mydatabase > ./backup/mydatabase_$(date +%Y%m%d%H%M%S).sql
“`
或者,您可以创建一个独立的备份服务或使用卷管理工具来定期备份 pgdata 卷。
恢复:
要恢复数据库,您可以将备份文件导入到新启动或清空的 PostgreSQL 容器中。
- 如果需要,停止并删除现有数据库容器(或创建一个新的):
bash
docker compose down -v - 启动新的数据库容器(或重新启动现有容器),确保 
pgdata卷是空的或已被清理。
bash
docker compose up -d db - 将备份文件复制到容器内,并执行 
psql命令恢复:
bash
# 假设你的备份文件在宿主机当前目录的 './backup/mydatabase.sql'
cat ./backup/mydatabase.sql | docker compose exec -T db psql -U user mydatabase
-T选项用于禁用伪 TTY 分配,因为我们通过管道输入数据。 
4.2 资源限制
在生产环境中,限制容器可以使用的 CPU 和内存资源非常重要,以防止单个服务耗尽宿主机资源。
yaml
services:
db:
# ...
deploy: # 注意:deploy部分通常用于Swarm模式,但在Docker Compose 3版本中也可用于配置单节点容器资源
resources:
limits:
cpus: '0.5' # 限制为0.5个CPU核心
memory: 1G # 限制内存为1GB
reservations:
cpus: '0.25' # 预留0.25个CPU核心
memory: 512M # 预留512MB内存
limits:容器可以使用的最大资源。reservations:为容器预留的资源。
4.3 读写分离与高可用(HA)——Docker Compose 的局限性
虽然 Docker Compose 可以轻松部署单个 PostgreSQL 实例,但它本身不适合用于生产环境下的真正读写分离和高可用(HA)集群。Compose 侧重于单机多服务编排,缺乏分布式系统管理、自动故障转移、共享存储等高可用特性。
然而,为了学习或在开发环境中模拟,您可以使用 Compose 部署一个简单的 primary-replica(主从)复制设置:
“`yaml
version: ‘3.8’
services:
db_primary:
image: postgres:14
container_name: postgres_primary
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
# 启用WAL归档,为复制做准备
POSTGRES_INITDB_ARGS: “–wal-segsize=64 –waldir=/var/lib/postgresql/data/pg_wal” # 示例,可能需要根据版本调整
ports:
– “5432:5432”
volumes:
– pgdata_primary:/var/lib/postgresql/data
– ./init-scripts:/docker-entrypoint-initdb.d
restart: unless-stopped
networks:
– my_app_network
db_replica:
image: postgres:14
container_name: postgres_replica
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
PGDATA: /var/lib/postgresql/data # 确保指向数据目录
volumes:
– pgdata_replica:/var/lib/postgresql/data
restart: unless-stopped
networks:
– my_app_network
depends_on:
– db_primary
command: # 启动为只读副本
– postgres
– -c
– “primary_conninfo=host=db_primary port=5432 user=${POSTGRES_USER} password=${POSTGRES_PASSWORD}”
– -c
– “hot_standby=on”
– -c
– “wal_level=replica”
– -c
– “max_wal_senders=10”
– -c
– “max_replication_slots=10”
– -c
– “listen_addresses=*”
– -c
– “standby_mode=on” # 旧版本PostgreSQL可能需要此项,新版本由primary_conninfo自动识别
volumes:
pgdata_primary:
pgdata_replica:
networks:
my_app_network:
driver: bridge
``pg_basebackup
**注意:**
*   这只是一个非常简化的主从配置示例,主要用于学习和测试。它没有处理初始数据同步、故障转移机制、连接字符串切换等生产级 HA 方案所需的复杂性。
*   在实际生产中,对于高可用 PostgreSQL 集群,通常会使用更专业的解决方案,如 Patroni、Stolon、Cloud Native PostgreSQL Operator (在 Kubernetes 上) 等。
*   **初次配置 Primary-Replica:** 首次启动时,replica 需要从 primary 复制数据。这可能需要手动干预,例如使用来初始化pgdata_replica卷。上述command参数假定 replica 卷已经与 primary 同步或可以从 primary 拉取数据。在实际操作中,通常在db_replica启动前,先执行pg_basebackup` 命令,从主库复制基础备份。
4.4 外部配置文件
除了环境变量,PostgreSQL 还有 postgresql.conf 和 pg_hba.conf 等重要的配置文件。您可以使用绑定挂载来管理这些文件。
- 创建 
config目录,并在其中放置您的自定义postgresql.conf和pg_hba.conf。 - 
修改
docker-compose.yml:yaml
services:
db:
# ...
volumes:
- pgdata:/var/lib/postgresql/data
- ./init-scripts:/docker-entrypoint-initdb.d
- ./config/postgresql.conf:/etc/postgresql/postgresql.conf # 覆盖默认配置
- ./config/pg_hba.conf:/etc/postgresql/pg_hba.conf # 覆盖默认认证配置
# ...
注意: 对于PostgreSQL官方镜像,postgresql.conf通常在/var/lib/postgresql/data/postgresql.conf。在大多数情况下,最好使用PGOPTIONS环境变量或command参数来覆盖部分配置,而不是直接替换整个文件,以避免与镜像的默认行为产生冲突。然而,对于pg_hba.conf,直接替换往往是必需的。务必小心修改,错误的配置可能导致数据库无法启动或无法连接。 
4.5 升级 PostgreSQL 版本
使用 Docker Compose 升级 PostgreSQL 版本相对简单,但需要谨慎操作以确保数据兼容性。
- 备份当前数据: 在升级前,务必对数据库进行完整备份。
 - 停止服务: 
docker compose down。 - 修改 
docker-compose.yml中的image版本: 例如,从postgres:14改为postgres:15。 - 数据迁移:
- 方法一(新卷,手动迁移): 这是最安全的方式。创建一个新的命名卷(例如 
pgdata_new),启动新版本的 PostgreSQL 容器,然后将旧卷中的数据导入到新容器中。 - 方法二(
pg_upgrade): Docker 官方 PostgreSQL 镜像并不直接支持pg_upgrade工具的自动运行。您需要在一个临时容器中手动执行pg_upgrade。这个过程比较复杂,通常涉及启动两个不同版本的容器,然后在一个容器中运行pg_upgrade工具,指向两个不同版本的数据目录。 - 方法三(少量数据,直接替换,不推荐): 对于开发环境中的少量非关键数据,您可以尝试直接将旧卷挂载到新版本容器。但PostgreSQL不同主版本之间的数据目录格式可能不兼容,这通常会导致容器启动失败。强烈不推荐在生产环境或数据重要时使用此方法。
 
 - 方法一(新卷,手动迁移): 这是最安全的方式。创建一个新的命名卷(例如 
 
因此,最推荐且最安全的升级策略是创建一个新的命名卷来运行新版本的 PostgreSQL,然后将旧数据库的数据导出(pg_dump)并导入(psql)到新数据库中。
第五章:故障排除与常见问题
即使是最简单的部署也可能遇到问题。
- 容器无法启动:
- 查看日志: 
docker compose logs db是您的第一站。错误消息通常会指示问题所在,例如配置错误、端口冲突、数据卷权限问题等。 - 端口冲突: 如果宿主机的 5432 端口已被占用,容器将无法绑定该端口。尝试更改 
ports映射(例如5433:5432)或停止占用该端口的进程。 - 环境变量错误: 检查 
environment部分的变量名称和值是否正确。 
 - 查看日志: 
 - 数据持久化问题:
- 确认 
volumes配置是否正确,以及命名卷是否已创建并挂载成功。 - 如果数据丢失,检查是否在 
docker compose down时使用了-v选项,或者卷是否被意外删除。 
 - 确认 
 - 连接问题:
- 确保端口映射正确。
 - 检查防火墙规则,确保宿主机的 5432 端口允许外部连接。
 - 确认数据库用户、密码和数据库名称是否正确。
 - 如果从其他容器连接,确保它们在同一个 Docker Compose 网络中,并使用服务名作为主机名。
 
 - 初始化脚本未执行:
- 确保初始化脚本放在 
/docker-entrypoint-initdb.d目录下。 - 最重要的一点: 只有当数据卷(
pgdata)是空的时候,初始化脚本才会在容器首次启动时执行。如果pgdata卷已经有数据了,这些脚本不会再次运行。要重新运行初始化脚本,您需要删除pgdata卷 (docker volume rm your_compose_project_pgdata),然后重新docker compose up -d。 
 - 确保初始化脚本放在 
 
总结与展望
通过本文的详细介绍,您应该已经全面掌握了如何利用 Docker Compose 快速、高效地部署和管理 PostgreSQL 数据库。我们从 Docker 和 Docker Compose 的基本优势出发,逐步构建了一个健壮的 PostgreSQL 服务,并探讨了数据持久化、环境变量管理、自定义网络、数据库初始化脚本、健康检查等核心概念和最佳实践。
Docker Compose 在简化本地开发、测试和小型应用部署方面展现出无与伦比的优势。它使得环境配置变得声明式且可重复,极大地减少了“它在我机器上能跑”的问题,提升了团队协作效率。
虽然 Docker Compose 对于单机多服务应用非常强大,但当您的应用需要进入大规模生产环境,特别是需要高可用、水平伸缩和更复杂的资源调度时,像 Kubernetes 这样的容器编排平台将是更合适的选择。然而,即使在那时,您对 Docker 和 Docker Compose 的理解仍将是无价的基础。
希望这篇详细的文章能帮助您更好地利用 Docker Compose 简化您的开发工作流,让 PostgreSQL 数据库的部署和管理变得前所未有的简单和高效。现在,就动手实践,体验容器化带来的便利吧!