A lightweight, opensource, selfhostable OpenStreetMap geocoder
  • Go 98.1%
  • Dockerfile 1.9%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-09-01 10:17:43 +02:00
.github/workflows fix: disable incompatible embedded attestations 2026-09-01 10:17:43 +02:00
cmd feat: inital commit 2026-07-15 13:56:51 +02:00
data feat: inital commit 2026-07-15 13:56:51 +02:00
docs fix: dont do all of pack processing in memory 2026-08-11 20:47:00 +02:00
examples feat: inital commit 2026-07-15 13:56:51 +02:00
internal fix: dont do all of pack processing in memory 2026-08-11 20:47:00 +02:00
.dockerignore feat: inital commit 2026-07-15 13:56:51 +02:00
.gitignore feat: inital commit 2026-07-15 13:56:51 +02:00
compose.yaml feat: inital commit 2026-07-15 13:56:51 +02:00
Dockerfile feat: inital commit 2026-07-15 13:56:51 +02:00
go.mod feat: inital commit 2026-07-15 13:56:51 +02:00
go.sum feat: inital commit 2026-07-15 13:56:51 +02:00
LICENSE feat: inital commit 2026-07-15 13:56:51 +02:00
README.md feat: inital commit 2026-07-15 13:56:51 +02:00
SECURITY.md feat: inital commit 2026-07-15 13:56:51 +02:00

geocoder-go

geocoder-go is a low-memory, offline forward and reverse geocoder designed for embedded and edge deployments. It converts source data into immutable SQLite packs off-device and serves those packs through a small Gin HTTP API.

The runtime has no PostgreSQL, CGO, Spatialite, DuckDB, or network dependency. The published container contains one statically linked binary in a rootless scratch image; data packs are mounted separately.

Features

  • Forward geocoding with Unicode normalization, prefix search, German spelling variants, bounded typo recovery, and optional coordinate bias.
  • Reverse geocoding using an SQLite R-tree and exact Haversine distances.
  • Named-road lookup with locality-aware disambiguation for OSM ways that lack addr:city tags.
  • Recursive discovery of any number of .db, .sqlite, and .sqlite3 packs.
  • Pack generation from CSV/TSV, GeoJSON, NDJSON, OSM PBF, existing packs, and Overture address GeoParquet.
  • Pure-Go SQLite runtime with one read-only connection per pack.
  • Graceful shutdown, liveness/readiness endpoints, and a built-in container healthcheck command.
  • Multi-architecture linux/amd64 and linux/arm64 container publishing.

Quick start

Create a small example pack and run the server:

go run ./cmd/packgen build \
  --country AT \
  --source example \
  --output data/example.sqlite \
  examples/addresses.csv

go run ./cmd/server --data data --listen :8080

Query it:

curl "http://localhost:8080/geocode?q=Stephansplatz%201%2C%20Wien&country=AT"
curl "http://localhost:8080/reverse?lat=48.2085&lon=16.3721&radius_m=500"
curl "http://localhost:8080/readyz"

Container

The image does not contain geographic data. Mount one or more generated packs read-only below /data:

docker run --rm \
  --read-only \
  --user 65532:65532 \
  --cap-drop ALL \
  --security-opt no-new-privileges \
  -p 8080:8080 \
  -v "$(pwd)/data:/data:ro" \
  ghcr.io/gametec-live/geocoder-go:latest

The included compose.yaml applies the same restrictions:

docker compose up -d

Pack files must be readable by UID/GID 65532, or at least world-readable.

Documentation

Building locally

Go 1.26 or later is required.

go mod download
go test ./...
go vet ./...
go build -trimpath -o bin/geocoder ./cmd/server
go build -trimpath -o bin/packgen ./cmd/packgen

Runtime build information is available without opening a pack:

./bin/geocoder version

Architecture

source data                     build machine
CSV / GeoJSON / PBF / Parquet ──> packgen ──> immutable SQLite packs
                                                    │
                                                    │ read-only mount
                                                    ▼
client ──HTTP──> Gin API ──> FTS5 / R-tree ──> ranked JSON results

Forward search uses an FTS5 prefix index. Reverse search first uses an R-tree bounding box and then calculates exact distances for nearby candidates. Packs are opened with SQLite mode=ro, immutable=1, and query_only=1.

Scope and limitations

This service intentionally stores a much smaller model than Nominatim or others:

  • OSM relations and administrative polygons are not imported.
  • Polygon and way features are represented by centroids.
  • Reverse lookup returns the nearest indexed point, not a containing boundary.
  • House-number interpolation and nearest-point-on-road calculations are not implemented.
  • A road result for a missing house number is a representative road segment, not a claimed building entrance.
  • Source data can be incomplete. The geocoder does not invent POI/address relationships absent from upstream data.

Applications using results as route destinations should retain kind, source, and source_id, distinguish exact addresses from road fallbacks, and allow users to confirm ambiguous destinations.

Data licensing

The software does not change upstream data licenses. OSM-derived packs remain subject to the ODbL and its attribution requirements. Overture, OpenAddresses, INSPIRE, national datasets, and business data may carry additional obligations. The source field is preserved in every result to support attribution.

geocoder-go itself is licensed under the MIT License and free for anyone to use.