Skip to content

vinayakkulkarni/tileserver-rs

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

760 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

tileserver-rs πŸ¦€

CI Pipeline Docker

tileserver-rs logo

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)
  • 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)
  • 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

Requirements

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):

# Install build dependencies
brew install ninja ccache libuv glfw bazelisk cmake

# Build MapLibre Native
cd maplibre-native-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:

# Install build dependencies (Ubuntu/Debian)
apt-get install ninja-build ccache libuv1-dev libglfw3-dev cmake

# Build MapLibre Native
cd maplibre-native-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:

# Clear Cargo's cached build to detect the new libraries
cd /path/to/tileserver-rs
rm -rf target/release/build/maplibre-native-sys-*
cargo build --release

You should see Building with real MapLibre Native renderer in the build output.

Quick Start

# Using Docker
docker compose up -d

# Or build from source
cargo build --release
./target/release/tileserver-rs --config config.toml

Installation

Using Homebrew (macOS)

# 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.

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
# 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 <binary> to remove the quarantine flag. Alternatively, right-click the binary in Finder and select "Open".

Using Docker

# 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:

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

# Clone the repository with submodules
git clone --recursive [email protected]: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 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 for detailed setup instructions.

Configuration

Create a config.toml file. Important: Root-level options (fonts, files) must come before any [section] headers:

# 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

[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"

See config.example.toml for a complete example, or config.offline.toml for a local development setup.

API Endpoints

Data Endpoints (Vector Tiles)

Endpoint Description
GET /health Health check
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)
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/[email protected]      # 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/[email protected]
    
  • 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

# 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

Project Structure

tileserver-rs/
β”œβ”€β”€ apps/
β”‚   └── client/              # Nuxt 4 frontend (embedded in binary)
β”œβ”€β”€ docs/                    # Documentation site (docs.tileserver.app)
β”œβ”€β”€ marketing/               # Landing page (tileserver.app)
β”œβ”€β”€ maplibre-native-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
β”‚   β”œβ”€β”€ config.rs            # Configuration
β”‚   β”œβ”€β”€ error.rs             # Error types
β”‚   β”œβ”€β”€ 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
β”œβ”€β”€ compose.yml              # Docker Compose (development)
β”œβ”€β”€ compose.prod.yml         # Docker Compose (production overrides)
β”œβ”€β”€ Dockerfile               # Multi-stage Docker build
└── config.example.toml      # Example configuration

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 for automated releases. The release process is fully automated based on Conventional Commits.

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)
  • Docker image (ghcr.io/vinayakkulkarni/tileserver-rs)
  • Homebrew formula auto-update

Contributing

We welcome contributions! Please see CONTRIBUTING.md for detailed guidelines.

Quick Start:

  1. Fork it (https://github.com/vinayakkulkarni/tileserver-rs/fork)
  2. Clone with submodules: git clone --recursive <your-fork-url>
  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

Working with Git Submodules:

# 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
  2. Commits & PRs will be allowed only if the commit messages & PR titles follow the conventional commit standard
  3. Ensure your commits are signed. Read why

Author

tileserver-rs Β© Vinayak, Released under the MIT License.

Authored and maintained by Vinayak Kulkarni with help from contributors (list).

vinayakkulkarni.dev Β· GitHub @vinayakkulkarni Β· Twitter @_vinayak_k

Special Thanks

  • tileserver-gl - Inspiration for this project
  • MapLibre - Open-source mapping library
  • PMTiles - Cloud-optimized tile archive format
  • PostGIS - Spatial database extension for PostgreSQL