PostgreSQL Docker 部署与初始化:开发到生产的完整配置手册

详解 PostgreSQL 的 Docker 化部署:从基础 docker-compose、开发环境初始 Schema,到生产级参数调优、PgBouncer 连接池、监控(Grafana + Prometheus)、备份策略(pg_dump + pg_basebackup),提供全链路配置清单。

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

相关阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「database」更多文章