Skip to main content

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

VariablePurpose
HOSTHost/interface to bind to (defaults to 0.0.0.0).
PORTHTTP port the API listens on (defaults to 8000).
URL_PREFIXPrefix prepended to every route (e.g. /v1). Empty by default.
HTTP_HEADERSJSON object of headers to attach to every response, e.g. '{"X-Foo":"bar"}'.

Database

VariablePurpose
POSTGRES_USERDatabase username.
POSTGRES_PASSWORDDatabase password.
POSTGRES_DATABASEDatabase name.
POSTGRES_HOSTDatabase host.
POSTGRES_PORTDatabase port.

Logging & metrics

VariablePurpose
LOG_NAMEName attached to JSON log output.
LOG_DESTINATIONstdout, perf (high-throughput stdout via pino.extreme), or a file path.
PROMETHEUS_USERNAMEEnables a basic-auth-protected /metrics endpoint when both PROMETHEUS_* vars are set.
PROMETHEUS_PASSWORDPassword 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.

VariableEndpointDefaultNotes
NEAREST_LIMIT_DEFAULTGET /postcodes?lon=&lat=10Applied when limit is omitted.
NEAREST_LIMIT_MAXGET /postcodes?lon=&lat=100Hard cap on limit.
NEAREST_RADIUS_DEFAULTGET /postcodes?lon=&lat=100Metres.
NEAREST_RADIUS_MAXGET /postcodes?lon=&lat=2000Metres.
SEARCH_LIMIT_DEFAULTGET /postcodes?q=10
SEARCH_LIMIT_MAXGET /postcodes?q=100
BULKLOOKUPS_POSTCODES_MAXPOST /postcodes (postcodes[])100Array length cap.
BULKGEOCODE_GEOLOCATIONS_MAXPOST /postcodes (geolocations[])100Array length cap.
NEARESTOUTCODES_LIMIT_DEFAULTGET /outcodes?lon=&lat=10
NEARESTOUTCODES_LIMIT_MAXGET /outcodes?lon=&lat=100
NEARESTOUTCODES_RADIUS_DEFAULTGET /outcodes?lon=&lat=5000Metres.
NEARESTOUTCODES_RADIUS_MAXGET /outcodes?lon=&lat=25000Metres.
PLACESSEARCH_LIMIT_DEFAULTGET /places?q=10
PLACESSEARCH_LIMIT_MAXGET /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.

info

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.