Docker 安装
Docker 是本地开发、CI 与测试环境中运行 PostgreSQL 最轻量的方式:一条命令即可获得干净的实例,用完删除不留痕迹。本节详解官方镜像拉取、docker run 全部常用参数、数据卷持久化、docker-compose 编排、初始化脚本挂载,以及生产上常见的坑。
前置知识
阅读本节前,建议先了解:Windows 安装
基础概念
- 镜像(Image):打包好的 PostgreSQL 运行时,官方镜像名为
postgres - 容器(Container):镜像的运行实例;容器内的数据目录默认是临时的,删除容器即丢失
- 卷(Volume):Docker 管理的持久化存储,用于把数据库数据保存到宿主机
- 绑定挂载(Bind Mount):把宿主机目录直接映射进容器,便于查看与备份
- 入口点脚本(Entrypoint Script):官方镜像在首次启动时执行的
docker-entrypoint.sh,负责初始化数据库、执行/docker-entrypoint-initdb.d下的脚本 - 初始化目录(Init Directory):
/docker-entrypoint-initdb.d,仅在数据目录为空时执行一次
| 存储方式 | 语法 | 特点 | 推荐度 |
|---|---|---|---|
| 匿名卷 | -v /var/lib/postgresql/data | Docker 自动分配路径,难定位 | 不推荐 |
| 命名卷 | -v pgdata:/var/lib/postgresql/data | 由 Docker 管理,跨平台一致 | 推荐 |
| 绑定挂载 | -v /data/pg:/var/lib/postgresql/data | 路径可见可控,需处理权限 | 生产推荐 |
版本说明
PostgreSQL 18 官方镜像的数据目录路径有重要变化:镜像内部统一使用 /var/lib/postgresql(而不是旧版的 /var/lib/postgresql/data)作为 PGDATA 的父目录,并将 PGDATA 指向带版本号的子目录。挂载时建议直接挂 /var/lib/postgresql,或显式设置 PGDATA。下文示例已按 18 的布局给出。
安装与配置
拉取镜像
bash
# 拉取指定大版本(推荐,避免 latest 漂移)
docker pull postgres:18
# 拉取精确小版本(生产环境推荐,可复现)
docker pull postgres:18.0
# 基于 Debian 的精简版本(体积更小)
docker pull postgres:18-alpine
# 查看已拉取镜像
docker images | grep postgrestext
REPOSITORY TAG IMAGE ID CREATED SIZE
postgres 18 8f0e1ba2f1b9 5 days ago 452MB
postgres 18-alpine 1c2b7d94aa31 5 days ago 279MBdocker run 完整参数详解
bash
docker run -d \
--name pg18 \
-e POSTGRES_PASSWORD='Str0ng_P@ssw0rd' \
-e POSTGRES_USER=postgres \
-e POSTGRES_DB=demo \
-e POSTGRES_INITDB_ARGS="--encoding=UTF8 --locale=C" \
-e TZ=Asia/Shanghai \
-p 5432:5432 \
-v pgdata18:/var/lib/postgresql \
--restart unless-stopped \
--shm-size=512m \
postgres:18| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
-d | flag | —— | 后台运行(detached) |
--name | string | 随机名 | 容器名,后续管理用 |
-e POSTGRES_PASSWORD | string | 无(必填) | 超级用户密码,不设置且未开启免密会导致启动失败 |
-e POSTGRES_USER | string | postgres | 超级用户名 |
-e POSTGRES_DB | string | 等于 POSTGRES_USER | 首次初始化时创建的数据库 |
-e POSTGRES_INITDB_ARGS | string | 空 | 传给 initdb 的额外参数 |
-e POSTGRES_HOST_AUTH_METHOD | string | scram-sha-256 | pg_hba.conf 中的默认认证方式,慎用 trust |
-e PGDATA | string | 镜像内默认值 | 数据目录路径,改路径时需同步改挂载点 |
-e TZ | string | UTC | 容器时区,影响 now() 显示 |
-p 5432:5432 | 映射 | 无 | 宿主机端口:容器端口 |
-v | 挂载 | 匿名卷 | 数据持久化 |
--restart | string | no | 重启策略:no / on-failure / always / unless-stopped |
--shm-size | size | 64MB | 共享内存大小,并行查询多时需调大 |
--health-cmd | string | 无 | 健康检查命令 |
环境变量一览
| 变量 | 作用 | 备注 |
|---|---|---|
POSTGRES_PASSWORD | 设置超级用户密码 | 与 POSTGRES_HOST_AUTH_METHOD=trust 二选一 |
POSTGRES_USER | 超级用户名 | 同时会创建同名数据库(除非指定 POSTGRES_DB) |
POSTGRES_DB | 初始数据库名 | 默认与超级用户名相同 |
POSTGRES_INITDB_ARGS | initdb 参数 | 如 --data-checksums、--encoding=UTF8 |
POSTGRES_INITDB_WALDIR | WAL 目录 | 把 pg_wal 分离到独立卷 |
POSTGRES_HOST_AUTH_METHOD | 主机认证方式 | trust 会允许无密码连接,仅限本地开发 |
PGDATA | 数据目录 | 必须与卷挂载点匹配 |
验证运行状态
bash
# 查看容器状态
docker ps --filter name=pg18
# 查看启动日志(首次启动会打印 initdb 过程)
docker logs -f pg18
# 进入容器内的 psql
docker exec -it pg18 psql -U postgres -d demotext
psql (18.0 (Debian 18.0-1.pgdg120+1))
Type "help" for help.
demo=# SELECT version();
version
-------------------------------------------------------
PostgreSQL 18.0 (Debian 18.0-1.pgdg120+1) on x86_64
(1 row)详细说明
数据卷持久化
bash
# 创建命名卷
docker volume create pgdata18
# 查看卷信息(含实际存储路径)
docker volume inspect pgdata18text
[
{
"CreatedAt": "2026-09-21T10:00:00+08:00",
"Driver": "local",
"Mountpoint": "/var/lib/docker/volumes/pgdata18/_data",
"Name": "pgdata18",
"Scope": "local"
}
]bash
# 用命名卷启动容器
docker run -d --name pg18 \
-e POSTGRES_PASSWORD='Str0ng_P@ssw0rd' \
-p 5432:5432 \
-v pgdata18:/var/lib/postgresql \
postgres:18
# 停止并删除容器,数据仍在卷中
docker rm -f pg18
docker volume ls | grep pgdata18
# 重新挂载同一卷,数据完整保留
docker run -d --name pg18-new \
-e POSTGRES_PASSWORD='Str0ng_P@ssw0rd' \
-p 5432:5432 \
-v pgdata18:/var/lib/postgresql \
postgres:18
docker exec pg18-new psql -U postgres -c "\l"bash
# 绑定挂载(宿主机目录直接可见,便于备份与查看)
mkdir -p /data/pg18
# 注意:容器内进程 UID 为 999(postgres),需给目录授权
sudo chown -R 999:999 /data/pg18
sudo chmod 700 /data/pg18
docker run -d --name pg18-bind \
-e POSTGRES_PASSWORD='Str0ng_P@ssw0rd' \
-p 5433:5432 \
-v /data/pg18:/var/lib/postgresql \
postgres:18bash
# 备份与恢复卷数据
docker run --rm -v pgdata18:/var/lib/postgresql -v $(pwd):/backup \
alpine tar czf /backup/pgdata18-$(date +%F).tar.gz -C /var/lib/postgresql .docker-compose.yml 完整示例
yaml
# docker-compose.yml
services:
postgres:
image: postgres:18.0
container_name: pg18
restart: unless-stopped
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: Str0ng_P@ssw0rd
POSTGRES_DB: demo
# initdb 参数:开启数据校验和,能提前发现静默损坏
POSTGRES_INITDB_ARGS: "--encoding=UTF8 --locale=C --data-checksums"
TZ: Asia/Shanghai
ports:
- "5432:5432"
volumes:
# 数据持久化
- pgdata18:/var/lib/postgresql
# 初始化脚本:仅在数据目录为空时执行
- ./initdb:/docker-entrypoint-initdb.d:ro
# 自定义配置:需要包含 PostgreSQL 标准配置片段
- ./conf/postgresql.conf:/etc/postgresql/postgresql.conf:ro
command:
- postgres
- -c
- config_file=/etc/postgresql/postgresql.conf
shm_size: 512m
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d demo"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
# 资源限制,避免容器吃满宿主机
deploy:
resources:
limits:
memory: 2G
# 常与 PostgreSQL 搭配的 pgAdmin
pgadmin:
image: dpage/pgadmin4:latest
container_name: pgadmin
restart: unless-stopped
environment:
PGADMIN_DEFAULT_EMAIL: admin@example.com
PGADMIN_DEFAULT_PASSWORD: Admin_P@ss
PGADMIN_CONFIG_SERVER_MODE: "False"
ports:
- "8080:80"
volumes:
- pgadmin_data:/var/lib/pgadmin
depends_on:
postgres:
condition: service_healthy
volumes:
pgdata18:
pgadmin_data:bash
# 启动编排
docker compose up -d
# 查看状态(含健康检查结果)
docker compose ps
# 查看日志
docker compose logs -f postgres
# 停止但保留数据
docker compose stop
# 停止并删除容器与网络(命名卷保留)
docker compose down
# 彻底清理(含卷,危险!)
# docker compose down -v初始化脚本挂载
/docker-entrypoint-initdb.d 下的 .sh、.sql、.sql.gz 会在数据目录为空时按文件名字典序执行一次:
bash
# 目录结构
# ./
# ├── docker-compose.yml
# └── initdb/
# ├── 01-extensions.sql
# ├── 02-schema.sql
# └── 03-seed.sh
mkdir -p initdbsql
-- initdb/01-extensions.sql
-- 启用常用扩展(幂等,重复执行不会报错)
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
CREATE EXTENSION IF NOT EXISTS pgcrypto;sql
-- initdb/02-schema.sql
-- 创建业务角色与库,注意这里不要创建已存在的 demo 库
CREATE ROLE app_rw LOGIN PASSWORD 'App_P@ss2026';
-- 建表:用户表
CREATE TABLE IF NOT EXISTS users (
id BIGSERIAL PRIMARY KEY,
username VARCHAR(64) NOT NULL UNIQUE,
email VARCHAR(128) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- 授权
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO app_rw;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO app_rw;
-- 插入种子数据
INSERT INTO users (username, email)
VALUES ('alice', 'alice@example.com'),
('bob', 'bob@example.com')
ON CONFLICT (username) DO NOTHING;bash
#!/usr/bin/env bash
# initdb/03-seed.sh —— 可执行脚本会被 source 执行
set -euo pipefail
# 用 psql 执行依赖外部文件的数据导入
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
-- 使用环境变量中的用户名,保持脚本通用
SELECT current_user AS initialized_by, now() AS initialized_at;
EOSQLbash
# 脚本需要可执行权限
chmod +x initdb/03-seed.sh
# 验证初始化结果
docker exec -it pg18 psql -U postgres -d demo -c "\dt"text
List of relations
Schema | Name | Type | Owner
--------+-------+-------+----------
public | users | table | postgres
(1 row)配置定制
容器默认的 postgresql.conf 位于 PGDATA 内,可通过 command 覆盖参数:
yaml
# docker-compose.yml 片段:命令行方式覆盖参数(优先级最高)
command:
- postgres
- -c
- max_connections=200
- -c
- shared_buffers=512MB
- -c
- log_min_duration_statement=1000ini
# /etc/postgresql/postgresql.conf(挂载进容器)
# 注意:直接挂载覆盖配置文件时,必须包含镜像默认的关键设置
listen_addresses = '*'
max_connections = 200
shared_buffers = 512MB
work_mem = 8MB
maintenance_work_mem = 128MB
effective_cache_size = 1536MB
wal_level = replica
log_min_duration_statement = 1000
logging_collector = offbash
# 修改配置后重载(不需要重启容器)
docker exec pg18 pg_ctl reload -D /var/lib/postgresql/data
# 或
docker exec pg18 psql -U postgres -c "SELECT pg_reload_conf();"常见坑
bash
# 坑 1:忘记设置密码,容器启动后立即退出
docker run -d --name pg-bad postgres:18
docker logs pg-badtext
Error: Database is uninitialized and superuser password is not specified.
You must specify POSTGRES_PASSWORD to a non-empty value for the
superuser. For example, "-e POSTGRES_PASSWORD=password" on "docker run".bash
# 坑 2:挂载宿主机目录时权限不足
docker run -d --name pg-perm -e POSTGRES_PASSWORD=x -v /data/pg:/var/lib/postgresql postgres:18
docker logs pg-permtext
initdb: error: could not change permissions of directory "/var/lib/postgresql/data": Operation not permittedbash
# 正确做法:在宿主机上把目录属主改为容器内 postgres 用户的 UID
sudo chown -R 999:999 /data/pg && sudo chmod 700 /data/pgbash
# 坑 3:初始化脚本没有执行
# 原因:数据目录非空时,/docker-entrypoint-initdb.d 完全被跳过
# 解决:仅在首次部署时挂载脚本,或手动执行
docker exec -i pg18 psql -U postgres -d demo < initdb/02-schema.sql
# 坑 4:共享内存太小导致并行查询报错
docker run -d --name pg-shm --shm-size=512m -e POSTGRES_PASSWORD=x postgres:18text
ERROR: could not resize shared memory segment "/PostgreSQL.1234567890" to 4194304 bytes:
No space left on device实战示例
用 docker-compose 搭建一套「PostgreSQL 18 + pgAdmin + 初始化脚本 + 健康检查」的完整本地开发环境,并验证连接。
bash
# ---------- 步骤 1:准备目录结构 ----------
mkdir -p pgdev/{initdb,conf}
cd pgdevyaml
# ---------- 步骤 2:编写 docker-compose.yml ----------
# pgdev/docker-compose.yml
services:
postgres:
image: postgres:18.0
container_name: pgdev-pg18
restart: unless-stopped
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: Str0ng_P@ssw0rd
POSTGRES_DB: demo
POSTGRES_INITDB_ARGS: "--encoding=UTF8 --locale=C --data-checksums"
TZ: Asia/Shanghai
ports:
- "5432:5432"
volumes:
- pgdata18:/var/lib/postgresql
- ./initdb:/docker-entrypoint-initdb.d:ro
shm_size: 512m
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d demo"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
pgadmin:
image: dpage/pgadmin4:latest
container_name: pgdev-pgadmin
restart: unless-stopped
environment:
PGADMIN_DEFAULT_EMAIL: admin@example.com
PGADMIN_DEFAULT_PASSWORD: Admin_P@ss
PGADMIN_CONFIG_SERVER_MODE: "False"
ports:
- "8080:80"
depends_on:
postgres:
condition: service_healthy
volumes:
pgdata18:sql
-- ---------- 步骤 3:编写初始化脚本 ----------
-- pgdev/initdb/01-schema.sql
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
CREATE ROLE app_rw LOGIN PASSWORD 'App_P@ss2026';
CREATE TABLE IF NOT EXISTS users (
id BIGSERIAL PRIMARY KEY,
username VARCHAR(64) NOT NULL UNIQUE,
email VARCHAR(128) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE IF NOT EXISTS orders (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id),
amount NUMERIC(12,2) NOT NULL CHECK (amount > 0),
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
INSERT INTO users (username, email) VALUES
('alice', 'alice@example.com'),
('bob', 'bob@example.com')
ON CONFLICT (username) DO NOTHING;
INSERT INTO orders (user_id, amount)
SELECT id, 199.00 FROM users WHERE username = 'alice';
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO app_rw;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO app_rw;bash
# ---------- 步骤 4:启动并确认健康 ----------
docker compose up -d
docker compose pstext
NAME IMAGE STATUS PORTS
pgdev-pg18 postgres:18.0 Up 35 seconds (healthy) 0.0.0.0:5432->5432/tcp
pgdev-pgadmin dpage/pgadmin4:latest Up 20 seconds 0.0.0.0:8080->80/tcpbash
# ---------- 步骤 5:验证初始化数据与查询 ----------
docker exec -it pgdev-pg18 psql -U postgres -d demo -c "
SELECT u.username, count(o.id) AS order_count, coalesce(sum(o.amount), 0) AS total
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
GROUP BY u.username
ORDER BY u.username;"text
username | order_count | total
----------+-------------+--------
alice | 1 | 199.00
bob | 0 | 0.00
(2 rows)bash
# ---------- 步骤 6:验证持久化 ----------
docker compose down # 删除容器与网络,保留命名卷
docker compose up -d # 重新创建容器
docker exec pgdev-pg18 psql -U postgres -d demo -c "SELECT count(*) FROM users;"text
count
-------
2
(1 row)注意事项
- 容器内数据默认不持久:不挂载卷时
docker rm会立刻丢掉全部数据 POSTGRES_PASSWORD与POSTGRES_USER只在数据目录为空时生效,之后修改环境变量不会有任何效果/docker-entrypoint-initdb.d只在首次初始化时执行,重建容器不会重跑;依赖它的脚本必须幂等- 不要用
POSTGRES_HOST_AUTH_METHOD=trust搭配-p 5432:5432暴露到公网,等同于公开数据库 - 绑定挂载时要处理 UID 权限(容器内
postgres为 uid 999),否则 initdb 会报权限错误 - 挂载自定义
postgresql.conf时,直接覆盖会丢掉镜像内必要的默认设置,建议用-c参数覆盖单项 latest标签会随上游漂移,生产环境必须固定到postgres:18.0这样的小版本- 默认
shm_size=64MB在大并发/并行查询下会触发共享内存不足,建议提升到 512MB 以上 - Apple Silicon 上镜像默认为 arm64 架构,与 x86 生产环境的扩展二进制不兼容;跨架构部署前需确认
docker compose down -v会删除命名卷,是不可逆的破坏性命令,执行前务必确认- 官方镜像不使用
systemd,systemctl相关命令在容器内不可用,应使用pg_ctl或pg_isready
最佳实践
- 生产环境固定镜像到小版本标签(如
18.0),并使用私有镜像仓库缓存,避免上游拉取失败 - 数据卷使用宿主机的独立数据盘路径做绑定挂载,便于扩容、快照与统一备份
- 关键配置通过
-c参数或环境变量注入,把「不可变配置」写进编排文件,纳入版本控制 - 设置
healthcheck+depends_on: condition: service_healthy,避免应用比数据库先就绪 - 显式设置容器资源上限(
memory、cpus),防止单容器拖垮宿主机 - 用
POSTGRES_INITDB_ARGS="--data-checksums"开启校验和,能提前暴露磁盘静默损坏 - 应用连接使用专用业务角色而非
postgres,并通过环境变量或 Docker secret 注入密码 - 定期用
pg_dump逻辑备份(容器外执行)而非只依赖卷快照,两者结合才能应对逻辑误操作 - 开发环境用
docker compose down而非down -v,避免误删数据 - CI 中可用
tmpfs挂载 PGDATA 提升测试速度(数据无需持久)
下一节
继续学习:源码编译安装