# maplibre/martin: a Rust tile server for PostGIS, PMTiles and MBTiles

> Martin serves vector tiles from PostGIS, PMTiles, MBTiles, GeoJSON and GeoParquet, and ships an mbtiles tool and a bulk copy subcommand. It is aimed at teams that already have spatial data and need a fast HTTP tile endpoint in front of it.

**maplibre/martin** — Blazing fast and lightweight PostGIS, MBtiles and PMtiles tile server, tile generation, and mbtiles tooling.

- Repository: https://github.com/maplibre/martin
- Website: https://martin.maplibre.org
- Stars: 3,960 · Forks: 399
- Language: Rust
- License: Apache-2.0
- Published: 2026-09-23 · Updated: 2026-09-23 · Language: en
- Canonical page: https://hysenlabs.com/projects/maplibre-martin

## What Martin solves, and for whom

A spatial database is not a tile server. PostGIS can store geometry and run spatial queries, but a browser map client expects an HTTP endpoint that answers z/x/y tile requests, and something has to translate between the two, handle caching, and stay up under load. Martin is that translation layer. It reads vector tiles from PostGIS databases, PMTiles files (local or over HTTP), MBTiles files, GeoJSON files and, unstably, GeoParquet files via DuckDB, then serves them over HTTP.

The intended user is a team that already has data in one of those formats and wants to put it on a map without writing a custom tile service. The README describes the project as optimizing for speed and heavy traffic, and it is written in Rust. If you are publishing a handful of static layers once a month, the operational weight of running a server may not be worth it. If you have a PostGIS instance that changes and you want clients to see current data, the trade-off flips.

Martin also covers the parts around tiles that usually become separate scripts: styles, sprite generation, font glyphs, and a passthrough mode that forwards tiles from an upstream HTTP tile server. That matters because a vector tile endpoint without a style is not directly renderable in a MapLibre GL client.

## How source discovery works inside Martin

The architecture is a Rust workspace rather than a single binary. Cargo.toml lists members including martin, martin-core, martin-tile-utils, martin-config-macros, mbtiles and e2e-tests. The mbtiles crate is a library and a CLI tool; martin-core holds shared logic; martin is the server. That split is why the mbtiles tooling is usable on its own, without running a server at all.

Against PostGIS, the notable behaviour is automatic discovery: the README says Martin discovers compatible tables and functions. You point it at a connection and it enumerates what it can serve, instead of requiring one configuration block per layer. The constraint hiding in that sentence is compatible. A table needs a geometry column that Martin can interpret, and the project documentation covers what makes a table or function eligible. A table without a spatial column, or with an SRID Martin cannot place, will not appear.

For file sources the model is simpler: PMTiles and MBTiles are read directly, GeoJSON is converted to vector tiles on the fly, and GeoParquet goes through DuckDB. The README marks the GeoParquet path as unstable, so treat it as something to evaluate rather than depend on. Composite sources let you merge several tile sources into one endpoint, and passthrough forwards tiles from an upstream server.

## Installing Martin and serving your first PostGIS layer

The README points to a dedicated Installation page in the documentation rather than listing package commands inline, and there is a Quick Start page alongside it. The repository also carries a docker-compose.yml used for development and testing, which is the clearest concrete example of the database setup Martin expects.

The compose file defines a db service on the postgis/postgis:18-3.6-alpine image, publishing port 5411 by default via the PGPORT variable, with POSTGRES_DB, POSTGRES_USER and POSTGRES_PASSWORD all set to db, postgres and postgres. A separate db-is-ready service polls pg_isready against localhost on the same port before anything else proceeds. That pattern is worth copying if you run Martin in a container next to PostGIS, because Martin will fail to discover sources if the database is not accepting connections yet.

```yaml
services:
  db:
    image: postgis/postgis:18-3.6-alpine
    ports:
      - '${PGPORT:-5411}:5432'
    environment:
      - POSTGRES_DB=db
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres
```

Once the database is reachable, Martin can be started either from the command line or from a configuration file. The documentation covers both paths under Running with CLI and Running with configuration file. The CLI path is the faster way to see whether discovery works on your schema, because you get the list of detected sources without writing any config first. If a table you expected is missing from that list, the problem is almost always the table definition, not the server.

## Bulk export and the mbtiles tool

Two features sit outside the serving path and are easy to overlook. The first is martin cp, a subcommand that generates tiles in bulk from any Martin-supported source into an MBTiles file. That is the answer when you want a static artifact for offline use or distribution rather than a live endpoint, and it uses the same source definitions as the server, so you are not maintaining two configurations.

The second is the mbtiles tool, which the README describes as able to examine, copy, validate, compare, and apply diffs between MBTiles files. The diff and compare operations are the interesting ones. If you regenerate a tile archive on a schedule, comparing the new file against the previous one tells you what actually changed, which is cheaper to review than a full re-export. Validation catches malformed archives before they reach a client.

Both live in the mbtiles crate in the workspace. That means the tooling has its own release line: the recent releases list shows mbtiles-v0.19.3 dated 2026-09-07, separate from martin-v1.16.0 and martin-v1.16.1. If you depend on the mbtiles CLI, track its version independently of the server version, because they do not move together.

## Where Martin is the wrong tool

Martin serves tiles. It does not edit data, manage users, or provide a styling interface. If your requirement is a browser-based editor where non-engineers change geometries and publish layers, Martin is one component of that system at most, and you would be building the rest yourself.

Automatic discovery is also a double-edged default. It is convenient when your schema is already tile-friendly, and awkward when it is not. A PostGIS table that stores geometry in a column Martin does not recognize, or that lacks the primary key the discovery logic expects, simply will not show up, and the failure is silent from the client side: the layer is absent rather than broken. Teams migrating from a hand-written tile query often find they need to adjust the schema or add a function before discovery produces the layer they had before.

The GeoParquet support is explicitly marked unstable in the README. Do not build a production pipeline on it. And the project is a server: if your use case is a one-time export of a few tiles, running a long-lived process plus a database is more moving parts than generating an MBTiles file with martin cp and shipping the file.

## How Martin differs from tiling inside the database

The main alternative for PostGIS users is generating tiles inside PostgreSQL itself, typically with the pg_tileserv family of extensions or a hand-written function that accepts z, x and y and returns MVT bytes. That approach has one real advantage: there is no second process to deploy, monitor or scale, and the tile logic lives next to the data it reads.

The difference in approach is where the work happens. With database-side tiling, every tile request is a SQL call, and connection pooling becomes the bottleneck under load. Martin sits in front and holds its own connections, which is what lets it target heavy traffic. It also decouples the tile endpoint from the database version, so a PostGIS upgrade does not change your HTTP surface.

The cost is a second thing to operate. You now have a Rust binary or container with its own configuration, its own health checks and its own upgrade cadence. For a small deployment with modest traffic, database-side tiling is genuinely simpler and the performance argument for Martin does not apply. For anything with real request volume, or with tiles coming from a mix of PostGIS and PMTiles files, the separate server earns its place.

## Licence, maintenance and upgrade cost

Martin is dual licensed under Apache-2.0 and MIT, at your option, and the README states that contributions are dual licensed the same way unless stated otherwise. Both are permissive licences with no copyleft obligation on your own code, which matters if you embed the mbtiles crate as a library rather than running the server. The workspace Cargo.toml declares license = "MIT OR Apache-2.0" for the workspace packages. This is a description of the licence terms, not legal advice; check the LICENSE-APACHE and LICENSE-MIT files in the repository for the actual text.

On maintenance, the last push to the default branch was on 2026-09-23, and the most recent server release is martin-v1.16.1 from 2026-09-09. The repository is not archived. The workspace pins rust-version = 1.98 and edition = 2024, so building from source requires a recent toolchain; if you are on an older Rust, plan for a toolchain upgrade before you can compile the workspace. The justfile defines a stable_features set covering contour, fonts, geojson, hillshade, lambda, mbtiles, metrics, mlt, passthrough, pmtiles, postgres, sprites, styles, tui and webui, with a full_features set that adds rendering. If you build your own binary rather than using a release artifact, those feature flags decide what ends up in it, and a missing feature is a runtime absence rather than a compile error you would notice early.

## Conclusion

Adopt Martin if your tiles already live in PostGIS, PMTiles or MBTiles and you want an HTTP endpoint that discovers sources instead of being configured per layer. Skip it if you need a full GIS platform with editing, styling workflows and user management, or if you cannot run a Rust binary or container next to your database. Verify first that your PostGIS tables have a geometry column with an SRID and a primary key, because automatic discovery depends on both, and check the configuration file reference for the exact source keys your deployment needs.

## FAQ

### What tile sources can maplibre/martin serve?

The README lists PostGIS databases, PMTiles files both local and over HTTP, MBTiles files, GeoJSON files converted on the fly, and GeoParquet files via DuckDB, which the README marks as unstable. It can also pass through tiles from an upstream HTTP tile server and combine multiple sources into one.

### Does maplibre/martin need a configuration file to start?

No. The documentation covers running with the CLI as well as running with a configuration file, and the CLI path is the quicker way to see which sources Martin discovers on a given database.

### Can maplibre/martin export tiles to a file instead of serving them?

Yes. The martin cp subcommand generates tiles in bulk from any Martin-supported source into an MBTiles file, and the separate mbtiles tool can examine, copy, validate, compare and apply diffs between MBTiles files.

### What licence is maplibre/martin released under?

It is dual licensed under Apache-2.0 and MIT at your option, and the README states that contributions are dual licensed the same way unless stated otherwise.

## Sources

- [License: Apache-2.0](https://github.com/maplibre/martin/blob/main/LICENSE)
- [maplibre/martin on GitHub](https://github.com/maplibre/martin)
- [Project website](https://martin.maplibre.org)
- [README](https://github.com/maplibre/martin/blob/main/README.md)
- [Releases](https://github.com/maplibre/martin/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/maplibre-martin
