pgAdmin 4
pgAdmin 4 是 PostgreSQL 的官方 Web 管理界面——在
浏览器里浏览 schema、执行 SQL、观察服务器状态。pgcli 将其作为独立的顶层插
件运行,CLI 表面与其他 web/infra 插件一致(install / start / stop / logs /
autostart / remove),全部通过 pg.yaml 管理。
它是插件族里唯一的 UI 插件,这一点决定了它的设计:
- 它是 Web 应用,不是数据库前置。 pgAdmin 连接哪些服务器由你自己在其 UI
里注册决定;它不像 PostgREST 或 PgBouncer
那样专职前置某一个实例。这就是它做成顶层-only 插件
(
addons.pgadmin.<name>)、没有实例 sidecar 的原因。 - 它有自己的登录。 Web UI 需要邮箱 + 密码登录。这不是 PostgreSQL 密码
——是 pgAdmin 自己的账号,首次安装时自动生成并存入
pg.yaml。之后用pg addon password pgadmin取回。 - 持久化的会话存储。 pgAdmin 把配置、已保存的服务器、浏览历史存在 bind
挂载数据目录里的 sqlite 库中。它跨
pg addon remove(不带--clean-data) 存活,重装同名实例即复活。 - 可选的一次性预置。
--dsn或--pg-name <实例名>在 install 时向 UI 的servers.json预注册一个服务器,让浏览器打开时列表里已有它。这只是一个便利 ——不是运行期耦合:pgAdmin 不会因它阻塞,之后可在 UI 里增删。
建议:每个数据库一个 pgAdmin,随开随用。 pgcli 管理的 pgAdmin 是单个轻 量容器,秒级启动、内存占用极小——完全可以当一次性工具用。与其跑一个共享的 pgAdmin 把所有服务器都注册进去,不如为当前正在操作的数据库创建专用实例,用完 即删:
每个实例有独立的端口(自动分配)、独立的登录、独立的 pgpass——数据库之间无 交叉污染,共享会话存储里不会残留陈旧连接。下次需要时,秒级重装。
不在范围内: 这里 pgAdmin 只提供明文 HTTP——没有接入 TLS 终止(需要 HTTPS 就在前面架反向代理)。
servers.json本身不能携带目标服务器的密码 (pgAdmin 拒绝导入),但当预置 DSN 带了密码时,pgcli 通过 pgAdmin 的 pgpass 支持预配置它,预置服务器连接时无需再输入——见预置服务器。
参数
单容器直接跑上游 docker.io/dpage/pgadmin4 镜像(pull-only——pgcli 从不构建
它),全部经由 entrypoint 读取的 -e 环境变量配置。下表列出每个旋钮及其默认
值:
参数 / pg.yaml 键 |
默认值 | 环境变量 | 含义 |
|---|---|---|---|
--image / image_tag |
docker.io/dpage/pgadmin4:9.18 |
— | 直接指定镜像标签 |
--port / host_port |
自动,取自 pgadmin_start_port(基址 5050) |
PGADMIN_LISTEN_PORT |
Web 宿主端口 |
--listen / listen |
127.0.0.1 |
PGADMIN_LISTEN_ADDRESS |
绑定地址(0.0.0.0 对外开放,见监听与暴露) |
--email / email |
admin@pgcli.lan |
PGADMIN_DEFAULT_EMAIL |
Web 登录账号(会经过镜像校验,见排障) |
--password / password |
自动生成,20 字符 | PGADMIN_DEFAULT_PASSWORD |
Web 登录密码——不是 PG 密码 |
--dsn / dsn |
不设 | —(渲染 servers.json + pgpass) |
以 URI 预注册一个服务器——带密码时同时预配置(host 是从容器内拨号的,见预置的 host 与 listen 地址) |
--pg-name |
不设 | —(渲染 servers.json + pgpass) |
改为从本机已管理实例解析该服务器 |
--data-dir / data_dir |
<base-dir>/addon/pgadmin/<name>/data |
(挂载到 /var/lib/pgadmin) |
会话存储位置 |
| — | autostart: false |
— | 开机自启(pg autostart enable --pgadmin) |
旋钮间的配合关系:
email+password只在空数据目录上生效。 pgAdmin 只在初始化pgadmin4.db期间读取PGADMIN_DEFAULT_*;该文件一旦存在,库里的账号就是 权威,环境变量被忽略。因此复活既有目录的重装会打印一个对它无效的密码(install 会明说)——要真正重置登录,先--clean-data移除;复活时也可以把--password钉成原值。--dsn与--pg-name互斥——二者都表示"预置这个服务器",一个给 URI, 一个从本实例解析端点。--name是插件自己的键,与--pg-name无关。预置里的 host 是 pgAdmin 的连接出发地,不是你浏览器的位置——见预置的 host 与 listen 地址。listen默认 loopback——Web UI 有登录门,所以除非传--listen 0.0.0.0从别的主机访问(summary 会警告登录成了唯一防线),它保持127.0.0.1。完整 情形见监听与暴露。email必须过得了镜像的校验器——entrypoint 用email_validator检查它, 直接拒绝保留 TLD(.local、.invalid、.test),命中就崩溃重启。默认值admin@pgcli.lan正是为通过校验而选;自己起名时同理(.lan、.internal这类由你掌控的名字都行——不要求是真实可投递的地址,投递检查是关掉的)。
安装
install summary 打印 URL 与(自动生成的)Web 登录:
Seeded: / Seed auth: 行只在传了 --dsn/--pg-name 时出现。当 DSN 携带密
码时 Seed auth: 显示 password pre-configured (pgpass);不带密码时显示
no password in the DSN — first connect will prompt。重复 install 是幂等的:
运行中的容器原样保留,已停的被启动。要让改动的端口/listen/email/预置生效,加
--force(容器按配置重建;数据目录不动)。
用 --force 重建
不带 --force 的 pg addon install pgadmin 对运行中的容器是空操作——它不动
现有配置,只确认容器在跑。当你正要修改某些东西(端口、listen、email、镜像、
预置 DSN、--pg-name 目标、data_dir)时,这恰好是错误的行为:运行中的容器
钉在创建它时的配置上,编辑看似被接受,但直到下次重建前什么都不会改变。
--force 就是应用这些改动的旋钮。它停止运行中的容器、移除它,并按当前配置创建
一个新的;数据目录不动,已存服务器和浏览历史在新容器里回来:
两个值得说清的坑:
email/password仍然只对空数据目录重新生效。 pgAdmin 只在初始化pgadmin4.db期间读取PGADMIN_DEFAULT_*;该文件一旦存在,库里的账号就是 权威,环境变量被忽略。单--force不会重置 Web 登录——要真正重置账号,得配合--clean-data(先把存储清掉)。- 运行中的容器会被重建,不是原样保留。 如果浏览器正开着 UI,
--force会杀 掉会话;下一次页面加载会重新认证新容器。
使用 Web UI
把浏览器指向 summary 里的 URL(在跑 podman 的那台主机上),用登录邮箱 + 密码
签名进入,pgAdmin 自己的服务器树就出现了。在 UI 里添加连接,或在安装时用
–dsn / –pg-name 预注册一个。
如果主机不在你的浏览器旁边:设 --listen 0.0.0.0(登录成了唯一防线):
监听与暴露
--listen(配置键 listen)是 pgAdmin HTTP 服务绑定的地址,默认 loopback。
三种姿态如下——都要配 --force,因为容器需重建才会采纳改动:
- Linux 上容器共享宿主网络,所以
--listen 0.0.0.0会在<宿主IP>:<端口>应答,--listen 127.0.0.1只在127.0.0.1:<端口>。放宽到0.0.0.0意味着 pgAdmin 之外只剩邮箱/密码登录这一道门——install 会警告这一 点——因此除非网络可信,保持 loopback。 - macOS 上情况相反:bridge 网络在 podman machine 内部总是绑
0.0.0.0, pgcli 发布端口,所以listen在那里实际上是恒被放宽的,你从 Mac 经 gvproxy 用127.0.0.1:<端口>访问 UI。见平台支持。
取回 Web 登录
pg addon list 显示 URL 和登录邮箱,但不加 --show-password 绝不打印密码。
预置服务器
--dsn 与 --pg-name 都渲染一份一次性的 servers.json,pgAdmin 在首次启动
时导入,浏览器打开时那个服务器已在树里。--pg-name 从本机已管理实例解析端点
(其 host/port/database/user 及密码,读自 pg.yaml);--dsn 以 URI 给出:
预置携带什么、不携带什么:
- 目标服务器的密码在 DSN 携带时被预配置。
servers.json本身不能持有密码 (pgAdmin 拒绝导入),所以 pgcli 在旁边写一个 libpqpgpass文件,并通过ConnectionParameters.passfile把预置条目指向它——这是 pgAdmin 9.18 导入/导 出服务器文档中正式记录的绝对路径机制。浏览器里的连接完全跳过密码输入。不带 密码的 DSN(如postgres://app@host/db)只渲染servers.json,pgAdmin 在首 次连接时照常提示输入。 - pgpass 文件以明文在磁盘上持有密码。 它位于
<base-dir>/addon/pgadmin/<name>/pgpass,mode 0600,只有宿主用户和容器内的 uid 5050 可读——但它确实在磁盘上,不像servers.json从不持有机密。权衡与 所有使用.pgpass的工具相同:免密连接以磁盘上的一个文件为代价。移除 pgAdmin 实例(无论是否带--clean-data)会把 pgpass 与servers.json一并删除——明文 机密不会比实例活得更久。 - 它是一次性便利,不是耦合。 安装后服务器活在 pgAdmin 自己的
pgadmin4.db里;在 UI 里增、删、改名都行。无论预置目标是否可达,插件照常运行。 PGADMIN_REPLACE_SERVERS_ON_STARTUP=True在有预置时被设置,所以改了--dsn/--pg-name的--force重装会声明式地重新应用预置,而不是留下过期 条目。
要去掉预置并停止声明式重载,用 --force 重装且不带 --dsn/--pg-name。
预置的 host 与 listen 地址
这两个旋钮指向不同的机器,却常被混为一谈。--listen 决定谁能访问 pgAdmin
的 Web UI;预置里的 host 决定pgAdmin 从哪里拨数据库。拨号是服务端动作——
由 pgAdmin 容器发起,不是你的浏览器——所以用 --pg-name 预置本机管理的实例
时,记录 127.0.0.1 是正确的:pgAdmin 与那个 PostgreSQL 共享 podman 宿主
的回环。把 UI 暴露到网络并不要求预置的 host 对浏览器可路由:
反过来的坑:用 --dsn 预置一个远程 host,再从别处用浏览器访问那个
pgAdmin——连接依然源自 pgAdmin 容器内部,所以那个 host 必须从容器的网络
可达,而不是从你的笔记本可达。
端口
每个实例从 pgAdmin 自己的端口池 pgadmin_start_port(默认 5050)取一个
端口——与 Redis、Predixy、对象存储的池各自独立游标,所以一个 pgadmin、一个
redis、一个 minio 实例永不撞车:
--port 可把实例钉在特定端口;自动分配会跳过任何已显式指定或宿主上已占用的
端口。
配置
实例位于 pg.yaml 的顶层 addons.pgadmin map:
对 listen、host_port、email、password、image_tag、dsn/
server_name、data_dir 的修改,在下一次
pg addon install pgadmin --name console --force(或对应 flags)后生效。记住:
email/password 只对空数据目录重新生效,所以对既有存储改它们需要先
--clean-data(或接受生效账号仍是原账号)。
列出
pg addon list 从不打印密码。要刻意揭示,加 --show-password(每实例追加一行
Login password:),或用 pg addon password pgadmin --name console 裸打印——
并且优先用 --file,让机密留在文件里而不是 shell 历史与回滚缓冲。
启停
start 只启动已存在的容器(并以按配置重建来自愈不当状态)。数据目录跨
stop/start 保留,pgAdmin 的已存服务器与浏览历史随之回来。
开机自启
容器带 --restart unless-stopped 策略(管崩溃,不管重启)。要在宿主重启后拉起
实例:
pgAdmin 自启是只启动(先 install)。开机时 entrypoint 重新 chown
/var/lib/pgadmin,并从数据目录重放 pgadmin4.db,已存服务器随之复活。pgAdmin
独立于 PostgreSQL 栈——它是自己拨号的浏览器 UI——所以没有排序约束,开机
pg start --autostart 把它与其他非数据库插件一起启动。
移除
不带 --clean-data 时宿主数据目录(连同 pgadmin4.db)原地保留——重装同名实
例即复活已存服务器与会话存储(复活后可用的登录是原密码,不是新生成的)。
--clean-data 删除它,当 rootless podman 的映射 uid 使 pgAdmin 的文件对宿主用
户不可删时,经 podman unshare rm 兜底。空的实例父目录被修剪;自定义
data_dir 的父目录从不动。servers.json(若预置过)宿主所有,两种移除都随实
例目录一并删掉。
日志
排障
- 用打印的密码登录被拒 —— 你在保留的数据目录上重装了。能用的登录是
pgAdmin 在首次初始化时写进
pgadmin4.db的那个;新生成的密码无效(install 会警告 “reviving an existing pgAdmin data dir”)。用--clean-data从零开 始,或用pg addon password pgadmin确认存的是什么,或重装时把--password钉成原值。 - “pgAdmin could not start” / 容器秒退(或崩溃循环) —— 空目录上 entrypoint
需要
PGADMIN_DEFAULT_EMAIL和PGADMIN_DEFAULT_PASSWORD都在;手改过pg.yaml、密码为空且数据目录被清过,就两个都没有。让 install 生成,或传--password。它还会校验邮箱并拒绝保留 TLD(.local、.invalid、.test),报 “does not appear to be a valid email address”——容器随即退出, 在重启策略下反复自旋。这正是默认值取admin@pgcli.lan而非.local的原因。 - 从别的机器打不开 UI ——
listen默认127.0.0.1。--listen 0.0.0.0 --force重装并接受 Web 登录成为唯一防线。 - 预置的服务器显示但连不上 —— 若预置 DSN 不带密码,pgAdmin 在首次连接时
提示输入(预置服务器)。若 DSN 带了密码却仍然提示,说明
pgpass 文件失效了——最常见原因是容器启动时 entrypoint 的
chown没跑(例如 直接传了--user 5050而不是--user 0,导致文件属主是宿主用户,libpq 静默 丢弃它)。确认 host/port 从 pgAdmin 所在处可达。 - install 时找不到镜像 ——
docker.io/dpage/pgadmin4按需拉取;离线宿主先podman load对应 tar(目录与导出步骤在仓库docs/images.md),pull 会被跳 过。 - 首次启动响应慢 —— pgAdmin 在 gunicorn 绑定之前先跑 sqlite 迁移;端口要
等它完成才回 302(跳
/login)。负载高的宿主上这可能要几十秒。
已知限制
- 无 TLS —— 这里 pgAdmin 提供明文 HTTP。要 HTTPS 就在前面架反向代理(配合
PGADMIN_URL_SCHEME/PGADMIN_DISABLE_STATIC_FILE_SERVER调参);pgcli 不接 这条线。loopback 默认让常见场景留在127.0.0.1。 - 不适合大数据量导入导出 —— pgAdmin 的数据流经 HTTP(导出:查询 → 容器 →
浏览器;导入:上传 → 容器 → 数据库),大表容易超时或内存溢出。应改用
pg export/pg import(pgcli 实例),或pg exec --dsn "..." -- pg_dump(任意数据库)。详见 数据导入导出。 - 预置密码以明文存于磁盘 ——
servers.json旁边的 pgpass 文件是 mode 0600 但没有加密;移除实例时一并清理。没有办法预配置密码而不落盘文件(pgAdmin 没有 KMS/keyring 集成)。 - 登录只对新存储生效 —— pgAdmin 只在初始化
pgadmin4.db期间读取PGADMIN_DEFAULT_*;该文件一旦存在,库里的账号就是权威。没有原地改密码的路, 只有--clean-data+ 重装。 - macOS 代码完备、未实测 —— bridge 路径(发布端口、bridge 下
proxyBindHost把 loopback 放宽到0.0.0.0)与 redis 同构;尚未在 Mac 上演 练过。