Databases AWS

PoWA 5.3.0 on AWS User Guide

| Product: PoWA 5.3.0 on Ubuntu 24.04 on AWS

Overview

This guide covers launching and using PoWA 5.3.0 on Ubuntu 24.04 from cloudimg on Amazon EC2. It is one version of the cloudimg Hardened Adminer & Database Admin and Backup Tools AMIs listing on AWS Marketplace; you pick it as the software version when you subscribe and launch. The other tools in the listing are described on the listing's guide hub.

PoWA, the PostgreSQL Workload Analyzer, keeps a history of what PostgreSQL does. Statistics extensions record query time, block access, wait events, predicates and configuration changes; a background worker snapshots them every minute into a repository database; and the powa-web dashboard turns the snapshots into time series charts, ranked query lists and index suggestions. Where pg_stat_statements tells you which query has used the most time since the counters were reset, PoWA tells you that the same query was fine all week and tripled its runtime at 14:20 on Thursday.

The cloudimg image makes PoWA safe to run on EC2:

  • Your own sign-in, no default account. powa-web has no account store: you sign in as a PostgreSQL role. On its first boot each instance creates its own PostgreSQL cluster and one sign-in role, powa_ui, with a random password, before the web server starts. The image contains no cluster, no role and no password. powa_ui can read PoWA's statistics but is not a superuser, and the postgres superuser has no password at all (local peer login only).
  • Per-instance secrets. The cookie secret that signs powa-web's session cookies and the TLS key are generated on each instance's first boot, so a cookie or certificate from one instance is worthless on another.
  • HTTPS from the first boot. nginx serves powa-web on port 443 with a certificate generated on your instance's first boot. Port 80 redirects to HTTPS. Requests for a host name the instance does not serve get no answer.
  • Everything else on localhost. powa-web listens on 127.0.0.1:8888 and PostgreSQL 18 on 127.0.0.1:5432 only, with scram-sha-256 passwords.
  • Other web sites cannot act with your login. nginx refuses the actions that run something (a forced snapshot, a collector reload, the index suggestion wizard, sign-out) when another web site's page sends them in your browser, refuses any post whose browser Origin is not this instance, rate limits sign-in attempts per address, and the sign-in page only redirects back into this site. Session cookies are Secure, HttpOnly and SameSite=Lax.
  • No outbound connections you did not ask for. powa-web and PostgreSQL can reach 127.0.0.1 and ::1 only: not the instance metadata service, not your VPC, not the internet. nginx is blocked from the instance metadata service. When you add a remote server to PoWA (Step 11) you allow exactly its address in one drop-in file.
  • Checked. powa-web 5.3.1 is installed from its release (checksum pinned) with tornado 6.6, with no known vulnerabilities at build time. It runs as the unprivileged powaweb user with no Linux capabilities.
  • Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.

What is on the image: powa-archivist 5.3.0 and the statistics extensions pg_stat_statements, pg_qualstats, pg_stat_kcache, pg_wait_sampling, pg_track_settings and hypopg, PostgreSQL 18 (all from the PostgreSQL Global Development Group's packages, so they keep receiving updates), powa-web 5.3.1 (/opt/powa-web), nginx, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent. PoWA registers its own PostgreSQL as server <local>, so it analyses a real database from the first minute, and no remote server is configured until you add one.

Prerequisites

  • An AWS account subscribed to the listing, an EC2 key pair and a VPC subnet.
  • A security group that allows TCP 22 (SSH), TCP 443 (PoWA) and TCP 80 (redirects to HTTPS) from the networks your administrators work from. PoWA shows the text of every query it records, so do not open 443 to the world.
  • To analyse your own PostgreSQL servers: network access from this instance to them on 5432, and a role on each (Step 11).

Step 1: Launch the instance

In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick PoWA 5.3.0 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large (a t3.medium is enough for PoWA analysing itself and one or two quiet servers), your key pair, the subnet and a security group as above. PoWA's repository lives on the root volume and grows with the number of distinct queries and the retention you keep (seven days by default), so give it room.

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 \
  --block-device-mappings 'DeviceName=/dev/sda1,Ebs={VolumeSize=30,VolumeType=gp3}' \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=powa}]'

Step 2: Connect to your instance

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

Step 3: Check the services

First boot sets the host name, generates the certificate and the cookie secret, creates this instance's PostgreSQL cluster, the PoWA repository and the powa_ui role, and then starts powa-web and nginx:

systemctl show -p UnitFileState --value powa-firstboot.service
systemctl is-active postgresql@18-main powa-web nginx
sudo test -f /var/lib/cloudimg/powa-firstboot.done && echo FIRSTBOOT_DONE

The first boot service is enabled, the three services are active and the first boot marker is present:

enabled
active
active
active
FIRSTBOOT_DONE

Only SSH and nginx listen on the network. powa-web and PostgreSQL listen on localhost only:

sudo ss -lntpH | awk '{print $4, $6}' | sed -E 's/users:\(\("([^"]+)".*/\1/' | grep -v '127.0.0.5' | sort -u
0.0.0.0:22 sshd
0.0.0.0:443 nginx
0.0.0.0:80 nginx
127.0.0.1:5432 postgres
127.0.0.1:8888 python3
[::]:22 sshd
[::]:443 nginx
[::]:80 nginx

A health check for a load balancer or uptime monitor answers without signing in:

curl -sk https://127.0.0.1/healthz
ok

Step 4: Get your URL and password

The credentials file is readable by root only and holds the URL, the user name and the password for this instance. Print it in your own session:

sudo cat /root/powa-credentials.txt
# PoWA 5.3.0 - generated on first boot by powa-firstboot.service
# These credentials are unique to this instance; none was baked into the image. Keep this file secret.

POWA_URL=https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/
POWA_ADMIN_USERNAME=powa_ui
POWA_ADMIN_PASSWORD=<POWA_ADMIN_PASSWORD>
#
# Open POWA_URL in a browser (self-signed certificate minted on this instance's first boot; port 80 redirects to https)
# and sign in with POWA_ADMIN_USERNAME / POWA_ADMIN_PASSWORD (Server: main). powa-web signs in AS that PostgreSQL role:
# it is a member of powa_admin and can read statistics, but is not a superuser. The postgres superuser has no password
# (local peer login only: sudo -u postgres psql).
# Populate the charts in a minute:  sudo powa-demo-workload        Self-test:  sudo powa-selftest
# Change the served name:  sudo powa-set-hostname <your-domain>
# Outbound connections from PostgreSQL and powa-web are blocked except loopback; the guide shows the allow-list.
# User guide: https://www.cloudimg.co.uk/guides/powa-5-3-0-on-ubuntu-24-04-aws/

The URL uses the instance's public DNS name from EC2 instance metadata. Opening https://<public-ip>/ sends you on to that name. To check the file without showing the password:

sudo stat -c '%a %U:%G %n' /root/powa-credentials.txt
sudo grep -E '^POWA_(URL|ADMIN_USERNAME)=' /root/powa-credentials.txt | sed -E 's#https://[^/]+/#https://<host>/#'
600 root:root /root/powa-credentials.txt
POWA_URL=https://<host>/
POWA_ADMIN_USERNAME=powa_ui

The image includes a self test. Through nginx it signs in as powa_ui and checks the pages, the request guards, powa-web's privileges, both localhost listeners and the outbound block, and it leaves nothing behind:

sudo powa-selftest
SELFTEST-OK https 301 + healthz + anonymous / 302 -> /login/ + anonymous data 302/403 + sign-in page 403 form + powa_ui sign-in 302 -> /, user/password/server cookies Secure/HttpOnly/SameSite=Lax + servers list <local> + overview 200 + chart config changes 200 + next=//evil -> / + cross-site 403 + cross-origin POST 403 + sign-out 302 + wrong password refused (403, no cookie) + 444 unknown host + powa-web uid powaweb, no capabilities, 127.0.0.1:8888 only + PostgreSQL 127.0.0.1:5432 only + metadata 000 inside powa-web, PostgreSQL and nginx and internet 000 inside powa-web and PostgreSQL (controls 200/200) + config guard OK + postgres no password, powa_ui not superuser, no predefined role can log in

Step 5: Sign in

Open the URL from the credentials file. Your browser warns about the self-signed certificate the first time; accept it for this host (Step 13 replaces it with your own). Sign in with powa_ui and the password, and leave Server on main, which is this instance's own PostgreSQL.

The PoWA sign-in page with the PoWA logo above the Username, Password and Server fields and the Login button

After signing in you land on All servers. The <local> entry is this instance's PostgreSQL 18, already being analysed.

The PoWA All servers page listing the local server with its snapshot frequency and retention

Step 6: Confirm collection is running

The background worker takes a snapshot every minute. Count the snapshots and statements PoWA has recorded for the local server (server id 0):

sudo -u postgres psql -w -tAd powa -c "SELECT count(*) > 0 AS has_snapshots FROM powa_snapshot_metas WHERE srvid = 0"
sudo -u postgres psql -w -tAc "SELECT name, setting FROM pg_settings WHERE name IN ('powa.frequency','powa.retention','pg_qualstats.sample_rate') ORDER BY name"
t
pg_qualstats.sample_rate|1
powa.frequency|60000
powa.retention|10080

A snapshot every minute (60000 ms), seven days of history (10080 minutes) and every predicate sampled. The policy lives in /etc/postgresql/18/main/conf.d/10-cloudimg-powa.conf (Step 12).

Step 7: Generate a demo workload

A new instance has little to chart: PoWA plots the difference between snapshots, and a quiet server produces nearly identical ones. The image ships a tool that creates a small powa_demo database, runs a real pgbench workload against it for 45 seconds and takes a snapshot on each side:

sudo powa-demo-workload

Pass a number of seconds for a longer run (sudo powa-demo-workload 120), and remove the demo database when you are done with sudo powa-demo-workload --drop. Reload the dashboard afterwards and the charts have data in them.

Step 8: Read the dashboards

Open the <local> server and you land on its overview: query runtime and rate, block access split into shared buffer hits, operating system cache hits and real disk reads, connection activity and more, over the selected time range. The tabs move between WALs, JIT, Background Writer / Checkpointer, Archiver / Replication, Database activity, Database Objects, System resources, SLRU and Wait Events; the time range control at the top right applies to every chart.

The PoWA server overview with query runtime, block access and connection charts over the last hour, showing the demo workload's spike

Step 9: Find the queries that cost the most

Choose a database (for example powa_demo) and scroll to Details for all queries: every statement PoWA has seen, ranked, with calls, total and average time, blocks read, hit and dirtied, temporary blocks, I/O time and WAL volume. Click a statement to see the same metrics over time for that one query, with the predicates it filters on.

The PoWA database page for powa_demo listing the pgbench statements ranked by total execution time

Wait events for all queries attributes lock waits to the statements that suffered them. Index suggestions takes the predicates pg_qualstats captured, proposes indexes and uses hypopg to cost each one against the real planner without building it; choose Optimize this database to run it.

Step 10: Analyse your own databases on this instance

PoWA records every database in this instance's PostgreSQL. For the per-query predicates and index suggestions in a database, create the two extensions there:

sudo -u postgres createdb orders
sudo -u postgres psql -d orders -c 'CREATE EXTENSION IF NOT EXISTS pg_stat_statements' -c 'CREATE EXTENSION IF NOT EXISTS hypopg'

The predicates and index suggestions read PostgreSQL's column statistics (pg_statistic), which only a superuser can read by default. powa_ui is not a superuser, so first boot grants PoWA's administrators (powa_admin, which powa_ui belongs to) read access to pg_statistic in template1, postgres and powa; every database you create afterwards inherits it. A database created from template0 or restored from a dump does not: run sudo -u postgres psql -d <database> -c 'GRANT SELECT ON pg_catalog.pg_statistic TO powa_admin' in it.

Step 11: Add a remote PostgreSQL server

PoWA can analyse other PostgreSQL servers (for example Amazon EC2 or on-premises servers you can install extensions on) in remote mode: the statistics extensions run on the remote server, and the powa-collector daemon on this instance snapshots it and stores the history in this repository. Amazon RDS and Aurora do not offer the powa extension, so they cannot be added. Follow the PoWA remote setup documentation; on this image three things are specific.

1. Allow the remote server's address. powa-web and PostgreSQL can reach localhost only. Add one IPAddressAllow= line per server to the shipped, empty allow-list drop-in of each service that has to reach it, then reload:

sudo tee -a /etc/systemd/system/powa-web.service.d/60-cloudimg-egress-allow.conf <<'EOF'
IPAddressAllow=<remote-server-ip>/32
EOF
sudo systemctl daemon-reload && sudo systemctl restart powa-web

Never allow 169.254.169.254 or fd00:ec2::254 (the instance metadata service). Check what each service may reach:

systemctl show -p IPAddressAllow --value powa-web
systemctl show -p IPAddressAllow --value postgresql@18-main
127.0.0.0/8 ::1/128
127.0.0.0/8 ::1/128

2. Register the server in the repository, as the PostgreSQL superuser over the local socket. Use a role on the remote server that has the pg_read_all_stats privilege; its password is stored in the repository's powa_servers table, which only postgres and PoWA's administrators can read:

sudo -u postgres psql -d powa <<'SQL'
SELECT powa_register_server(hostname => '<remote-server-ip>', port => 5432, alias => 'orders-db',
                            username => 'powa_collector', password => '<your-server-password>', dbname => 'powa');
SQL

3. Run powa-collector under the same block. powa-collector is a small Python program published on PyPI; it is not in Ubuntu's or the PostgreSQL project's package repositories. Install it in a virtual environment and configure it against this repository as the documentation describes, for example:

sudo apt-get install -y python3-venv
sudo python3 -m venv /opt/powa-collector
sudo /opt/powa-collector/bin/pip install powa-collector
printf '{"repository": {"dsn": "postgresql:///powa"}, "debug": false}\n' | sudo tee /etc/powa-collector.conf

Run it as postgres (it reaches this repository over the local socket) and give its service the same outbound rules as powa-web, so it too can reach only localhost and the servers you name:

sudo tee /etc/systemd/system/powa-collector.service <<'EOF'
[Unit]
Description=PoWA collector
After=postgresql@18-main.service

[Service]
User=postgres
ExecStart=/opt/powa-collector/bin/powa-collector.py
Restart=on-failure
IPAddressDeny=any
IPAddressAllow=localhost
IPAddressAllow=<remote-server-ip>/32

[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload && sudo systemctl enable --now powa-collector

Step 12: Tune retention and snapshot frequency

/etc/postgresql/18/main/conf.d/10-cloudimg-powa.conf sets powa.frequency (1 minute), powa.retention (7 days) and pg_qualstats.sample_rate (1, every predicate). These are generous for one instance and a few servers; relax them when you add busy servers. The configuration tree is owned by root and only readable by PostgreSQL, so write your changes as a separate root-owned file and reload:

printf "powa.retention = '3d'\npg_qualstats.sample_rate = 0.1\n" > /tmp/20-local.conf
sudo install -o root -g postgres -m 0644 /tmp/20-local.conf /etc/postgresql/18/main/conf.d/20-local.conf && rm /tmp/20-local.conf
sudo systemctl reload postgresql@18-main

Step 13: Use your own domain and certificate

  1. In your DNS, point <your-domain> (an A record) at the instance's public IP address. An Elastic IP keeps that address stable.
  2. Tell the instance to use it. This sets the name nginx accepts and its request guards, updates the credentials file and generates a new self-signed certificate for it:
sudo powa-set-hostname <your-domain>
  1. For a certificate your browser trusts, put the certificate chain for <your-domain> in /etc/ssl/powa/powa.crt and its private key in /etc/ssl/powa/powa.key (root only, mode 600), and run sudo systemctl reload nginx. Alternatively put the instance behind an Application Load Balancer with an AWS Certificate Manager certificate that forwards to port 443 on the instance.

Step 14: Reach PostgreSQL from your workstation

PostgreSQL listens on localhost because the repository holds the text of every query PoWA has seen. The safe way to use a desktop client is an SSH tunnel, which needs no change on the instance:

ssh -i <your-key.pem> -L 5432:127.0.0.1:5432 ubuntu@<public-ip>

Then connect to 127.0.0.1:5432, database powa, as powa_ui with the password from Step 4.

If clients in your VPC must connect directly, open 5432 to them only, and only with TLS:

  1. Put a certificate and key for the server in /etc/postgresql/18/main/ owned by root (key root:postgres mode 640).
  2. Add a root-owned conf.d file (as in Step 12) with listen_addresses = '127.0.0.1,<private-ip>', ssl = on, ssl_cert_file and ssl_key_file.
  3. Add a hostssl all <role> <your-vpc-cidr> scram-sha-256 line to /etc/postgresql/18/main/pg_hba.conf for a role with only the rights it needs, never postgres.
  4. Add IPAddressAllow=<your-vpc-cidr> to /etc/systemd/system/postgresql@18-main.service.d/60-cloudimg-egress-allow.conf, run sudo systemctl daemon-reload and restart PostgreSQL.
  5. In the security group, allow TCP 5432 from <your-vpc-cidr> only. Never open 5432 to 0.0.0.0/0.

Step 15: Security checks

powa-web and PostgreSQL may reach localhost only; nginx is blocked from the instance metadata service and the special-purpose ranges:

for u in powa-web postgresql@18-main; do printf '%s deny=%s allow=%s\n' "$u" "$(systemctl show -p IPAddressDeny --value "$u" | tr ' ' '\n' | grep -cE '^(0\.0\.0\.0/0|::/0)$')" "$(systemctl show -p IPAddressAllow --value "$u")"; done
systemctl show -p IPAddressDeny --value nginx | tr ' ' '\n' | sort | tr '\n' ' '; echo
powa-web deny=2 allow=127.0.0.0/8 ::1/128
postgresql@18-main deny=2 allow=127.0.0.0/8 ::1/128
168.63.129.16/32 169.254.169.254/32 192.0.0.0/24 198.18.0.0/15 224.0.0.0/4 240.0.0.0/4 fd00:ec2::254/128

deny=2 means everything (0.0.0.0/0 and ::/0) is denied apart from the allowed addresses. The block is two-way: nothing outside the instance can reach powa-web or PostgreSQL directly either. It applies to the whole service, so every connection powa-web opens and every process PostgreSQL starts is blocked too.

powa-web runs as powaweb with no Linux capabilities and cannot gain any:

P=$(systemctl show -p MainPID --value powa-web)
ps -o user= -p "$P"
grep -E '^(CapEff|CapBnd|CapAmb|NoNewPrivs)' /proc/"$P"/status
powaweb
CapEff: 0000000000000000
CapBnd: 0000000000000000
CapAmb: 0000000000000000
NoNewPrivs: 1

PostgreSQL accepts the postgres superuser over the local socket by peer (root's maintenance and PoWA's background worker) and powa_ui from localhost with a password, nothing else. postgres has no password and powa_ui is not a superuser:

sudo -u postgres psql -w -tA -c "SELECT type, database, user_name, address, auth_method FROM pg_hba_file_rules ORDER BY rule_number"
sudo -u postgres psql -w -tA -c "SELECT rolname, rolsuper, rolpassword IS NULL AS no_password FROM pg_authid WHERE rolname IN ('postgres','powa_ui') ORDER BY rolname"
sudo -u postgres psql -w -tA -d powa -c "SELECT has_table_privilege('powa_ui', 'pg_catalog.pg_statistic', 'SELECT'), has_table_privilege('powa_ui', 'public.pg_track_settings_history', 'SELECT')"
local|{all}|{postgres}||peer
host|{all}|{powa_ui}|127.0.0.1|scram-sha-256
postgres|t|t
powa_ui|f|f
t|t

powa_ui reads PoWA's repository, the configuration-change history (pg_track_settings) and the column statistics behind the predicates and index suggestions (Step 10). It is not a superuser, so it cannot change your databases or run programs on the server.

Signed-out callers are sent to the sign-in page, nginx refuses a forced snapshot that another web site's page sends and a post from a foreign Origin, and a request for a host name the instance does not serve gets no answer at all:

H=$(sudo sed -n 's#^POWA_URL=https://\([^/]*\)/$#\1#p' /root/powa-credentials.txt)
curl -sk -o /dev/null -w 'signed-out overview: %{http_code}\n' --resolve "$H:443:127.0.0.1" -H 'Sec-Fetch-Site: same-origin' "https://$H/server/0/overview/"
curl -sk -o /dev/null -w 'cross-site forced snapshot: %{http_code}\n' --resolve "$H:443:127.0.0.1" -X POST -H 'Sec-Fetch-Site: cross-site' "https://$H/force_snapshot/0/"
curl -sk -o /dev/null -w 'cross-origin POST: %{http_code}\n' --resolve "$H:443:127.0.0.1" -X POST -H 'Origin: https://evil.example' "https://$H/login/"
curl -sk -o /dev/null -w 'unknown host name: %{http_code}\n' --resolve evil.example:443:127.0.0.1 https://evil.example/ || true
signed-out overview: 302
cross-site forced snapshot: 403
cross-origin POST: 403
unknown host name: 000

The credentials, the TLS key and powa-web's configuration (which holds the cookie secret) are readable by root, and the last by the powaweb service, only; the PostgreSQL configuration is owned by root:

sudo stat -c '%a %U:%G %n' /root/powa-credentials.txt /etc/ssl/powa/powa.key /etc/powa-web.conf /etc/postgresql/18/main/pg_hba.conf /etc/postgresql/18/main/conf.d
600 root:root /root/powa-credentials.txt
600 root:root /etc/ssl/powa/powa.key
640 root:powaweb /etc/powa-web.conf
640 root:postgres /etc/postgresql/18/main/pg_hba.conf
755 root:postgres /etc/postgresql/18/main/conf.d

Other notes:

  • Sign-in limits. nginx allows about 10 sign-in attempts a minute per address (a burst of 5) and answers more with 429, using the address that connected to nginx: forwarding headers sent by a client are replaced.
  • Who can reach it. Anyone who signs in sees the text of every query PoWA records. Open ports 80 and 443 in the security group only to the networks your administrators work from.
  • The build. /usr/share/doc/cloudimg-powa/BUILD-INFO.txt records the release, its checksums and the library versions, and NOTICE.txt next to it the licences.
  • Logs. nginx logs are in /var/log/nginx; powa-web and PostgreSQL log to the journal (journalctl -u powa-web, journalctl -u postgresql@18-main).

Step 16: Keep it up to date

Ubuntu, PostgreSQL and the PoWA extensions update from their package repositories; Ubuntu security updates install automatically. To apply all updates now:

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

After an upgrade of the postgresql-18-powa package, update the extension in the repository with sudo -u postgres psql -d powa -c 'ALTER EXTENSION powa UPDATE': powa-web refuses to sign in when its version and the extension's differ in major or minor version. New powa-web releases arrive as new versions of this listing. Read the PoWA release notes before you move.

Troubleshooting

The page does not load. Check that first boot finished: until it has created the cluster, the role and the cookie secret, powa-web and nginx stay stopped on purpose.

sudo journalctl -u powa-firstboot --no-pager -o cat | tail -3

The sign-in page says "Auth failed". The user name and password are a PostgreSQL role, so this is a database sign-in failure. Re-read /root/powa-credentials.txt and keep Server on main. After about 6 quick attempts nginx answers 429 for a minute.

Forgot the password. Set a new one for powa_ui as the PostgreSQL superuser (\password asks for it twice, so it never appears on a command line), and update the credentials file:

sudo -u postgres psql -c '\password powa_ui'

The charts are empty. PoWA needs at least two snapshots across a period with activity. Check that snapshots are growing (Step 6) and run the demo workload (Step 7).

A remote server shows no data. Check that its address is in the allow-list drop-ins (Step 11) and that journalctl -u powa-collector shows no connection error.

Support and licensing

PoWA (powa-archivist and powa-web) is developed by the PoWA team and distributed under the PostgreSQL licence, as are PostgreSQL and the statistics extensions. powa-web uses tornado (Apache-2.0) and psycopg2 (LGPL-3.0-or-later). cloudimg changed one line of powa-web so the sign-in page only redirects within this site. The licence texts and the full list of components are on the instance:

ls /usr/share/doc/cloudimg-powa/
BUILD-INFO.txt
LICENSE-powa-web.txt
NOTICE.txt
SHA256SUMS
build-added-packages.txt

cloudimg is not affiliated with, sponsored by or endorsed by the PoWA team or the PostgreSQL Community Association. PostgreSQL and PoWA are used only to identify the software this image contains.