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
常见问题(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 专题导航
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。