MobilityDB
Overview
MobilityDB (mobilitydb) is a PostgreSQL extension that adds temporal and spatiotemporal types for moving objects, right inside the database engine. This SecureAnyCloud image pairs PostgreSQL 17 with PostGIS and MobilityDB on Ubuntu 24.04 on Azure. The mobilitydb extension is already created, and a small demo trips table of vehicle trajectories with a spatiotemporal index is already built, so you can run a real moving-object query within a minute of first boot.
Instead of storing a position as a single point, you store a whole trajectory, meaning how an object moved through space over time, in one temporal geometry point (tgeompoint) column. You then query that trajectory directly with SQL. You can ask where a vehicle was at a given moment, the path it followed over an interval, how far it travelled, its speed, and how close any two objects ever came.
MobilityDB adds these temporal types, plus hundreds of functions and operators, on top of PostGIS geometry. They are indexable, so spatial-window, time-slice and nearest-approach queries stay fast as the trajectory history grows.
What you get:
- Trajectories as first-class data. A
tgeompointcolumn holds an object's full movement over time, so one row describes an entire trip rather than a single fix. - Position at a time.
valueAtTimestamp(trip, ...)returns exactly where an object was at any instant, andatTime(trip, ...)returns the movement over an interval. - Spatiotemporal analytics in SQL. Use trajectory
length(),speed(), spatial-window filtering withatGeometry(), and nearest-approach distance with the|=|operator, all queried like ordinary columns. - PostgreSQL 17, running under systemd as
postgresql.service, installed from the official PostgreSQL PGDG repository - PostGIS 3.6 and MobilityDB 1.3.0 from the same PGDG repository, with
mobilitydbalready created in a demo database. It pulls in PostGIS automatically, and PostGIS is loaded viashared_preload_libraries, which is configured for you - A demo database
mobilitywith atripstable of three vehicle trajectories stored astgeompoint, and aUSING gistspatiotemporal index already built - A least-privilege application role
fleet_appwith DML on the demo schema, for your application to run spatiotemporal queries - Per-VM passwords for both roles and a per-VM TLS certificate, generated on first boot and written to a root-only credentials file
- A boot-time credential guard that fails closed if a published or example credential is in effect
- Unattended security upgrades left enabled, so the appliance keeps receiving patches
- 24/7 SecureAnyCloud support
No baked credential. Every role in the shipped image is password-less by construction, and TLS is off, so the database is not reachable off-box at all in the image itself.
On first boot, each VM mints a unique password for the postgres superuser and for the least-privilege application role fleet_app. It also generates a unique self-signed TLS server certificate, writes everything to the root-only file /root/mobilitydb-credentials.txt, and then enables TLS. Two VMs launched from this image never share a secret.
A credential guard runs on every boot. It refuses to leave the database serving if a published or example credential is ever actually in effect.
Prerequisites
You need:
- An active Azure subscription, an SSH public key, and a VNet with a subnet in the target region
- A subscription to this listing on Azure Marketplace
- A network security group (NSG) that allows TCP 22 (administration) and TCP 5432 (the PostgreSQL wire protocol) from your client network. In production, restrict
5432to your application subnet.
Recommended virtual machine size: Standard_B2s (2 vCPU, 4 GB RAM) for development, evaluation and light workloads. For larger trajectory histories and higher query rates, choose a memory-rich size such as Standard_E2s_v5 or larger.
Deploy the virtual machine
Create the VM from the image with the Azure CLI. Open only SSH and the PostgreSQL port, and only to your own network.
# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:mobilitydb:default:latest
az vm create \
--resource-group my-rg \
--name mobilitydb-1 \
--image sac1647283583153:mobilitydb:default:latest \
--size Standard_B2s \
--admin-username azureuser \
--generate-ssh-keys \
--public-ip-sku Standard
az vm open-port --resource-group my-rg --name mobilitydb-1 --port 5432 --priority 900Retrieve your per-VM credentials
On first boot, the VM mints its own passwords and TLS certificate. SSH in and read the root-only credentials file:
sudo cat /root/mobilitydb-credentials.txtThe file contains the postgres superuser password, the fleet_app application role password and ready-to-paste connection strings. The file is mode 0600 and owned by root, and the values in it exist only on this VM.
The postgres.host line is filled in from the VM's own network metadata. Azure's instance metadata service returns an empty value for Standard SKU public IPs, so the image falls back to the load-balancer metadata and then to the VM's private address. When you connect from outside Azure, use the VM's public IP address from the portal or from az vm list-ip-addresses.
Confirm the service is healthy
Check that PostgreSQL and the credential guard are active and that both extensions are loaded:
systemctl is-active postgresql.service mobilitydb-credguard.service
sudo -u postgres psql -tAc "SELECT version();"
sudo -u postgres psql -d mobility -tAc "SELECT extname || ' ' || extversion FROM pg_extension WHERE extname IN ('mobilitydb','postgis') ORDER BY extname;"
sudo -u postgres psql -d mobility -tAc "SELECT a.amname FROM pg_class c JOIN pg_am a ON a.oid = c.relam WHERE c.relname = 'trips_trip_gist';"The last query must return gist. This confirms that the demo trajectory index uses the GiST access method, so spatiotemporal predicates against it are index-assisted rather than sequential scans.
Query trajectories: position, length and time slices
This is the core of MobilityDB. The mobility database ships a trips table. Each row is a vehicle and its whole path over the interval 08:00 to 08:20, stored in a single tgeompoint (temporal geometry point) column. Because the movement is stored as data, you can query it directly.
To ask where a vehicle was at a specific instant, use valueAtTimestamp(). It interpolates the position along the trajectory:
sudo -u postgres psql -d mobility -c "SELECT vehicle, ST_AsText(valueAtTimestamp(trip, timestamptz '2000-01-01 08:10')) AS position_at_0810 FROM trips WHERE vehicle = 'bus-A';"To measure how far each vehicle travelled, use length(). It returns the spatial length of the whole trajectory:
sudo -u postgres psql -d mobility -c "SELECT vehicle, round(length(trip)::numeric, 3) AS trajectory_length FROM trips ORDER BY vehicle;"To restrict a trajectory to a time window and get the path it took, use atTime() to slice the movement. Then use trajectory() to project it to a geometry:
sudo -u postgres psql -d mobility -c "SELECT ST_AsText(trajectory(atTime(trip, tstzspan '[2000-01-01 08:05, 2000-01-01 08:15]'))) AS path_0805_to_0815 FROM trips WHERE vehicle = 'bus-A';"Spatial windows and nearest approach, over TLS
MobilityDB combines the time dimension with PostGIS space. Connect as the least-privilege application role over TLS, then ask which vehicles ever pass through a map window and how close any two vehicles ever came.
atGeometry() restricts a trajectory to the part that falls inside a geometry. An IS NOT NULL test then tells you which trajectories ever entered a spatial window:
PGPASSWORD='<APP_PASSWORD>' psql "host=127.0.0.1 port=5432 dbname=mobility user=fleet_app sslmode=require" -c "SELECT vehicle FROM trips WHERE atGeometry(trip, ST_MakeEnvelope(3,-1,5,1)) IS NOT NULL ORDER BY vehicle;"The |=| operator returns the nearest approach distance, the closest two moving objects ever came to each other, taking time into account. A distance of 0 means their paths crossed the same point at the same instant:
PGPASSWORD='<APP_PASSWORD>' psql "host=127.0.0.1 port=5432 dbname=mobility user=fleet_app sslmode=require" -c "SELECT a.vehicle AS v1, b.vehicle AS v2, round((a.trip |=| b.trip)::numeric, 3) AS nearest_approach FROM trips a JOIN trips b ON a.id < b.id ORDER BY nearest_approach;"To confirm that the connection uses TLS, run:
PGPASSWORD='<APP_PASSWORD>' psql "host=127.0.0.1 port=5432 dbname=mobility user=fleet_app sslmode=require" -tAc "SELECT ssl FROM pg_stat_ssl WHERE pid = pg_backend_pid();"Store your own trajectories
Adding your own moving objects is ordinary SQL. Create a table with a tgeompoint column and a GiST index. Then insert trajectories in MobilityDB's temporal literal form [Point(x y)@timestamp, ...].
Create the table and index as the postgres owner, because schema DDL is an owner operation. The least-privilege fleet_app role then reads and writes the rows:
sudo -u postgres psql -d mobility -c "CREATE TABLE IF NOT EXISTS my_trips (id int PRIMARY KEY, name text, trip tgeompoint);"
sudo -u postgres psql -d mobility -c "CREATE INDEX IF NOT EXISTS my_trips_gist ON my_trips USING gist (trip);"
sudo -u postgres psql -d mobility -c "GRANT SELECT, INSERT, UPDATE, DELETE ON my_trips TO fleet_app;" PGPASSWORD='<APP_PASSWORD>' psql "host=127.0.0.1 port=5432 dbname=mobility user=fleet_app sslmode=require" -c "INSERT INTO my_trips (id, name, trip) VALUES (1, 'van-1', tgeompoint '[Point(0 0)@2000-01-01 10:00, Point(5 5)@2000-01-01 10:30]') ON CONFLICT (id) DO NOTHING;"Now query your own trajectory, for example its length and where it was halfway through:
PGPASSWORD='<APP_PASSWORD>' psql "host=127.0.0.1 port=5432 dbname=mobility user=fleet_app sslmode=require" -c "SELECT name, round(length(trip)::numeric, 3) AS len, ST_AsText(valueAtTimestamp(trip, timestamptz '2000-01-01 10:15')) AS at_1015 FROM my_trips WHERE id = 1;"MobilityDB also provides temporal types for floats, integers, text and geographies (tfloat, tint, tgeogpoint, ...). Set-of-time types (tstzspan, tstzspanset) let you reason about when things happened. See the upstream reference for the full type and function set.
Security posture
- No baked credential. Every role ships with no password at all. Both role passwords are minted on first boot from a cryptographic random source and are unique per VM.
- No shared certificate. TLS is off in the image, and the certificate is generated per VM on first boot, so no two VMs share a server key.
- TLS enforced for remote clients.
pg_hba.confaccepts remote connections only throughhostsslwithscram-sha-256. There is notrustrule anywhere. On-box administration stays password-less through the local Unix socket. - A credential guard that fails closed. On every boot,
mobilitydb-credguard.serviceproves that the recorded per-VM credentials authenticate. It then tries a list of published and example values. If any of them succeeds, the guard stops PostgreSQL rather than serve a database with a known credential. - Least privilege for applications. The
fleet_approle has DML on the demo schema but is not a superuser. Point your application at it rather than atpostgres.
You can rotate a password at any time. Generate a fresh random secret and record it in the credentials file in the same step, so the credential guard keeps agreeing with reality:
NEWPW="$(openssl rand -base64 40 | tr -dc 'A-Za-z0-9' | cut -c1-28)" sudo -u postgres psql -c "ALTER ROLE fleet_app PASSWORD '${NEWPW}';"
sudo sed -i "s|^fleet_app.password=.*|fleet_app.password=${NEWPW}|" /root/mobilitydb-credentials.txt echo "fleet_app password rotated and recorded in the credentials file"Always update /root/mobilitydb-credentials.txt when you rotate. On every boot, the credential guard verifies that the recorded password authenticates, and it stops PostgreSQL if it does not. This check makes a stale or mismatched credential impossible to ignore.
Never set a password to a value published in documentation. The guard tries a list of such values and refuses to serve if one of them works.
Operations
Check the service status and the first-boot log:
systemctl status postgresql.service --no-pager sudo journalctl -u mobilitydb-firstboot.service --no-pager | tail -20Back up the demo database to a SQL file:
sudo -u postgres pg_dump -d mobility -f /tmp/mobility-backup.sql && ls -la /tmp/mobility-backup.sqlThe image includes third-party licence attribution for MobilityDB, PostGIS and PostgreSQL. Read it with:
cat /usr/share/doc/mobilitydb-sac/ATTRIBUTION.txtSupport
SecureAnyCloud images include 24/7 support. Raise an issue through the Azure Marketplace listing, or contact SecureAnyCloud support with the VM name, the region and the output of systemctl status postgresql.service.
MobilityDB is developed at the Université libre de Bruxelles and distributed under The PostgreSQL License. PostGIS is GPL-2.0+, and PostgreSQL is under The PostgreSQL License. Upstream documentation is at mobilitydb.com.
Deploy on Azure
Launch PostgreSQL 17 with PostGIS and MobilityDB on Ubuntu 24.04 on Azure with 24/7 support from SecureAnyCloud.
View this image
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com