Why
The RYTA marketplace puts every cargo, truck and machine on a map and geocodes addresses all day. Third-party map APIs in the region are either paid per request, rate-limited, or politically fragile. Owning the stack for the whole CIS was cheaper than any of them — provided it ran on the hardware we already had: a VPS with 2 GB of RAM.
Build heavy, serve light
Everything expensive happens on a build machine; the VPS only serves files and answers small queries.
Basemap. Thirteen Geofabrik PBF extracts (Russia and twelve neighbours) are merged with osmium, imported into PostGIS with osm2pgsql — the architecture notes contain the sizing math for osm2pgsql cache versus flat-nodes and the disk multipliers — and rendered to a vector basemap with Planetiler/tippecanoe. The output is a single ~10 GB PMTiles file. PMTiles is served as a static file with HTTP range requests; no tile server process, no cache to warm, and MapLibre GL reads it directly.
Geocoding. A Photon index is built off-server from a Nominatim import and copied over: about 5.5 GB on disk and ~2 GB of heap for OpenSearch, which is the one component that actually needs memory. It gives forward search with Russian and English autocomplete and reverse geocoding.
Live tiles when needed. For layers that change, pg-map renders MVT tiles straight from PostGIS with ST_AsMVT; there is also a Martin tile-server config and a kepler.gl v3 setup over pg_featureserv that needs no Mapbox token.
The gateway: keys, quotas, money
A Node/Express gateway fronts everything as a small SaaS. Two key scopes — rgk_* for geocoding and rmk_* for maps — with daily and monthly quotas that answer HTTP 429 on overrun, a usage dashboard, an admin, and a keyless public demo. Sign-up is passwordless: the user proves a phone number by calling an 8-800 number and the Rostelecom virtual PBX confirms it through a signed webhook. Free, Starter and Business plans are paid through T-Bank.
Editing the map yourself
edit-osm is a Flask tool: draw a bounding box on your own map, cut it out with osmium, edit it in JOSM through Remote Control, merge the result back and rebuild the basemap. Custom places therefore appear both on the tiles and in search. There is also an experiment with a full OSM API database (cgimap) for a private editing workflow.
Tooling and deployment
geo-demo is a MapLibre test bench showing Photon timing statistics, per-result OSM tags, click-to-reverse-geocode and a vector-tile feature inspector. Two deployment targets — one VPS reusing an existing Photon, one main server with a private registry — are driven by dry-run-capable PowerShell scripts and Caddy host blocks. About 7 600 lines of my own code excluding the vendored pieces.
What I would do differently
Decide the tile schema before the first Planetiler run; changing attributes later means re-rendering 10 GB. And put the quota accounting in from the first day — it was added when the demo started getting real traffic, which is the wrong moment.