# Tile38: an in-memory geofencing server you query over the Redis protocol

> Tile38 is an MIT-licensed Go server that indexes points, bounding boxes, Geohashes and GeoJSON in memory and fires geofence events over webhooks or pub/sub. It is a good fit when you want spatial queries and live geofence triggers in one process, and a poor fit when your data does not fit in RAM or you need SQL-style joins.

**tidwall/tile38** — Real-time Geospatial and Geofencing

- Repository: https://github.com/tidwall/tile38
- Website: https://tile38.com
- Stars: 9,739 · Forks: 622
- Language: Go
- License: MIT
- Published: 2026-09-21 · Updated: 2026-09-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/tidwall-tile38

## What Tile38 actually solves, and for whom

Tile38 is an in-memory geolocation data store, spatial index, and realtime geofencing server, according to its README. Those three roles are usually separate products: a spatial index in your application process, a database for the objects, and a message bus for the events. Tile38 puts them behind one network port, 9851, and speaks the Redis RESP protocol as well as HTTP, websockets, and telnet.

The audience is narrower than the feature list suggests. If you are building fleet tracking, asset monitoring, or delivery dispatch, you have a stream of positions arriving and a set of questions that are all spatial: which vehicles are inside this polygon, what is within six kilometers of this point, what crossed this fence. Tile38 answers those directly. If your queries are mostly relational, or your data is mostly non-spatial with a latitude and longitude column bolted on, a general database with a spatial extension will serve you better because you get transactions and joins in the same place.

The object types are broader than points: lat/lon, bounding box, Geohash, GeoJSON, QuadKey, and XYZ tile. That matters for map serving, because a QuadKey or XYZ tile can be stored and searched as an object rather than as an opaque blob.

## Spatial index, object model and the geofence event path

The core is a spatial index with three search methods: Nearby, Within, and Intersects. Nearby returns objects intersecting a radius around a point. Within returns objects fully contained inside a specified bounding area. Intersects returns objects that intersect a bounding area. The distinction between Within and Intersects is the difference between a vehicle entirely inside a delivery zone and a route that merely clips its edge, and both are needed in practice.

Objects live in collections. The README example puts two trucks into a collection named fleet. Each object can carry fields, which are always double precision floating point values, and the README states there is no limit to the number of fields an object can have. Fields are what make queries useful: a WHERE option filters results by field value, so a nearby search can be narrowed to vehicles above a speed threshold rather than returning everything in range.

Geofencing is the part that changes the architecture of an application. Instead of polling the index on a timer, you register a hook and Tile38 pushes events when objects enter or leave a fence. The README lists two delivery mechanisms: webhooks, configured with the sethook command, and pub/sub channels. Webhooks push to an HTTP endpoint you control; pub/sub requires your consumer to be connected and subscribed. That choice determines your failure mode. A webhook consumer that is down will receive events when it returns, depending on your endpoint's buffering; a pub/sub subscriber that is disconnected misses whatever was published while it was away. The README does not document delivery guarantees for either path, so treat that as something to test rather than assume.

Persistence is in-memory with on-disk persistence. The Docker image runs tile38-server with -d /data and declares /data as a volume, which is where the on-disk state lands. The README does not document rollback, point-in-time recovery, or what happens to in-flight writes during a crash, so the durability story is the weakest documented part of the project.

## Installing Tile38 with Docker and running a first nearby query

The README gives pre-built release binaries for OSX, Linux, FreeBSD, and Windows, plus a Docker image and a Homebrew formula. Docker is the shortest path because it needs no Go toolchain. The README shows pulling the image and running it with port 9851 published.

```bash
docker pull tile38/tile38
docker run -p 9851:9851 tile38/tile38
```

The container starts tile38-server and listens on 9851. The Dockerfile in the repository adds the same command with a data directory: it runs tile38-server -d /data and declares /data as a volume.

```dockerfile
VOLUME /data

EXPOSE 9851
CMD ["tile38-server", "-d", "/data"]
```

If you build from source instead, Go must be installed, and the repository Makefile builds the server, the CLI, and the benchmark tool with a single target.

```bash
make
./tile38-server
```

With the server running, the CLI connects to localhost:9851. The README's own walkthrough sets two points in a collection called fleet and then searches around a coordinate.

```bash
./tile38-cli
> set fleet truck1 point 33.5123 -112.2693
> set fleet truck2 point 33.4626 -112.1695
> nearby fleet point 33.462 -112.268 6000
```

The nearby command searches six kilometers around the given point and, per the README comment on that example, returns one truck. scan fleet returns both. You should see one result for the nearby query and two for the scan, which confirms the index is filtering by distance rather than returning the whole collection.

Fields are set inline or updated later, and the README shows both forms. Setting a field at write time keeps the object and its attributes in one command; fset updates an existing object without rewriting its geometry.

```bash
> set fleet truck1 field speed 90 point 33.5123 -112.2693
> fset fleet truck1 speed 90
> fget fleet truck1 speed
```

## Where Tile38 is the wrong tool

The in-memory design is the constraint that decides most adoption questions. Every object in the index occupies RAM, and the README does not describe a tiered or disk-resident index. If your object count grows into the hundreds of millions with rich GeoJSON geometries, you are sizing a machine by memory rather than by disk, and the cost curve is different from a disk-backed spatial database. There is no documented sharding or partitioning scheme in the README, so horizontal scaling of the index itself is not something the project claims to solve.

The query surface is also deliberately narrow. There are spatial searches with field filters, and there are key-value operations like get, del, and drop. There is no join, no aggregation across collections, and no SQL. If your reporting layer needs to combine geofence history with billing records, that work happens outside Tile38, which means you are maintaining a second store and a synchronization path.

The geofence delivery model deserves scrutiny before you build on it. Hooks fire on enter and leave transitions, and the README does not document ordering guarantees, deduplication, or replay. A consumer that processes events idempotently will be safer than one that assumes exactly-once delivery. If your application cannot tolerate a missed or duplicated fence event, you need to verify the behaviour against your own failure scenarios rather than infer it from the feature list.

Finally, the README is explicitly a quick start document and points to tile38.com for detailed documentation. Anything not covered there, including operational runbooks, backup procedures, and replication failover, is outside what the repository itself describes.

## Tile38 compared with Redis and PostGIS

The most common comparison is with Redis, and the relationship is unusual: Tile38 speaks the Redis RESP protocol and can be queried with Redis clients, but it is not a Redis module and does not share Redis's data model. Redis with the geospatial commands stores members in a sorted set keyed by geohash and supports radius and bounding box searches over points. What it does not give you is a geofence event stream, mixed object types like GeoJSON polygons and XYZ tiles, or field-based filtering on search results in the same command. If you already run Redis and only need point-in-radius lookups, adding Tile38 means running a second service for capabilities you may not use. If you need fence transitions pushed to your application, Tile38 is doing work Redis would leave to you.

PostGIS is the other reference point. It is a spatial extension to PostgreSQL, so you get ACID transactions, joins against non-spatial tables, and a query planner, at the cost of disk-backed reads and a heavier operational footprint. The difference in approach is where the index lives: PostGIS keeps it on disk with a buffer cache, Tile38 keeps it in memory and persists to disk. That makes Tile38 faster for the workload it targets and less suitable for analytical queries over large datasets. A team that needs both often runs PostGIS as the system of record and Tile38 as the live query and notification layer, which means accepting a synchronization path between them.

The go.mod file lists client libraries for Kafka, MQTT, NATS, AMQP, Google Pub/Sub, and Azure Event Hubs. Those integrations are how Tile38 hooks into an existing event backbone instead of forcing a new one, which is a practical argument in its favour for teams already running a message broker.

## Releases, licence and the cost of upgrading

Tile38 is MIT licensed, which places few restrictions on commercial use, modification, or redistribution. The practical implication is that you can embed it in a product without a copyleft obligation. This is not legal advice; if the licence interacts with your distribution model in a way you are unsure about, that is a question for your own counsel.

The release cadence visible in the repository is steady rather than rapid: 1.36.5 in October 2025, 1.37.0 in January 2026, and 1.38.0 in June 2026, with the last push to the default branch on 2026-09-02. The project is not archived. Version numbers in the 1.x line suggest the command surface is stable, which lowers the cost of upgrading: a minor bump is unlikely to require rewriting queries. The CHANGELOG.md at the repository root is where the actual changes are listed, and reading it before an upgrade is cheaper than discovering a behaviour change in production.

Upgrade cost is dominated by the data directory rather than the binary. Because the server persists to disk under the -d flag, a version that changes the on-disk format would require a migration path. The README does not document one, and the repository does not describe a downgrade procedure. If you run in Docker, the practical approach is to pin an image tag rather than track latest, snapshot the /data volume before upgrading, and confirm the server starts and serves a known query before switching traffic. The go.mod file pins Go 1.25.0, so building from source requires a toolchain at least that recent; the Docker image avoids that requirement entirely.

## Conclusion

Adopt Tile38 if you need spatial queries and live geofence notifications in one process and can keep the working set in memory; skip it if your dataset will not fit in RAM or you need relational joins, since the README documents no such capability. Before committing, verify the persistence behaviour of the -d data directory under your own restart and crash scenarios, and confirm that the geofence delivery path you plan to use (webhook or pub/sub channel) survives a consumer outage the way your application requires.

## FAQ

### What is Tile38?

Tile38 is an open source, MIT licensed, in-memory geolocation data store, spatial index, and realtime geofencing server written in Go. It supports lat/lon points, bounding boxes, XYZ tiles, Geohashes, and GeoJSON, and exposes Nearby, Within, and Intersects searches.

### How do I install Tile38 with Docker?

The README shows pulling the image with docker pull tile38/tile38 and starting it with docker run -p 9851:9851 tile38/tile38. The Dockerfile runs tile38-server with -d /data and declares /data as a volume.

### Which commands does the Tile38 CLI support for basic operations?

The README walkthrough uses set to add objects, scan and nearby to search a collection, and get, del, and drop for key-value operations. Fields are handled with set ... field, fset, and fget.

### Does Tile38 persist data to disk?

The README describes it as an in-memory database that persists on disk, and the Docker image runs the server with -d /data. The README does not document rollback or point-in-time recovery.

### Is geofencing free in Tile38?

Tile38 is MIT licensed, and geofencing is a core feature of the server rather than a paid tier, delivered through webhooks or pub/sub channels. The project's own documentation does not describe a commercial edition.

## Sources

- [License: MIT](https://github.com/tidwall/tile38/blob/master/LICENSE)
- [Project website](https://tile38.com)
- [README](https://github.com/tidwall/tile38/blob/master/README.md)
- [Releases](https://github.com/tidwall/tile38/releases)
- [tidwall/tile38 on GitHub](https://github.com/tidwall/tile38)

---

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