MobilityDB 1.3.1 on AWS User Guide
Overview
This guide covers launching and verifying MobilityDB 1.3.1 on Ubuntu 24.04 from cloudimg on Amazon EC2. It is one version of the cloudimg Hardened PostgreSQL 18, PostGIS & Postgres Extension AMIs listing on AWS Marketplace; you pick it as the software version when you subscribe and launch. The other PostgreSQL builds and extensions in the listing are described on the listing's guide hub.
MobilityDB is a PostgreSQL and PostGIS extension for moving objects: vehicles, vessels, aircraft, people and sensors whose position changes over time. It adds temporal types such as tgeompoint (a point that moves) and tfloat (a number that changes), and hundreds of functions to ask questions such as "where was this bus at 08:10?", "how far did it travel?", "which vehicles passed through this area?" and "how close did these two trajectories come?".
The image installs PostgreSQL 17.11 and PostGIS 3.6.4 from the official PostgreSQL (PGDG) apt repository and compiles MobilityDB 1.3.1 from the upstream release tag. MobilityDB 1.3.1 fixes CVE-2026-102639, an out-of-bounds read in the binary (WKB) input of temporal values that let a database user crash a backend; the PGDG package for Ubuntu 24.04 is still 1.3.0, so cloudimg builds 1.3.1 from source and removes the compiler, headers and source tree before the image is captured. The PostgreSQL and PostGIS packages are not held, so apt upgrade keeps delivering PGDG minor (security) releases, and unattended security upgrades stay on for the operating system.
What is on the image:
- PostgreSQL 17.11 (
postgresql@17-main.service) with PostGIS preloaded, as MobilityDB requires - A
mobilitydatabase with themobilitydb1.3.1 andpostgis3.6.4 extensions and a smalltripstable of three example trajectories with a GiST index, so queries return data straight away - A non-superuser application role
fleet_appwith read and write access to the example table, and thepostgressuperuser; both passwords are generated on your instance's first boot - TLS with a certificate generated on your instance's first boot (TLS 1.2 minimum)
- A credential guard that runs on every boot and refuses to serve if a published or example password ever authenticates
Default user: ubuntu · Architecture: x86_64 · Root volume: 20 GiB gp3
Security posture
- No default password. The image ships with no password on any database role. First boot generates two different passwords, one for
postgresand one forfleet_app, and writes them to/root/mobilitydb-credentials.txt, readable by root only. Locally,postgresis also reachable as thepostgressystem user (peer authentication). - Nothing is served before first boot finishes. PostgreSQL will not start until first boot has created the certificate and the passwords. While it sets them, the server listens on its local socket only, with no TCP port.
- Per-instance TLS certificate. First boot creates this instance's own private key and self-signed certificate under
/etc/postgresql/17/main/and turns TLS on. No key ships in the image, so no two instances share one. - Localhost only. PostgreSQL listens on
127.0.0.1:5432, and the listing's security group opens SSH (22) only. Reach the database through an SSH tunnel, or open it to your VPC as described in Step 6. - Remote connections require TLS.
pg_hba.confaccepts non-loopback clients only overhostsslwithscram-sha-256; there is no plain-text remote rule and notrustrule. - No metadata access for the database. A database user can make PostgreSQL open outbound connections (for example
COPY ... FROM PROGRAMas a superuser), so every PostgreSQL unit and both first-boot services are blocked from reaching the instance metadata service (169.254.169.254 and fd00:ec2::254). Amazon Time Sync (169.254.169.123) still works. If you add backups to S3 from inside the database, give them explicit credentials; they cannot use the instance role. - IMDSv2 required. The AMI is registered with
ImdsSupport v2.0, so instances launched from it refuse token-less (IMDSv1) metadata requests. - Fails closed. If first boot cannot finish, PostgreSQL stays stopped and first boot tries again on the next boot.
- Per-instance identity. SSH host keys and the machine ID are generated fresh on every instance.
Prerequisites
- An AWS account and permission to launch EC2 instances and create security groups
- An EC2 key pair in the Region you launch in
- A VPC subnet, with a public IP on the instance if you connect over the internet
Recommended instance type: m5.large. Size up for large trajectory sets or heavy concurrent analytics.
Connecting to your instance
| Version | Operating system | SSH user |
|---|---|---|
| MobilityDB 1.3.1 on Ubuntu 24.04 | Ubuntu 24.04 LTS | ubuntu |
Step 1: Launch the instance
Option A: AWS Marketplace console
- Open the cloudimg Hardened PostgreSQL 18, PostGIS & Postgres Extension AMIs listing on AWS Marketplace and choose View purchase options, then Subscribe. One subscription covers every version in the listing.
- Once the subscription is active, choose Launch your software.
- Choose One-click launch from AWS Marketplace (or Launch from EC2 Console for the full launch wizard). In Version, choose MobilityDB 1.3.1 on Ubuntu 24.04, then choose your Region.
- Pick an instance type (
m5.largerecommended; the software charge is $0.04 per vCPU per hour) and your key pair. - Security group: the listing's default group allows SSH (22) only. Restrict it to your own IP address. Do not open 5432 to the internet.
- Choose your VPC and subnet, leave the metadata defaults (the image already requires IMDSv2), then choose Launch.
Option B: AWS CLI
Run this on your own workstation. Replace the AMI ID with the one shown for your Region on the Marketplace launch page, and the VPC, subnet, key pair and CIDR with your own:
SG=$(aws ec2 create-security-group --group-name mobilitydb \
--description "MobilityDB" --vpc-id vpc-0123456789abcdef0 \
--query GroupId --output text)
aws ec2 authorize-security-group-ingress --group-id "$SG" --protocol tcp --port 22 --cidr <your-mgmt-cidr>
aws ec2 run-instances \
--image-id ami-0123456789abcdef0 \
--instance-type m5.large \
--key-name my-key-pair \
--security-group-ids "$SG" \
--subnet-id subnet-0123456789abcdef0 \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=mobilitydb}]'
You do not need --metadata-options: the AMI already makes IMDSv2 required.
Step 2: Connect and read your credentials
Connect as ubuntu with the key pair you chose:
ssh -i ~/.ssh/my-key-pair.pem ubuntu@<public-ip>
First boot finishes within a minute of launch. Show the connection details (the two password lines are left out here; read them with sudo cat /root/mobilitydb-credentials.txt and keep them in your secrets manager):
sudo grep -v -e '^postgres.password=' -e '^fleet_app.password=' /root/mobilitydb-credentials.txt | head -16
# MobilityDB 1.3.1 on PostgreSQL 17.11 + PostGIS 3.6.4 (cloudimg AWS Marketplace image)
# Generated on THIS instance's first boot by mobilitydb-firstboot.service; these passwords
# exist nowhere else and are shown in plain text only here. Rotate a password (never on a
# command line) with: sudo -u postgres psql then \password fleet_app
# and update this file to match.
postgres.host=127.0.0.1
postgres.port=5432
postgres.role=postgres
postgres.database=mobility
postgres.sslmode=require
postgres.private_ip=172.31.83.13
# The application role: least-privilege login for your application (DML on the demo schema, not a superuser).
fleet_app.role=fleet_app
The commands below read the fleet_app password from that file at run time, so it never appears on screen. In your own sessions you can instead put it in ~/.pgpass (mode 0600) as 127.0.0.1:5432:mobility:fleet_app:<APP_PASSWORD>.
Step 3: Confirm PostgreSQL and MobilityDB are running
psql --version
systemctl is-active postgresql@17-main.service mobilitydb-firstboot.service mobilitydb-credguard.service
sudo ss -Hltn
psql (PostgreSQL) 17.11 (Ubuntu 17.11-1.pgdg24.04+2)
active
active
active
LISTEN 0 4096 127.0.0.53%lo:53 0.0.0.0:*
LISTEN 0 200 127.0.0.1:5432 0.0.0.0:*
LISTEN 0 4096 0.0.0.0:22 0.0.0.0:*
LISTEN 0 4096 127.0.0.54:53 0.0.0.0:*
LISTEN 0 4096 [::]:22 [::]:*
PostgreSQL listens on 127.0.0.1:5432 only; port 53 on 127.0.0.53/127.0.0.54 is the operating system's local DNS stub. Now connect as fleet_app over TLS, list the extension and show the versions it was built with:
export PGPASSWORD="$(sudo sed -n 's/^fleet_app.password=//p' /root/mobilitydb-credentials.txt)"
psql "host=127.0.0.1 dbname=mobility user=fleet_app sslmode=require" \
-c "\dx mobilitydb" \
-c "SELECT mobilitydb_full_version();" \
-c "SELECT ssl, version AS tls_version FROM pg_stat_ssl WHERE pid = pg_backend_pid();"
List of installed extensions
Name | Version | Schema | Description
------------+---------+--------+----------------------------------------------------------------------
mobilitydb | 1.3.1 | public | MobilityDB geospatial trajectory data management & analysis platform
(1 row)
mobilitydb_full_version
---------------------------------------------------------------------------------------------------------------------------------------------
MobilityDB 1.3.1, PostgreSQL 17.11 (Ubuntu 17.11-1.pgdg24.04+2), PostGIS 3.6.4, GEOS 3.14.1-CAPI-1.20.5, PROJ 9.8.1, JSON-C 0.17, GSL 2.7.1
(1 row)
ssl | tls_version
-----+-------------
t | TLSv1.3
(1 row)
Step 4: Query the example trajectories
The trips table holds three vehicles moving between 08:00 and 08:20 on 1 January 2000. bus-A goes from (0,0) to (2,2) to (4,0), bus-B goes north from (1,0) to (1,4), and tram-C runs far away along y = 10. List them, ask where each one was at 08:10, and how far each travelled:
export PGPASSWORD="$(sudo sed -n 's/^fleet_app.password=//p' /root/mobilitydb-credentials.txt)"
psql "host=127.0.0.1 dbname=mobility user=fleet_app sslmode=require" \
-c "SELECT id, vehicle, numInstants(trip) AS points, startTimestamp(trip) AS starts, endTimestamp(trip) AS ends FROM trips ORDER BY id;" \
-c "SELECT vehicle, ST_AsText(valueAtTimestamp(trip, timestamptz '2000-01-01 08:10')) AS position_at_0810 FROM trips ORDER BY id;" \
-c "SELECT vehicle, round(length(trip)::numeric, 3) AS distance FROM trips ORDER BY id;"
id | vehicle | points | starts | ends
----+---------+--------+------------------------+------------------------
1 | bus-A | 3 | 2000-01-01 08:00:00+00 | 2000-01-01 08:20:00+00
2 | bus-B | 2 | 2000-01-01 08:00:00+00 | 2000-01-01 08:20:00+00
3 | tram-C | 2 | 2000-01-01 08:00:00+00 | 2000-01-01 08:20:00+00
(3 rows)
vehicle | position_at_0810
---------+------------------
bus-A | POINT(2 2)
bus-B | POINT(1 2)
tram-C | POINT(12 10)
(3 rows)
vehicle | distance
---------+----------
bus-A | 5.657
bus-B | 4.000
tram-C | 4.000
(3 rows)
MobilityDB interpolates between the recorded points, so you can ask for a position at any moment, not only at the instants you stored. Now cut bus-A's journey to the 08:05 to 08:15 window, find how close the other vehicles came to bus-A, and find which vehicles ever entered the box from (3,-1) to (5,1):
export PGPASSWORD="$(sudo sed -n 's/^fleet_app.password=//p' /root/mobilitydb-credentials.txt)"
psql "host=127.0.0.1 dbname=mobility user=fleet_app sslmode=require" \
-c "SELECT ST_AsText(trajectory(atTime(trip, tstzspan '[2000-01-01 08:05, 2000-01-01 08:15]'))) AS bus_a_0805_0815 FROM trips WHERE vehicle = 'bus-A';" \
-c "SELECT b.vehicle, round((a.trip |=| b.trip)::numeric, 3) AS closest_approach FROM trips a, trips b WHERE a.vehicle = 'bus-A' AND b.vehicle <> 'bus-A' ORDER BY 2;" \
-c "SELECT vehicle FROM trips WHERE atGeometry(trip, ST_MakeEnvelope(3,-1,5,1)) IS NOT NULL;"
bus_a_0805_0815
-------------------------
LINESTRING(1 1,2 2,3 1)
(1 row)
vehicle | closest_approach
---------+------------------
bus-B | 0.000
tram-C | 12.806
(2 rows)
vehicle
---------
bus-A
(1 row)
|=| is MobilityDB's nearest-approach distance: bus-A and bus-B meet at (1,1) at 08:05, so their distance is 0, while tram-C never comes closer than 12.8 units.
Step 5: Add your own trajectory
The fleet_app role can write to the table. Insert a van that drives north along x = 3 for 30 minutes, read its top speed (units per hour), find every vehicle that ever entered a small area around (3,1), then delete the van again:
export PGPASSWORD="$(sudo sed -n 's/^fleet_app.password=//p' /root/mobilitydb-credentials.txt)"
psql "host=127.0.0.1 dbname=mobility user=fleet_app sslmode=require" \
-c "INSERT INTO trips (id, vehicle, trip) VALUES (4, 'van-D', tgeompoint '[Point(3 0)@2000-01-01 08:00, Point(3 3)@2000-01-01 08:30]');" \
-c "SELECT vehicle, round(maxValue(speed(trip))::numeric * 3600, 1) AS max_units_per_hour FROM trips WHERE vehicle = 'van-D';" \
-c "SELECT vehicle FROM trips WHERE eIntersects(trip, ST_MakeEnvelope(2.5,0.5,3.5,1.5)) ORDER BY vehicle;" \
-c "DELETE FROM trips WHERE id = 4;"
INSERT 0 1
vehicle | max_units_per_hour
---------+--------------------
van-D | 6.0
(1 row)
vehicle
---------
bus-A
van-D
(2 rows)
DELETE 1
For real data, store GPS fixes with a spatial reference system (for example tgeompoint 'SRID=4326;[Point(-0.1278 51.5074)@2026-10-20 08:00, ...]') and use tgeogpoint when you want distances in metres on the globe. The MobilityDB documentation lists every type and function.
Step 6: Connect from your application
PostgreSQL accepts connections on localhost only. Two ways to reach it:
- SSH tunnel (no change to the instance). On your workstation run
ssh -N -L 5432:127.0.0.1:5432 ubuntu@<public-ip>, then connect your client to127.0.0.1:5432asfleet_appwithsslmode=require. - Open it to your VPC. In
/etc/postgresql/17/main/conf.d/10-cloudimg-mobilitydb.confchangelisten_addresses = '127.0.0.1'tolisten_addresses = '*', restart PostgreSQL, then add an inbound rule for 5432 to the instance's security group from your application subnet or VPC CIDR only (never 0.0.0.0/0). Remote clients must use TLS (sslmode=require, orverify-fullwith this instance's certificate/etc/postgresql/17/main/cloudimg-server.crt) and the role's password:
sudo sed -i "s/^listen_addresses = '127.0.0.1'/listen_addresses = '*'/" /etc/postgresql/17/main/conf.d/10-cloudimg-mobilitydb.conf
sudo systemctl restart postgresql@17-main.service
A plain-text (non-TLS) connection from another host is refused by pg_hba.conf, and a wrong password is refused by scram-sha-256:
OUT="$(PGPASSWORD=wrong-password psql "host=127.0.0.1 dbname=mobility user=fleet_app sslmode=require" -c "SELECT 1" 2>&1 || true)"
echo "${OUT#*failed: }"
FATAL: password authentication failed for user "fleet_app"
Step 7: Check the security controls
The PostgreSQL unit carries the metadata block, fleet_app is not a superuser, PostgreSQL listens on loopback and uses this instance's own certificate:
systemctl show -p IPAddressDeny postgresql@17-main.service
sudo -u postgres psql -tAc "SELECT rolname, rolsuper FROM pg_roles WHERE rolname IN ('fleet_app', 'postgres') ORDER BY 1;"
sudo -u postgres psql -tAc "SHOW listen_addresses;"
sudo -u postgres psql -tAc "SHOW ssl_cert_file;"
IPAddressDeny=168.63.129.16/32 fd00:ec2::254/128 169.254.169.254/32
fleet_app|f
postgres|t
127.0.0.1
/etc/postgresql/17/main/cloudimg-server.crt
Maintenance
- Change a password. Run
sudo -u postgres psqland type\password fleet_app(or\password postgres) at the prompt; it asks for the new password twice and never puts it on a command line. Then update/root/mobilitydb-credentials.txt(the credential guard reads it on every boot) and your secrets manager. - Configuration. cloudimg's settings are in
/etc/postgresql/17/main/conf.d/10-cloudimg-mobilitydb.conf(listener, PostGIS preload,max_locks_per_transaction) and20-cloudimg-tls.conf(this instance's certificate). Apply changes withsudo systemctl restart postgresql@17-main.service. - Updates.
sudo apt update && sudo apt upgradeinstalls PostgreSQL 17 and PostGIS 3.6 minor releases from PGDG and Ubuntu security updates. MobilityDB 1.3.1 is compiled for PostgreSQL 17; when a newer MobilityDB release is available from cloudimg or PGDG, install it and runALTER EXTENSION mobilitydb UPDATE;in each database. - Backups. Take EBS snapshots or
pg_dump -Fc mobilitybackups regularly. - Logs.
/var/log/postgresql/postgresql-17-main.logandjournalctl -u postgresql@17-main.service; first boot and the credential guard log tojournalctl -u mobilitydb-firstboot.serviceandjournalctl -u mobilitydb-credguard.service. - Licences. MobilityDB is under The PostgreSQL License and includes PostGIS's liblwgeom (GPL-2.0-or-later); PostGIS is GPL-2.0-or-later.
/usr/share/doc/mobilitydb-cloudimg/ATTRIBUTION.txtnames the exact source release.
Support
cloudimg supports this image by email at support@cloudimg.co.uk. Include the instance ID, the version (MobilityDB 1.3.1 on Ubuntu 24.04) and what you were doing.
PostgreSQL is a trademark of the PostgreSQL Community Association of Canada. Ubuntu is a trademark of Canonical Ltd. MobilityDB and PostGIS are the names of their respective open-source projects. cloudimg is not affiliated with or endorsed by these organisations or projects.