Developer Tools AWS

PostGraphile 5.2.1 on AWS User Guide

| Product: PostGraphile 5.2.1 on Ubuntu 24.04 on AWS

Overview

This guide covers launching and using the PostGraphile 5.2.1 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg developer and API tools listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.

PostGraphile reads a PostgreSQL schema and serves a complete GraphQL API for it: tables, views, relations and functions become queries and mutations, with no resolvers to write. The Ruru IDE (PostGraphile's edition of GraphiQL) lets you explore and run that API in the browser. This image runs PostGraphile 5.2.1 on Node.js 24 LTS against PostgreSQL 16 on the same instance, with a small demo schema (authors, posts, a view and a search function) so that you have something to query straight away.

The cloudimg image runs PostGraphile as a private service:

  • HTTPS from the first boot. nginx serves PostGraphile on port 443 with a certificate generated on your instance's first boot. Port 80 only redirects to HTTPS, and a request for any name other than your instance's own gets no answer.
  • A password on everything. The Ruru IDE, the /graphql endpoint and the IDE's files all require the user admin and a password generated on your instance's first boot. There is no default password. The password is stored as a bcrypt hash; the plain text is only in a root-only file (Step 4).
  • Cross-site requests are refused. Because a browser re-sends a saved password to any site that asks, nginx refuses any request that changes data unless it is JSON, and refuses any request from a web page on another site (Step 8). A page you visit elsewhere cannot run a mutation with your password.
  • A least-privilege database role with no password. PostGraphile connects to PostgreSQL over its local socket as the role postgraphile_public, which can read and write the demo tables and nothing else: it is not a superuser, cannot create anything, cannot read server files or run programs, and has no password because PostgreSQL identifies it by its Linux user (Step 10). The database itself is created on your instance's first boot: the image ships an empty one.
  • Production settings, checked on every start. PostGraphile listens on loopback only, with watch mode, SQL EXPLAIN output, GraphQL over GET and websockets off, errors masked to an id, a 100 kB request limit, a query depth limit and time limits. A configuration check refuses to start PostGraphile if any of these is turned off by mistake (Step 11).
  • No outbound connections. PostGraphile and PostgreSQL cannot open a network connection to anything, the EC2 instance metadata service included.
  • Rate limits that cannot be dodged. Requests are limited per client address, and nginx always replaces the X-Forwarded-For header with the address it accepted the connection from, so a client cannot pretend to be someone else to escape the limit.
  • Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.

What is on the image: PostGraphile 5.2.1 (installed from npm with a pinned lockfile, every package checked against its published integrity hash), Node.js 24.22.2 LTS (the official nodejs.org build, checked against its published SHA-256), PostgreSQL 16, nginx, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent. npm itself is not on the image. The build record and the MIT licences of PostGraphile and Node.js are in /usr/share/doc/cloudimg-postgraphile/.

Default user: ubuntu · Architecture: x86_64 · Recommended instance type: m5.large

Prerequisites

  • An AWS account subscribed to the listing, an EC2 key pair and a VPC subnet.
  • A security group that allows TCP 443 (PostGraphile and the Ruru IDE), TCP 80 (redirects to HTTPS) and TCP 22 (SSH). Open 443 to the networks your users and applications connect from; keep 22 open to your own networks only.

Step 1: Launch the instance

In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick PostGraphile 5.2.1 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large (a t3.medium is enough to try it out), your key pair, your subnet and the security group above.

With the AWS CLI (replace the values in angle brackets):

aws ec2 run-instances --region <region> --image-id <ami-id-from-the-listing> \
  --instance-type m5.large --key-name <your-key-pair> \
  --security-group-ids <your-security-group> --subnet-id <your-subnet> \
  --metadata-options HttpTokens=required,HttpEndpoint=enabled \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=postgraphile}]'

An Elastic IP keeps the server's address, and so its name and certificate, the same across stops and starts.

Step 2: Connect to your instance

Version SSH user
PostGraphile 5.2.1 on Ubuntu 24.04 ubuntu
ssh -i <your-key.pem> ubuntu@<public-ip>

Step 3: Check the services

First boot takes under a minute: it creates the PostgreSQL database and the demo schema, sets the server's name, generates the certificate and the admin password, and only then starts PostGraphile and nginx. Check that everything is running:

systemctl is-active postgraphile-firstboot postgraphile-egress-guard postgresql@16-main postgraphile nginx \
  && sudo test -f /var/lib/cloudimg/postgraphile-firstboot.done && echo FIRSTBOOT_DONE
active
active
active
active
active
FIRSTBOOT_DONE

Only SSH and nginx listen on the network. PostGraphile (a Node.js process, which names its main thread MainThread) listens on the loopback address 127.0.0.1:5000 behind nginx, and PostgreSQL has no network port at all:

sudo ss -lntp | awk 'NR>1 && $4 !~ /^127\.0\.0\.5[34]/ {print $4, $6}' | sed -E 's/,pid=[0-9]+,fd=[0-9]+//g' | sort -u | tee /dev/stderr \
  | awk '$1 !~ /^127\./ {sub(/.*:/, "", $1); print $1}' | sort -un | xargs | grep -x '22 80 443' >/dev/null && echo "network listeners: 22 80 443"
0.0.0.0:22 users:(("sshd"),("systemd"))
0.0.0.0:443 users:(("nginx"),("nginx"),("nginx"))
0.0.0.0:80 users:(("nginx"),("nginx"),("nginx"))
127.0.0.1:5000 users:(("MainThread"))
[::]:22 users:(("sshd"),("systemd"))
[::]:443 users:(("nginx"),("nginx"),("nginx"))
[::]:80 users:(("nginx"),("nginx"),("nginx"))
network listeners: 22 80 443

Step 4: Get your address and password

Run sudo cat /var/lib/cloudimg/postgraphile-credentials.txt to see your addresses and the admin password. The file is readable by root only and holds these settings, all generated for this instance:

sudo sed -n 's/^\(POSTGRAPHILE_[A-Z0-9_]*\)=.*/\1/p' /var/lib/cloudimg/postgraphile-credentials.txt | tee /dev/stderr \
  | grep -x POSTGRAPHILE_PASSWORD >/dev/null && echo "credentials present"
POSTGRAPHILE_URL
POSTGRAPHILE_GRAPHQL_URL
POSTGRAPHILE_HOST
POSTGRAPHILE_USERNAME
POSTGRAPHILE_PASSWORD
POSTGRAPHILE_TLS_CERT
POSTGRAPHILE_TLS_SHA256_FINGERPRINT
POSTGRAPHILE_DATABASE
credentials present

POSTGRAPHILE_URL is https://<your instance's public DNS name>/, where the Ruru IDE opens, and POSTGRAPHILE_GRAPHQL_URL is the GraphQL endpoint your applications call. The settings that are not secret can be shown on their own:

sudo grep -E '^POSTGRAPHILE_(URL|GRAPHQL_URL|USERNAME|TLS_SHA256_FINGERPRINT)=' /var/lib/cloudimg/postgraphile-credentials.txt \
  | tee /dev/stderr | grep -x 'POSTGRAPHILE_USERNAME=admin' >/dev/null
POSTGRAPHILE_URL=https://ec2-203-0-113-10.compute-1.amazonaws.com/
POSTGRAPHILE_GRAPHQL_URL=https://ec2-203-0-113-10.compute-1.amazonaws.com/graphql
POSTGRAPHILE_USERNAME=admin
POSTGRAPHILE_TLS_SHA256_FINGERPRINT=3A:5F:...:C2

The certificate is self-signed and generated on your instance, so your browser warns about it until you trust it (Step 5). For a certificate everyone trusts already, use your own domain (Step 12).

Step 5: Open the Ruru IDE

Copy the certificate to your computer and check that it is the one first boot generated: the SHA-256 of its DER form must match POSTGRAPHILE_TLS_SHA256_FINGERPRINT (ignoring the colons).

scp -i <your-key.pem> ubuntu@<public-ip>:/etc/ssl/postgraphile/postgraphile.crt postgraphile.crt
openssl x509 -in postgraphile.crt -outform der | shasum -a 256

Add postgraphile.crt to your operating system's or browser's trusted certificates (on macOS, open it in Keychain Access and set it to Always Trust), then browse to POSTGRAPHILE_URL. The browser asks for a user name and password: enter admin and the POSTGRAPHILE_PASSWORD from Step 4. Ruru opens with the schema already loaded. Type a query on the left and press the pink run button (or Ctrl-Enter):

The Ruru IDE served by the cloudimg image, listing the demo authors and their posts

The Docs button (top of the left-hand bar) shows every query and mutation PostGraphile generated from the schema:

Ruru's documentation explorer showing the mutations PostGraphile generated for the demo tables

Step 6: Query the API

Applications send GraphQL as a JSON POST to /graphql with the same user name and password. On the instance, this asks for the demo authors and their published posts. The password is read from the credentials file into a curl configuration, so it never appears on a command line:

C=/var/lib/cloudimg/postgraphile-credentials.txt
H="$(sudo sed -n 's/^POSTGRAPHILE_HOST=//p' "$C")"
curl -s --cacert /etc/ssl/postgraphile/postgraphile.crt \
  -K <(sudo sed -n 's/^POSTGRAPHILE_PASSWORD=\(.*\)/user = "admin:\1"/p' "$C") \
  -H 'Content-Type: application/json' \
  --data-binary '{"query":"{ allAuthors { nodes { name } } searchPosts(search: \"compiler\") { nodes { title authorByAuthorId { name } } } }"}' \
  "https://$H/graphql" | python3 -m json.tool | tee /dev/stderr | grep '"name": "Ada Lovelace"' >/dev/null
{
    "data": {
        "allAuthors": {
            "nodes": [
                {
                    "name": "Ada Lovelace"
                },
                {
                    "name": "Alan Turing"
                },
                {
                    "name": "Grace Hopper"
                }
            ]
        },
        "searchPosts": {
            "nodes": [
                {
                    "title": "The First Compiler",
                    "authorByAuthorId": {
                        "name": "Grace Hopper"
                    }
                }
            ]
        }
    }
}

searchPosts is the SQL function app_public.search_posts, which PostGraphile turned into a query field by itself. From your own computer, the same request is (curl -u admin asks for the password instead of taking it on the command line):

curl --cacert postgraphile.crt -u admin -H 'Content-Type: application/json' \
  --data-binary '{"query":"{ allAuthors { nodes { name } } }"}' https://<public-dns-name>/graphql

Step 7: Create data with a mutation

Every table also gets create, update and delete mutations. This creates a post for Ada Lovelace (author rowId 1) and reads it back by its new rowId:

C=/var/lib/cloudimg/postgraphile-credentials.txt
H="$(sudo sed -n 's/^POSTGRAPHILE_HOST=//p' "$C")"
gql() { curl -s --cacert /etc/ssl/postgraphile/postgraphile.crt \
  -K <(sudo sed -n 's/^POSTGRAPHILE_PASSWORD=\(.*\)/user = "admin:\1"/p' "$C") \
  -H 'Content-Type: application/json' --data-binary "$1" "https://$H/graphql"; }
NEW="$(gql '{"query":"mutation { createPost(input: {post: {authorId: 1, title: \"Hello from the guide\", body: \"Written through PostGraphile\", published: true}}) { post { rowId title } } }"}')"
ID="$(python3 -c 'import json, sys; print(json.loads(sys.argv[1])["data"]["createPost"]["post"]["rowId"])' "$NEW")" \
  && echo "created post $ID"
gql "{\"query\":\"{ postByRowId(rowId: $ID) { title published authorByAuthorId { name } } }\"}" | tee /dev/stderr \
  | grep '"title":"Hello from the guide","published":true,"authorByAuthorId":{"name":"Ada Lovelace"}' >/dev/null && echo && echo "read back"
created post 6
{"data":{"postByRowId":{"title":"Hello from the guide","published":true,"authorByAuthorId":{"name":"Ada Lovelace"}}}}
read back

The same mutation in Ruru:

A createPost mutation run in Ruru, returning the new post and its author

Step 8: What the front door refuses

Without the password, or with a wrong one, nginx answers 401 before the request reaches PostGraphile, for the IDE and for the API alike:

C=/var/lib/cloudimg/postgraphile-credentials.txt
H="$(sudo sed -n 's/^POSTGRAPHILE_HOST=//p' "$C")"
Q='{"query":"{ allAuthors { totalCount } }"}'
{ curl -s -o /dev/null -w '%{http_code}\n' --cacert /etc/ssl/postgraphile/postgraphile.crt -H 'Content-Type: application/json' --data-binary "$Q" "https://$H/graphql"
  curl -s -o /dev/null -w '%{http_code}\n' --cacert /etc/ssl/postgraphile/postgraphile.crt -K <(printf 'user = "admin:not-the-password"\n') \
    -H 'Content-Type: application/json' --data-binary "$Q" "https://$H/graphql"
  curl -s -o /dev/null -w '%{http_code}\n' --cacert /etc/ssl/postgraphile/postgraphile.crt "https://$H/"
} | xargs | tee /dev/stderr | grep -x '401 401 401' >/dev/null && echo "no password, wrong password and the IDE without a password: refused"
401 401 401
no password, wrong password and the IDE without a password: refused

A web page on another site cannot use your saved password either. An HTML form can only send application/x-www-form-urlencoded, multipart/form-data or text/plain without the browser asking first, and nginx refuses all three with 415; a JSON request carrying another site's Origin gets 403. Both are refused even with the right password, and the same request from your own origin works:

C=/var/lib/cloudimg/postgraphile-credentials.txt
H="$(sudo sed -n 's/^POSTGRAPHILE_HOST=//p' "$C")"
auth() { sudo sed -n 's/^POSTGRAPHILE_PASSWORD=\(.*\)/user = "admin:\1"/p' "$C"; }
{ curl -s -o /dev/null -w '%{http_code}\n' --cacert /etc/ssl/postgraphile/postgraphile.crt -K <(auth) -H 'Origin: https://attacker.example' \
    --data 'query=mutation%20%7B%20deletePostByRowId(input%3A%20%7BrowId%3A%201%7D)%20%7B%20deletedPostId%20%7D%20%7D' "https://$H/graphql"
  curl -s -o /dev/null -w '%{http_code}\n' --cacert /etc/ssl/postgraphile/postgraphile.crt -K <(auth) -H 'Origin: https://attacker.example' \
    -H 'Content-Type: application/json' --data-binary '{"query":"{ allAuthors { totalCount } }"}' "https://$H/graphql"
  curl -s -o /dev/null -w '%{http_code}\n' --cacert /etc/ssl/postgraphile/postgraphile.crt -K <(auth) -H "Origin: https://$H" \
    -H 'Content-Type: application/json' --data-binary '{"query":"{ allAuthors { totalCount } }"}' "https://$H/graphql"
} | xargs | tee /dev/stderr | grep -x '415 403 200' >/dev/null && echo "cross-site form 415, cross-site JSON 403, same origin 200"
415 403 200
cross-site form 415, cross-site JSON 403, same origin 200

nginx also answers only for the server's own names, keeps a constant health check outside the password, and redirects port 80:

H="$(sudo sed -n 's/^POSTGRAPHILE_HOST=//p' /var/lib/cloudimg/postgraphile-credentials.txt)"
{ curl -s -w ' %{http_code}\n' --cacert /etc/ssl/postgraphile/postgraphile.crt "https://$H/healthz" | xargs
  curl -s -o /dev/null -w '%{http_code}\n' -k -H 'Host: someone-else.example' https://127.0.0.1/
  curl -s -o /dev/null -w '%{http_code}\n' "http://$H/"
} | xargs | tee /dev/stderr | grep -x 'ok 200 000 301' >/dev/null && echo "front door OK"
ok 200 000 301
front door OK
  • Rate limit. Every request that reaches the password check is limited to 5 a second per client address, with bursts of 100, and at most 32 connections per address; over the limit nginx answers 429. The limit applies to every spelling of every path (//graphql, /GRAPHQL, /%67raphql, /graphql;x and so on), because it is set for the whole server, not per path. The password is stored with bcrypt at a low cost so that each check stays cheap; it is 32 random characters, which is what keeps it safe from guessing.
  • X-Forwarded-For. nginx replaces any X-Forwarded-For header with the address it accepted the connection from, and blanks X-Real-IP, Forwarded and similar headers, so a client cannot change the address the rate limit and the logs use. If you put a load balancer in front of the instance, every client then shares the load balancer's address for the rate limit.
  • What reaches PostGraphile. nginx does not pass on the Authorization header or cookies (PostGraphile never sees your password), and never forwards a WebSocket upgrade.
  • Sizes and types. Request bodies are limited to 128 KiB by nginx and 100 kB by PostGraphile. Compressed request bodies (Content-Encoding) get 415 and TRACE gets 405. GraphQL operations are accepted only in a JSON POST body, never in a URL.
  • Headers. Responses carry Strict-Transport-Security, X-Content-Type-Options: nosniff, X-Frame-Options: DENY, a Content-Security-Policy that lets Ruru load only its own scripts, Referrer-Policy: no-referrer and Cache-Control: no-store, and nginx does not advertise its version.

What is logged: nginx's access log (/var/log/nginx/access.log) records the client address, request path and status, without query strings. nginx's error log records only start-up problems, not refused requests, so a password in a rejected request never reaches the disk. PostGraphile logs to the journal (journalctl -u postgraphile); it masks every database error to an id in its reply and logs the detail there.

Step 9: Change the password

To give admin a new random password and write it to the credentials file (the old one stops working at once):

sudo postgraphile-rotate-password
sudo grep '^POSTGRAPHILE_PASSWORD=' /var/lib/cloudimg/postgraphile-credentials.txt

The new password goes to htpasswd on standard input, never on a command line.

Step 10: The database and its least-privilege role

PostgreSQL 16 runs on the same instance and listens on its local socket only. The demo database postgraphile_demo has two schemas: app_public, which PostGraphile turns into the API, and app_private, which the API cannot reach. PostGraphile connects as the role postgraphile_public, and PostgreSQL lets only the Linux user postgraphile sign in as that role (peer authentication with a user map), so no database password exists:

sudo -u postgres psql -X -tA -c "SELECT 'roles with a password: ' || count(*) FROM pg_authid WHERE rolpassword IS NOT NULL" \
  -c "SELECT rolname, rolsuper, rolcreatedb, rolcreaterole, rolinherit, rolbypassrls FROM pg_roles WHERE rolname = 'postgraphile_public'" \
  | tee /dev/stderr | grep -x 'roles with a password: 0' >/dev/null && echo "no database password"
roles with a password: 0
postgraphile_public|f|f|f|f|f
no database password

postgraphile_public can read and write the demo tables in app_public and nothing else. As that role, reading the private schema, the password table or a server file, and creating a table, are all refused:

for q in "SELECT count(*) FROM app_private.secrets" "SELECT count(*) FROM pg_authid" "SELECT pg_read_file('/etc/hostname')" "CREATE TABLE app_public.x (id int)"; do
  OUT="$(sudo setpriv --reuid=postgraphile --regid=postgraphile --clear-groups -- \
    psql -X -tA -h /var/run/postgresql -U postgraphile_public -d postgraphile_demo -c "$q" 2>&1 || true)"
  case "$OUT" in *"permission denied"*) echo "refused: $q" ;; *) echo "NOT refused: $q" ;; esac
done | tee /dev/stderr | grep -c '^refused' | grep -x 4 >/dev/null && echo "4 of 4 refused"
refused: SELECT count(*) FROM app_private.secrets
refused: SELECT count(*) FROM pg_authid
refused: SELECT pg_read_file('/etc/hostname')
refused: CREATE TABLE app_public.x (id int)
4 of 4 refused

To serve your own schema, create it as the postgres superuser (sudo -u postgres psql -d postgraphile_demo, or a new database), grant postgraphile_public only what the API should do (GRANT USAGE ON SCHEMA ..., GRANT SELECT, INSERT, ... ON ...), add the schema to schemas in /etc/postgraphile/graphile.config.js (and the database to DATABASE_URL in /etc/postgraphile/postgraphile.env if you use a new one), then run sudo systemctl restart postgraphile. PostGraphile only shows what the role is allowed to use.

Step 11: The production settings

PostGraphile's settings are in /etc/postgraphile/graphile.config.js. Before every start, postgraphile-config-check loads that file the way PostGraphile does and refuses to start the service if it listens on anything but 127.0.0.1, turns on watch mode, EXPLAIN output, GraphQL over GET, websockets or CORS, accepts bodies other than JSON, lifts the size, depth or time limits, or connects to the database with a password or as a superuser:

sudo /usr/local/sbin/postgraphile-config-check | tee /dev/stderr | grep '^postgraphile-config-check: OK ' >/dev/null
postgraphile-config-check: OK (127.0.0.1:5000; watch, explain, GET operations, websockets off; JSON bodies only; 100000 bytes; planning depth 50; 1 database service(s), no password)

A deeply nested query is refused rather than planned (the planning depth limit is 50), queries have 5 seconds to plan and 30 seconds to run, and an error from the database reaches the caller only as An error occurred (logged with hash: ..., id: ...), with the detail in journalctl -u postgraphile.

Step 12: Use your own domain

Point a DNS name at the instance (an Elastic IP keeps it stable), then make it the server's address. This generates a new certificate for that name, pins nginx to it and updates the addresses in the credentials file:

sudo postgraphile-set-site graphql.example.com

To use a certificate from a public CA instead, for example one issued by certbot with a DNS challenge, run postgraphile-set-site first, then replace /etc/ssl/postgraphile/postgraphile.crt (full chain) and /etc/ssl/postgraphile/postgraphile.key (mode 0600) and run sudo systemctl reload nginx.

Step 13: Run the self-test

The image ships a self-test that checks the configuration, that no password and a wrong one are refused, that Ruru opens and the demo schema answers over HTTPS with this instance's certificate, and that cross-site requests are refused:

sudo postgraphile-selftest | tail -n 1 | tee /dev/stderr | grep '^POSTGRAPHILE_SELFTEST_OK ' >/dev/null
POSTGRAPHILE_SELFTEST_OK no password and a wrong one refused (401), admin opens Ruru and queries the demo schema over https://ec2-203-0-113-10.compute-1.amazonaws.com/ with this instance's certificate; cross-site form, cross-origin JSON and text/plain refused; no GraphQL over GET

Step 14: Back up and restore

Everything the API stores is in the PostgreSQL database postgraphile_demo (or your own). Back it up with:

sudo -u postgres pg_dump -Fc postgraphile_demo > postgraphile_demo-$(date +%F).dump

Restore it on a new instance of this image with sudo -u postgres pg_restore --clean --if-exists -d postgraphile_demo postgraphile_demo-<date>.dump, then sudo systemctl restart postgraphile.

Step 15: Security notes and the build record

The certificate generated at first boot is a server certificate only: it cannot sign other certificates.

openssl x509 -in /etc/ssl/postgraphile/postgraphile.crt -noout -ext basicConstraints,keyUsage,extendedKeyUsage | tee /dev/stderr | grep 'CA:FALSE' >/dev/null
X509v3 Basic Constraints: critical
    CA:FALSE
X509v3 Key Usage: critical
    Digital Signature, Key Encipherment
X509v3 Extended Key Usage: 
    TLS Web Server Authentication

The build record names every component and its checksum:

sed -n '1,6p' /usr/share/doc/cloudimg-postgraphile/BUILD-INFO.txt | tee /dev/stderr | grep '^postgraphile      5\.2\.1 ' >/dev/null
postgraphile      5.2.1 (npm, github.com/graphile/crystal, 2026-11-02), MIT (postgraphile-LICENSE.md); grafserv
                  1.0.3, grafast 1.2.0, ruru 2.0.3; installed with 'npm ci --omit=dev --ignore-scripts' from the embedded
                  package-lock.json (189 packages, sha512-integrity from registry.npmjs.org; lockfile sha256 d8fa0f0283052e9b64562ab960708ae491fa2f16c1436669d385cfb311ee2aef)
node              24.22.2 LTS (nodejs.org node-v24.22.2-linux-x64.tar.xz, sha256 48cc0e21e048790e54c0cf5749c2725005313d652651c40b7a21feb9697aaf75 = nodejs.org SHASUMS256.txt),
                  MIT (node-LICENSE); binary /opt/cloudimg-node/bin/node sha256 904a6d3484241d466989f9df9ba3aab822b1496191bdb28230eb491a55c32a52; npm/npx/corepack removed
advisories        npm audit, OSV and trivy over the installed tree: 0 vulnerabilities at build time

PostGraphile and Node.js are MIT licensed; their licences are in /usr/share/doc/cloudimg-postgraphile/. PostGraphile runs as its own unprivileged user postgraphile with no login shell, no capabilities, a read-only view of the system and no access to the credentials file or the TLS key. PostGraphile and PostgreSQL cannot open any network connection (an nftables rule refuses every new connection from their users), and PostGraphile, nginx and PostgreSQL cannot reach the EC2 instance metadata service. PostgreSQL 16 and nginx come from Ubuntu's main archive; the image adds no third-party package repository.

Step 16: Keep it up to date

Ubuntu security updates, including PostgreSQL and nginx, install automatically. To apply everything now:

sudo apt-get update && sudo apt-get upgrade -y

New PostGraphile and Node.js releases arrive as new versions of the AWS Marketplace listing: launch the new version, restore your database backup into it (Step 14), copy over your changes to /etc/postgraphile/graphile.config.js, and run sudo postgraphile-set-site with your domain if you use one.

Support

  • Email: support@cloudimg.co.uk
  • Website: https://www.cloudimg.co.uk

The other versions on this listing are on the developer and API tools guide.