Docker
是部署 PostgreSQL 最便捷的方式,无论本地开发、CI 测试还是小型生产环境,都可以通过一键 docker-compose up 快速起一套完整的数据库环境。本文按 开发 → 测试 → 生产 三阶段递进,提供每一阶段的配置清单。
一句话总结:开发期用 docker-compose 一键起全套,生产期给 Docker 装上参数调优、连接池、监控和备份。
一、开发环境一键配置
1.1 基础 Docker Compose(最小可用)
# docker-compose.dev.yml
services:
postgres:
image: postgres:17-alpine
container_name: postgres-dev
restart: unless-stopped
environment:
POSTGRES_USER: devuser
POSTGRES_PASSWORD: devpass
POSTGRES_DB: myapp
ports:
- "5432:5432"
volumes:
- pg_data:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U devuser -d myapp"]
interval: 5s
timeout: 5s
retries: 5
pgadmin:
image: dpage/pgadmin4:8
container_name: pgadmin-dev
restart: unless-stopped
environment:
PGADMIN_DEFAULT_EMAIL: admin@example.com
PGADMIN_DEFAULT_PASSWORD: admin
ports:
- "5050:80"
depends_on:
postgres:
condition: service_healthy
volumes:
pg_data:
启动命令:
docker-compose -f docker-compose.dev.yml up -d
# 初始化脚本无需手动执行
1.2 初始化 Schema 自动导入
Docker 镜像支持在首次启动时自动执行 /docker-entrypoint-initdb.d/ 下的脚本:
-- init.sql
-- 创建业务 Schema
CREATE SCHEMA IF NOT EXISTS app;
-- 创建用户表
CREATE TABLE app.users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
name VARCHAR(100),
created_at TIMESTAMPTZ DEFAULT now()
);
-- 创建角色与权限
CREATE ROLE app_read WITH LOGIN PASSWORD 'readpass';
GRANT USAGE ON SCHEMA app TO app_read;
GRANT SELECT ON ALL TABLES IN SCHEMA app TO app_read;
-- 创建初始数据
INSERT INTO app.users (email, name) VALUES
('demo@example.com', 'Demo User'),
('admin@example.com', 'Administrator');
注意:初始化脚本只在数据目录为空时执行一次。如果修改脚本后也想重新执行,需删除 volume 重新创建。
二、测试环境配置
2.1 测试专用 Compose(接近生产,但资源有限)
services:
postgres:
image: postgres:17-alpine
container_name: postgres-test
restart: unless-stopped
environment:
POSTGRES_USER: testuser
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-testpass}
POSTGRES_DB: testdb
command:
- "postgres"
- "-c"
- "max_connections=100"
- "-c"
- "shared_buffers=256MB"
- "-c"
- "effective_cache_size=512MB"
- "-c"
- "work_mem=8MB"
- "-c"
- "maintenance_work_mem=64MB"
- "-c"
- "max_wal_size=1GB"
- "-c"
- "synchronous_commit=off" # 测试环境可提升写入性能
ports:
- "5433:5432"
volumes:
- pg_test_data:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro
healthcheck:
test: ["CMD-SHELL", "pg_isready -U testuser -d testdb"]
interval: 5s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
container_name: redis-test
restart: unless-stopped
ports:
- "6379:6379"
volumes:
pg_test_data:
三、生产环境配置
3.1 生产级 Docker Compose
services:
postgres:
image: postgres:17-alpine
container_name: postgres-prod
restart: always
# 安全性:非 root 运行
user: "999:999"
env_file:
- .env.postgres
command:
- "postgres"
- "-c"
- "max_connections=200"
- "-c"
- "shared_buffers=512MB"
- "-c"
- "effective_cache_size=1536MB"
- "-c"
- "work_mem=16MB"
- "-c"
- "maintenance_work_mem=256MB"
- "-c"
- "wal_buffers=16MB"
- "-c"
- "random_page_cost=1.1"
- "-c"
- "max_wal_size=4GB"
- "-c"
- "wal_level=replica"
- "-c"
- "max_wal_senders=5"
- "-c"
- "synchronous_commit=remote_apply"
- "-c"
- "log_min_duration_statement=1000" # 慢查询阈值为 1s
- "-c"
- "log_destination=stderr"
- "-c"
- "logging_collector=on"
- "-c"
- "log_directory=/var/log/postgresql"
ports:
- "5432:5432"
volumes:
- pg_data:/var/lib/postgresql/data
- pg_logs:/var/log/postgresql
- ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro
- /etc/localtime:/etc/localtime:ro
ulimits:
nofile:
soft: 65536
hard: 65536
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
pgbouncer:
image: pgbouncer/pgbouncer:1.22
container_name: pgbouncer-prod
restart: always
environment:
DATABASES_HOST: postgres
DATABASES_PORT: 5432
DATABASES_DATABASE: $${POSTGRES_DB}
POOL_MODE: transaction
MAX_CLIENT_CONN: 10000
DEFAULT_POOL_SIZE: 25
MIN_POOL_SIZE: 10
AUTH_TYPE: md5
ports:
- "6432:6432"
depends_on:
postgres:
condition: service_healthy
postgres_exporter:
image: prometheuscommunity/postgres-exporter:v0.15
container_name: postgres-exporter
restart: always
environment:
DATA_SOURCE_NAME: "postgresql://prometheus:${POSTGRES_PASSWORD}@postgres:5432/postgres?sslmode=disable"
ports:
- "9187:9187"
depends_on:
postgres:
condition: service_healthy
volumes:
pg_data:
driver: local
pg_logs:
driver: local
3.2 环境变量文件(.env.postgres)
POSTGRES_USER=myapp_prod
POSTGRES_PASSWORD=<strong-password>
POSTGRES_DB=myapp
POSTGRES_INITDB_ARGS="--encoding=UTF8 --locale=en_US.utf8"
安全注意:密码使用 Docker Secrets(Swarm 模式)或 Vault 注入,切勿将 .env 文件提交到 Git。
四、备份策略
4.1 逻辑备份(pg_dump)
# 每日全量备份(通过 Docker exec)
docker compose exec postgres pg_dump -U myapp_prod -d myapp -Fc -f /backups/myapp_$(date +%Y%m%d).dump
# 恢复逻辑备份
pg_restore -U myapp_prod -d myapp myapp_20250801.dump
4.2 物理备份(WAL 归档 + pg_basebackup)
# 配置 WAL 归档
# postgresql.conf(Docker 中通过 command 传递或通过 config 卷挂载)
archive_mode = on
archive_command = 'cp %p /backups/wal/%f'
wal_level = replica
# 基础备份
pg_basebackup -D /backups/basebackup -Ft -z -P -h postgres -p 5432 -U repluser -W
4.3 备份策略矩阵
| 备份类型 | 工具 | 频率 | 恢复时间 | 用途 |
|---|---|---|---|---|
| 逻辑全量 | pg_dump | 每日 | 分钟级 | 数据迁移、迁移验证 |
| 逻辑增量 | pg_dump –data-only + 条件 | 每小时 | 分钟级 | 表级恢复 |
| 物理基础 | pg_basebackup | 每周 | 分钟级 | 完整实例恢复 |
| WAL 归档 | archive_command | 持续 | 秒级 PITR | 时间点恢复 |
五、监控与告警
5.1 监控架构
PostgreSQL
↓
postgres_exporter(指标采集)
↓
Prometheus(指标存储)
↓
Grafana(可视化/告警)
5.2 关键监控指标
| 指标 | 告警阈值 | 说明 |
|---|---|---|
| pg_stat_database_active | > 80% max_connections | 连接数告警 |
| pg_stat_database_blks_hit_ratio | < 90% | 缓存命中率低 |
| pg_stat_database_commits | 趋势监控 | 吞吐量 |
| pg_stat_database_deadlocks | > 0 持续 | 死锁告警 |
| pg_stat_activity_wait_event | > 5s | 长等待查询 |
5.3 快速启动监控
# docker-compose.monitoring.yml
services:
prometheus:
image: prom/prometheus:v2.50
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
grafana:
image: grafana/grafana:10.4
ports:
- "3004:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
volumes:
- grafana_data:/var/lib/grafana
volumes:
grafana_data:
# prometheus.yml
scrape_configs:
- job_name: 'postgres'
static_configs:
- targets: ['postgres-exporter:9187']
scrape_interval: 15s
六、高可用与集群部署
6.1 Patroni + etcd 自动故障转移
Docker 环境下部署 PostgreSQL 高可用集群,Patroni 是首选方案:
# docker-compose.ha.yml
services:
etcd:
image: quay.io/coreos/etcd:v3.5
command: >
etcd --name etcd0
--initial-advertise-peer-urls http://etcd:2380
--listen-peer-urls http://0.0.0.0:2380
--listen-client-urls http://0.0.0.0:2379
--advertise-client-urls http://etcd:2379
--initial-cluster-token patroni-cluster
--initial-cluster etcd0=http://etcd:2380
--initial-cluster-state new
postgres-primary:
image: patroni:latest
environment:
PATRONI_ETCD3_HOSTS: "'etcd:2379'"
PATRONI_SCOPE: mycluster
PATRONI_NAME: node1
PATRONI_RESTAPI_LISTEN: 0.0.0.0:8008
PATRONI_POSTGRESQL_LISTEN: 0.0.0.0:5432
PATRONI_POSTGRESQL_DATA_DIR: /var/lib/postgresql/data
volumes:
- pg_ha_data:/var/lib/postgresql/data
depends_on:
- etcd
postgres-replica:
image: patroni:latest
environment:
PATRONI_ETCD3_HOSTS: "'etcd:2379'"
PATRONI_SCOPE: mycluster
PATRONI_NAME: node2
PATRONI_RESTAPI_LISTEN: 0.0.0.0:8008
PATRONI_POSTGRESQL_LISTEN: 0.0.0.0:5432
PATRONI_POSTGRESQL_DATA_DIR: /var/lib/postgresql/data
volumes:
- pg_ha_data2:/var/lib/postgresql/data
depends_on:
- etcd
haproxy:
image: haproxy:2.9
volumes:
- ./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
ports:
- "5432:5432"
- "7000:7000"
depends_on:
- postgres-primary
- postgres-replica
Patroni 自动管理 Leader 选举、故障检测和切换,应用通过 HAProxy 访问虚拟 IP,无需感知后端拓扑变化。
6.2 流复制与读写分离
| 角色 | 连接串 | 用途 |
|---|---|---|
| 主库 | postgresql://writer@pg-primary:5432/myapp | 写入 |
| 从库 | postgresql://reader@pg-replica:5432/myapp | 只读查询 |
| PgBouncer | postgresql://app@pgbouncer:6432/myapp | 连接池 + 路由 |
应用层可通过环境变量切换读写节点,或使用 PgPool-II 在数据库层自动分发。
七、扩展插件容器化
7.1 PostGIS(地理空间扩展)
FROM postgis/postgis:17-3.4-alpine
# 或在初始化脚本中启用
# CREATE EXTENSION postgis;
7.2 TimescaleDB(时序数据)
FROM timescale/timescaledb:latest-pg17
# 初始化:CREATE EXTENSION timescaledb;
7.3 pgvector(向量检索 / AI 场景)
FROM ankane/pgvector:latest
# 初始化:CREATE EXTENSION vector;
# CREATE INDEX ON items USING ivfflat (embedding vector_cosine_ops);
扩展镜像选型原则:优先用官方维护版本,测试扩展兼容性(部分扩展依赖 glibc,Alpine 镜像可能不支持)。
八、Docker 卷性能优化
| 卷类型 | 性能 | 适用场景 | 注意 |
|---|---|---|---|
| Named Volume | 接近原生 | 生产首选 | 由 Docker 管理,迁移方便 |
| Bind Mount | 依赖宿主机文件系统 | 开发调试 | 权限问题多 |
| Volume Driver (NFS) | 网络延迟 | 多节点共享 | 不适合高并发写入 |
| tmpfs | 内存级速度 | 临时数据 / 测试 | 容器停止数据丢失 |
生产环境建议使用 本地 NVMe SSD + Named Volume,配合 Docker 的 io_cgroup 限制防止单个容器耗尽磁盘 IOPS。
九、安全加固
9.1 TLS/SSL 通信加密
services:
postgres:
volumes:
- ./ssl/server.crt:/var/lib/postgresql/server.crt:ro
- ./ssl/server.key:/var/lib/postgresql/server.key:ro
command:
- "postgres"
- "-c"
- "ssl=on"
- "-c"
- "ssl_cert_file=/var/lib/postgresql/server.crt"
- "-c"
- "ssl_key_file=/var/lib/postgresql/server.key"
9.2 网络安全
- 数据库容器不暴露端口到公网,仅通过内部网络访问
- 使用 Docker Network 隔离:
docker network create --internal pg_internal - 配合防火墙规则限制源 IP
9.3 Secret 管理
| 方案 | 安全级别 | 复杂度 |
|---|---|---|
.env 文件 | 低 | 低 |
| Docker Secrets (Swarm) | 中 | 中 |
| HashiCorp Vault | 高 | 高 |
| AWS Secrets Manager | 高 | 中 |
生产环境推荐 Vault 或云厂商 Secret 服务,结合 init container 在启动时注入凭证。
常见问题(FAQ)
Docker 中的数据如何持久化?
使用具名 Volume(上例中的 pg_data)。即使容器被删除,pg_data 的数据仍然存在。
生产环境应该使用 postgres:alpine 吗?
推荐。Alpine 镜像体积小(~80MB vs ~150MB),生产环境更轻量。但如果需要安装系统级扩展(如 PostGIS 需要 glibc 兼容库),应使用标准 Debian 镜像 postgres:17。
PgBouncer 事务池模式下,SET 语句和临时表还能用吗?
不能。事务池模式(POOL_MODE=transaction)在同一个连接上的不同事务之间没有会话状态(临时表被清理)。如果应用需要会话级状态,需改用 Session 池或调整事务边界。
如何升级 PostgreSQL 版本?
# 方法一:pg_upgrade(原地升级)
docker compose exec postgres pg_upgrade \
--old-datadir /var/lib/postgresql/16/data \
--new-datadir /var/lib/postgresql/17/data \
--check
# 方法二:更安全——pg_dumpall + 新版本恢复
docker compose exec postgres pg_dumpall -U myapp_prod > full_backup.sql
# 停止旧版本、启动新版本、导入 full_backup.sql
相关阅读
- PostgreSQL 详解 — 核心架构、MVCC、JSONB、索引
- PostgreSQL vs MySQL vs MongoDB — 12 维度选型
- PostgreSQL 性能优化 — EXPLAIN ANALYZE、索引选择
- PostgreSQL 高可用与备份 — Patroni 流复制
- Docker 专题 — 容器化完整指南
- PostgreSQL 专题导航
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。