目录
Docker PostgreSQL 教程:安装、连接、备份与升级
PostgreSQL 18 的重要变更:从
postgres:18开始,官方镜像的数据目录改为/var/lib/postgresql/18/docker,Volume 必须挂载到/var/lib/postgresql。网上大多数教程写的/var/lib/postgresql/data是 17 版以前的路径,用在 18 版会导致启动失败,下面有实测。
一条命令启动
$ docker run -d --name pg -e POSTGRES_PASSWORD=secret -p 127.0.0.1:5432:5432 -v pgdata:/var/lib/postgresql --restart unless-stopped postgres:18
1b34d05a195f347c86e431441162b760167a5c04016a528e10342d0199be016d
$ docker logs pg 2>&1 | tail -n 3
2026-10-13 00:53:32.071 UTC [1] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
2026-10-13 00:53:32.075 UTC [72] LOG: database system was shut down at 2026-10-13 00:53:31 UTC
2026-10-13 00:53:32.080 UTC [1] LOG: database system is ready to accept connections
$ docker exec pg psql -U postgres -c "SHOW data_directory;"
data_directory
-------------------------------
/var/lib/postgresql/18/docker
(1 row)
| 参数 | 说明 |
|---|---|
POSTGRES_PASSWORD | 超级用户的密码(必填) |
-p 127.0.0.1:5432:5432 | 仅开放给本机 |
-v pgdata:/var/lib/postgresql | 数据存放在 Volume pgdata 中。18 版需要挂载此路径 |
SHOW data_directory 确认实际数据目录是 /var/lib/postgresql/18/docker,位于我们挂载的 /var/lib/postgresql 下。目录名称包含大版本号,今后升级时,新旧版本的数据可以并存于同一个 Volume 中。
其他常用环境变量:
| 环境变量 | 说明 | 默认值 |
|---|---|---|
POSTGRES_USER | 超级用户名 | postgres |
POSTGRES_DB | 默认数据库 | 与 POSTGRES_USER 相同 |
PGDATA | 数据目录 | /var/lib/postgresql/18/docker(18 版) |
TZ | 时区 | UTC |
和 MySQL 一样,这些变量仅在数据目录为空时生效。之后修改
POSTGRES_PASSWORD不会改变已有密码,需要使用ALTER USER ... PASSWORD '...'修改。
实测:18 版挂载旧路径会失败
按照旧教程将 Volume 挂载到 /var/lib/postgresql/data:
$ docker run -d --name pgold -e POSTGRES_PASSWORD=secret -v pgold:/var/lib/postgresql/data postgres:18
b480eb9c72b29fa99f1ed8df17972e2bfc2d6da4b86c058a36cf21fd63579bfa
$ docker ps -a --filter name=pgold --format "{{.Status}}"
Exited (1) 5 seconds ago
$ docker logs pgold 2>&1 | head -n 12
Error: in 18+, these Docker images are configured to store database data in a
format which is compatible with "pg_ctlcluster" (specifically, using
major-version-specific directory names). This better reflects how
PostgreSQL itself works, and how upgrades are to be performed.
See also https://github.com/docker-library/postgres/pull/1259
Counter to that, there appears to be PostgreSQL data in:
/var/lib/postgresql/data (unused mount/volume)
This is usually the result of upgrading the Docker image without
upgrading the underlying database using "pg_upgrade" (which requires both
容器在几秒内以 Exited (1) 结束。错误信息说明 18 版改用按大版本命名的目录,并且在 /var/lib/postgresql/data 中发现了“未使用的挂载”。此检查是为了避免你以为数据已保存,实际上数据全写在容器里,删除容器后就会消失。将挂载路径改为 /var/lib/postgresql 即可。
使用 Compose 管理
实际项目建议使用 Compose。创建文件夹 pg-demo:
pg-demo/
├── compose.yaml
├── .env
└── initdb/
└── 01-init.sql
.env:
DB_PASSWORD=change-this-password
initdb/01-init.sql(首次初始化时运行,用于创建扩展、数据表和示例数据;最后的 pg_sleep(8) 用于模拟耗时的初始化,例如导入大量数据,便于观察健康检查):
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE TABLE products (
id serial PRIMARY KEY,
name text NOT NULL,
price integer NOT NULL
);
INSERT INTO products (name, price) VALUES ('珍珠奶茶', 65), ('鳳梨酥', 45), ('牛肉麵', 180);
-- 模拟耗时的初始化(例如导入大量数据)
SELECT pg_sleep(8);
compose.yaml:
services:
db:
image: postgres:18
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: app
TZ: Asia/Taipei
command: ["postgres", "-c", "max_connections=200", "-c", "shared_buffers=256MB"]
ports:
- "127.0.0.1:5432:5432"
volumes:
- pgdata:/var/lib/postgresql
- ./initdb:/docker-entrypoint-initdb.d:ro
healthcheck:
test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -U app -d app"]
interval: 2s
timeout: 3s
retries: 30
shm_size: 256mb
restart: unless-stopped
volumes:
pgdata:
| 配置 | 说明 |
|---|---|
POSTGRES_USER: app | 将超级用户改名为 app,不再有 postgres 这个用户 |
command: ["postgres", "-c", ...] | 使用 -c 传入 PostgreSQL 配置,无需另外准备配置文件 |
pg_isready -h 127.0.0.1 | PostgreSQL 内置的检查工具。必须添加 -h 127.0.0.1,原因见下方实测 |
shm_size: 256mb | Docker 默认的共享内存 /dev/shm 只有 64 MB,较复杂的查询可能出现 could not resize shared memory segment 错误 |
$ docker compose up -d --wait
[+] up 3/3
✔ Network pg-demo_default Created 0.0s
✔ Volume pg-demo_pgdata Created 0.0s
✔ Container pg-demo-db-1 Healthy 11.3s
$ docker compose exec db psql -U app -h 127.0.0.1 -c "SELECT * FROM products;"
id | name | price
----+----------+-------
1 | 珍珠奶茶 | 65
2 | 鳳梨酥 | 45
3 | 牛肉麵 | 180
(3 rows)
$ docker compose logs db 2>&1 | grep -E "running /docker-entrypoint-initdb.d|init process complete|listening on IPv4"
db-1 | /usr/local/bin/docker-entrypoint.sh: running /docker-entrypoint-initdb.d/01-init.sql
db-1 | PostgreSQL init process complete; ready for start up.
db-1 | 2026-10-13 08:52:58.663 CST [1] LOG: listening on IPv4 address "0.0.0.0", port 5432
$ docker compose exec db psql -U app -c "SHOW max_connections;" -c "SHOW shared_buffers;" -c "SELECT now();"
max_connections
-----------------
200
(1 row)
shared_buffers
----------------
256MB
(1 row)
now
-------------------------------
2026-10-13 08:53:01.293902+08
(1 row)
$ docker compose exec db df -h /dev/shm
Filesystem Size Used Avail Use% Mounted on
shm 256M 1.1M 255M 1% /dev/shm
$ docker compose exec db psql -U app -c "\dx"
List of installed extensions
Name | Version | Default version | Schema | Description
---------+---------+-----------------+------------+-------------------------------------------------------------------
pg_trgm | 1.6 | 1.6 | public | text similarity measurement and index searching based on trigrams
plpgsql | 1.0 | 1.0 | pg_catalog | PL/pgSQL procedural language
(2 rows)
Healthy用了 11.3 秒,正好是初始化加上pg_sleep(8)的时间。健康检查通过后立即使用 TCP 连接,数据都在,中文显示正常。- 日志显示初始化脚本已运行,并且在初始化完成后才开始监听
0.0.0.0:5432。 max_connections为 200、shared_buffers为 256MB,说明command的参数已生效;now()显示+08时区。/dev/shm为 256M,说明shm_size已生效。\dx列出了已安装的扩展,pg_trgm已由初始化脚本创建。
健康检查陷阱:pg_isready 不加 -h 会过早报告 healthy
网上常见的写法是 pg_isready -U app -d app(没有 -h)。实测改用这种写法:
$ docker compose up -d --wait
[+] up 3/3
✔ Network pg-demo_default Created 0.0s
✔ Volume pg-demo_pgdata Created 0.0s
✔ Container pg-demo-db-1 Healthy 3.2s
$ docker compose exec db psql -U app -h 127.0.0.1 -c "SELECT count(*) FROM products;"
psql: error: connection to server at "127.0.0.1", port 5432 failed: Connection refused
Is the server running on that host and accepting TCP/IP connections?
$ docker compose logs db 2>&1 | grep -E "pg_sleep|init process complete|listening on IPv4"
仅过了 3.2 秒就报告 Healthy,但通过 TCP 连接却得到 Connection refused。
原因与 MySQL 一样:初始化期间,启动脚本会先启动一个仅接受 Unix socket 连接的临时服务器来运行 initdb 中的脚本。pg_isready 不加 -h 时会走 socket,因此在初始化尚未完成时就报告可以连接。依赖它的应用(depends_on 的 condition: service_healthy)会在此时启动,通过 TCP 连接就会失败。添加 -h 127.0.0.1 强制使用 TCP,就能等到正式服务器启动。
连接与 psql
使用 psql 操作
容器内有 psql 客户端。交互式使用 docker compose exec db psql -U app(单独的容器则是 docker exec -it pg psql -U postgres),进入 app=# 提示符后输入 SQL 或下面的命令。也可以用 -c 直接执行:
$ docker compose exec db psql -U app -c "SELECT version();"
version
--------------------------------------------------------------------------------------------------------------------
PostgreSQL 18.6 (Debian 18.6-1.pgdg13+2) on x86_64-pc-linux-gnu, compiled by gcc (Debian 14.2.0-19) 14.2.0, 64-bit
(1 row)
$ docker compose exec db psql -U app -c "\l"
List of databases
Name | Owner | Encoding | Locale Provider | Collate | Ctype | Locale | ICU Rules | Access privileges
-----------+-------+----------+-----------------+------------+------------+--------+-----------+-------------------
app | app | UTF8 | libc | en_US.utf8 | en_US.utf8 | | |
postgres | app | UTF8 | libc | en_US.utf8 | en_US.utf8 | | |
template0 | app | UTF8 | libc | en_US.utf8 | en_US.utf8 | | | =c/app +
| | | | | | | | app=CTc/app
template1 | app | UTF8 | libc | en_US.utf8 | en_US.utf8 | | | =c/app +
| | | | | | | | app=CTc/app
(4 rows)
$ docker compose exec db psql -U app -c "\dt"
List of tables
Schema | Name | Type | Owner
--------+----------+-------+-------
public | products | table | app
(1 row)
$ docker compose exec db psql -U app -c "\d products"
Table "public.products"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+--------------------------------------
id | integer | | not null | nextval('products_id_seq'::regclass)
name | text | | not null |
price | integer | | not null |
Indexes:
"products_pkey" PRIMARY KEY, btree (id)
$ docker compose exec db psql -U app -c "\du"
List of roles
Role name | Attributes
-----------+------------------------------------------------------------
app | Superuser, Create role, Create DB, Replication, Bypass RLS
| 命令 | 作用 |
|---|---|
\l | 列出数据库 |
\c 名称 | 切换数据库 |
\dt | 列出数据表 |
\d 数据表 | 查看数据表结构 |
\du | 列出用户(角色) |
\dx | 列出已安装的扩展 |
\q | 退出 |
\du 显示 app 是 Superuser,\l 中所有数据库的 Owner 也都是 app:设置 POSTGRES_USER 后,app 就取代了默认的 postgres。
为什么在容器内无需输入密码
上面的 psql 都没有输入密码。查看数据目录中的 pg_hba.conf(PostgreSQL 的连接身份验证规则):
$ docker compose exec db grep -v "^#" /var/lib/postgresql/18/docker/pg_hba.conf | grep -v "^$"
local all all trust
host all all 127.0.0.1/32 trust
host all all ::1/128 trust
local replication all trust
host replication all 127.0.0.1/32 trust
host replication all ::1/128 trust
host all all all scram-sha-256
$ docker run --rm --network pg-demo_default -e PGPASSWORD=wrong postgres:18 psql -h db -U app -c "SELECT 1;"
psql: error: connection to server at "db" (172.19.0.2), port 5432 failed: FATAL: password authentication failed for user "app"
$ docker run --rm --network pg-demo_default -e PGPASSWORD=change-this-password postgres:18 psql -h db -U app -c "SELECT current_user, inet_server_addr();"
current_user | inet_server_addr
--------------+------------------
app | 172.19.0.2
(1 row)
- 官方镜像的配置:容器内的连接(Unix socket、
127.0.0.1、::1)均为trust,不检查密码。 - 其他来源的连接(最后一行
host all all all)使用scram-sha-256密码验证。 - 从同一个 Compose 网络中的另一个容器连接到
db,密码错误时会得到password authentication failed;密码正确才能连接。
因此,能够进入容器的人就能直接操作数据库,这也是需要保护 Docker 权限的原因(参见 Docker 安全性)。从主机通过 -p 映射的端口连接时,来源 IP 是 Docker 网络的网关,同样需要密码。
从主机和其他容器连接
postgres://app:密码@127.0.0.1:5432/app # 从主机(GUI 工具、应用程序)
postgres://app:密码@db:5432/app # 从同一 Compose 项目的其他服务
常见连接错误
$ docker compose exec db psql -c "SELECT 1;"
psql: error: connection to server on socket "/var/run/postgresql/.s.PGSQL.5432" failed: FATAL: role "root" does not exist
$ docker compose exec db psql -U postgres -c "SELECT 1;"
psql: error: connection to server on socket "/var/run/postgresql/.s.PGSQL.5432" failed: FATAL: role "postgres" does not exist
$ docker compose exec db psql -U app -d shop -c "SELECT 1;"
psql: error: connection to server on socket "/var/run/postgresql/.s.PGSQL.5432" failed: FATAL: database "shop" does not exist
$ docker compose exec -e PGPASSWORD=wrong db psql -h 127.0.0.1 -U app -c "SELECT 1;"
?column?
----------
1
(1 row)
$ docker compose exec db psql -U app -h 127.0.0.1 -p 5433 -c "SELECT 1;"
psql: error: connection to server at "127.0.0.1", port 5433 failed: Connection refused
Is the server running on that host and accepting TCP/IP connections?
| 错误 | 原因 |
|---|---|
role "root" does not exist | psql 没有添加 -U,默认使用当前系统用户名(容器内是 root) |
role "postgres" does not exist | 设置了 POSTGRES_USER,默认的 postgres 用户就不存在 |
database "shop" does not exist | POSTGRES_DB 只在初始化时创建;其他数据库需要自行 CREATE DATABASE |
Connection refused | 端口错误、数据库尚未启动完成,或连接到了错误的主机 |
password authentication failed | 密码错误,或 Volume 使用其他密码初始化 |
注意第 4 条命令:在容器内使用错误密码连接 127.0.0.1 也成功了,这就是上面提到的 trust 规则。
备份与还原
$ docker compose exec -T db pg_dump -U app app > app.sql
$ ls -lh app.sql && grep -E "^CREATE (TABLE|EXTENSION)" app.sql
-rw-r--r-- 1 root root 2.3K Oct 13 00:53 app.sql
CREATE EXTENSION IF NOT EXISTS pg_trgm WITH SCHEMA public;
CREATE TABLE public.products (
$ docker compose exec -T db pg_dump -U app -Fc app > app.dump
$ ls -lh app.dump
-rw-r--r-- 1 root root 3.1K Oct 13 00:53 app.dump
$ docker compose exec db psql -U app -c "DROP TABLE products;"
DROP TABLE
$ docker compose exec -T db pg_restore -U app -d app --clean --if-exists < app.dump
$ docker compose exec db psql -U app -c "SELECT count(*) FROM products;"
count
-------
3
(1 row)
$ rm app.sql app.dump
| 命令 | 说明 |
|---|---|
pg_dump app > app.sql | 导出为 SQL 纯文本,可以直接阅读,也可以用 psql 导入 |
pg_dump -Fc app > app.dump | 自定义格式,已经压缩,用 pg_restore 还原,并且可以选择只还原部分数据表 |
pg_restore --clean --if-exists | 还原前先删除已存在的对象,避免重复 |
exec -T | Compose 的 exec 默认会分配 TTY,使用重定向时需要添加 -T 关闭 |
备份中包含 CREATE EXTENSION,还原到新数据库时也会一并创建扩展。此示例的数据很少,因此自定义格式(3.1K)反而比纯文本(2.3K)更大;数据量较大时,自定义格式会小很多。
其他常用命令:
# 备份所有数据库和角色(用户、密码)
docker compose exec -T db pg_dumpall -U app > all.sql
# 还原 SQL 纯文本
docker compose exec -T db psql -U app -d app < app.sql
可以使用 cron 配合上面的命令定期备份,并将文件复制到其他机器或云存储。
Windows 用户的备份与还原
PowerShell 不支持 <,Windows PowerShell 5.1 的 > 会改变文件编码,-Fc 的二进制备份甚至会直接损坏。改用 pg_dump -f 在容器内生成文件,再用 docker compose cp 复制,这种写法适用于所有系统:
# 备份
docker compose exec db pg_dump -U app -Fc -f /tmp/app.dump app
docker compose cp db:/tmp/app.dump app.dump
# 还原
docker compose cp app.dump db:/tmp/restore.dump
docker compose exec db pg_restore -U app -d app --clean --if-exists /tmp/restore.dump
这组命令没有用到 $(pwd)、<、>,bash、PowerShell、CMD 都可以直接运行。实测(先创建数据表、备份、删除、还原):
$ docker compose exec db psql -U app -c 'CREATE TABLE t (id int); INSERT INTO t VALUES (1),(2);'
CREATE TABLE
INSERT 0 2
$ docker compose exec db pg_dump -U app -Fc -f /tmp/app.dump app
$ docker compose cp db:/tmp/app.dump app.dump
pgwin-db-1 Copying pgwin-db-1:/tmp/app.dump to app.dump
pgwin-db-1 Copied pgwin-db-1:/tmp/app.dump to app.dump
$ ls -l app.dump
-rw-rw-rw- 1 root root 1251 Oct 13 05:30 app.dump
$ docker compose exec db psql -U app -c 'DROP TABLE t;'
DROP TABLE
$ docker compose cp app.dump db:/tmp/restore.dump
pgwin-db-1 Copying app.dump to pgwin-db-1:/tmp/restore.dump
pgwin-db-1 Copied app.dump to pgwin-db-1:/tmp/restore.dump
$ docker compose exec db pg_restore -U app -d app /tmp/restore.dump
$ docker compose exec db psql -U app -c 'SELECT count(*) FROM t;'
count
-------
2
(1 row)
大版本升级(例如 16 → 18)
小版本更新(例如 18.5 → 18.6)只需拉取新镜像并重新创建容器。大版本则不同:PostgreSQL 不同大版本的数据文件格式不兼容。
先使用 16 版创建一些数据:
$ docker run -d --name pg16 -e POSTGRES_PASSWORD=secret -v pg16data:/var/lib/postgresql/data postgres:16
20691764a98828d24c958eaaa71e83c08ebff29b984350ff7f6782ba8d74b4ca
$ docker exec pg16 psql -U postgres -c "CREATE TABLE notes (id serial PRIMARY KEY, body text); INSERT INTO notes (body) VALUES ('written by 16');"
CREATE TABLE
INSERT 0 1
$ docker stop pg16
pg16
直接更换镜像会失败
使用 postgres:18 打开 16 版的数据(这里用 PGDATA 指定旧路径,模拟直接把 tag 从 16 改成 18):
$ docker run --name pg18 -e POSTGRES_PASSWORD=secret -e PGDATA=/var/lib/postgresql/data -v pg16data:/var/lib/postgresql/data postgres:18 2>&1 | tail -n 3
2026-10-13 00:54:09.740 UTC [1] FATAL: database files are incompatible with server
2026-10-13 00:54:09.740 UTC [1] DETAIL: The data directory was initialized by PostgreSQL version 16, which is not compatible with this version 18.6 (Debian 18.6-1.pgdg13+2).
$ docker rm pg18
pg18
database files are incompatible with server:数据目录由 16 版初始化,18.6 无法使用。
导出再导入
最简单安全的做法:使用旧版 pg_dumpall 导出所有数据,再导入新版的新 Volume。
$ docker start pg16
pg16
$ docker exec pg16 pg_dumpall -U postgres > upgrade.sql && ls -lh upgrade.sql
-rw-r--r-- 1 root root 3.4K Oct 13 00:54 upgrade.sql
$ docker run -d --name pg18 -e POSTGRES_PASSWORD=secret -v pg18data:/var/lib/postgresql postgres:18
ec651837b76a0c9cc8b2468ec5a2ff4002334dcf015514ef94490a6c38cfe73d
$ docker exec -i pg18 psql -U postgres -q < upgrade.sql
ERROR: role "postgres" already exists
set_config
------------
(1 row)
set_config
------------
(1 row)
setval
--------
1
(1 row)
$ docker exec pg18 psql -U postgres -c "SHOW server_version;" -c "SELECT * FROM notes;"
server_version
-------------------------------
18.6 (Debian 18.6-1.pgdg13+2)
(1 row)
id | body
----+---------------
1 | written by 16
(1 row)
$ rm upgrade.sql
- 导入时的
ERROR: role "postgres" already exists可以忽略:新的 18 版容器已经创建了postgres用户,pg_dumpall的输出又尝试创建一次。 - 最后确认版本为 18.6,16 版写入的
written by 16数据仍然存在。 - 注意,新容器的 Volume 挂载在
/var/lib/postgresql(18 版的新路径)。
确认数据和应用都正常后,再删除旧容器和 Volume。数据量很大、无法接受长时间停机时,可以研究 pg_upgrade 或逻辑复制(logical replication),但步骤复杂得多,请先在测试环境演练。
重点回顾
- PostgreSQL 18 的 Volume 挂载到
/var/lib/postgresql。 - 健康检查使用
pg_isready -h 127.0.0.1。 - 容器内连接使用 trust,外部连接需要密码。
- 大版本升级:旧版
pg_dumpall导出,新版新 Volume 导入。
动手练习
练习 1:使用 psql 在 app 数据库中创建一个数据表 notes,再用 \dt 确认。
答案
docker compose exec db psql -U app -c "CREATE TABLE notes (id serial PRIMARY KEY, body text);"
docker compose exec db psql -U app -c "\dt"
练习 2:自定义格式(-Fc)和纯文本 SQL 备份有什么区别?
答案
自定义格式经过压缩,需要用 pg_restore 还原,并且可以选择只还原部分数据表;纯文本可以直接阅读,也可以用 psql 导入。
常见问题
postgres:18 启动后立即退出?
18 版需要挂载 /var/lib/postgresql,挂载到旧路径 /var/lib/postgresql/data 会因检测到不兼容的目录而退出,实测见本章。
小版本更新(18.5 → 18.6)怎么做?
拉取新镜像并重建容器即可:docker compose pull db && docker compose up -d db。只有大版本升级才需要导出再导入。
Windows 上 pg_restore 失败?
PowerShell 不支持 <,而 5.1 的 > 会破坏二进制备份。使用 pg_dump -f 在容器内生成文件,再用 docker compose cp 复制,本章附有写法。
更新于 2026年10月14日