selfhostable maps
Find a file
2026-03-30 11:55:40 +02:00
backend add bzip2 2026-03-30 11:55:40 +02:00
docs draft 2026-03-30 09:22:16 +02:00
mobile draft 2026-03-30 09:22:16 +02:00
.gitignore draft 2026-03-30 09:22:16 +02:00
README.md fix pbf path 2026-03-30 11:30:00 +02:00

Privacy Maps

A privacy-first Google Maps alternative. No tracking, no accounts, no third-party API calls. All services are self-hosted.

Stack: Flutter (mobile) · Rust/Actix-web (backend) · PostgreSQL/PostGIS · Martin (tiles) · Photon (geocoding) · OSRM (routing)


Project Structure

maps/
├── docs/           # Specs, architecture, API contracts, data model
├── backend/        # Rust API gateway + Docker Compose for all services
│   ├── scripts/    # Data import scripts
│   └── initdb/     # PostgreSQL init SQL (runs on first startup)
└── mobile/         # Flutter app

Backend Setup

Requirements

  • Docker or Podman + Compose
  • ~50 GB free disk (for OSM data, OSRM graphs, Photon index)
  • ARM64 (Raspberry Pi) or amd64 host

1. First-time setup

cd backend

# Build the custom images (PostGIS arm64 + importer toolchain)
podman compose build

# Start PostgreSQL first — extensions are enabled automatically on first start
podman compose up -d postgres

# Wait until ready
podman compose exec postgres pg_isready -U maps

2. Import data (first time)

Run each script individually rather than update_all.sh on first setup — the download takes ~6 minutes and you only need it once.

# Step 1: Download OSM PBF extract (~1.2 GB for Netherlands, ~6 min)
podman compose run --rm importer /app/scripts/01_download.sh

# Step 2: Import tile data into PostGIS (~10-20 min)
podman compose run --rm importer /app/scripts/02_import_tiles.sh

# Step 3: Import POI data into PostGIS
podman compose run --rm importer /app/scripts/03_import_pois.sh

# Step 4: Download Photon geocoding index (~500 MB for Netherlands)
podman compose run --rm importer /app/scripts/04_import_geocoding.sh

# Step 5: Preprocess OSRM routing graphs (runs osrm containers internally)
podman compose run --rm importer /app/scripts/05_import_routing.sh

# Step 6: Register offline regions in the database
podman compose run --rm importer /app/scripts/06_build_offline_packages.sh

To change the country, set PHOTON_COUNTRY_CODE before step 4:

PHOTON_COUNTRY_CODE=de podman compose run --rm importer /app/scripts/04_import_geocoding.sh

3. Start all services

podman compose up -d

After startup, restart the services that depend on the imported data:

podman compose restart martin
podman compose restart osrm-driving osrm-walking osrm-cycling

4. Weekly data refresh

Use update_all.sh for scheduled updates. It re-downloads the PBF only if the file has changed on the server (wget -N), then reimports everything.

podman compose run --rm importer /app/scripts/update_all.sh

To refresh only specific data (e.g. tiles changed but routing didn't):

podman compose run --rm importer /app/scripts/01_download.sh
podman compose run --rm importer /app/scripts/02_import_tiles.sh
podman compose restart martin

Service ports

Service Port Description
backend 8080 Rust API gateway
postgres 5432 PostGIS database
redis 6379 Tile/route cache
martin 3000 Vector tile server
photon 2322 Geocoding (search)
osrm-driving 5000 Car routing
osrm-walking 5001 Walking routing
osrm-cycling 5002 Cycling routing

Mobile App Setup

cd mobile
flutter pub get
dart run build_runner build --delete-conflicting-outputs
flutter run

On first launch, go to Settings and enter your backend URL (e.g. http://your-pi-ip:8080).


Troubleshooting

PostGIS extension error — The postgres container must be recreated to pick up the init scripts:

podman compose down postgres
podman compose up -d postgres

Podman short-name error — All images use full docker.io/ registry paths. If you see this on another service, prefix its image with docker.io/.

Exec format error on Pi — The postgres image is built locally from postgis.Dockerfile using arm64v8/postgres as base. Run podman compose build postgres to rebuild it.


Attribution