Article

Running Nominatim with Docker and PostGIS
A hands-on guide to self-hosting geocoding with Nominatim, Docker, and PostGIS — covering setup, imports, spatial queries, and the RAM/storage trade-offs to plan for before you start.
Introduction
Modern applications often need to convert addresses into geographic coordinates and coordinates back into meaningful locations. This process is known as geocoding and reverse geocoding.
Nominatim is an open-source search engine for OpenStreetMap data. Instead of relying on a third-party geocoding API — with its rate limits, usage costs, and data-sharing terms — you can run your own Nominatim instance using Docker.
In this setup, PostgreSQL + PostGIS provides the spatial database functionality required by Nominatim, while Docker makes the environment easier to deploy and reproduce.
Architecture
The basic setup looks like this:
Your Application
|
v
Nominatim API
|
v
PostgreSQL + PostGIS
|
v
OpenStreetMap DataNominatim receives a location query, searches the imported OpenStreetMap data, and returns information such as:
Latitude and longitude
Address components
City
Country
Postal code
Nearby geographic information
Prerequisites
Before starting, make sure you have:
Docker and Docker Compose installed
At least 2 GB of RAM for a small city/country extract (large regions or full-planet imports need significantly more — often 64 GB+)
Enough free disk space — a country extract typically needs 10–50 GB depending on region size; the full planet needs 1 TB+
An
.osm.pbfextract for your target region, available from Geofabrik
Running Nominatim with Docker
A Docker-based setup avoids manually installing PostgreSQL, PostGIS, and Nominatim's dependency chain (which includes PHP, osm2pgsql, and various Perl/Python tooling).
Step 0: Check that Docker is running
Before pulling any image, confirm Docker is installed and the daemon is actually running:
bash
docker --versionThis confirms Docker is installed and shows the version. Then check that the daemon itself is active:
bash
docker infoIf Docker is running, this prints details about the daemon (containers, images, storage driver, etc.). If it's not running, you'll see an error like:
Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?If you see that error:
Linux: start the Docker service with
sudo systemctl start dockermacOS/Windows: open Docker Desktop and wait for it to show "Docker Desktop is running"
You can also quickly test it end-to-end with:
bash
docker run hello-worldIf this pulls and runs successfully, Docker is fully operational and you're ready to move on to pulling the Nominatim image.
Step 1: Pull the image
bash
docker pull mediagis/nominatim:4.4Always pin a version tag rather than using latest — Nominatim's import format has changed across major versions, and a mismatched image/data version can break imports.
Step 2: Create a persistent volume
bash
docker volume create nominatim-dataThis ensures your imported database survives container restarts — re-importing OpenStreetMap data can take anywhere from minutes to many hours, so you don't want to lose it.
Step 3: Run the container with an import
Instead of importing the entire planet, use a smaller regional .osm.pbf file. For example, importing just Pakistan:
bash
docker run -it \
-e PBF_URL=https://download.geofabrik.de/asia/pakistan-latest.osm.pbf \
-e REPLICATION_URL=https://download.geofabrik.de/asia/pakistan-updates/ \
-e IMPORT_WIKIPEDIA=false \
-p 8080:8080 \
-v nominatim-data:/var/lib/postgresql/14/main \
--shm-size=1g \
--name nominatim \
mediagis/nominatim:4.4Key flags explained:
FlagPurposePBF_URLThe OpenStreetMap extract to importREPLICATION_URLEnables incremental updates after the initial importIMPORT_WIKIPEDIAAdds Wikipedia importance ranking data (skip for faster/smaller imports)--shm-sizeIncreases shared memory — Postgres needs this for larger imports-v nominatim-data:...Mounts the persistent volume so data survives restarts
Using Docker Compose (recommended)
For a more reproducible setup, a docker-compose.yml is usually cleaner than a long docker run command:
yaml
version: "3"
services:
nominatim:
image: mediagis/nominatim:4.4
container_name: nominatim
ports:
- "8080:8080"
environment:
PBF_URL: https://download.geofabrik.de/asia/pakistan-latest.osm.pbf
REPLICATION_URL: https://download.geofabrik.de/asia/pakistan-updates/
IMPORT_WIKIPEDIA: "false"
NOMINATIM_PASSWORD: changeme
volumes:
- nominatim-data:/var/lib/postgresql/14/main
shm_size: 1g
restart: always
volumes:
nominatim-data:Then start it with:
bash
docker compose up -dThe first run will trigger the import process. Depending on the extract size, this can take anywhere from 10 minutes (a small city) to several hours (a large country).
You can check that the container itself is up and healthy with:
bash
docker psThis should show the nominatim container with a status of Up. If it's missing or shows Exited, check the logs with docker logs nominatim to see what went wrong.
PostGIS
PostGIS extends PostgreSQL with geographic and geometric data types and functions. It's the spatial engine underneath Nominatim.
A location can be represented as a geographic point:
sql
POINT(longitude latitude)PostGIS can then perform spatial operations such as:
sql
ST_DWithin()
ST_Distance()
ST_Within()
ST_Intersects()This makes it useful for application-level queries such as:
sql
-- Find all locations within 5 km of a given coordinate
SELECT name
FROM places
WHERE ST_DWithin(
geom,
ST_SetSRID(ST_MakePoint(73.0652, 33.6938), 4326)::geography,
5000
);This kind of query runs directly against the same PostGIS database Nominatim uses internally, which means you can combine your own application data with OpenStreetMap-derived location data in a single spatial query.
Geocoding with Nominatim
Once Nominatim is running, an address can be converted into coordinates via a simple HTTP request:
GET /search?q=Islamabad&format=jsonAs a full curl example against a local instance:
bash
curl "http://localhost:8080/search?q=Islamabad&format=json"A response contains geographic information such as:
json
[
{
"lat": "33.6938",
"lon": "73.0652",
"display_name": "Islamabad, Pakistan"
}
]Your application can then use these coordinates for maps, distance calculations, location searches, or other geospatial operations.
Reverse Geocoding
Nominatim can also perform the opposite operation — converting coordinates into an address.
Given:
Latitude: 33.6938
Longitude: 73.0652You can request:
GET /reverse?lat=33.6938&lon=73.0652&format=jsonAs a full curl example against a local instance:
bash
curl "http://localhost:8080/reverse?lat=33.6938&lon=73.0652&format=json"A response looks like this:
json
{
"place_id": 123456789,
"lat": "33.6938118",
"lon": "73.0651511",
"display_name": "Islamabad, Islamabad Capital Territory, Pakistan",
"address": {
"city": "Islamabad",
"state": "Islamabad Capital Territory",
"country": "Pakistan",
"country_code": "pk"
}
}Instead of an address being converted into coordinates, the coordinates are converted into a human-readable address — broken down into structured components like city, state, and country, in addition to the full display_name string. This structured address object is especially useful when your application needs to display or filter by a specific component (e.g., just the city or country) rather than parsing the full display string.
Why Docker + PostGIS + Nominatim?
This combination is useful when you want:
Self-hosted geocoding, with no per-request cost or external rate limits
Full control over which OpenStreetMap data you import and how often it updates
PostgreSQL-based spatial storage you can query directly alongside your own data
PostGIS spatial functions for distance, containment, and intersection queries
No dependency on a third-party geocoding provider
A reproducible environment you can spin up identically in dev, staging, and production
Important Considerations
Running your own Nominatim instance is more involved than starting a typical Docker container. Some things to plan for:
Import time: Even a mid-sized country extract can take hours on the first import. Budget time accordingly, especially in CI/CD pipelines.
RAM: Nominatim's import process is memory-hungry. Small regions can run on 2–4 GB; larger regions or full-planet imports may need 64 GB or more, plus a
flatnodefile to keep memory usage manageable.Disk space: Country-level data can require tens of gigabytes; the full planet requires well over a terabyte.
Update strategy: If you enable
REPLICATION_URL, Nominatim can pull incremental OpenStreetMap updates rather than requiring a full re-import — useful for keeping data current without repeating the entire import process.Rate limiting your own instance: Even though it's self-hosted, consider adding rate limiting or caching in front of it if multiple internal services will query it heavily — the underlying Postgres queries aren't free.
For development, it's almost always better to start with a small geographic extract (a city or small country) rather than importing the entire planet.
Conclusion
Nominatim, PostgreSQL, and PostGIS provide a powerful foundation for building self-hosted geospatial applications. Docker simplifies deployment, while PostGIS provides the spatial database capabilities needed to work with geographic data at scale.
A setup like this can serve as the foundation for applications involving address search, reverse geocoding, location-based search, distance calculations, maps, and geospatial APIs — without relying on an external, rate-limited, or paid geocoding service.