Self Host (Docker)
Postcodes.io can be self-hosted with two Docker images: the API container (idealpostcodes/postcodes.io) and a pre-seeded PostgreSQL/PostGIS database container (idealpostcodes/postcodes.io.db).
The database image bundles a pre-built pg_dump produced by our upstream data pipeline — there is no in-container ingest step. The API container is stateless and connects to whatever Postgres you point it at.
Requirements
- A local clone of postcodes.io
- Docker (Compose v2 ships with modern Docker installations)
Quickstart
docker-compose up -d
This brings up the database and API containers and networks them. The HTTP service is exposed on port 8000.
Application container
idealpostcodes/postcodes.io provides the HTTP interface. It is stateless and expects a populated PostgreSQL/PostGIS instance reachable over the network.
docker run -d -p 8000:8000 idealpostcodes/postcodes.io
Configuration is environment-variable-driven.
HTTP
| Variable | Purpose |
|---|---|
HOST | Host/interface to bind to (defaults to 0.0.0.0). |
PORT | HTTP port the API listens on (defaults to 8000). |
URL_PREFIX | Prefix prepended to every route (e.g. /v1). Empty by default. |
HTTP_HEADERS | JSON object of headers to attach to every response, e.g. '{"X-Foo":"bar"}'. |
Database
| Variable | Purpose |
|---|---|
POSTGRES_USER | Database username. |
POSTGRES_PASSWORD | Database password. |
POSTGRES_DATABASE | Database name. |
POSTGRES_HOST | Database host. |
POSTGRES_PORT | Database port. |
Logging & metrics
| Variable | Purpose |
|---|---|
LOG_NAME | Name attached to JSON log output. |
LOG_DESTINATION | stdout, perf (high-throughput stdout via pino.extreme), or a file path. |
PROMETHEUS_USERNAME | Enables a basic-auth-protected /metrics endpoint when both PROMETHEUS_* vars are set. |
PROMETHEUS_PASSWORD | Password for the /metrics endpoint. |
Request limits
All limit variables are integers. Defaults are tuned for a single-node deployment; raise them only after benchmarking your hardware. The API clamps any incoming limit / radius query parameter to the *_MAX ceiling — it does not reject the request.
| Variable | Endpoint | Default | Notes |
|---|---|---|---|
NEAREST_LIMIT_DEFAULT | GET /postcodes?lon=&lat= | 10 | Applied when limit is omitted. |
NEAREST_LIMIT_MAX | GET /postcodes?lon=&lat= | 100 | Hard cap on limit. |
NEAREST_RADIUS_DEFAULT | GET /postcodes?lon=&lat= | 100 | Metres. |
NEAREST_RADIUS_MAX | GET /postcodes?lon=&lat= | 2000 | Metres. |
SEARCH_LIMIT_DEFAULT | GET /postcodes?q= | 10 | |
SEARCH_LIMIT_MAX | GET /postcodes?q= | 100 | |
BULKLOOKUPS_POSTCODES_MAX | POST /postcodes (postcodes[]) | 100 | Array length cap. |
BULKGEOCODE_GEOLOCATIONS_MAX | POST /postcodes (geolocations[]) | 100 | Array length cap. |
NEARESTOUTCODES_LIMIT_DEFAULT | GET /outcodes?lon=&lat= | 10 | |
NEARESTOUTCODES_LIMIT_MAX | GET /outcodes?lon=&lat= | 100 | |
NEARESTOUTCODES_RADIUS_DEFAULT | GET /outcodes?lon=&lat= | 5000 | Metres. |
NEARESTOUTCODES_RADIUS_MAX | GET /outcodes?lon=&lat= | 25000 | Metres. |
PLACESSEARCH_LIMIT_DEFAULT | GET /places?q= | 10 | |
PLACESSEARCH_LIMIT_MAX | GET /places?q= | 100 |
Database container
idealpostcodes/postcodes.io.db is built on top of the official postgis/postgis image and ships a pre-loaded pg_dump of the latest ONSPD / OS Open Names / Scottish Postcode Directory data, denormalised into the public schema (public.postcodes, public.places, public.scottish_postcodes, public.outcodes). The image is rebuilt and re-tagged each time the upstream dump is refreshed; see the Changelog for dataset cadence.
On first start the container enables PostGIS and restores the dump. Restoration takes 1-3 minutes depending on hardware. Subsequent starts skip the restore.
docker run -p 5432:5432 \
-e POSTGRES_USER=postcodesio \
-e POSTGRES_DB=postcodesiodb \
-e POSTGRES_PASSWORD=password \
idealpostcodes/postcodes.io.db
This image inherits the upstream PostgreSQL configuration surface — see the postgres image documentation for the full set of environment variables.
Running the API against your own database
If you already have a Postgres instance loaded with the dump (e.g. by piping the raw pg_dump SQL into your own DB), point the API at it via the POSTGRES_* variables above. The API is read-only at runtime — no migrations, no support tables to rebuild.