部署安全的pg数据库,搭配pgadmin实现web界面管理

说明

此方法适合以下环境,内网隔离,联网时需要安全防护,适合无数据库管理工具,希望通过非命令行环境管理pg数据库,pg数据库不可对外暴露端口。

PostgreSQL 18.3 + pgAdmin 部署说明

说明

本目录提供一个单一 Docker Compose 编排,用于部署 PostgreSQL 18.3 与 pgAdmin 4 9.17。

架构与安全边界如下:

  • PostgreSQL 服务名为 postgresql,只暴露到内部 Docker 网络 pg-net,不会向物理机发布 5432 端口。
  • pgAdmin 服务名为 pgadmin,通过物理机 80 端口提供 Web 管理界面。
  • PostgreSQL 与 pgAdmin 共同位于内部 Docker 网络 pg-net,并通过 postgresql:5432 连接;pgAdmin 另外连接 pgadmin-net,用于接收物理机浏览器访问。
  • PostgreSQL 业务账户为 hive,默认数据库为 metastore_db
  • pgAdmin 管理员账户为 admin@example.com
  • PostgreSQL 与 pgAdmin 使用独立的 32 位随机密码,密码仅保存于 .envpgpass,两者都被 .gitignore 排除。
  • servers.jsonpgpass 会预置 pgAdmin 的数据库连接。启动完成后,只需登录 pgAdmin,即可直接管理 PostgreSQL,无需在页面中新增服务器或填写数据库密码。

组件版本:

组件 镜像 用途
PostgreSQL postgres:18.3 关系型数据库
pgAdmin dpage/pgadmin4:9.17 PostgreSQL Web 管理界面

网络拓扑:

text 复制代码
浏览器
  |
  | http://<服务器IP>:80
  v
pgAdmin (80/tcp,物理机端口发布)
  |
  | postgresql:5432,仅 Docker 内部网络
  v
PostgreSQL (5432/tcp,不发布到物理机)

准备环境

1. 前置条件

  • Docker Engine 24+。
  • Docker Compose Plugin v2+。
  • 物理机 80/tcp 未被 Nginx、Apache、IIS 或其他容器占用。
  • 建议主机至少提供 4 CPU、8 GB 内存和足够的数据盘空间。Compose 对 PostgreSQL 设置 2 CPU / 4 GB 上限,对 pgAdmin 设置 1 CPU / 1 GB 上限。

在本目录执行以下命令检查环境:

bash 复制代码
docker --version
docker compose version
docker ps --format "table {{.Names}}\t{{.Ports}}"

如果 80 端口被占用,先停止冲突服务,或将 compose.yaml 中 pgAdmin 的 "80:80" 修改为未占用端口,例如 "5050:80"。修改后访问地址也要改为 http://<服务器IP>:5050

2. 保护密码文件

.envpgpass 包含真实密码,不应提交到 Git、共享到聊天记录或复制到不受控位置。

Linux 服务器建议限制文件权限:

bash 复制代码
chmod 600 .env pgpass

Windows 上应限制目录访问权限,仅允许部署账户读取本目录中的 .envpgpass

3. 首次部署前确认

postgresql-datapgadmin-data 是命名卷。若本机此前已使用同名卷运行过其他 PostgreSQL 或 pgAdmin 环境,先检查卷是否存在:

bash 复制代码
docker volume ls --filter name=postgresql-data
docker volume ls --filter name=pgadmin-data

不要在不确认数据归属的情况下删除已有卷。

配置文件

文件清单

文件 作用 是否包含密码
compose.yaml PostgreSQL 与 pgAdmin 的统一编排
.env 镜像环境变量、账户名、密码和数据库参数
servers.json pgAdmin 自动注册的 PostgreSQL 连接
pgpass pgAdmin 自动连接 PostgreSQL 所需密码
.gitignore 防止 .envpgpass 进入 Git
v2.yaml 原始 PostgreSQL 单服务配置,仅作历史参考 是,含旧弱密码

compose.yaml

bash 复制代码
name: pg-stack

services:
  postgresql:
    image: postgres:18.3
    container_name: postgresql
    restart: unless-stopped
    environment:
      TZ: ${TZ}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_INITDB_ARGS: ${POSTGRES_INITDB_ARGS}
    volumes:
      - postgresql-data:/var/lib/postgresql
    networks:
      - pg-net
    expose:
      - "5432"
    command:
      - postgres
      - -c
      - max_connections=${POSTGRES_MAX_CONNECTIONS}
      - -c
      - shared_buffers=${POSTGRES_SHARED_BUFFERS}
      - -c
      - work_mem=${POSTGRES_WORK_MEM}
      - -c
      - maintenance_work_mem=${POSTGRES_MAINTENANCE_WORK_MEM}
      - -c
      - effective_cache_size=${POSTGRES_EFFECTIVE_CACHE_SIZE}
      - -c
      - checkpoint_completion_target=${POSTGRES_CHECKPOINT_COMPLETION_TARGET}
      - -c
      - wal_buffers=${POSTGRES_WAL_BUFFERS}
      - -c
      - default_statistics_target=${POSTGRES_DEFAULT_STATISTICS_TARGET}
      - -c
      - log_min_messages=${POSTGRES_LOG_MIN_MESSAGES}
      - -c
      - log_rotation_size=${POSTGRES_LOG_ROTATION_SIZE}
      - -c
      - password_encryption=scram-sha-256
      - -c
      - listen_addresses=${POSTGRES_LISTEN_ADDRESSES}
    deploy:
      resources:
        limits:
          cpus: "2"
          memory: 4g
    healthcheck:
      test:
        - CMD-SHELL
        - pg_isready -U "$${POSTGRES_USER}" -d "$${POSTGRES_DB}"
      interval: 10s
      timeout: 5s
      retries: 6
      start_period: 30s

  pgadmin:
    image: dpage/pgadmin4:9.17
    container_name: pgadmin
    restart: unless-stopped
    depends_on:
      postgresql:
        condition: service_healthy
    environment:
      TZ: ${TZ}
      PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL}
      PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD}
      PGADMIN_SERVER_JSON_FILE: /pgadmin4/servers.json
      PGPASS_FILE: /pgadmin4/pgpass
      PGADMIN_REPLACE_SERVERS_ON_STARTUP: "True"
      PGADMIN_CONFIG_ENHANCED_COOKIE_PROTECTION: "True"
    ports:
      - "80:80"
    volumes:
      - pgadmin-data:/var/lib/pgadmin
      - ./servers.json:/pgadmin4/servers.json:ro
      - ./pgpass:/pgadmin4/pgpass:ro
    networks:
      - pg-net
      - pgadmin-net
    deploy:
      resources:
        limits:
          cpus: "1"
          memory: 1g
    healthcheck:
      test:
        - CMD-SHELL
        - wget --no-verbose --tries=1 --spider http://127.0.0.1:80/login || exit 1
      interval: 15s
      timeout: 5s
      retries: 6
      start_period: 45s

volumes:
  postgresql-data:
    name: postgresql-data
  pgadmin-data:
    name: pgadmin-data

networks:
  pg-net:
    name: pg-net
    driver: bridge
    internal: true
  pgadmin-net:
    name: pgadmin-net
    driver: bridge

compose.yaml 是唯一的运行编排文件。

PostgreSQL 配置要点:

  • 未配置 ports,使用 expose: "5432" 仅声明容器网络内端口。
  • pg-net 设置为 internal: true,仅承载 PostgreSQL 与 pgAdmin 的数据库连接;pgAdmin 额外接入 pgadmin-net 并通过自身发布的 80 端口接收浏览器请求。
  • 数据目录使用 PostgreSQL 18 的父目录挂载路径 /var/lib/postgresql,由镜像根据主版本自动选择内部数据目录,避免将 PostgreSQL 18 数据写入不兼容的历史路径。
  • max_connections=200,适配 4 GB 容器内存的保守基线。高并发短连接场景应使用应用侧连接池,例如 PgBouncer,而不是直接提高连接数。
  • password_encryption=scram-sha-256 使后续新建或修改的角色密码使用 SCRAM-SHA-256 哈希保存。默认镜像初始化的本地认证不需要额外暴露密码认证规则;应用容器通过受控内部网络访问。
  • shared_buffers=1GBeffective_cache_size=3GBwork_mem=8MBmaintenance_work_mem=256MB 是 4 GB 上限下的通用基线;大型排序、哈希连接和维护任务会叠加使用内存,应结合实际负载调优。
  • 健康检查使用 pg_isready,只有数据库就绪后 pgAdmin 才会启动。

pgAdmin 配置要点:

  • ports: "80:80" 将 Web 控制台发布到物理机 80 端口。
  • 使用命名卷 pgadmin-data 持久化登录会话、用户配置和 pgAdmin 内置配置库。
  • PGADMIN_REPLACE_SERVERS_ON_STARTUP=True 使 servers.json 在每次启动时按文件内容同步,便于声明式维护预置连接。
  • PGPASS_FILE=/pgadmin4/pgpass 将挂载的 pgpass 文件用于数据库认证。该文件格式为 主机:端口:数据库:用户名:密码
  • 设置 PGADMIN_CONFIG_ENHANCED_COOKIE_PROTECTION=True,加强会话 Cookie 防护。

.env

bash 复制代码
TZ=Asia/Shanghai

POSTGRES_USER=hive
POSTGRES_PASSWORD=82iCQba@zkgyHA4K1_7MGDkg+qw+sTco
POSTGRES_DB=metastore_db
POSTGRES_INITDB_ARGS=--encoding=UTF8 --lc-collate=C --lc-ctype=C
POSTGRES_MAX_CONNECTIONS=200
POSTGRES_SHARED_BUFFERS=1GB
POSTGRES_WORK_MEM=8MB
POSTGRES_MAINTENANCE_WORK_MEM=256MB
POSTGRES_EFFECTIVE_CACHE_SIZE=3GB
POSTGRES_CHECKPOINT_COMPLETION_TARGET=0.9
POSTGRES_WAL_BUFFERS=16MB
POSTGRES_DEFAULT_STATISTICS_TARGET=100
POSTGRES_LOG_MIN_MESSAGES=warning
POSTGRES_LOG_ROTATION_SIZE=100MB
POSTGRES_LISTEN_ADDRESSES=*

PGADMIN_DEFAULT_EMAIL=admin@example.com
PGADMIN_DEFAULT_PASSWORD=LIPli9McIaYgHEQvDAeiLk+1J=JtepID

.env 保存运行参数和密码。当前已配置:

dotenv 复制代码
POSTGRES_USER=hive
POSTGRES_DB=metastore_db
PGADMIN_DEFAULT_EMAIL=admin@example.com

POSTGRES_PASSWORD 是 PostgreSQL 中 hive 用户的密码;PGADMIN_DEFAULT_PASSWORD 是 pgAdmin Web 登录密码。这两个密码相互独立。PGADMIN_DEFAULT_EMAIL 必须使用有效、非保留域名格式的邮箱地址;example.local 会被 pgAdmin 9.17 拒绝,不能使用。

更换密码时必须同步修改:

  1. .env 中的 POSTGRES_PASSWORD
  2. pgpass 中对应 PostgreSQL 连接的最后一列。
  3. 已初始化数据库内 hive 用户的实际密码。

首次初始化后,仅修改 .env 不会自动修改 PostgreSQL 已存在用户密码。请通过 pgAdmin 查询工具或容器命令执行:

bash 复制代码
docker compose exec -T postgresql psql -U hive -d metastore_db -c "ALTER ROLE hive WITH PASSWORD '替换为新密码';"

随后更新 pgpass,并重启 pgAdmin:

bash 复制代码
docker compose restart pgadmin

servers.json 与 pgpass

servers.json

bash 复制代码
{
  "Servers": {
    "1": {
      "Name": "PostgreSQL 18.3",
      "Group": "Local Docker",
      "Host": "postgresql",
      "Port": 5432,
      "MaintenanceDB": "metastore_db",
      "Username": "hive",
      "SSLMode": "prefer",
      "PassFile": "/pgadmin4/pgpass"
    }
  }
}

pgpass

bash 复制代码
postgresql:5432:metastore_db:hive:82iCQba@zkgyHA4K1_7MGDkg+qw+sTco

servers.json 预置以下连接:

项目
显示名称 PostgreSQL 18.3
分组 Local Docker
主机 postgresql
端口 5432
数据库 metastore_db
用户 hive
SSL 模式 prefer

这里的 postgresql 是 Compose 服务名,不是物理机 IP,也不是 localhost。容器内使用 localhost 会指向 pgAdmin 容器自身,无法连接数据库。

pgAdmin 的服务器清单在首次初始化时导入;本编排额外启用了 PGADMIN_REPLACE_SERVERS_ON_STARTUP=True,因此修改 servers.json 后重启 pgAdmin 即可同步服务器定义:

bash 复制代码
docker compose restart pgadmin

pgpass 是密码文件,必须与 .envPOSTGRES_PASSWORD 保持一致。不要将其提交到仓库。

使用外部应用连接数据库

PostgreSQL 没有物理机端口映射,因此宿主机上的客户端和其他物理机不能直接连接 5432。需要连接数据库的其他容器应加入现有外部网络 pg-net,并配置以下连接信息:

text 复制代码
host=postgresql
port=5432
database=metastore_db
username=hive

外部 Compose 项目加入网络的示例:

yaml 复制代码
networks:
  pg-net:
    external: true
    name: pg-net

services:
  application:
    networks:
      - pg-net

只有受控容器应加入该网络。避免为了临时排障将 PostgreSQL 映射为 5432:5432

部署

1. 校验配置

pg 目录执行:

bash 复制代码
docker compose --env-file .env config

命令应成功输出渲染后的 Compose 配置。确认 postgresql 服务没有 ports 字段,pgadmin 服务存在 80:80 映射。

2. 启动服务

bash 复制代码
docker compose up -d

查看服务状态:

bash 复制代码
docker compose ps
docker compose logs --tail=100 postgresql
docker compose logs --tail=100 pgadmin

预期结果:

  • postgresql 状态为 healthy
  • pgadmin 状态为 healthyrunning
  • docker compose ps 中 PostgreSQL 不应显示 0.0.0.0:5432->5432/tcp
  • docker compose ps 中 pgAdmin 应显示 0.0.0.0:80->80/tcp

3. 登录 pgAdmin

浏览器访问:

text 复制代码
http://<服务器IP>:80

使用 .env 中的以下凭据登录:

text 复制代码
邮箱:PGADMIN_DEFAULT_EMAIL
密码:PGADMIN_DEFAULT_PASSWORD

登录后在左侧依次展开:

text 复制代码
Servers > Local Docker > PostgreSQL 18.3

连接由 servers.jsonpgpass 自动配置,应可直接展开 Databases > metastore_db

pgAdmin 管理员邮箱校验失败

如果 docker logs pgadmin 出现以下错误:

text 复制代码
'<邮箱地址>' does not appear to be a valid email address
The part after the @-sign is a special-use or reserved name

原因是 PGADMIN_DEFAULT_EMAIL 使用了 .local 等保留域名。pgAdmin 9.17 会校验管理员邮箱,即使不配置邮件发送功能也会拒绝此类域名。

修复步骤:

bash 复制代码
sed -i 's/^PGADMIN_DEFAULT_EMAIL=.*/PGADMIN_DEFAULT_EMAIL=admin@example.com/' .env
docker compose up -d --force-recreate pgadmin
docker compose logs --tail=100 pgadmin

PGADMIN_DEFAULT_EMAIL 仅用于 pgAdmin Web 登录标识,不要求邮箱实际可收信;但域名必须不属于 .local.localhost.test.invalid 等保留域。

访问验证

1. 验证数据库不向物理机发布

bash 复制代码
docker compose ps
docker port postgresql

正确结果是 PostgreSQL 没有宿主机端口输出。docker compose ps 中仅 pgAdmin 显示物理机端口 80

2. 验证数据库健康状态

bash 复制代码
docker compose exec postgresql pg_isready -U hive -d metastore_db
docker compose exec postgresql psql -U hive -d metastore_db -c "SELECT current_user, current_database(), version();"

第一条命令应返回 accepting connections;第二条命令应显示当前用户为 hive、当前数据库为 metastore_db,并输出 PostgreSQL 版本。

3. 验证 pgAdmin 自动连接

登录 pgAdmin 后,在 PostgreSQL 18.3 上右键选择 Query Tool,执行:

sql 复制代码
SELECT current_user, current_database(), inet_server_addr(), inet_server_port();

预期:

  • current_userhive
  • current_databasemetastore_db
  • inet_server_port5432
  • 服务器地址是 Docker 内部 IP,不是物理机 IP。

日常运维

停止和启动

bash 复制代码
docker compose stop
docker compose start

停止不会删除命名卷和数据。

重启服务

bash 复制代码
docker compose restart postgresql
docker compose restart pgadmin

修改 servers.jsonpgpass 后,重启 pgAdmin;修改 PostgreSQL 运行参数后,重启 PostgreSQL。

查看日志

bash 复制代码
docker compose logs -f postgresql
docker compose logs -f pgadmin

备份数据库

逻辑备份应输出到受控的宿主机备份目录,而不是容器临时目录:

bash 复制代码
mkdir -p backups
docker compose exec -T postgresql pg_dump -U hive -d metastore_db -Fc > backups/metastore_db-$(date +%F).dump

验证备份文件:

bash 复制代码
docker compose exec -T postgresql pg_restore -l < backups/metastore_db-$(date +%F).dump

生产环境应将备份目录同步到独立存储,并定期执行还原演练。

恢复数据库

恢复会覆盖目标数据库中的对象。执行前确认备份来源和目标环境:

bash 复制代码
docker compose exec -T postgresql dropdb -U hive metastore_db
docker compose exec -T postgresql createdb -U hive metastore_db
docker compose exec -T postgresql pg_restore -U hive -d metastore_db --clean --if-exists < backups/metastore_db-YYYY-MM-DD.dump

安全注意事项

  1. 当前 pgAdmin 使用 HTTP 发布在 80 端口。若需从不可信网络访问,必须在前置反向代理上配置 HTTPS、访问控制和可信证书,或限制物理机防火墙仅允许管理网段访问 80/tcp
  2. PostgreSQL 仅对 Docker 内部网络开放,不应额外添加 ports: "5432:5432"
  3. .envpgpass 和备份文件均可能包含敏感信息,应执行最小权限控制、定期轮换密码和异机备份。
  4. 自动保存数据库密码是为了满足"启动后 pgAdmin 可直接使用"的要求。仅限受控管理环境;若需要更高安全等级,删除 pgpass 挂载并改为首次连接手工输入密码。
  5. POSTGRES_USERPOSTGRES_DBPOSTGRES_INITDB_ARGS 只在 postgresql-data 为空时生效。不要在已有数据卷上随意变更这些初始化参数。
  6. 不要使用 docker compose down -v 清理环境,除非已完成备份且明确要永久删除 PostgreSQL 与 pgAdmin 数据。

访问使用

账户密码来自文件.env

相关推荐
行百里er1 小时前
HandlerInterceptor 和 WebFilter:两套 HTTP 请求拦截
后端·架构·监控
爱勇宝1 小时前
初创公司的“自己人”,到底能当多久?
前端·后端·程序员
掘金者阿豪1 小时前
Mac mini Intel 报错 DNS_PROBE_FINISHED_BAD_CONFIG 完整排障指南
后端
大模型丫丫2 小时前
如何提升单体 Spring Boot 应用的并发数?
java·spring boot·后端
Terra.K2 小时前
ThreadLocal解决一个线程里面跨层传递问题
java·开发语言·后端
Lost of 程序猿2 小时前
ASP.NET Core API 版本管理与契约演进:船岸版本碎片化下的兼容性工程
后端·asp.net
吃饱了得干活2 小时前
Redis 从单机到集群:持久化、主从复制、哨兵与集群完全指南
redis·后端
老孙讲技术2 小时前
【监控开发】把车间未戴帽和烟火报警接进安监值班群:workwearDetect 对图与 setMessageCallback 订 smokeAlarm
后端·物联网·音视频开发
计算机毕设定制辅导-无忧学长3 小时前
基于Spring Boot的玄幻小说个性化推荐平台设计与实现
java·vue.js·spring boot·后端·mysql·推荐算法