news 2026/9/18 10:17:36

Docker-Compose 部署 PostGIS:版本锁定、初始化与生产就绪实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Docker-Compose 部署 PostGIS:版本锁定、初始化与生产就绪实践

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 --versionpostgis_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 的networksdepends_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.515.6的 WAL 格式变更导致数据目录不兼容;
  • PostGIS 锁定到补丁版本(如3.4.3),因为3.4.23.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_DBPOSTGRES_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_isreadycurl 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 connectionscreating template1 databasecreating default databasesinitializing 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.ymlcommand中无法直接执行 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.d

Docker 官方 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.conflisten_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 existPostGIS 扩展未加载,或加载在错误 schemaSELECT * 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 backup

6.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标签在官方镜像中不存在(最新是1516)。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 -
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/18 10:15:11

Create React App 完全指南:零配置创建 React 应用

Create React App 完全指南:零配置创建 React 应用 【免费下载链接】create-react-app Set up a modern web app by running one command. 项目地址: https://gitcode.com/gh_mirrors/cr/create-react-app [!CAUTION] 弃用说明(Deprecated&#xf…

作者头像 李华
网站建设 2026/9/18 10:10:22

评估数据管道把 Anthropic SDK 地址切到 TaoToken 后回填标签

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 10:10:09

时序数据库核心原理与主流方案选型指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 10:09:31

MATLAB STFT-SVM轴承故障诊断与时频特征提取GUI实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 10:08:47

车载SOA入门:从SOME/IP到服务设计测试

前阵子有个在传统零部件厂做了五年总线测试的朋友问我:现在到处都在说车载SOA,我去面试总被问,但实在不知道它到底做了什么。这不是他一个人的困惑。做汽车电子这几年,我被问得最多的不是CAN报文怎么抓,而是车载SOA到底…

作者头像 李华