Databases AWS

pgRouting 4.0.2 with PostGIS on AWS User Guide

| Product: pgRouting 4.0.2 with PostGIS on Ubuntu 24.04 on AWS

Overview

This guide covers launching and verifying pgRouting 4.0.2 with PostGIS 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.

pgRouting adds routing and network analysis to PostgreSQL and PostGIS. Your road, rail, pipe or cable network is an ordinary table of PostGIS LINESTRING edges with a cost per edge, and functions such as pgr_dijkstra, pgr_aStar, pgr_bellmanFord and pgr_drivingDistance answer "what is the shortest route from A to B?" or "what can I reach within 1 km?" in plain SQL. Results come back as rows you can join to your own data, or as PostGIS geometry you can hand straight to a web map.

The image installs PostgreSQL 18.6, PostGIS 3.6.4 and pgRouting 4.0.2, all unmodified official PostgreSQL (PGDG) packages. PostgreSQL and PostGIS come from the PGDG apt repository. pgRouting 4.0.2 is the current upstream release; PGDG's stable channel still carries 4.0.1, so the image installs PGDG's own 4.0.2 packages checked against pinned SHA-256 digests, and keeps only the stable channel enabled. Unattended upgrades are configured for the PGDG archive as well as Ubuntu, so later PostgreSQL, PostGIS and pgRouting releases keep arriving through apt.

What this image is not:

  • No road data ships. The image carries a small synthetic demonstration network (Step 4) and nothing else. To route on real streets, load your own network, for example an OpenStreetMap extract (see "Loading your own network").
  • Not a routing web service. There is no HTTP API, tile server or map UI. pgRouting is a SQL library inside the database; your application calls it.
  • One PostgreSQL instance. No replication or clustering is configured.

What is on the image:

  • PostgreSQL 18.6 (postgresql@18-main.service) with the PostGIS 3.6.4 and pgRouting 4.0.2 extensions
  • A routingdb database with postgis and pgrouting already enabled, owned by the application role routingapp, holding the demonstration tables routing_edges and routing_vertices
  • A non-superuser application role routingapp and the postgres superuser; 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: 12 GiB gp3

Security posture

  • No database ships in the image. The image contains no PostgreSQL cluster at all, so it carries no role, password, data or key from the build. First boot creates a fresh cluster, the routingdb database with the demonstration network and two different passwords, one for postgres and one for routingapp, and writes them to /root/pgrouting-credentials.txt, readable by root only. Locally, postgres is also reachable as the postgres system user (peer authentication).
  • Nothing is served before first boot finishes. PostgreSQL will not start until first boot has created the cluster, 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/18/main/ and turns TLS on, so no two instances share a key.
  • 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 8.
  • Remote connections require TLS. pg_hba.conf accepts non-loopback clients only over hostssl with scram-sha-256 and rejects plain-text remote connections; there is no trust rule.
  • No metadata access for the database. A database user can make PostgreSQL open outbound connections (for example COPY ... FROM PROGRAM as a superuser, or PostGIS raster network drivers if you enable them), 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.
  • No core dumps. The database and first-boot services may not write core files, so process memory holding a password never lands on disk.
  • 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. SSH allows key login only, root cannot log in over SSH, and root has no password.
  • What we changed in Ubuntu: the fwupd-refresh timer is masked (EC2 has no firmware that fwupd can update), the C header packages the base image carried are removed (no compiler is installed), and only the running kernel is kept.

Prerequisites

  1. An AWS account and permission to launch EC2 instances and create security groups
  2. An EC2 key pair in the Region you launch in
  3. 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 networks (a country-scale OpenStreetMap graph needs far more memory than the demonstration network) or many concurrent routing queries.

Connecting to your instance

Version Operating system SSH user
pgRouting 4.0.2 with PostGIS on Ubuntu 24.04 Ubuntu 24.04 LTS ubuntu

Step 1: Launch the instance

Option A: AWS Marketplace console

  1. 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.
  2. Once the subscription is active, choose Launch your software.
  3. Choose One-click launch from AWS Marketplace (or Launch from EC2 Console for the full launch wizard). In Version, choose pgRouting 4.0.2 with PostGIS on Ubuntu 24.04, then choose your Region.
  4. Pick an instance type (m5.large recommended; the software charge is $0.04 per vCPU per hour) and your key pair.
  5. 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.
  6. 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 pgrouting \
  --description "pgRouting" --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=pgrouting}]'

You do not need --metadata-options: the AMI already makes IMDSv2 required. To give the database more room, add a --block-device-mappings entry for /dev/sda1 with a bigger VolumeSize.

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/pgrouting-credentials.txt and keep them in your secrets manager):

sudo grep -v -e '^POSTGRES_SUPERUSER_PASSWORD=' -e '^ROUTINGAPP_PASSWORD=' /root/pgrouting-credentials.txt | head -12
# pgRouting 4.0.2 with PostGIS 3.6.4 on PostgreSQL 18.6 (cloudimg AWS Marketplace image)
# Generated on THIS instance's first boot by pgrouting-firstboot.service; these passwords exist
# nowhere else. Rotate a password (never on a command line) with:  sudo -u postgres psql
# then  \password routingapp  and update this file to match (pgrouting-credguard reads it at boot).


# Routing database 'routingdb' (owner routingapp; postgis 3.6.4 + pgrouting 4.0.2
# enabled; demonstration tables routing_edges + routing_vertices) on 127.0.0.1:5432. Connect locally with TLS:
#   psql "host=127.0.0.1 dbname=routingdb user=routingapp sslmode=require"
# Private address of this instance: 172.31.81.209
#
# NO ROAD DATA SHIPS: the demonstration network is a small synthetic lattice. Load your own network

The commands below read the routingapp 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:routingdb:routingapp:<ROUTINGAPP_PASSWORD>.

Step 3: Confirm PostgreSQL, PostGIS and pgRouting are running

psql --version
systemctl is-active postgresql@18-main.service pgrouting-firstboot.service pgrouting-credguard.service
sudo ss -Hltn
psql (PostgreSQL) 18.6 (Ubuntu 18.6-1.pgdg24.04+2)
active
active
active
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      200        127.0.0.1:5432 0.0.0.0:*
LISTEN 0      4096   127.0.0.53%lo: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 routingapp over TLS, list the extensions in routingdb and show the versions:

export PGPASSWORD="$(sudo sed -n 's/^ROUTINGAPP_PASSWORD=//p' /root/pgrouting-credentials.txt)"
psql "host=127.0.0.1 dbname=routingdb user=routingapp sslmode=require" \
  -c "\dx" \
  -c "SELECT pgr_version() AS pgrouting, postgis_lib_version() AS postgis, version() ~ '^PostgreSQL 18\.6' AS postgresql_18_6;" \
  -c "SELECT ssl, version AS tls_version FROM pg_stat_ssl WHERE pid = pg_backend_pid();"
                                          List of installed extensions
   Name    | Version | Default version |   Schema   |                        Description
-----------+---------+-----------------+------------+------------------------------------------------------------
 pgrouting | 4.0.2   | 4.0.2           | public     | pgRouting Extension
 plpgsql   | 1.0     | 1.0             | pg_catalog | PL/pgSQL procedural language
 postgis   | 3.6.4   | 3.6.4           | public     | PostGIS geometry and geography spatial types and functions
(3 rows)

 pgrouting | postgis | postgresql_18_6
-----------+---------+-----------------
 4.0.2     | 3.6.4   | t
(1 row)

 ssl | tls_version
-----+-------------
 t   | TLSv1.3
(1 row)

Step 4: Explore the demonstration network

First boot loads a small synthetic network into routingdb so you can try routing straight away: a 6 x 6 grid of junctions in south-west London (60 streets) plus a separate three-junction "island" about 40 km away that is deliberately not connected to it. Each street is a PostGIS LINESTRING in EPSG:4326 whose cost is its real length in metres, and routing_vertices holds the junctions, built from the street geometry with pgr_extractVertices. The SQL that builds it is /usr/local/share/cloudimg/pgrouting-demo-network.sql.

export PGPASSWORD="$(sudo sed -n 's/^ROUTINGAPP_PASSWORD=//p' /root/pgrouting-credentials.txt)"
psql "host=127.0.0.1 dbname=routingdb user=routingapp sslmode=require" -v ON_ERROR_STOP=1 <<'SQL'
SELECT kind, count(*) AS streets, round(sum(cost)::numeric) AS total_metres FROM routing_edges GROUP BY kind ORDER BY kind;
SELECT id, name, source, target, round(cost::numeric, 1) AS metres, ST_AsText(geom) AS wkt FROM routing_edges ORDER BY id LIMIT 3;
SELECT count(*) AS junctions FROM routing_vertices;
SELECT component, count(*) AS junctions
FROM pgr_connectedComponents('SELECT id, source, target, cost, reverse_cost FROM routing_edges')
GROUP BY component ORDER BY junctions DESC;
SQL
  kind   | streets | total_metres
---------+---------+--------------
 island  |       2 |         1380
 lattice |      60 |        54221
(2 rows)

 id |   name   | source | target | metres |                wkt
----+----------+--------+--------+--------+------------------------------------
  1 | ew-c0-r0 |      4 |     10 |  695.2 | LINESTRING(-0.2 51.45,-0.19 51.45)
  2 | ew-c0-r1 |      5 |     11 |  695.0 | LINESTRING(-0.2 51.46,-0.19 51.46)
  3 | ew-c0-r2 |      6 |     12 |  694.9 | LINESTRING(-0.2 51.47,-0.19 51.47)
(3 rows)

 junctions
-----------
        39
(1 row)

 component | junctions
-----------+-----------
         4 |        36
         1 |         3
(2 rows)

pgr_connectedComponents confirms two separate pieces: the 36-junction grid and the 3-junction island.

Step 5: Find the shortest route

Pick the junctions nearest two points (here the south-west and north-east corners of the grid, found with the PostGIS <-> distance operator) and ask pgr_dijkstra for the shortest route between them. Then check that two other algorithms, pgr_aStar (which uses the street coordinates as a heuristic) and pgr_bellmanFord, find a route of the same length:

export PGPASSWORD="$(sudo sed -n 's/^ROUTINGAPP_PASSWORD=//p' /root/pgrouting-credentials.txt)"
psql "host=127.0.0.1 dbname=routingdb user=routingapp sslmode=require" -v ON_ERROR_STOP=1 <<'SQL'
-- The junctions nearest two points: the south-west and north-east corners of the lattice
CREATE TEMP VIEW ends AS
SELECT (SELECT id FROM routing_vertices ORDER BY geom <-> ST_SetSRID(ST_MakePoint(-0.20, 51.45), 4326) LIMIT 1) AS a,
       (SELECT id FROM routing_vertices ORDER BY geom <-> ST_SetSRID(ST_MakePoint(-0.15, 51.50), 4326) LIMIT 1) AS b;

-- Shortest path, street by street, with the running distance in metres
SELECT d.seq, d.node, e.name AS street, round(d.agg_cost::numeric, 1) AS metres_so_far
FROM ends, pgr_dijkstra('SELECT id, source, target, cost, reverse_cost FROM routing_edges', ends.a, ends.b) d
LEFT JOIN routing_edges e ON e.id = d.edge
ORDER BY d.seq;

-- Three independent algorithms must agree on the length of the best route
SELECT 'pgr_dijkstra' AS algorithm, round(max(agg_cost)::numeric, 1) AS metres
  FROM ends, pgr_dijkstra('SELECT id, source, target, cost, reverse_cost FROM routing_edges', ends.a, ends.b)
UNION ALL
SELECT 'pgr_aStar', round(max(agg_cost)::numeric, 1)
  FROM ends, pgr_aStar('SELECT id, source, target, cost, reverse_cost, ST_X(ST_StartPoint(geom)) AS x1, ST_Y(ST_StartPoint(geom)) AS y1, ST_X(ST_EndPoint(geom)) AS x2, ST_Y(ST_EndPoint(geom)) AS y2 FROM routing_edges', ends.a, ends.b)
UNION ALL
SELECT 'pgr_bellmanFord', round(max(agg_cost)::numeric, 1)
  FROM ends, pgr_bellmanFord('SELECT id, source, target, cost, reverse_cost FROM routing_edges', ends.a, ends.b);
SQL
CREATE VIEW
 seq | node |  street  | metres_so_far
-----+------+----------+---------------
   1 |    4 | ns-c0-r0 |           0.0
   2 |    5 | ns-c0-r1 |        1112.6
   3 |    6 | ns-c0-r2 |        2225.1
   4 |    7 | ns-c0-r3 |        3337.7
   5 |    8 | ns-c0-r4 |        4450.3
   6 |    9 | ew-c0-r5 |        5562.9
   7 |   15 | ew-c1-r5 |        6257.3
   8 |   21 | ew-c2-r5 |        6951.7
   9 |   27 | ew-c3-r5 |        7646.1
  10 |   33 | ew-c4-r5 |        8340.5
  11 |   39 |          |        9034.9
(11 rows)

    algorithm    | metres
-----------------+--------
 pgr_dijkstra    | 9034.9
 pgr_aStar       | 9034.9
 pgr_bellmanFord | 9034.9
(3 rows)

On a grid many routes have the same length; any of them is a correct answer. The route is also available as one PostGIS geometry, ready for a web map (GeoJSON) or for further spatial queries:

export PGPASSWORD="$(sudo sed -n 's/^ROUTINGAPP_PASSWORD=//p' /root/pgrouting-credentials.txt)"
psql "host=127.0.0.1 dbname=routingdb user=routingapp sslmode=require" -v ON_ERROR_STOP=1 <<'SQL'
-- The route as ONE geometry, ready for a web map (GeoJSON) or another PostGIS query
WITH ends AS (
  SELECT (SELECT id FROM routing_vertices ORDER BY geom <-> ST_SetSRID(ST_MakePoint(-0.20, 51.45), 4326) LIMIT 1) AS a,
         (SELECT id FROM routing_vertices ORDER BY geom <-> ST_SetSRID(ST_MakePoint(-0.15, 51.50), 4326) LIMIT 1) AS b),
route AS (
  SELECT ST_LineMerge(ST_Union(e.geom)) AS geom
  FROM ends, pgr_dijkstra('SELECT id, source, target, cost, reverse_cost FROM routing_edges', ends.a, ends.b) d
  JOIN routing_edges e ON e.id = d.edge)
SELECT ST_GeometryType(geom) AS type, ST_NumPoints(geom) AS points, round(ST_Length(geom::geography)::numeric) AS metres,
       left(ST_AsGeoJSON(geom, 4), 70) || '...' AS geojson
FROM route;
SQL
     type      | points | metres |                                  geojson
---------------+--------+--------+---------------------------------------------------------------------------
 ST_LineString |     11 |   9035 | {"type":"LineString","coordinates":[[-0.2,51.45],[-0.2,51.46],[-0.2,51...
(1 row)

Step 6: Road closures, service areas and unreachable places

Three common questions on the same network:

  • A road closure. Inside a transaction, give one street a negative cost (pgRouting skips edges with a negative cost), compare the route before and after, then roll the change back so the table is untouched.
  • A service area. pgr_drivingDistance returns every junction reachable within a distance (here 1,000 m of the centre junction).
  • No route. Routing to the island returns an empty result, not an error and not an invented route.
export PGPASSWORD="$(sudo sed -n 's/^ROUTINGAPP_PASSWORD=//p' /root/pgrouting-credentials.txt)"
psql "host=127.0.0.1 dbname=routingdb user=routingapp sslmode=require" -v ON_ERROR_STOP=1 <<'SQL'
\set edges 'SELECT id, source, target, cost, reverse_cost FROM routing_edges'
CREATE TEMP VIEW pts AS
SELECT (SELECT id FROM routing_vertices ORDER BY geom <-> ST_SetSRID(ST_MakePoint(-0.20, 51.45), 4326) LIMIT 1) AS sw,
       (SELECT id FROM routing_vertices ORDER BY geom <-> ST_SetSRID(ST_MakePoint(-0.15, 51.45), 4326) LIMIT 1) AS se,
       (SELECT id FROM routing_vertices ORDER BY geom <-> ST_SetSRID(ST_MakePoint(-0.175, 51.475), 4326) LIMIT 1) AS mid,
       (SELECT id FROM routing_vertices ORDER BY geom <-> ST_SetSRID(ST_MakePoint(-0.50, 51.80), 4326) LIMIT 1) AS island;

-- Close a street (a road closure) inside a transaction, compare the routes, then roll the closure back
BEGIN;
SELECT 'open' AS street_ew_c2_r0, round(max(agg_cost)::numeric, 1) AS metres FROM pts, pgr_dijkstra(:'edges', pts.sw, pts.se);
UPDATE routing_edges SET cost = -1, reverse_cost = -1 WHERE name = 'ew-c2-r0';
SELECT 'closed' AS street_ew_c2_r0, round(max(agg_cost)::numeric, 1) AS metres FROM pts, pgr_dijkstra(:'edges', pts.sw, pts.se);
ROLLBACK;

-- Everything reachable within 1,000 m of the centre junction (a service area)
SELECT count(*) AS junctions_within_1000m FROM pts, pgr_drivingDistance(:'edges', pts.mid, 1000);

-- The island is not connected to the lattice: no route is an empty result, not an error
SELECT count(*) AS route_rows_to_island FROM pts, pgr_dijkstra(:'edges', pts.sw, pts.island);
SQL
CREATE VIEW
BEGIN
 street_ew_c2_r0 | metres
-----------------+--------
 open            | 3475.8
(1 row)

UPDATE 1
 street_ew_c2_r0 | metres
-----------------+--------
 closed          | 5700.2
(1 row)

ROLLBACK
 junctions_within_1000m
------------------------
                      3
(1 row)

 route_rows_to_island
----------------------
                    0
(1 row)

With the street closed the route detours one block north and grows from 3,475.8 m to 5,700.2 m. The east-west streets are about 695 m and the north-south streets about 1,113 m, so only the centre junction and its two east-west neighbours are within 1,000 m.

Loading your own network

Replace the demonstration tables with your own network when you are ready. pgRouting needs an edge table with id, source, target, cost (and optionally reverse_cost) columns; with PostGIS geometry you can build source/target exactly as the demonstration does, with pgr_extractVertices. Common sources:

  • OpenStreetMap. Load an extract (for example from Geofabrik) with osm2pgrouting or osm2pgsql on your workstation or a separate loader instance, through an SSH tunnel to this database. OpenStreetMap data is licensed under the ODbL; its licence and currency are yours to manage.
  • Your own GIS data. Load a shapefile or GeoPackage with ogr2ogr (GDAL) or QGIS over the same tunnel.
ogr2ogr -f PostgreSQL "PG:host=127.0.0.1 dbname=routingdb user=routingapp sslmode=require" roads.gpkg -nln roads -lco GEOMETRY_NAME=geom

To drop the demonstration network: DROP TABLE routing_edges, routing_vertices; as routingapp.

Step 7: Check the security controls

The PostgreSQL unit carries the metadata block, routingapp is not a superuser, PostgreSQL listens on loopback and uses this instance's own certificate, and the fwupd refresh timer is masked:

systemctl show -p IPAddressDeny postgresql@18-main.service
sudo -u postgres psql -tAc "SELECT rolname, rolsuper FROM pg_roles WHERE rolname IN ('routingapp', 'postgres') ORDER BY 1;"
sudo -u postgres psql -tAc "SHOW listen_addresses;"
sudo -u postgres psql -tAc "SHOW ssl_cert_file;"
systemctl is-enabled fwupd-refresh.timer 2>/dev/null || true
IPAddressDeny=169.254.169.254/32 fd00:ec2::254/128 168.63.129.16/32
postgres|t
routingapp|f
127.0.0.1
/etc/postgresql/18/main/cloudimg-server.crt
masked

Step 8: 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 to 127.0.0.1:5432 as routingapp with sslmode=require.
  • Open it to your VPC. In /etc/postgresql/18/main/conf.d/10-cloudimg.conf change listen_addresses = '127.0.0.1' to listen_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, or verify-full with this instance's certificate /etc/postgresql/18/main/cloudimg-server.crt) and the role's password:
sudo sed -i "s/^listen_addresses = '127.0.0.1'/listen_addresses = '*'/" /etc/postgresql/18/main/conf.d/10-cloudimg.conf
sudo systemctl restart postgresql@18-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=routingdb user=routingapp sslmode=require" -c "SELECT 1" 2>&1 || true)"
echo "${OUT#*failed: }"
FATAL:  password authentication failed for user "routingapp"

Maintenance

  • Change a password. Run sudo -u postgres psql and type \password routingapp (or \password postgres) at the prompt; it asks for the new password twice and never puts it on a command line. Then update /root/pgrouting-credentials.txt (the credential guard reads it on every boot) and your secrets manager.
  • Configuration. cloudimg's settings are in /etc/postgresql/18/main/conf.d/10-cloudimg.conf (listener, port, password hashing) and 20-cloudimg-tls.conf (this instance's certificate). Apply changes with sudo systemctl restart postgresql@18-main.service.
  • Updates. sudo apt update && sudo apt upgrade installs PostgreSQL 18, PostGIS 3.6 and pgRouting releases from PGDG and Ubuntu updates; unattended upgrades also apply them automatically. After a PostGIS or pgRouting upgrade, run ALTER EXTENSION postgis UPDATE; and ALTER EXTENSION pgrouting UPDATE; in each database.
  • PostGIS raster. postgis_raster is installed but not enabled. It is the only part of the image that uses the GDAL library; if you enable it (CREATE EXTENSION postgis_raster; as postgres), note that Ubuntu publishes some GDAL security fixes for Ubuntu 24.04 only through Ubuntu Pro (ESM Apps), so attach Ubuntu Pro if you rely on raster import of untrusted files.
  • Backups. Take EBS snapshots of the root volume or pg_dump -Fc routingdb backups regularly.
  • Logs. /var/log/postgresql/postgresql-18-main.log and journalctl -u postgresql@18-main.service; first boot and the credential guard log to journalctl -u pgrouting-firstboot.service and journalctl -u pgrouting-credguard.service.
  • Licences. pgRouting and PostGIS are GPL-2.0-or-later and PostgreSQL is under The PostgreSQL License. All are unmodified PGDG packages; /usr/share/doc/cloudimg/GPL-2-WRITTEN-OFFER.txt explains how to get the pgRouting and PostGIS source (apt-get source, or ask support@cloudimg.co.uk), and /usr/share/doc/cloudimg/LICENCE-AUDIT.txt records the audited licence files.

Support

cloudimg supports this image by email at support@cloudimg.co.uk. Include the instance ID, the version (pgRouting 4.0.2 with PostGIS 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. OpenStreetMap is a trademark of the OpenStreetMap Foundation. PostGIS and pgRouting are the names of their open-source projects. cloudimg is not affiliated with or endorsed by these organisations or projects.