GIS项目环境配置太头疼?Docker一键部署GDAL与PostGIS教程(含:镜像脚本)

编程与开发
Dr.GIS
wowwwai GIS研习社 · 工具流程与项目排障

GIS项目环境配置太头疼?Docker一键部署GDAL与PostGIS教程(含:镜像脚本)这篇文章解决一个很具体的问题:新建 GIS 项目时,如何用 Docker 快速搭建一套包含 GDAL、PostGIS、PostgreSQL 的可复用开发环境,避免在 Windows、macOS、Linux 上反复处理依赖冲突、动态库缺失和版本不一致。

如果你做过栅格切片、矢量格式转换、空间入库、坐标转换或 WebGIS 后端开发,大概率遇到过这些问题:本机 GDAL 装不上、PostGIS 扩展创建失败、Python 调不到 ogr2ogr、同事电脑运行结果不一致。Docker 的价值不是“更高级”,而是把 GIS 项目环境配置固化成脚本,让团队成员用同一套环境跑同一套命令。

Docker一键部署GDAL与PostGIS教程,GIS项目环境配置流程图
Docker 管理 GDAL 与 PostGIS 环境后,GIS 数据处理、空间入库和项目复现都可以通过统一脚本完成。

引言:为什么 GIS项目环境配置 经常比写代码还耗时间

普通 Web 项目通常只需要数据库、运行时和几个依赖包;GIS 项目不一样,它经常同时依赖空间数据库、坐标库、矢量/栅格读写库、命令行工具和 Python 包。

例如一个常见的 GIS 后端项目,可能同时需要:

  • PostgreSQL 与 PostGIS,用来存储空间数据和执行空间查询。
  • GDAL / OGR,用来转换 Shapefile、GeoJSON、GeoPackage、TIFF 等数据格式。
  • PROJ,用来处理坐标系与投影转换。
  • GEOS,用来支持拓扑关系、缓冲区、相交判断等几何计算。
  • Python GIS 包,例如 GeoPandas、Rasterio、Shapely、Fiona。

这些组件之间存在版本依赖。你本机能运行,不代表服务器能运行;Windows 能运行,不代表 Linux 能运行。本文的核心目标就是用 Docker 一键部署 GDAL 与 PostGIS,让 GIS 项目环境配置从“手工安装”变成“脚本启动”。

背景:本文要搭建的 GIS Docker 环境包含什么

本文示例使用 Docker Compose 管理两个核心服务:

  • postgis:PostgreSQL + PostGIS 数据库服务。
  • gdal:带 GDAL 命令行工具的临时处理容器,用于 ogr2ogr、gdalinfo、gdalwarp 等操作。

最终你可以完成这些常见任务:

  • 一条命令启动 PostGIS 数据库。
  • 用 ogr2ogr 将 Shapefile、GeoJSON、GeoPackage 导入 PostGIS。
  • 在容器内执行 gdalinfo、ogrinfo 检查数据。
  • 把项目数据目录挂载到容器中,避免反复复制文件。
  • 把环境配置提交到 Git,让团队成员直接复用。

本文假设你已经安装 Docker Desktop 或 Docker Engine,并且终端可以正常执行 docker --versiondocker compose version

原理:Docker 如何解决 GDAL 与 PostGIS 依赖冲突

Docker 的基本思路是:把运行环境打包到镜像里,再用容器运行这个镜像。对于 GIS 项目来说,这意味着 GDAL、PROJ、GEOS、PostGIS 等复杂依赖不再直接污染本机系统。

你可以把它理解为三层:

  • 镜像:预先安装好 GDAL 或 PostGIS 的环境模板。
  • 容器:从镜像启动的运行实例。
  • 数据卷:把本机目录或数据库数据持久化,防止容器删除后数据丢失。

GDAL 与 PostGIS 的配合方式也很直接:GDAL 负责读取和转换空间数据,PostGIS 负责存储和查询空间数据。比如你可以用 ogr2ogr 把 Shapefile 导入 PostGIS,然后用 SQL 执行 ST_IntersectsST_BufferST_Area 等空间分析。

重点不是把所有 GIS 软件都塞进一个容器,而是把“数据库服务”和“数据处理工具”分开管理。PostGIS 容器长期运行,GDAL 容器按需执行命令,用完即走。

步骤:Docker一键部署GDAL与PostGIS

步骤 1:创建项目目录结构

建议先创建一个清晰的项目目录,方便后续挂载数据和管理脚本。

gis-docker-env/
├── docker-compose.yml
├── Dockerfile.gdal
├── data/
│   ├── input/
│   └── output/
└── scripts/
    ├── import_shp.sh
    └── check_env.sh

其中 data/input 放原始 GIS 数据,data/output 放处理结果,scripts 放常用命令脚本。

步骤 2:编写 docker-compose.yml

在项目根目录创建 docker-compose.yml

services:
  postgis:
    image: postgis/postgis:16-3.4
    container_name: gisyxs-postgis
    environment:
      POSTGRES_DB: gisdb
      POSTGRES_USER: gisuser
      POSTGRES_PASSWORD: gispass
    ports:
      - "5432:5432"
    volumes:
      - postgis_data:/var/lib/postgresql/data
      - ./data:/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U gisuser -d gisdb"]
      interval: 10s
      timeout: 5s
      retries: 5

  gdal:
    build:
      context: .
      dockerfile: Dockerfile.gdal
    container_name: gisyxs-gdal
    volumes:
      - ./data:/data
      - ./scripts:/scripts
    depends_on:
      postgis:
        condition: service_healthy
    entrypoint: ["/bin/bash"]

volumes:
  postgis_data:

这里使用 postgis/postgis 镜像启动数据库,并将本地 data 目录挂载到容器内的 /data。这样 GDAL 容器和 PostGIS 容器都能访问同一份数据文件。

步骤 3:编写 GDAL 镜像脚本 Dockerfile.gdal

在项目根目录创建 Dockerfile.gdal

FROM osgeo/gdal:ubuntu-small-latest

RUN apt-get update && apt-get install -y 
    postgresql-client 
    bash 
    curl 
    ca-certificates 
    && rm -rf /var/lib/apt/lists/*

WORKDIR /work

CMD ["/bin/bash"]

这个镜像基于 OSGeo GDAL 镜像,并额外安装 PostgreSQL 客户端,方便在 GDAL 容器里使用 psql 测试数据库连接。

步骤 4:启动 PostGIS 服务并构建 GDAL 容器

在项目根目录执行:

docker compose up -d --build

查看容器状态:

docker compose ps

如果 postgis 服务显示为 healthy,说明数据库已经可用。

步骤 5:检查 GDAL 与 PostGIS 是否可用

进入 GDAL 容器:

docker compose run --rm gdal

在容器中执行:

gdalinfo --version
ogrinfo --version
psql -h postgis -U gisuser -d gisdb -c "SELECT PostGIS_Version();"

输入密码 gispass 后,如果能看到 PostGIS 版本号,说明 GDAL 容器已经可以访问 PostGIS 数据库。

步骤 6:用 ogr2ogr 导入 Shapefile 到 PostGIS

假设你有一个 Shapefile 数据:

data/input/roads.shp

在项目根目录执行:

docker compose run --rm gdal -c '
ogr2ogr -f "PostgreSQL" 
  PG:"host=postgis dbname=gisdb user=gisuser password=gispass" 
  /data/input/roads.shp 
  -nln public.roads 
  -lco GEOMETRY_NAME=geom 
  -lco FID=id 
  -overwrite
'

这条命令的含义如下:

  • -f "PostgreSQL":输出目标是 PostgreSQL/PostGIS。
  • PG:"...":PostGIS 数据库连接字符串。
  • /data/input/roads.shp:容器内的数据路径。
  • -nln public.roads:目标表名为 public.roads
  • -overwrite:如果表已存在则覆盖。

步骤 7:验证空间表是否导入成功

执行 SQL 检查表结构和要素数量:

docker compose exec postgis psql -U gisuser -d gisdb -c "d public.roads"

docker compose exec postgis psql -U gisuser -d gisdb -c "SELECT COUNT(*) FROM public.roads;"

检查空间参考:

docker compose exec postgis psql -U gisuser -d gisdb -c "
SELECT Find_SRID('public', 'roads', 'geom');
"

如果 SRID 返回 0,说明源数据可能没有正确的坐标系信息,后续空间分析前需要补充或修正。

步骤 8:为 PostGIS 空间表创建索引

导入数据后,建议立即创建 GiST 空间索引。否则数据量稍大时,ST_IntersectsST_Within 等空间查询会明显变慢。

docker compose exec postgis psql -U gisuser -d gisdb -c "
CREATE INDEX IF NOT EXISTS roads_geom_gix
ON public.roads
USING GIST (geom);

ANALYZE public.roads;
"

ANALYZE 会更新 PostgreSQL 查询统计信息,帮助数据库优化查询计划。

步骤 9:添加一键导入脚本

为了让团队成员少敲长命令,可以把导入命令写入 scripts/import_shp.sh

#!/usr/bin/env bash
set -e

INPUT_FILE=${1:-/data/input/roads.shp}
TABLE_NAME=${2:-public.roads}

ogr2ogr -f "PostgreSQL" 
  PG:"host=postgis dbname=gisdb user=gisuser password=gispass" 
  "$INPUT_FILE" 
  -nln "$TABLE_NAME" 
  -lco GEOMETRY_NAME=geom 
  -lco FID=id 
  -overwrite

psql -h postgis -U gisuser -d gisdb -c "
CREATE INDEX IF NOT EXISTS $(echo "$TABLE_NAME" | tr '.' '_')_geom_gix
ON $TABLE_NAME
USING GIST (geom);
ANALYZE $TABLE_NAME;
"

在 macOS 或 Linux 中给脚本添加执行权限:

chmod +x scripts/import_shp.sh

运行脚本:

docker compose run --rm gdal /scripts/import_shp.sh /data/input/roads.shp public.roads

如果你在 Windows PowerShell 中执行,建议使用 Git Bash、WSL 或把脚本改成单行命令,避免换行符和路径格式导致问题。

步骤 10:添加环境检查脚本

创建 scripts/check_env.sh

#!/usr/bin/env bash
set -e

echo "检查 GDAL 版本"
gdalinfo --version

echo "检查 OGR 版本"
ogrinfo --version

echo "检查 PostGIS 连接"
PGPASSWORD=gispass psql -h postgis -U gisuser -d gisdb -c "SELECT PostGIS_Version();"

echo "检查数据目录"
ls -lah /data
ls -lah /data/input

运行:

docker compose run --rm gdal /scripts/check_env.sh

这个脚本非常适合放进项目 README,作为新成员拉取项目后的第一步检查。

常见坑:Docker部署GDAL与PostGIS时最容易出错的地方

坑 1:在容器里使用了本机路径

很多人会把命令写成:

C:gisdataroads.shp

但 GDAL 容器内看不到这个路径。本文把本机 ./data 挂载成容器内的 /data,所以容器内应该使用:

/data/input/roads.shp

坑 2:PostGIS 主机名写成 localhost

在 GDAL 容器里连接 PostGIS 时,不能写 localhost。因为 localhost 指的是 GDAL 容器自己,不是数据库容器。

在 Docker Compose 网络中,应使用服务名作为主机名:

host=postgis

坑 3:Shapefile 中文字段或中文路径乱码

Shapefile 的编码问题很常见,尤其是历史数据中使用 GBK、GB2312 或缺少 .cpg 文件时。可以尝试在导入时指定编码:

docker compose run --rm gdal -c '
ogr2ogr -f "PostgreSQL" 
  PG:"host=postgis dbname=gisdb user=gisuser password=gispass" 
  /data/input/roads.shp 
  -nln public.roads 
  -oo ENCODING=GBK 
  -overwrite
'

如果条件允许,建议在入库前统一转换为 GeoPackage 或 UTF-8 GeoJSON,减少后续乱码问题。

坑 4:SRID 为 0 导致空间分析结果不可靠

如果导入后 Find_SRID 返回 0,说明 PostGIS 不知道几何字段的坐标系。此时不能直接计算面积、距离或叠加分析。

如果你确认数据是 CGCS2000 经纬度坐标,可按实际 SRID 修正。以下仅以 EPSG:4490 为例:

docker compose exec postgis psql -U gisuser -d gisdb -c "
SELECT UpdateGeometrySRID('public', 'roads', 'geom', 4490);
"

如果数据只是缺少 SRID,但坐标值本身已经是正确坐标,可以更新 SRID;如果坐标值需要投影转换,则应该使用 ST_Transform 生成新表。

坑 5:忘记创建空间索引

PostGIS 表导入成功不等于查询性能正常。对于道路、地块、建筑物、行政区等空间表,建议创建 GiST 索引:

CREATE INDEX 表名_geom_gix ON 表名 USING GIST (geom);

如果不创建索引,WebGIS 接口中按范围查询、相交查询、邻近查询都可能变慢。

坑 6:Docker 卷删除导致数据库数据丢失

本文使用了命名卷 postgis_data 保存数据库数据。普通的 docker compose down 不会删除这个卷,但下面命令会删除数据卷:

docker compose down -v

在生产或长期项目中,不要随意执行带 -v 的删除命令。重要数据应定期使用 pg_dump 备份。

方法比较:本机安装、Conda、Docker 哪种更适合 GIS 项目环境配置

方法 适合场景 优点 局限
本机手动安装 GDAL 与 PostGIS 个人学习、简单实验 使用直观,命令直接可用 容易出现版本冲突,迁移到服务器麻烦
Conda 环境 Python GIS 分析、GeoPandas 项目 管理 Python 包方便,适合数据分析 数据库服务仍需单独安装,团队环境仍可能不一致
Docker Compose WebGIS 后端、PostGIS 项目、团队协作 环境可复现,服务编排清晰,适合部署 需要理解容器网络、卷挂载和镜像构建
虚拟机 完整桌面 GIS 软件环境、隔离测试 隔离彻底,可运行完整操作系统 资源占用较大,启动慢,脚本化程度较低

如果你的项目重点是 Python 空间分析,Conda 很方便;如果你的项目需要 PostGIS 服务、GDAL 命令行、WebGIS 后端和多人协作,Docker Compose 更适合作为基础环境。

检查清单:交付前确认这套 Docker GIS 环境是否可靠

  • 是否能执行 docker compose up -d --build 并正常启动?
  • postgis 服务是否显示 healthy?
  • GDAL 容器内是否能执行 gdalinfo --version
  • GDAL 容器内是否能通过服务名 postgis 连接数据库?
  • 项目数据是否统一放在 data/inputdata/output
  • 导入 PostGIS 后是否检查了要素数量?
  • 空间表是否有正确 SRID?
  • 空间字段是否创建了 GiST 索引?
  • 脚本是否避免写死本机绝对路径?
  • 数据库密码是否只用于本地开发,生产环境是否改用环境变量或密钥管理?
  • 是否提供了 README,说明启动、导入、检查和清理命令?

FAQ:Docker一键部署GDAL与PostGIS常见问题

Docker 部署 PostGIS 后,QGIS 可以连接吗?

可以。本文把容器的 5432 端口映射到了本机 5432。在 QGIS 中新建 PostGIS 连接时,主机填写 localhost,端口填写 5432,数据库填写 gisdb,用户名填写 gisuser,密码填写 gispass

GDAL 容器为什么连接数据库时不能用 localhost?

因为在容器内部,localhost 表示当前容器自身。GDAL 容器要访问 PostGIS 容器,应使用 Docker Compose 服务名 postgis。但 QGIS 在本机连接数据库时,可以使用 localhost,因为端口已经映射到本机。

这个 Docker GIS 环境可以用于生产吗?

本文配置更适合开发和测试。生产环境需要额外处理密码安全、数据库备份、数据卷挂载路径、日志、资源限制、网络访问控制和版本锁定。不要直接把示例密码用于生产。

PostGIS 镜像版本需要固定吗?

建议固定。示例中使用 postgis/postgis:16-3.4,比使用 latest 更利于团队复现。GDAL 镜像在正式项目中也建议固定到明确版本,避免后续镜像更新导致命令行为变化。

为什么 ogr2ogr 导入后字段名变了?

常见原因是源数据字段名太长、包含特殊字符、大小写被数据库规范化,或 Shapefile 本身字段名限制较多。建议入库前检查字段结构,必要时先转换为 GeoPackage,再整理字段名后导入 PostGIS。

如何导入 GeoJSON 或 GeoPackage?

只需要把输入文件路径换成对应文件即可。例如导入 GeoJSON:

docker compose run --rm gdal -c '
ogr2ogr -f "PostgreSQL" 
  PG:"host=postgis dbname=gisdb user=gisuser password=gispass" 
  /data/input/roads.geojson 
  -nln public.roads 
  -overwrite
'

导入 GeoPackage 时同理:

docker compose run --rm gdal -c '
ogr2ogr -f "PostgreSQL" 
  PG:"host=postgis dbname=gisdb user=gisuser password=gispass" 
  /data/input/city.gpkg 
  -nln public.city_layer 
  -overwrite
'

如何彻底重建测试数据库?

如果只是重启服务,执行:

docker compose restart

如果要删除容器但保留数据库数据,执行:

docker compose down

如果要连数据库卷一起删除,执行:

docker compose down -v

注意:docker compose down -v 会删除数据库数据卷,执行前请确认已经备份。

结论:把 GIS 环境配置写成脚本,才是真正可复现

GIS项目环境配置之所以让人头疼,根本原因是 GDAL、PostGIS、PROJ、GEOS、数据库客户端和操作系统之间存在复杂依赖。手工安装可以解决一次问题,但很难保证团队协作和服务器部署时仍然一致。

本文用 Docker Compose 拆分了两个核心角色:PostGIS 负责空间数据库服务,GDAL 负责空间数据读写与转换。通过 docker-compose.ymlDockerfile.gdal 和导入脚本,你可以把 Docker一键部署GDAL与PostGIS 固化为项目标准流程。

建议你在真实项目中继续补充三件事:固定镜像版本、完善数据备份脚本、把常用 ogr2ogr 和 SQL 操作整理成可复用脚本。这样下次新建 GIS 项目时,环境配置就不再是阻碍,而是项目交付的一部分。