Tile38 on Ubuntu 24.04 on Azure User Guide
Overview
Tile38 is an open source geospatial database and realtime geofencing server. It keeps points, polygons, bounding boxes and GeoJSON objects in memory, indexes them spatially, and answers proximity, containment and intersection queries in microseconds. It also watches objects in motion and pushes enter, exit, cross and inside events to endpoints you nominate.
The cloudimg image installs Tile38 1.38.0 as a hardened systemd service with append only persistence, and secures it with a password unique to your VM that is generated on first boot.
What is included:
- Tile38 1.38.0 server at
/usr/local/bin/tile38-serverand the matching client at/usr/local/bin/tile38-cli - One multiplexed port,
9851, serving the Redis RESP protocol, HTTP and WebSocket - A 48 character password generated on first boot and written into Tile38's own config before the server ever listens
- Append only persistence at
/var/lib/tile38/appendonly.aof tile38.servicerunning as the unprivilegedtile38user- 24/7 cloudimg support
Prerequisites
An active Azure subscription, an SSH key, and a VNet with a subnet. Standard_B2s is the recommended size for development and evaluation; scale up for larger datasets, because Tile38 holds its working set in memory.
Tile38 listens on 0.0.0.0:9851, but your network security group governs who can reach it. The image does not open 9851 for you. Add an inbound rule scoped to the application hosts that need it, and never open it to the whole internet.
Step 1: Deploy and connect
Deploy the image from the Azure Marketplace, then connect over SSH as azureuser:
ssh azureuser@<vm-ip>
Confirm the service came up. ExecStartPre is the fail closed gate that refuses to start Tile38 unless a per VM password is present, so seeing it exit 0/SUCCESS is your proof the server is authenticated:
sudo systemctl status tile38.service --no-pager
You should see Active: active (running), the ExecStartPre line reporting status=0/SUCCESS, and the server running with -d /var/lib/tile38 -h 0.0.0.0 -p 9851 --appendonly yes.

Step 2: Retrieve your password
Every VM built from this image generates its own password on first boot. Nothing is shared between VMs and nothing is baked into the image. Read it with:
sudo cat /stage/scripts/tile38-credentials.log
The file is mode 0600, owned by root, and records TILE38_PASSWORD, TILE38_HOST, TILE38_PORT and TILE38_URL.
Confirm the listener is bound:
sudo ss -tlnp | grep 9851

Step 3: Confirm the server is authenticated
Tile38 answers four commands without a password, all of them no ops that read no data and no configuration: PING, ECHO, healthz and OUTPUT. healthz is the liveness probe:
curl -sS http://127.0.0.1:9851/healthz
Every command that touches data or configuration is refused. Prove it before you trust the server, using a real command rather than PING:
curl -sS 'http://127.0.0.1:9851/KEYS+*'
curl -sS 'http://127.0.0.1:9851/SET+probe+p1+POINT+1+1'
Both return {"ok":false,"err":"authentication required"}. If either ever returns "ok":true, stop and contact support: the server is not secured.
Now authenticate. Over HTTP the password goes in the Authorization header as the raw value, with no Bearer prefix:
PASS=$(sudo grep '^TILE38_PASSWORD=' /stage/scripts/tile38-credentials.log | cut -d= -f2-)
curl -sS -H "Authorization: $PASS" http://127.0.0.1:9851/SERVER
That returns "ok":true along with "version":"1.38.0" and live statistics for objects, points, collections and heap size.

Step 4: Store and query geospatial data
Tile38 organises objects into collections. SET writes an object, NEARBY finds objects within a radius, WITHIN finds objects inside a bounding box or polygon, and SCAN reads a whole collection. Over HTTP the command and its arguments are the URL path, separated by +.
Write two points and query them back:
PASS=$(sudo grep '^TILE38_PASSWORD=' /stage/scripts/tile38-credentials.log | cut -d= -f2-)
curl -sS -H "Authorization: $PASS" 'http://127.0.0.1:9851/SET+fleet+truck1+POINT+51.5007+-0.1246'
curl -sS -H "Authorization: $PASS" 'http://127.0.0.1:9851/SET+fleet+truck2+POINT+51.5033+-0.1195'
curl -sS -H "Authorization: $PASS" 'http://127.0.0.1:9851/NEARBY+fleet+POINT+51.5007+-0.1246+1000'
NEARBY returns both trucks as GeoJSON, with a count of 2:
{"ok":true,"objects":[{"id":"truck1","object":{"type":"Point","coordinates":[-0.1246,51.5007]}},{"id":"truck2","object":{"type":"Point","coordinates":[-0.1195,51.5033]}}],"count":2,"cursor":0}
Note the coordinate order: Tile38 takes POINT <latitude> <longitude> on input and returns GeoJSON, which is [longitude, latitude].
The bundled client reads the password from the TILE38_PASSWORD environment variable and issues AUTH for you. There is no password flag:
PASS=$(sudo grep '^TILE38_PASSWORD=' /stage/scripts/tile38-credentials.log | cut -d= -f2-)
TILE38_PASSWORD="$PASS" tile38-cli -h 127.0.0.1 -p 9851 SCAN fleet
TILE38_PASSWORD="$PASS" tile38-cli -h 127.0.0.1 -p 9851 GET fleet truck1
Run tile38-cli with no trailing command for an interactive prompt.

Step 5: Realtime geofencing
A geofence is a standing query. Register one with SETHOOK and Tile38 pushes an event to your endpoint the moment an object enters, exits or crosses the fence, so your application never has to poll.
The example below registers a fence named warehouse that watches the fleet collection within 500 metres of a point, then lists and removes it. Replace the endpoint with a URL your own service listens on:
PASS=$(sudo grep '^TILE38_PASSWORD=' /stage/scripts/tile38-credentials.log | cut -d= -f2-)
curl -sS -H "Authorization: $PASS" 'http://127.0.0.1:9851/SETHOOK+warehouse+http://127.0.0.1:9999/hook+NEARBY+fleet+FENCE+POINT+51.5007+-0.1246+500'
curl -sS -H "Authorization: $PASS" 'http://127.0.0.1:9851/HOOKS+*'
curl -sS -H "Authorization: $PASS" 'http://127.0.0.1:9851/DELHOOK+warehouse'
Hook endpoints can be HTTP, gRPC, Redis, Kafka, NATS, RabbitMQ, MQTT or Amazon SQS. Use DETECT to narrow which transitions fire, for example DETECT enter,exit.
For a live feed instead of a webhook, open a WebSocket to the same port and issue a FENCE query on it.
Step 6: Components
| Component | Path |
|---|---|
| Server binary | /usr/local/bin/tile38-server |
| Client binary | /usr/local/bin/tile38-cli |
| Data directory | /var/lib/tile38/ |
| Config (holds the password) | /var/lib/tile38/config (mode 0600) |
| Append only file | /var/lib/tile38/appendonly.aof |
| Systemd unit | /etc/systemd/system/tile38.service |
| Auth gate | /usr/local/sbin/tile38-require-auth.sh |
| First boot script | /usr/local/sbin/tile38-firstboot.sh |
| Credentials | /stage/scripts/tile38-credentials.log (mode 0600) |
tile38-server --version
sudo ls -la /var/lib/tile38/

Step 7: Security
- Scope the network security group. Port 9851 carries the whole database. Allow it only from the application subnets that need it, and never from
0.0.0.0/0. SSH should be equally restricted. - Keep the password out of shell history. Read it into a variable from the credentials file, as every example above does, rather than typing it inline.
- Rotate the password when you need to by connecting with the current one and running
CONFIG SET requirepass <new-password>followed byCONFIG REWRITE, then update your clients. Record the new value in your own secret store. - Terminate TLS in front of Tile38. The server speaks plaintext, so put it behind a TLS terminating proxy or keep it on a private subnet reachable only over a VPN or peering.
- Remember which commands are unauthenticated.
PING,ECHO,healthzandOUTPUTanswer without a password. They expose only liveness, so they are safe for a load balancer probe, but they are not an authentication test. - Patch regularly. Unattended security upgrades are enabled in this image. Apply kernel updates and reboot on your own maintenance schedule.
Step 8: Support and licensing
Tile38 is MIT licensed and free to use. cloudimg provides commercial support for this image separately.
- Email: support@cloudimg.co.uk
- Support hours: 24/7, one hour average response for critical issues
Deploy on Azure
Launch Tile38 with 24/7 support from cloudimg. View on Marketplace.