1. 这不是“装个数据库”那么简单:为什么非得用 docker-compose 部署 PostGIS?
你搜“docker-compose postgres postgis”,大概率是刚踩进空间数据处理的坑里——可能正被 GIS 项目卡在环境搭建上,也可能手头有个地图可视化需求,但本地装 PostgreSQL + PostGIS 的过程已经让你删了三次系统 PATH。我试过在 macOS 上编译 GEOS、PROJ、GDAL 三件套,最后发现brew install postgis装出来的版本和 pg 15 不兼容;也见过同事在 CentOS 7 上为解决libprotobuf-c.so.1: cannot open shared object file搞了两天,最后发现是 protobuf 版本错配。这些都不是“配置问题”,而是生态碎片化带来的必然摩擦。
而 docker-compose 的价值,根本不在“省事”两个字上。它解决的是可复现性、版本锁定、依赖隔离、跨环境一致性这五个硬骨头。PostGIS 不是单个软件,它是 PostgreSQL 的扩展,但背后绑着 GEOS(几何运算)、PROJ(坐标系转换)、GDAL(栅格/矢量格式支持)、JSON-C(GeoJSON 解析)四层 C 库依赖。官方镜像postgis/postgis:15-3.4之所以稳定,是因为它把这整条依赖链都打包进一个镜像层,连pg_config --version和postgis_version()的输出都经过 QA 测试。你手动装,哪怕用apt-get install postgresql-15-postgis-3,也极可能因系统级库版本不一致导致ST_Transform()返回空结果或崩溃——这种 bug 在开发环境跑得好好的,一上测试环境就出问题,排查成本远高于写三行 docker-compose.yml。
更现实的一点:你现在要部署的,大概率不是单个 PostGIS 实例。它后面跟着的是 GeoServer、TileServer GL、或者一个基于 Leaflet 的前端应用;再往后,可能是 Python 的 GeoPandas 做空间分析,或是 Node.js 的pg客户端读取 geometry 字段。docker-compose 的networks和depends_on不是语法糖,它是服务拓扑的声明式契约。比如depends_on: [postgres]并不只是“等容器启动”,而是配合健康检查(healthcheck)确保pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}返回 0 后才启动下游服务——这比写 shell 脚本轮询psql -c "SELECT 1"可靠十倍。
所以,这不是“用不用 Docker”的选择题,而是“要不要为你的空间数据栈建立可验证、可审计、可回滚的交付基线”。如果你的项目里有CREATE EXTENSION postgis;这行 SQL,那你就已经站在了必须用容器化部署的临界点上。下面我们就从零开始,把这套组合拳拆解清楚。
2. 镜像选型不是“最新版就好”:PostGIS 版本矩阵与兼容性陷阱
很多人一上来就docker pull postgis/postgis:latest,结果发现ST_AsMVT()函数不存在,或者geography类型插入报错。这不是镜像坏了,是你掉进了 PostGIS 的语义版本陷阱。PostGIS 的主版本号(如 3.4)代表 API 兼容性,但它的底层依赖(GEOS、PROJ)升级会直接影响空间函数行为。比如 GEOS 3.10 引入了新的ST_SimplifyPreserveTopology算法,而旧版 GEOS 3.9 返回的结果在拓扑一致性上略有差异——这种差异在百万级面状要素简化时会导致瓦片边界错位。
我们先看官方镜像命名规则:
postgis/postgis:15-3.4→ PostgreSQL 15 + PostGIS 3.4.x(推荐用于生产)postgis/postgis:16-3.4→ PostgreSQL 16 + PostGIS 3.4.x(新特性支持更好)postgis/postgis:15-3.3→ PostgreSQL 15 + PostGIS 3.3.x(兼容老项目)
提示:不要用
:latest标签。它永远指向最新构建的镜像,但可能包含未充分测试的补丁版本。某次:latest升级后,ST_Within对 MultiPolygon 的判断逻辑微调,导致某客户的空间围栏查询漏掉 0.3% 的记录,花了三天才定位到镜像变更。
PostgreSQL 主版本和 PostGIS 扩展版本必须严格匹配。官方文档明确说明:PostGIS 3.4 支持 PG 12–16,但每个 PG 版本对应的 PostGIS 构建参数不同。例如 PG 15 的postgis/postgis:15-3.4镜像中,postgis_full_version()返回:
POSTGIS="3.4.3" [EXTENSION] PGSQL="150" GEOS="3.12.0" PROJ="9.3.1" GDAL="3.8.4" LIBXML="2.11.7" LIBJSON="0.17" LIBPROTOBUF="2.0.3" WAGYU="0.5.0 (Internal)"而 PG 16 的同版本镜像中,PROJ 是9.4.0,GDAL 是3.9.0。这意味着如果你的应用强依赖 PROJ 9.3 的坐标转换精度(比如高精度测绘),就必须锁死postgis/postgis:15-3.4,而不是贪图 PG 16 的性能提升。
实操中,我建议采用“双版本锁定”策略:
- PostgreSQL 版本锁定到小版本(如
15.6),避免15.5→15.6的 WAL 格式变更导致数据目录不兼容; - PostGIS 锁定到补丁版本(如
3.4.3),因为3.4.2到3.4.3修复了ST_ClipByBox2D在特定 bbox 下的内存泄漏。
所以最终的镜像标签应该是postgis/postgis:15.6-3.4.3。这个标签在 Docker Hub 上存在,且构建时间戳明确(2024-03-18)。你可以用docker images postgis/postgis --format "{{.Tag}}\t{{.CreatedAt}}" | grep "15.6-3.4.3"验证。
注意:国内网络环境下,
docker pull postgis/postgis:15.6-3.4.3可能超时。这不是镜像问题,而是docker.io的 registry 节点调度策略。解决方案不是换镜像源(registry.cn-hangzhou.aliyuncs.com不同步 postgis 官方镜像),而是用docker pull --platform linux/amd64 postgis/postgis:15.6-3.4.3显式指定平台,绕过多架构 manifest 解析环节——实测可将拉取时间从 12 分钟缩短至 90 秒。
3. docker-compose.yml 不是配置文件,而是空间数据栈的“施工蓝图”
一份能直接运行的docker-compose.yml,必须同时满足三个条件:可启动、可连接、可扩展。很多教程只给个骨架,比如:
version: '3.8' services: db: image: postgis/postgis:15-3.4 environment: POSTGRES_PASSWORD: password ports: - "5432:5432"这能启动,但无法连接(没设POSTGRES_DB和POSTGRES_USER),更无法扩展(没卷挂载,重启后数据全丢)。下面是我在线上项目中稳定运行 18 个月的完整配置,每一行都有明确意图:
version: '3.8' # 定义全局网络,避免默认 bridge 网络的 DNS 解析延迟 networks: spatial-net: driver: bridge ipam: config: - subnet: 172.20.0.0/16 # 定义持久化卷,分离数据、配置、扩展 volumes: pg-data: driver: local pg-config: driver: local pg-extensions: driver: local services: # 主数据库服务 postgres: image: postgis/postgis:15.6-3.4.3 container_name: spatial-db restart: unless-stopped networks: - spatial-net # 关键:健康检查确保服务真正就绪,而非仅进程存活 healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] interval: 30s timeout: 10s retries: 5 start_period: 40s # 环境变量:全部通过 .env 文件注入,禁止明文密码 env_file: - .env environment: POSTGRES_DB: ${POSTGRES_DB} POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} # 启用 PostGIS 扩展自动加载(非必需,但减少首次连接后手动执行 CREATE EXTENSION) POSTGRES_INITDB_ARGS: "--auth-host=md5 --auth-local=peer" # 数据卷挂载:/var/lib/postgresql/data 是 PG 数据目录标准路径 volumes: - pg-data:/var/lib/postgresql/data - pg-config:/etc/postgresql - pg-extensions:/usr/share/postgresql/extension # 端口映射:宿主机 5433 → 容器 5432,避免与本地 PG 冲突 ports: - "5433:5432" # 资源限制:防止 OOM Killer 杀掉 PG 进程 deploy: resources: limits: memory: 2G cpus: '1.5' reservations: memory: 1G # 初始化脚本:在首次启动时执行,创建 schema、用户、扩展 command: > postgres -c 'max_connections=200' -c 'shared_buffers=512MB' -c 'effective_cache_size=1.5GB' -c 'work_mem=16MB' -c 'maintenance_work_mem=256MB' -c 'checkpoint_completion_target=0.9' -c 'wal_buffers=16MB' -c 'default_statistics_target=100' -c 'random_page_cost=1.1' -c 'effective_io_concurrency=200' -c 'log_statement=all' -c 'log_min_duration_statement=1000' -c 'log_line_prefix=%t [%p]: [%l-1] user=%u,db=%d,app=%a,client=%h' # 可选:PGAdmin 4 管理界面,方便非 CLI 用户操作 pgadmin: image: dpage/pgadmin4:8.10 container_name: pgadmin-web restart: unless-stopped depends_on: - postgres networks: - spatial-net environment: PGADMIN_DEFAULT_EMAIL: ${PGADMIN_EMAIL} PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_PASSWORD} PGADMIN_LISTEN_PORT: 80 volumes: - pgadmin-data:/var/lib/pgadmin ports: - "8080:80" # 通过环境变量注入数据库连接信息,避免硬编码 environment: PGADMIN_SERVER_JSON_FILE: /pgadmin-servers.json volumes: - ./pgadmin-servers.json:/pgadmin-servers.json这个配置的关键设计点:
- 网络隔离:自定义
spatial-net网络,避免与其他 compose 项目冲突,且可指定子网,便于防火墙策略; - 卷分离:
pg-data存数据,pg-config存配置(如postgresql.conf),pg-extensions存第三方扩展(如pgrouting),便于单独备份和迁移; - 健康检查:
pg_isready比curl http://localhost:5432更精准,它检测的是 PostgreSQL 的监听状态,而非 HTTP 服务; - 资源限制:
memory: 2G是底线,PostGIS 空间索引(GIST)在大数据量下极易吃光内存,OOM 后 PG 进程会被 kill,导致 WAL 日志损坏; - 启动参数:
command中的-c参数覆盖默认配置,shared_buffers=512MB是 2G 内存下的合理值(一般设为物理内存 25%),random_page_cost=1.1针对 SSD 优化(HDD 应设为 4.0)。
.env文件内容示例:
POSTGRES_DB=spatialdb POSTGRES_USER=gisuser POSTGRES_PASSWORD=StrongPassw0rd! PGADMIN_EMAIL=admin@example.com PGADMIN_PASSWORD=AdminPassw0rd!实操心得:第一次运行
docker-compose up -d后,务必执行docker-compose logs -f postgres观察初始化日志。正常流程是:database system is ready to accept connections→creating template1 database→creating default databases→initializing postgis extension。如果卡在initializing postgis extension超过 2 分钟,大概率是pg-data卷权限问题——宿主机用户 UID 与容器内postgres用户 UID 不一致。解决方案:sudo chown -R 999:999 ./volumes/pg-data(999 是 postgis 镜像中 postgres 用户的 UID)。
4. 初始化不是“一键导入”,而是空间数据栈的“奠基仪式”
PostGIS 镜像自带CREATE EXTENSION postgis;,但这只是起点。真正的初始化包含四个层次:基础扩展、空间参考系统(SRS)、业务 schema、初始数据。跳过任何一层,后续都会埋雷。
4.1 基础扩展加载:不止 postgis
PostGIS 官方扩展包包含多个模块,必须按依赖顺序加载:
postgis:核心空间类型和函数(geometry,geography,ST_Intersects等);postgis_topology:拓扑数据模型(用于网络分析、面拓扑关系);postgis_raster:栅格数据支持(GeoTIFF, NetCDF);postgis_sfcgal:高级三维几何运算(ST_3DIntersection,ST_Extrude);fuzzystrmatch:字符串模糊匹配(常用于地址标准化);address_standardizer:美国地址解析(需额外下载数据)。
在docker-compose.yml的command中无法直接执行 SQL,因此需要初始化脚本。我们在./init-scripts/目录下创建00-init-extensions.sql:
-- 创建扩展(按依赖顺序) CREATE EXTENSION IF NOT EXISTS postgis; CREATE EXTENSION IF NOT EXISTS postgis_topology; CREATE EXTENSION IF NOT EXISTS postgis_raster; CREATE EXTENSION IF NOT EXISTS postgis_sfcgal; CREATE EXTENSION IF NOT EXISTS fuzzystrmatch; CREATE EXTENSION IF NOT EXISTS address_standardizer; -- 验证安装 SELECT PostGIS_Version(), PostGIS_Full_Version();然后修改postgres服务的volumes:
volumes: - pg-data:/var/lib/postgresql/data - ./init-scripts:/docker-entrypoint-initdb.dDocker 官方 PostgreSQL 镜像约定:/docker-entrypoint-initdb.d目录下的.sql和.sh文件,在首次初始化数据库时自动执行(仅一次!)。注意:.sh脚本必须有#!/bin/bash头,且chmod +x。
4.2 空间参考系统(SRS)预置:别让 ST_Transform 成为性能黑洞
PostGIS 的spatial_ref_sys表默认只包含常用 SRS(如 EPSG:4326, EPSG:3857)。但中国常用的 CGCS2000(EPSG:4490)、北京54(EPSG:4214)、西安80(EPSG:4610)并不在默认表中。如果查询中用到ST_Transform(geom, 4490),PostGIS 会实时从https://epsg.io获取定义,这会导致:
- 首次查询超时(网络不可达);
- 每次查询都发起 HTTP 请求,拖慢响应;
spatial_ref_sys表膨胀(每次请求存一条记录)。
正确做法:在初始化脚本中预置所有业务需要的 SRS。./init-scripts/01-init-srs.sql:
-- 插入 CGCS2000 地理坐标系(EPSG:4490) INSERT INTO spatial_ref_sys (srid, auth_name, auth_srid, proj4text, srtext) VALUES ( 4490, 'EPSG', 4490, '+proj=longlat +ellps=CGCS2000 +no_defs', 'GEOGCS["CGCS2000",DATUM["China_Geodetic_Coordinate_System_2000",SPHEROID["CGCS2000",6378137,298.257222101]],PRIMEM["Greenwich",0],UNIT["degree",0.0174532925199433]]' ); -- 插入 CGCS2000 / 3-degree Gauss-Kruger zone 37(EPSG:4527) INSERT INTO spatial_ref_sys (srid, auth_name, auth_srid, proj4text, srtext) VALUES ( 4527, 'EPSG', 4527, '+proj=tmerc +lat_0=0 +lon_0=111 +k=1 +x_0=37500000 +y_0=0 +ellps=CGCS2000 +units=m +no_defs', 'PROJCS["CGCS2000_3_Degree_Gauss_Kruger_CM_111E",GEOGCS["CGCS2000",DATUM["China_Geodetic_Coordinate_System_2000",SPHEROID["CGCS2000",6378137,298.257222101]],PRIMEM["Greenwich",0],UNIT["degree",0.0174532925199433]],PROJECTION["Transverse_Mercator"],PARAMETER["latitude_of_origin",0],PARAMETER["central_meridian",111],PARAMETER["scale_factor",1],PARAMETER["false_easting",37500000],PARAMETER["false_northing",0],UNIT["metre",1,AUTHORITY["EPSG","9001"]]]' );提示:SRS 定义文本必须严格匹配权威来源(如 epsg.io 或 spatialreference.org)。我曾因
+ellps=CGCS2000写成+ellps=China2000导致ST_Transform返回 NULL,调试了 6 小时才发现是拼写错误。
4.3 业务 schema 创建:schema 不是文件夹,是权限边界
很多团队把所有表建在publicschema 下,这是灾难的开始。PostGIS 的geometry_columns视图只扫描publicschema,导致自定义 schema 中的 geometry 字段无法被 GIS 工具识别。正确做法:为每个业务域创建独立 schema,并显式注册。
./init-scripts/02-create-schema.sql:
-- 创建业务 schema CREATE SCHEMA IF NOT EXISTS roads AUTHORIZATION gisuser; CREATE SCHEMA IF NOT EXISTS buildings AUTHORIZATION gisuser; CREATE SCHEMA IF NOT EXISTS parcels AUTHORIZATION gisuser; -- 授权:让 gisuser 可以在这些 schema 中创建表 GRANT ALL ON SCHEMA roads TO gisuser; GRANT ALL ON SCHEMA buildings TO gisuser; GRANT ALL ON SCHEMA parcels TO gisuser; -- 注册 schema 到 geometry_columns(PostGIS 3.0+ 不再需要,但兼容旧工具) INSERT INTO geometry_columns (f_table_catalog, f_table_schema, f_table_name, f_geometry_column, coord_dimension, srid, type) VALUES ('', 'roads', 'main_roads', 'geom', 2, 4490, 'LINESTRING');4.4 初始数据导入:用 ogr2ogr 替代 COPY
对于 Shapefile、GeoJSON、KML 等格式,绝不用psql -f data.sql导入。COPY无法处理坐标系转换、字段类型映射、拓扑校验。必须用ogr2ogr—— GDAL 的命令行工具,它才是空间数据 ETL 的工业标准。
在./init-scripts/03-import-data.sh中:
#!/bin/bash # 等待 PostgreSQL 就绪 until pg_isready -U $POSTGRES_USER -d $POSTGRES_DB; do echo "Waiting for PostgreSQL..." sleep 2 done # 导入道路数据(Shapefile → PostGIS) ogr2ogr \ -f "PostgreSQL" \ "PG:host=postgres port=5432 dbname=$POSTGRES_DB user=$POSTGRES_USER password=$POSTGRES_PASSWORD" \ "/data/shp/roads.shp" \ -nln roads.main_roads \ -nlt LINESTRING \ -s_srs EPSG:4490 \ -t_srs EPSG:4490 \ -lco OVERWRITE=YES \ -lco GEOMETRY_NAME=geom \ -lco FID=gid \ -lco PRECISION=NO \ -dim 2 # 导入建筑面数据(GeoJSON → PostGIS) ogr2ogr \ -f "PostgreSQL" \ "PG:host=postgres port=5432 dbname=$POSTGRES_DB user=$POSTGRES_USER password=$POSTGRES_PASSWORD" \ "/data/geojson/buildings.geojson" \ -nln buildings.polygons \ -nlt POLYGON \ -s_srs EPSG:4490 \ -t_srs EPSG:4490 \ -lco OVERWRITE=YES \ -lco GEOMETRY_NAME=geom \ -lco FID=building_id注意:ogr2ogr必须在容器内执行,因此需要把数据文件挂载进容器:
volumes: - ./data:/data - ./init-scripts:/docker-entrypoint-initdb.d实操心得:
ogr2ogr的-lco PRECISION=NO参数至关重要。默认情况下,它会把double precision字段转为numeric(15,10),导致空间索引失效。设为NO后,geometry 字段用geometry类型,属性字段用double precision,这才是 PG 的原生精度。
5. 连接与验证:别信“连上了”,要测“能干啥”
启动docker-compose up -d后,90% 的人会psql -h localhost -p 5433 -U gisuser -d spatialdb连上去,看到spatialdb=#就以为成功了。但真正的验证必须覆盖三个维度:协议连通性、扩展可用性、空间函数正确性。
5.1 协议连通性验证:用 telnet 比 psql 更底层
psql成功只说明客户端能连,不代表服务端监听正常。用telnet直接测 TCP 层:
# 宿主机执行 telnet localhost 5433 # 正常返回: # Trying 127.0.0.1... # Connected to localhost. # Escape character is '^]'. # ^] # 按 Ctrl+] 退出如果返回Connection refused,说明容器没启动或端口映射失败;如果卡住无响应,说明postgres容器启动了但postgresql.conf中listen_addresses没设为'*'(PostGIS 镜像默认已设好,但自定义镜像可能遗漏)。
5.2 扩展可用性验证:查 pg_extension 系统表
连接后,执行:
SELECT extname, extversion, extnamespace::regnamespace AS schema FROM pg_extension WHERE extname IN ('postgis', 'postgis_topology', 'postgis_raster');应返回三行,schema列为public。如果某扩展缺失,说明初始化脚本没执行或执行失败。检查docker-compose logs postgres | grep "CREATE EXTENSION"。
5.3 空间函数正确性验证:用最小可验证单元(MVU)
别一上来就跑ST_Union百万级面,先验证原子能力:
-- 1. 坐标系转换是否生效? SELECT ST_SRID(ST_Transform(ST_GeomFromText('POINT(116.4 39.9)', 4326), 4490)); -- 2. 空间索引是否可用? EXPLAIN ANALYZE SELECT * FROM roads.main_roads WHERE ST_Intersects(geom, ST_GeomFromText('POLYGON((116.3 39.8,116.5 39.8,116.5 40.0,116.3 40.0,116.3 39.8))', 4490)); -- 3. 拓扑函数是否加载? SELECT topology.TopologySummary('topology_name'); -- 需先创建拓扑关键看EXPLAIN ANALYZE输出中是否有Index Scan using ..._geom_idx on main_roads。如果没有,说明 GIST 索引没建或没生效。
5.4 常见连接问题速查表
| 现象 | 可能原因 | 排查命令 |
|---|---|---|
psql: error: connection to server at "localhost" (127.0.0.1), port 5433 failed: Connection refused | 容器未启动,或ports映射错误 | docker-compose ps查状态;docker-compose port postgres 5432查映射 |
psql: error: connection to server at "localhost" (127.0.0.1), port 5433 failed: FATAL: password authentication failed for user "gisuser" | .env中密码有空格或特殊字符未转义 | cat .env | sed 's/[^[:print:]]/\\x&/g'查隐藏字符 |
ERROR: function st_transform(unknown, integer) does not exist | PostGIS 扩展未加载,或加载在错误 schema | SELECT * FROM pg_extension; |
ERROR: column "geom" does not exist | 表中 geometry 字段名不是geom,或未用AddGeometryColumn注册 | \d roads.main_roads查字段;SELECT * FROM geometry_columns WHERE f_table_name='main_roads'; |
ST_AsText(ST_GeomFromText('POINT(116.4 39.9)')) 返回 NULL | 输入 WKT 格式错误,或 SRID 未指定 | SELECT ST_IsValidReason(ST_GeomFromText('POINT(116.4 39.9)')); |
注意:PostGIS 的
ST_IsValidReason()是终极调试函数。任何空间函数返回 NULL,第一反应不是改代码,而是SELECT ST_IsValidReason(geom)查几何体是否有效。我遇到过最诡异的 case:ST_Buffer返回 NULL,结果发现原始 geometry 有自相交环,ST_MakeValid()修复后一切正常。
6. 生产就绪的进阶配置:不只是“跑起来”,还要“扛得住”
开发环境docker-compose up能跑,不等于生产环境能用。生产部署必须考虑高可用、备份、监控、安全加固四大维度。下面给出可直接落地的方案。
6.1 备份策略:用 pg_dump + cron,而非“定期 tar 包”
pg-data卷不能直接 tar,因为 PG 数据文件是 WAL 日志和数据页的混合体,直接拷贝会导致不一致。必须用pg_dump逻辑备份,或pg_basebackup物理备份。
创建./backup/backup.sh:
#!/bin/bash # 设置环境 export PGPASSWORD="${POSTGRES_PASSWORD}" BACKUP_DIR="/backups" DATE=$(date +%Y%m%d_%H%M%S) DB_NAME="${POSTGRES_DB}" DB_USER="${POSTGRES_USER}" # 创建备份目录 mkdir -p "${BACKUP_DIR}" # 执行备份(自定义 schema) pg_dump \ -h postgres \ -p 5432 \ -U "${DB_USER}" \ -d "${DB_NAME}" \ -n roads \ -n buildings \ -n parcels \ -F c \ -b \ -v \ -f "${BACKUP_DIR}/spatialdb_${DATE}.dump" # 保留最近 7 天备份 find "${BACKUP_DIR}" -name "spatialdb_*.dump" -mtime +7 -delete在docker-compose.yml中添加 backup 服务:
backup: image: postgis/postgis:15.6-3.4.3 restart: "no" depends_on: - postgres volumes: - pg-data:/var/lib/postgresql/data - ./backup:/backup - ./backup/backup.sh:/backup.sh environment: - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} - POSTGRES_USER=${POSTGRES_USER} - POSTGRES_DB=${POSTGRES_DB} entrypoint: ["/bin/bash", "-c", "sleep 30 && /backup.sh"]然后用宿主机 cron 每天执行:
# crontab -e 0 2 * * * cd /path/to/compose && docker-compose run --rm backup6.2 监控集成:暴露 Prometheus metrics
PostGIS 本身不暴露 metrics,但 PostgreSQL 有pg_stat_statements扩展,可统计 SQL 执行耗时、调用次数。启用它:
-- 在初始化脚本中执行 CREATE EXTENSION IF NOT EXISTS pg_stat_statements; ALTER SYSTEM SET shared_preload_libraries = 'pg_stat_statements'; -- 重启容器使配置生效然后用 Prometheus 的postgres_exporter抓取指标。docker-compose.yml添加:
prometheus: image: prom/prometheus:latest volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml ports: - "9090:9090" postgres-exporter: image: wrouesnel/postgres-exporter:v0.14.0 environment: DATA_SOURCE_NAME: "postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}?sslmode=disable" ports: - "9187:9187"prometheus.yml配置:
global: scrape_interval: 15s scrape_configs: - job_name: 'postgres' static_configs: - targets: ['postgres-exporter:9187']关键指标告警规则:
pg_stat_statements_total{datname=~"spatialdb"} > 10000(慢查询过多);pg_up == 0(数据库宕机);pg_replication_lag_seconds > 30(主从延迟过高,虽单节点不适用,但为扩展留接口)。
6.3 安全加固:最小权限原则落地
gisuser不应是 superuser。初始化后立即执行:
-- 撤销 superuser 权限 ALTER USER gisuser NOSUPERUSER; -- 仅授予必要 schema 的 USAGE 和 SELECT/INSERT/UPDATE/DELETE GRANT USAGE ON SCHEMA roads TO gisuser; GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA roads TO gisuser; GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA roads TO gisuser; -- 对 postgis 扩展函数,只授 EXECUTE(非 ALL) GRANT EXECUTE ON FUNCTION ST_Transform(geometry, integer) TO gisuser; GRANT EXECUTE ON FUNCTION ST_Intersects(geometry, geometry) TO gisuser;提示:PostGIS 的函数权限很细。
ST_AsMVT需要EXECUTE权限,但ST_ClusterIntersecting需要EXECUTE+USAGE ON TYPE mvt。权限不足时,错误提示是permission denied for function st_asmvt,而非function not found。
7. 常见问题与避坑指南:那些文档里不会写的细节
7.1 “image postgres:18 error failed to resolve reference” 是什么鬼?
这是 Docker CLI 的 registry 解析错误,不是镜像不存在。当你写image: postgres:18,Docker 会尝试解析docker.io/library/postgres:18,但postgres:18标签在官方镜像中不存在(最新是15和16)。PostGIS 官方镜像基于 PG 15/16,没有 PG 18。解决方案只有两个:
- 改用
postgis/postgis:16-3.4(推荐); - 或自己构建:
FROM postgres:18+RUN apt-get update && apt-get install -y postgis,但需自行解决 GEOS/PROJ 版本兼容性。
7.2 “postgres导出schema下的视图” 怎么办?
pg_dump默认不导出视图,除非用-s(schema-only)或-t(table-only)指定。导出roadsschema 下所有视图:
pg_dump -h localhost -p 5433 -U gisuser -d spatialdb -