https://github.com/vinayakkulkarni/tileserver-rs
High-performance Rust tile server for PMTiles, MBTiles, PostGIS, and Cloud Optimized GeoTIFFs
https://github.com/vinayakkulkarni/tileserver-rs
gis maplibre-gl-js maplibre-rs openmaptiles raster-map raster-tiles rust tileserver tileserver-rs vector-tiles
Last synced: 2 months ago
JSON representation
High-performance Rust tile server for PMTiles, MBTiles, PostGIS, and Cloud Optimized GeoTIFFs
- Host: GitHub
- URL: https://github.com/vinayakkulkarni/tileserver-rs
- Owner: vinayakkulkarni
- License: mit
- Created: 2023-01-10T06:07:55.000Z (over 3 years ago)
- Default Branch: main
- Last Pushed: 2026-03-30T17:00:44.000Z (2 months ago)
- Last Synced: 2026-03-30T18:24:00.039Z (2 months ago)
- Topics: gis, maplibre-gl-js, maplibre-rs, openmaptiles, raster-map, raster-tiles, rust, tileserver, tileserver-rs, vector-tiles
- Language: Rust
- Homepage: https://tileserver.app
- Size: 54.3 MB
- Stars: 10
- Watchers: 2
- Forks: 2
- Open Issues: 19
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
- Code of conduct: CODE_OF_CONDUCT.md
Awesome Lists containing this project
README
# tileserver-rs ๐ฆ
[](https://github.com/vinayakkulkarni/tileserver-rs/actions/workflows/pipeline.yml)
[](https://github.com/vinayakkulkarni/tileserver-rs/actions/workflows/release-docker.yml)
[](https://crates.io/crates/mbgl-sys)
[](https://docs.rs/mbgl-sys)
[](https://railway.com/deploy/1u-LMi)
[](https://render.com/deploy?repo=https://github.com/vinayakkulkarni/tileserver-rs)
[](https://cloud.digitalocean.com/apps/new?repo=https://github.com/vinayakkulkarni/tileserver-rs/tree/main)

High-performance vector tile server built in Rust with a modern Nuxt 4 frontend.
## Features
- **PMTiles Support** - Serve tiles from local and remote PMTiles archives
- **MBTiles Support** - Serve tiles from SQLite-based MBTiles files
- **Native Raster Rendering** - Generate PNG/JPEG/WebP tiles using MapLibre Native (C++ FFI)
- **MLT (MapLibre Tiles)** - Serve and transcode MLT tiles with MLTโMVT on-the-fly conversion (feature-gated)
- **PostgreSQL Out-DB Rasters** - Serve VRT/COG tiles via PostGIS functions with dynamic filtering
- **Static Map Images** - Create embeddable map screenshots (like Mapbox/Maptiler Static API)
- **Zero-Config Auto-Detect** - Point at a directory or file and start serving instantly
- **Hot Reload** - Reload configuration via `SIGHUP` signal or admin API without downtime
- **High Performance** - ~100ms per tile (warm cache), ~800ms cold cache
- **TileJSON 3.0** - Full TileJSON metadata API
- **MapLibre GL JS** - Built-in map viewer and data inspector
- **Docker Ready** - Easy deployment with Docker Compose v2
- **Fast** - Built in Rust with Axum for maximum performance
## Tech Stack
- **Backend**: Rust 1.75+, Axum 0.8, Tokio
- **Native Rendering**: MapLibre Native (C++) via FFI bindings
- **Frontend**: Nuxt 4, Vue 3.5, Tailwind CSS v4, shadcn-vue
- **Tooling**: Bun workspaces, Docker multi-stage builds
## Table of Contents
- [Features](#features)
- [Tech Stack](#tech-stack)
- [Requirements](#requirements)
- [Quick Start](#quick-start)
- [Installation](#installation)
- [Using Docker](#using-docker)
- [Building from Source](#building-from-source)
- [Configuration](#configuration)
- [API Endpoints](#api-endpoints)
- [Deploy](#deploy)
- [Development](#development)
- [Contributing](#contributing)
- [Author](#author)
## Requirements
- [Rust 1.75+](https://www.rust-lang.org/)
- [Bun 1.0+](https://bun.sh/)
- (Optional) [Docker](https://www.docker.com/)
### For Native Rendering (Optional)
Native raster tile rendering requires building MapLibre Native. If you don't need raster tiles, the server runs without it (stub implementation returns placeholder images).
**macOS (Apple Silicon/Intel):**
```bash
# Install build dependencies
brew install ninja ccache libuv glfw bazelisk cmake
# Build MapLibre Native
cd crates/mbgl-sys/vendor/maplibre-native
git submodule update --init --recursive
cmake --preset macos-metal
cmake --build build-macos-metal --target mbgl-core mlt-cpp -j8
```
**Linux:**
```bash
# Install build dependencies (Ubuntu/Debian)
apt-get install ninja-build ccache libuv1-dev libglfw3-dev cmake
# Build MapLibre Native
cd crates/mbgl-sys/vendor/maplibre-native
git submodule update --init --recursive
cmake --preset linux
cmake --build build-linux --target mbgl-core mlt-cpp -j8
```
**After building MapLibre Native:**
```bash
# Clear Cargo's cached build to detect the new libraries
cd /path/to/tileserver-rs
rm -rf target/release/build/mbgl-sys-*
cargo build --release
```
You should see `Building with real MapLibre Native renderer` in the build output.
## Quick Start
```bash
# Zero-config: point at a directory of tile files
./tileserver-rs /path/to/data
# Or with an explicit config file
./tileserver-rs --config config.toml
# Using Docker
docker compose up -d
```
The server auto-detects `.pmtiles`, `.mbtiles`, `style.json`, fonts, and GeoJSON files from the given path. See the [Auto-Detect Guide](https://docs.tileserver.app/guides/auto-detect) for details.
## Installation
### Using Homebrew (macOS)
```bash
# Add the tap and install
brew tap vinayakkulkarni/tileserver-rs https://github.com/vinayakkulkarni/tileserver-rs
brew install vinayakkulkarni/tileserver-rs/tileserver-rs
# Run the server
tileserver-rs --config config.toml
```
### Pre-built Binaries
Download the latest release from [GitHub Releases](https://github.com/vinayakkulkarni/tileserver-rs/releases).
| Platform | Architecture | Download |
|----------|--------------|----------|
| macOS | Apple Silicon (ARM64) | `tileserver-rs-aarch64-apple-darwin.tar.gz` |
| macOS | Intel (x86_64) | `tileserver-rs-x86_64-apple-darwin.tar.gz` |
| Linux | x86_64 | `tileserver-rs-x86_64-unknown-linux-gnu.tar.gz` |
| Linux | ARM64 | `tileserver-rs-aarch64-unknown-linux-gnu.tar.gz` |
```bash
# macOS ARM64 (Apple Silicon)
curl -L https://github.com/vinayakkulkarni/tileserver-rs/releases/latest/download/tileserver-rs-aarch64-apple-darwin.tar.gz | tar xz
chmod +x tileserver-rs
# Remove macOS quarantine (required for unsigned binaries)
xattr -d com.apple.quarantine tileserver-rs
# Linux x86_64
curl -L https://github.com/vinayakkulkarni/tileserver-rs/releases/latest/download/tileserver-rs-x86_64-unknown-linux-gnu.tar.gz | tar xz
chmod +x tileserver-rs
# Run
./tileserver-rs --config config.toml
```
> **macOS Security Note:** If you download via a browser, macOS Gatekeeper will block the unsigned binary. Either use the `curl` command above, or after downloading, run `xattr -d com.apple.quarantine ` to remove the quarantine flag. Alternatively, right-click the binary in Finder and select "Open".
### Using Docker
```bash
# Development (builds locally, mounts ./data directory)
docker compose up -d
# Production (uses pre-built image with resource limits)
docker compose -f compose.yml -f compose.prod.yml up -d
# View logs
docker compose logs -f tileserver
# Stop
docker compose down
```
**Or run directly with Docker:**
```bash
docker run -d \
-p 8080:8080 \
-v /path/to/data:/data:ro \
-v /path/to/config.toml:/app/config.toml:ro \
ghcr.io/vinayakkulkarni/tileserver-rs:latest
```
### Building from Source
```bash
# Clone the repository with submodules
git clone --recursive git@github.com:vinayakkulkarni/tileserver-rs.git
cd tileserver-rs
# Or using HTTPS
git clone --recursive https://github.com/vinayakkulkarni/tileserver-rs.git
# If you already cloned without --recursive:
git submodule update --init --recursive
# Install dependencies
bun install
# Build the Rust backend
cargo build --release
# Build with MLT transcoding support (optional)
cargo build --release --features mlt
# Build the frontend
bun run build:client
# Run the server
./target/release/tileserver-rs --config config.toml
```
> **Note:** The `--recursive` flag fetches the MapLibre Native submodule (~200MB) required for native raster rendering. If the clone times out, use `git submodule update --init --depth 1` for a shallow clone. See [CONTRIBUTING.md](./CONTRIBUTING.md) for detailed setup instructions.
## Configuration
Create a `config.toml` file. **Important:** Root-level options (`fonts`, `files`) must come before any `[section]` headers:
```toml
# Root-level options (must come BEFORE [sections])
fonts = "/data/fonts"
files = "/data/files"
[server]
host = "0.0.0.0"
port = 8080
cors_origins = ["*", "https://example.com"] # Supports multiple origins
# Admin server bind address for hot-reload endpoint (default: disabled)
# admin_bind = "127.0.0.1:9099"
[telemetry]
enabled = false
[[sources]]
id = "openmaptiles"
type = "pmtiles"
path = "/data/tiles.pmtiles"
name = "OpenMapTiles"
attribution = "ยฉ OpenMapTiles ยฉ OpenStreetMap contributors"
[[sources]]
id = "terrain"
type = "mbtiles"
path = "/data/terrain.mbtiles"
name = "Terrain Data"
[[styles]]
id = "osm-bright"
path = "/data/styles/osm-bright/style.json"
# PostgreSQL Out-of-Database Rasters (optional)
[postgres]
connection_string = "postgresql://user:pass@localhost:5432/gis"
[[postgres.outdb_rasters]]
id = "imagery" # Also used as function name if 'function' is omitted
schema = "public"
# function = "get_raster_paths" # Optional: defaults to 'id' value
name = "Satellite Imagery"
```
### Admin Server (Hot Reload)
Enable the admin server by setting `admin_bind` in `[server]`:
```toml
[server]
admin_bind = "127.0.0.1:9099"
```
This exposes `POST /__admin/reload` on a separate port for reloading configuration without restarting the server. You can also send `SIGHUP` to the process for the same effect. See the [Hot Reload Guide](https://docs.tileserver.app/guides/hot-reload) for details.
See [configs/example.toml](./configs/example.toml) for a complete example, or [configs/offline.toml](./configs/offline.toml) for a local development setup.
## API Endpoints
### Health & Admin Endpoints
| Endpoint | Description |
|----------|-------------|
| `GET /health` | Health check (returns `OK`) |
| `GET /ping` | Runtime metadata (config hash, loaded sources/styles, version) |
| `POST /__admin/reload` | Hot-reload configuration (admin server only) |
| `POST /__admin/reload?flush=true` | Force reload even if config unchanged |
### Data Endpoints (Vector Tiles)
| Endpoint | Description |
|----------|-------------|
| `GET /data.json` | List all tile sources |
| `GET /data/{source}.json` | TileJSON for a source |
| `GET /data/{source}/{z}/{x}/{y}.{format}` | Get a vector tile (`.pbf`, `.mvt`, `.mlt`) |
| `GET /data/{source}/{z}/{x}/{y}.geojson` | Get tile as GeoJSON (for debugging) |
### Style Endpoints
| Endpoint | Description |
|----------|-------------|
| `GET /styles.json` | List all styles |
| `GET /styles/{style}/style.json` | Get MapLibre GL style JSON |
| `GET /styles/{style}/sprite[@2x].{png,json}` | Get sprite image/metadata |
| `GET /styles/{style}/wmts.xml` | WMTS capabilities (for QGIS/ArcGIS) |
### Font Endpoints
| Endpoint | Description |
|----------|-------------|
| `GET /fonts.json` | List available font families |
| `GET /fonts/{fontstack}/{range}.pbf` | Get font glyphs (PBF format) |
### Other Endpoints
| Endpoint | Description |
|----------|-------------|
| `GET /files/{filepath}` | Serve static files (GeoJSON, icons, etc.) |
| `GET /index.json` | Combined TileJSON for all sources and styles |
### PostgreSQL Out-DB Raster Endpoints
| Endpoint | Description |
|----------|-------------|
| `GET /data/{outdb_source}/{z}/{x}/{y}.{format}` | Raster tile from PostgreSQL-referenced VRT/COG |
| `GET /data/{outdb_source}/{z}/{x}/{y}.{format}?satellite=...` | With dynamic filtering via query params |
### Rendering Endpoints (Native MapLibre)
| Endpoint | Description |
|----------|-------------|
| `GET /styles/{style}/{z}/{x}/{y}[@{scale}x].{format}` | Raster tile (PNG/JPEG/WebP) |
| `GET /styles/{style}/static/{type}/{size}[@{scale}x].{format}` | Static map image |
**Raster Tile Examples:**
```
/styles/protomaps-light/14/8192/5461.png # 512x512 PNG @ 1x
/styles/protomaps-light/14/8192/5461@2x.webp # 1024x1024 WebP @ 2x (retina)
```
**Performance:**
- Warm cache: ~100ms per tile
- Cold cache: ~700-800ms per tile (includes tile fetching)
- Static images: ~3s for 800x600
**Static Image Types:**
- **Center**: `{lon},{lat},{zoom}[@{bearing}[,{pitch}]]`
```
/styles/protomaps-light/static/-122.4,37.8,12/800x600.png
/styles/protomaps-light/static/-122.4,37.8,12@45,60/800x600@2x.webp
```
- **Bounding Box**: `{minx},{miny},{maxx},{maxy}`
```
/styles/protomaps-light/static/-123,37,-122,38/1024x768.jpeg
```
- **Auto-fit**: `auto` (with `?path=` or `?marker=` query params)
```
/styles/protomaps-light/static/auto/800x600.png?path=path-5+f00(-122.4,37.8|-122.5,37.9)
```
**Static Image Limits:**
- Maximum dimensions: 4096x4096 pixels
- Maximum scale: 4x
## Development
```bash
# Install dependencies
bun install
# Start Rust backend (with hot reload via cargo-watch)
cargo watch -x run
# Start Nuxt frontend (in another terminal)
bun run dev:client
# Start marketing site (landing page)
bun run dev:marketing
# Run linters
bun run lint
cargo clippy
# Build for production
cargo build --release
bun run build:client
```
### Cargo Feature Flags
| Feature | Description |
|---------|-------------|
| `http` | Enable serving PMTiles from remote HTTP URLs |
| `mlt` | Enable MLT (MapLibre Tiles) transcoding support |
```bash
# Build with MLT support
cargo build --release --features mlt
# Build with all optional features
cargo build --release --features http,mlt
```
### Project Structure
```
tileserver-rs/
โโโ apps/
โ โโโ client/ # Nuxt 4 frontend (embedded in binary)
โโโ docs/ # Documentation site (docs.tileserver.app)
โโโ marketing/ # Landing page (tileserver.app)
โโโ crates/
โ โโโ mbgl-sys/ # FFI bindings to MapLibre Native (C++)
โ โโโ cpp/ # C/C++ wrapper code
โ โ โโโ maplibre_c.h # C API header
โ โ โโโ maplibre_c.cpp # C++ implementation
โ โโโ src/lib.rs # Rust FFI bindings
โ โโโ build.rs # Build script
โ โโโ vendor/maplibre-native/ # MapLibre Native source (submodule)
โโโ src/ # Rust backend
โ โโโ main.rs # Entry point, routes
โ โโโ admin.rs # Admin server + /ping endpoint
โ โโโ autodetect.rs # Zero-config auto-detection
โ โโโ config.rs # Configuration
โ โโโ error.rs # Error types
โ โโโ reload.rs # Hot-reload (ArcSwap + SIGHUP)
โ โโโ startup.rs # Config resolution priority chain
โ โโโ render/ # Native MapLibre rendering
โ โ โโโ pool.rs # Renderer pool (per scale factor)
โ โ โโโ renderer.rs # High-level render API
โ โ โโโ native.rs # Safe Rust wrappers around FFI
โ โ โโโ types.rs # RenderOptions, ImageFormat, etc.
โ โโโ sources/ # Tile source implementations
โ โโโ styles/ # Style management + rewriting
โ โโโ transcode.rs # MLTโMVT transcoding (feature-gated)
โโโ compose.yml # Docker Compose (development)
โโโ compose.prod.yml # Docker Compose (production overrides)
โโโ Dockerfile # Multi-stage Docker build
โโโ configs/ # Configuration files
โ โโโ example.toml # Example configuration
โ โโโ offline.toml # Offline/local development
โ โโโ dev.toml # Development config
โ โโโ geoparquet.toml # GeoParquet source testing
โ โโโ benchmark-raster.toml # Raster benchmark config
โโโ tests/ # Integration tests + test configs
```
## Deploy
### One-Click Cloud Deploy
Deploy a fully working tileserver-rs instance with sample data in minutes. No configuration needed โ sample tile data is automatically downloaded on first start.
| Platform | Deploy | Notes |
|----------|--------|-------|
| **Render** | [](https://render.com/deploy?repo=https://github.com/vinayakkulkarni/tileserver-rs) | Uses `render.yaml` blueprint |
| **DigitalOcean** | [](https://cloud.digitalocean.com/apps/new?repo=https://github.com/vinayakkulkarni/tileserver-rs/tree/main) | Uses `.do/deploy.template.yaml` |
| **Railway** | [](https://railway.com/deploy/1u-LMi) | Uses `railway.toml` config |
| **Fly.io** | `fly launch --copy-config` | Uses `fly.toml` โ see below |
| **Docker** | `docker compose up -d` | Uses `compose.yml` (already included) |
### How Sample Data Works
When the Docker container starts with an empty `/data` directory (no tile files mounted), it automatically downloads sample data (~15 MB) from the [latest GitHub release](https://github.com/vinayakkulkarni/tileserver-rs/releases). This includes:
- **Protomaps sample tiles** (PMTiles) โ world basemap extract
- **Zurich MBTiles** โ detailed city extract
- **Noto Sans fonts** โ for label rendering
- **Protomaps Light style** โ ready-to-use map style
- **Sample raster data** โ COG test files
To use your own data, mount a volume at `/data`:
```bash
docker run -d -p 8080:8080 -v /path/to/your/data:/data:ro ghcr.io/vinayakkulkarni/tileserver-rs:latest
```
Set `SAMPLE_DATA_VERSION=v2.12.1` to pin a specific release version instead of `latest`.
### Deploy on Fly.io
```bash
# Install flyctl: https://fly.io/docs/flyctl/install/
fly launch --copy-config
fly deploy
```
The included `fly.toml` configures auto-stop/start machines, health checks, and 512MB RAM. Add a persistent volume for your own tile data:
```bash
fly volumes create tile_data --size 10 --region iad
```
## Deployments
### Documentation Site (docs.tileserver.app)
The docs site is deployed automatically via Cloudflare Pages (linked repo). Any changes to `docs/` trigger a rebuild.
### Marketing Site (tileserver.app)
The marketing/landing page is deployed via GitHub Actions to a separate CF Pages project.
**Setup (one-time):**
1. Create a new CF Pages project named `tileserver-marketing` (Direct Upload, not linked to repo)
2. Add custom domain `tileserver.app` to the project
3. Add these secrets to GitHub repo settings:
- `CLOUDFLARE_API_TOKEN` - API token with "Cloudflare Pages: Edit" permission
- `CLOUDFLARE_ACCOUNT_ID` - Your Cloudflare account ID
Deployments are triggered on push to `main` when files in `marketing/` change.
## Releases
This project uses [Release Please](https://github.com/googleapis/release-please) for automated releases. The release process is fully automated based on [Conventional Commits](https://www.conventionalcommits.org/).
**How it works:**
1. Commits to `main` with conventional commit messages (`feat:`, `fix:`, etc.) trigger Release Please
2. Release Please creates/updates a **Release PR** with version bumps and changelog
3. Merging the Release PR creates a GitHub Release and triggers platform builds
**Version bumping:**
- `feat:` commits โ minor version (0.1.0 โ 0.2.0)
- `fix:` commits โ patch version (0.1.0 โ 0.1.1)
- `feat!:` or `BREAKING CHANGE:` โ major version (0.1.0 โ 1.0.0)
**Release artifacts:**
- GitHub Release with changelog
- macOS ARM64 binary (`.tar.gz`)
- macOS AMD64 (Intel) binary (`.tar.gz`)
- Linux x86_64 + ARM64 binaries โ full (with MapLibre Native) and headless variants
- Docker image (`ghcr.io/vinayakkulkarni/tileserver-rs`) โ multi-arch `linux/amd64` + `linux/arm64`
- Homebrew formula auto-update
## Contributing
We welcome contributions! Please see [CONTRIBUTING.md](./CONTRIBUTING.md) for detailed guidelines.
**Quick Start:**
1. Fork it ([https://github.com/vinayakkulkarni/tileserver-rs/fork](https://github.com/vinayakkulkarni/tileserver-rs/fork))
2. Clone with submodules: `git clone --recursive `
3. Create your feature branch (`git checkout -b feat/new-feature`)
4. Commit your changes (`git commit -Sam 'feat: add feature'`)
5. Push to the branch (`git push origin feat/new-feature`)
6. Create a new [Pull Request](https://github.com/vinayakkulkarni/tileserver-rs/compare)
**Working with Git Submodules:**
```bash
# After cloning (if you forgot --recursive)
git submodule update --init --recursive
# After pulling changes from upstream
git pull
git submodule update --init --recursive
# If clone times out (shallow clone)
git submodule update --init --depth 1
```
**Notes:**
1. Please contribute using [GitHub Flow](https://guides.github.com/introduction/flow/)
2. Commits & PRs will be allowed only if the commit messages & PR titles follow the [conventional commit standard](https://www.conventionalcommits.org/)
3. Ensure your commits are signed. [Read why](https://withblue.ink/2020/05/17/how-and-why-to-sign-git-commits.html)
## Author
**tileserver-rs** ยฉ [Vinayak](https://vinayakkulkarni.dev), Released under the [MIT](./LICENSE) License.
Authored and maintained by Vinayak Kulkarni with help from contributors ([list](https://github.com/vinayakkulkarni/tileserver-rs/contributors)).
> [vinayakkulkarni.dev](https://vinayakkulkarni.dev) ยท GitHub [@vinayakkulkarni](https://github.com/vinayakkulkarni) ยท Twitter [@_vinayak_k](https://twitter.com/_vinayak_k)
### Special Thanks
- [tileserver-gl](https://github.com/maptiler/tileserver-gl) - Inspiration for this project
- [MapLibre](https://maplibre.org/) - Open-source mapping library
- [PMTiles](https://github.com/protomaps/PMTiles) - Cloud-optimized tile archive format
- [PostGIS](https://postgis.net/) - Spatial database extension for PostgreSQL