Skip to content

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/dataDocker 自动分配路径,难定位不推荐
命名卷-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 postgres
text
REPOSITORY   TAG        IMAGE ID       CREATED       SIZE
postgres     18         8f0e1ba2f1b9   5 days ago    452MB
postgres     18-alpine  1c2b7d94aa31   5 days ago    279MB

docker 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
参数类型默认值说明
-dflag——后台运行(detached)
--namestring随机名容器名,后续管理用
-e POSTGRES_PASSWORDstring无(必填)超级用户密码,不设置且未开启免密会导致启动失败
-e POSTGRES_USERstringpostgres超级用户名
-e POSTGRES_DBstring等于 POSTGRES_USER首次初始化时创建的数据库
-e POSTGRES_INITDB_ARGSstring空传给 initdb 的额外参数
-e POSTGRES_HOST_AUTH_METHODstringscram-sha-256pg_hba.conf 中的默认认证方式,慎用 trust
-e PGDATAstring镜像内默认值数据目录路径,改路径时需同步改挂载点
-e TZstringUTC容器时区,影响 now() 显示
-p 5432:5432映射无宿主机端口:容器端口
-v挂载匿名卷数据持久化
--restartstringno重启策略:no / on-failure / always / unless-stopped
--shm-sizesize64MB共享内存大小,并行查询多时需调大
--health-cmdstring无健康检查命令

环境变量一览 ​

变量作用备注
POSTGRES_PASSWORD设置超级用户密码与 POSTGRES_HOST_AUTH_METHOD=trust 二选一
POSTGRES_USER超级用户名同时会创建同名数据库(除非指定 POSTGRES_DB)
POSTGRES_DB初始数据库名默认与超级用户名相同
POSTGRES_INITDB_ARGSinitdb 参数如 --data-checksums、--encoding=UTF8
POSTGRES_INITDB_WALDIRWAL 目录把 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 demo
text
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 pgdata18
text
[
    {
        "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:18
bash
# 备份与恢复卷数据
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 initdb
sql
-- 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;
EOSQL
bash
# 脚本需要可执行权限
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=1000
ini
# /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 = off
bash
# 修改配置后重载(不需要重启容器)
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-bad
text
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-perm
text
initdb: error: could not change permissions of directory "/var/lib/postgresql/data": Operation not permitted
bash
# 正确做法:在宿主机上把目录属主改为容器内 postgres 用户的 UID
sudo chown -R 999:999 /data/pg && sudo chmod 700 /data/pg
bash
# 坑 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:18
text
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 pgdev
yaml
# ---------- 步骤 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 ps
text
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/tcp
bash
# ---------- 步骤 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 提升测试速度(数据无需持久)

下一节

继续学习:源码编译安装

参考链接 ​