Databases Azure

PolarDB for PostgreSQL on Ubuntu 24.04 on Azure User Guide

| Product: PolarDB for PostgreSQL on Ubuntu 24.04 on Azure

Overview

This guide covers the deployment and use of PolarDB for PostgreSQL on Ubuntu 24.04 on Azure using cloudimg Azure Marketplace images.

PolarDB for PostgreSQL is Alibaba Cloud's open source PostgreSQL distribution, released under the Apache License 2.0 over PostgreSQL 17 (PostgreSQL Licence). It is not an extension bolted onto stock PostgreSQL — it is a distribution with its own storage manager (PolarVFS), its own replay machinery (LogIndex), its own instrumentation surface and its own release train. Ask the running server what it is and it tells you:

PostgreSQL 17.10 (PolarDB 17.10.1.0 build accf02e2) on x86_64-linux-gnu

Because it is PostgreSQL 17 underneath, everything you already know applies unchanged: ACID transactions, MVCC, roles and permissions, indexes, pg_dump, and the entire driver ecosystem. psycopg, JDBC, pgx, Npgsql and the rest connect to it without knowing it is anything other than PostgreSQL.

This is a single node deployment, and that is deliberate

PolarDB's headline architecture is shared storage with compute and storage separated: many compute nodes reading one copy of the data, with millisecond replication latency and a shared storage MPP engine for analytics. That architecture needs more than one machine and a shared block device.

This image is the single node, local storage deployment, which upstream ships, documents and supports as a first class shape. Upstream's own polardb.conf.sample sets polar_vfs.localfs_mode = true, upstream's own initialiser is polar-initdb.sh <pgdata>/ <polardata>/ primary localfs, and upstream publishes a dedicated container image for exactly this configuration.

The engine is unmodified and the storage layout is PolarDB's real shared storage layout — base/, global/, pg_wal/, pg_logindex/ and pg_xact/ all live in the polar_datadir and are reached through PolarVFS with direct I/O — it simply sits on a local filesystem instead of PFS on a shared device.

What a single node does not give you, stated plainly so there are no surprises:

  • no read only replicas sharing one copy of the data
  • no cross node HA failover
  • no shared storage MPP / HTAP parallel execution across nodes

All three need more than one compute node. What you get on one node is the PolarDB engine itself, its storage layer, its instrumentation, and the whole vendored extension catalogue.

Who this is for

  • Teams running managed PolarDB on Alibaba Cloud who want a bit compatible engine to develop, test and CI against elsewhere.
  • Anyone who wants a PostgreSQL 17 database that arrives with 93 extensions already compiled and available — including pg_cron, pgaudit, pg_partman, pg_repack, pg_hint_plan, pg_squeeze, hll, roaringbitmap and pase — with no build toolchain and no repository juggling.
  • Anyone evaluating PolarDB's storage and instrumentation model before committing to a multi node deployment.

There is no web interface

PolarDB is a database engine. This appliance ships no dashboard, no admin console and no HTTP surface of any kind. You work with it through psql or any PostgreSQL client. The only inbound port the offer advertises is SSH (22).

What is included:

  • PolarDB for PostgreSQL 17.10.1.0, built from the pinned upstream tag v17.10.1.0 (commit accf02e2fda3d8e47e78afa61f42795e0c4065c4), running under systemd as polardb.service
  • The full PostgreSQL client tool set — psql, pg_dump, pg_restore, pg_basebackup, pg_ctl, initdb — on the default PATH
  • 93 extensions, including the eleven PolarDB only ones (polar_vfs, polar_monitor, polar_monitor_preload, polar_io_stat, polar_worker, polar_resource_manager, polar_parameter_manager, polar_feature_utils, polar_smgrperf, polar_tde_utils, polar_advisor) and the whole vendored external/ tree
  • A per VM superuser password and a per VM application role password, generated on your first boot, never baked into the image
  • Full build provenance and a dependency licence inventory shipped at /usr/share/polardb-postgresql/
  • No swap in the image, no build toolchain, no container runtime

Deploying the VM

Launch the image from the Azure Marketplace as you would any other VM. The recommended size is Standard_D2s_v5 (2 vCPU / 8 GiB). A database wants sustained CPU, so the non burstable D series is the honest recommendation over a B series whose CPU credits quietly run out under sustained load.

Honest sizing envelope. The shipped configuration (shared_buffers 256 MB, max_connections 200) is right for development, test, evaluation and small production workloads up to a few tens of GB. Beyond that, scale the VM up, raise shared_buffers towards 25 % of RAM, and move the data directory onto a managed data disk. Both are covered below.

The Azure network security group only needs TCP 22. PolarDB itself is bound to loopback and is reached over an SSH tunnel by default. Exposing 5432 is a deliberate opt in and is covered in its own section below.

First boot

The captured image contains no database at all. Not an empty password, not a rotated password — no cluster, no roles, no configuration. initdb itself runs on your machine, at your first boot.

The first time the VM boots, polardb-firstboot.service runs once and then disables itself. It:

  1. generates this VM's superuser and application passwords from the kernel CSPRNG (32 characters each, independently generated),
  2. runs initdb with scram-sha-256 host authentication and data checksums enabled,
  3. appends the cloudimg security overrides so PolarDB binds to loopback only,
  4. writes pg_hba.conf whole — local peer, plus 127.0.0.1/32 and ::1/128 with scram-sha-256, and nothing else,
  5. runs polar-initdb.sh ... primary localfs to lay the cluster out on PolarDB's shared storage layout,
  6. writes the credentials to /root/polardb-credentials.txt (mode 0600, owned by root),
  7. starts polardb.service and proves the whole appliance end to end before declaring success.

It takes roughly 30 to 60 seconds. Read what it did:

sudo journalctl -u polardb-firstboot.service --no-pager | tail -30

(Add -f instead of | tail -30 if you want to watch it live on a boot in progress.)

polardb.service cannot start until first boot has finished. It carries a start time guard that refuses to run the database if the credentials file is missing, is not 0600 root:root, or is still the shipped placeholder; if listen_addresses is anything but loopback; if pg_hba.conf contains trust, md5, password or ident, or permits a non loopback client; or if PolarVFS is not loaded. If any of those is true, the database does not start at all.

Retrieving your per VM credentials

sudo cat /root/polardb-credentials.txt

Terminal showing the extension count from pg_available_extensions, the first two dozen extension names from the shipped catalogue, every listening socket enumerated from the kernel with ss showing PostgreSQL bound only to 127.0.0.1 port 5432 and SSH the only routable listener, and the head of the per VM credentials file generated on this VM at first boot

The file is readable only by root and contains:

POLARDB_HOST=127.0.0.1
POLARDB_PORT=5432
POLARDB_SUPERUSER=polardb
POLARDB_SUPERUSER_PASSWORD=<polardb-superuser-password>
POLARDB_APP_ROLE=polarapp
POLARDB_APP_PASSWORD=<polardb-app-password>
POLARDB_APP_DATABASE=polarapp

Two roles, deliberately:

  • polardb is the superuser. Use it for administration — creating extensions, tuning, backups.
  • polarapp is an unprivileged login role that owns the polarapp database. It is NOSUPERUSER NOCREATEROLE NOCREATEDB NOBYPASSRLS. Point your application at this one.

This is the only copy of both passwords. Store them somewhere safe.

Checking service health

sudo systemctl status polardb --no-pager
/opt/polardb/bin/pg_isready -h 127.0.0.1 -p 5432

Terminal showing polardb.service active and running under systemd, pg_isready reporting the server accepting connections on 127.0.0.1 port 5432, and the version query returning PostgreSQL 17.10 with the PolarDB 17.10.1.0 build accf02e2 identifier

Connecting on the VM

psql is on the default PATH. Read the application password out of the credentials file and connect over loopback:

APP_PASS=$(sudo sed -n 's/^POLARDB_APP_PASSWORD=//p' /root/polardb-credentials.txt)
PGPASSWORD="$APP_PASS" psql -h 127.0.0.1 -p 5432 -U polarapp -d polarapp -c 'SELECT version();'

That version string is the proof that this is PolarDB and not stock PostgreSQL — and the build accf02e2 suffix is the exact upstream commit this image was compiled from.

Connecting from your workstation

PolarDB is bound to loopback, so the default access path is an SSH tunnel. Nothing needs to be opened in the network security group beyond the SSH port you already have.

On your workstation:

ssh -N -L 15432:127.0.0.1:5432 azureuser@<your-vm-public-ip>

Leave that running, and in another terminal:

psql -h 127.0.0.1 -p 15432 -U polarapp -d polarapp

Any PostgreSQL client works the same way — point it at 127.0.0.1:15432. Connection string form:

postgresql://polarapp:<polardb-app-password>@127.0.0.1:15432/polarapp

A real SQL round trip

APP_PASS=$(sudo sed -n 's/^POLARDB_APP_PASSWORD=//p' /root/polardb-credentials.txt)
PGPASSWORD="$APP_PASS" psql -h 127.0.0.1 -p 5432 -U polarapp -d polarapp <<'SQL'
CREATE TABLE demo_orders (
    id      bigserial PRIMARY KEY,
    region  text NOT NULL,
    amount  numeric(12,2) NOT NULL,
    placed  timestamptz DEFAULT now()
);
INSERT INTO demo_orders (region, amount)
SELECT 'region-' || (g % 4), (g * 1.5)::numeric(12,2)
FROM generate_series(1, 5000) g;
CREATE INDEX ON demo_orders (region);
ANALYZE demo_orders;
SELECT region, count(*) AS orders, round(sum(amount), 2) AS revenue
FROM demo_orders GROUP BY region ORDER BY region;
SQL

Terminal showing a psql session against PolarDB creating a demo_orders table, inserting five thousand generated rows, building an index, running ANALYZE, and returning a grouped aggregate of orders and revenue per region

What makes this PolarDB

Four settings distinguish this from PostgreSQL wearing a different name. Ask the server:

SUPER_PASS=$(sudo sed -n 's/^POLARDB_SUPERUSER_PASSWORD=//p' /root/polardb-credentials.txt)
PGPASSWORD="$SUPER_PASS" psql -h 127.0.0.1 -p 5432 -U polardb -d polarapp <<'SQL'
SHOW shared_preload_libraries;
SHOW polar_datadir;
SHOW polar_enable_shared_storage_mode;
SHOW polar_vfs.localfs_mode;
SQL

shared_preload_libraries contains polar_vfs, polar_io_stat, polar_monitor_preload and polar_worker. polar_datadir is file-dio:///var/lib/polardb/shared, and shared storage mode is on.

You can see the split on disk. The heap, the catalogue and the WAL live in the PolarDB data directory, not in the local PGDATA:

ls /var/lib/polardb/shared
ls /var/lib/polardb/primary

/var/lib/polardb/shared holds base/, global/, pg_wal/, pg_logindex/, pg_xact/ and friends. /var/lib/polardb/primary keeps only node local state — it has no pg_wal/ and no base/ content of its own. That is the PolarDB shared storage layout, running over a local filesystem.

polar_monitor is PolarDB's own instrumentation extension and has no equivalent in stock PostgreSQL:

SUPER_PASS=$(sudo sed -n 's/^POLARDB_SUPERUSER_PASSWORD=//p' /root/polardb-credentials.txt)
PGPASSWORD="$SUPER_PASS" psql -h 127.0.0.1 -p 5432 -U polardb -d polarapp \
  -c 'CREATE EXTENSION IF NOT EXISTS polar_monitor;' \
  -c "SELECT extname, extversion FROM pg_extension ORDER BY extname;"

Terminal showing the PolarDB storage settings — shared_preload_libraries including polar_vfs, polar_datadir pointing at file-dio slash var slash lib slash polardb slash shared, shared storage mode on and localfs mode on — followed by the polar_monitor extension being created, the extension list, and directory listings proving the heap and WAL live in the shared data directory while the local PGDATA has none

The extension catalogue

Every extension is already compiled and installed; you only need CREATE EXTENSION.

cat /usr/share/polardb-postgresql/extensions.txt

Highlights beyond the standard PostgreSQL contrib set:

Extension What it gives you
pg_cron in database job scheduler
pgaudit detailed session and object audit logging
pg_partman automated partition management
pg_repack table and index bloat removal without an exclusive lock
pg_squeeze automatic table bloat removal
pg_hint_plan planner hints via SQL comments
pg_profile historic workload reporting
pg_stat_kcache per query kernel level I/O and CPU statistics
hll HyperLogLog approximate distinct counts
roaringbitmap compressed bitmap type and operations
ip4r indexed IPv4/IPv6 address and range types
pase approximate nearest neighbour vector index
pg_bigm, pg_jieba bigram and Chinese full text search
pgtap, pldebugger unit testing and PL/pgSQL debugging
polar_monitor, polar_io_stat, polar_smgrperf PolarDB engine instrumentation

To enable one:

SUPER_PASS=$(sudo sed -n 's/^POLARDB_SUPERUSER_PASSWORD=//p' /root/polardb-credentials.txt)
PGPASSWORD="$SUPER_PASS" psql -h 127.0.0.1 -p 5432 -U polardb -d polarapp \
  -c 'CREATE EXTENSION IF NOT EXISTS hll;'

A few extensions (pg_cron, pgaudit, pg_stat_kcache, pg_hint_plan) must also be added to shared_preload_libraries and need a restart. Append to the existing list rather than replacing it — polar_vfs and its siblings must stay, and the start time guard will refuse to start the database if polar_vfs is removed.

Exposing PostgreSQL to your network

By default PolarDB listens on loopback only and the offer advertises only SSH. That is the safe default and the SSH tunnel above is the recommended access path. If your application genuinely needs to reach the database directly, this is a deliberate, three part opt in — and all three parts are required.

1. Restrict the network security group first, before anything else. Never open 5432 to the internet.

az network nsg rule create \
  --resource-group <your-rg> --nsg-name <your-nsg> \
  --name allow-postgres-from-my-app \
  --priority 300 --direction Inbound --access Allow --protocol Tcp \
  --source-address-prefixes <your-app-subnet-or-office-cidr> \
  --destination-port-ranges 5432

Use your own subnet or office CIDR. A source prefix of * or Internet here puts a database on the public internet and is never the right answer.

2. Add a pg_hba.conf rule for exactly that range, keeping scram-sha-256:

echo 'host    polarapp    polarapp    <your-app-subnet-cidr>    scram-sha-256' \
  | sudo tee -a /var/lib/polardb/primary/pg_hba.conf

3. Bind the listener, then restart:

sudo sed -i "s/^listen_addresses = 'localhost'/listen_addresses = '*'/" \
  /var/lib/polardb/primary/postgresql.conf
sudo systemctl restart polardb

The start time guard is what keeps the first two steps honest: it refuses to start the database if pg_hba.conf contains trust, md5, password or ident. It also refuses a non loopback host rule while listen_addresses is still localhost, which is why step 3 comes last.

Consider TLS as well. PolarDB is built with OpenSSL; drop a certificate and key into the data directory, set ssl = on, ssl_cert_file and ssl_key_file, and change your pg_hba.conf line from host to hostssl so plaintext connections are refused outright.

Moving the data directory to a managed disk

The shipped image keeps everything on the OS disk. For anything beyond evaluation, attach a managed data disk and move both PolarDB directories onto it.

# after attaching and mounting the disk at /data
sudo systemctl stop polardb
sudo rsync -aHAX /var/lib/polardb/ /data/polardb/
sudo mv /var/lib/polardb /var/lib/polardb.old
sudo ln -s /data/polardb /var/lib/polardb
sudo systemctl start polardb

Keep the polar_datadir path in postgresql.conf consistent with wherever shared/ ends up — the start time guard checks that the directory exists and is populated, and will refuse to start if it is not.

Backup and restore

Standard PostgreSQL tooling, unchanged:

APP_PASS=$(sudo sed -n 's/^POLARDB_APP_PASSWORD=//p' /root/polardb-credentials.txt)
SUPER_PASS=$(sudo sed -n 's/^POLARDB_SUPERUSER_PASSWORD=//p' /root/polardb-credentials.txt)

# dump the application database
PGPASSWORD="$APP_PASS" pg_dump -h 127.0.0.1 -p 5432 -U polarapp \
  -Fc -f /var/tmp/polarapp.dump polarapp

# restore it into a fresh database alongside the original
PGPASSWORD="$SUPER_PASS" psql -h 127.0.0.1 -p 5432 -U polardb -d postgres \
  -c "SELECT 'CREATE DATABASE restored' WHERE NOT EXISTS (SELECT 1 FROM pg_database WHERE datname='restored')\gexec"
PGPASSWORD="$SUPER_PASS" pg_restore -h 127.0.0.1 -p 5432 -U polardb \
  -d restored /var/tmp/polarapp.dump

pg_basebackup and polar_basebackup are both installed if you want a physical copy.

Where things live

Path What it is
/opt/polardb binaries, libraries and extensions (bin/, lib/postgresql/, share/postgresql/)
/var/lib/polardb/primary the local PGDATA — configuration and node local state
/var/lib/polardb/shared the PolarDB data directory — heap, catalogue, WAL, LogIndex
/var/log/polardb server logs (logging_collector is on)
/run/polardb the Unix domain socket
/root/polardb-credentials.txt this VM's generated passwords, mode 0600
/usr/share/polardb-postgresql licence, provenance, extension list, dependency licence inventory
/etc/systemd/system/polardb.service the engine unit
/usr/local/sbin/polardb-preflight.sh the start time guard

Logs:

sudo journalctl -u polardb -n 50 --no-pager
sudo ls -la /var/log/polardb

Tuning

The shipped settings are deliberately modest. On a larger VM, the two that matter most:

SUPER_PASS=$(sudo sed -n 's/^POLARDB_SUPERUSER_PASSWORD=//p' /root/polardb-credentials.txt)
PGPASSWORD="$SUPER_PASS" psql -h 127.0.0.1 -p 5432 -U polardb -d postgres \
  -c "ALTER SYSTEM SET shared_buffers = '2GB';" \
  -c "ALTER SYSTEM SET effective_cache_size = '6GB';"
sudo systemctl restart polardb

Rules of thumb: shared_buffers around 25 % of RAM, effective_cache_size around 75 %. Raise max_connections only if you genuinely need it — a connection pooler is almost always the better answer.

PolarDB adds its own knobs on top, all visible with SHOW ALL and all prefixed polar_. polar_logindex_mem_size, polar_xlog_queue_buffers and polar_xlog_page_buffers govern the replay machinery and are worth raising alongside shared_buffers on a large instance.

Security checklist

  • The image ships no database: initdb runs on your VM, so no default or blank password can exist.
  • Both passwords are generated per VM from the kernel CSPRNG and written only to /root/polardb-credentials.txt (0600, root).
  • password_encryption is scram-sha-256; every login role carries a SCRAM verifier. There is no trust and no md5 anywhere.
  • listen_addresses is localhost. The offer advertises only SSH.
  • polardb.service runs as an unprivileged system account with NoNewPrivileges, PrivateTmp, ProtectSystem=full, ProtectHome and a restricted address family set.
  • The start time guard refuses to start the engine if the credentials or the hardened configuration are missing or weakened.
  • The application role is NOSUPERUSER NOCREATEROLE NOCREATEDB NOBYPASSRLS.
  • No compiler, no headers and no build tree ship in the image.
  • Rotate a password with ALTER ROLE ... PASSWORD '...' and update the credentials file to match.

Troubleshooting

polardb.service will not start. The guard is refusing it. Ask why:

sudo /usr/local/sbin/polardb-preflight.sh; echo "exit=$?"

It prints exactly which condition failed. The most common cause on a rebuilt VM is a missing or placeholder /root/polardb-credentials.txt.

First boot did not run. Check the sentinel and the unit:

ls -la /var/lib/cloudimg/polardb-firstboot.done
sudo journalctl -u polardb-firstboot.service --no-pager | tail -40

Connection refused from your workstation. Expected — the database is on loopback. Use the SSH tunnel, or follow the exposure section above.

Password authentication failed. Re read the credentials file; the passwords are unique to this VM and are not the ones in any other document.

sudo sed -n 's/^POLARDB_APP_PASSWORD=//p' /root/polardb-credentials.txt

Checking what shipped. The build provenance and the full dependency licence inventory are on the machine:

cat /usr/share/polardb-postgresql/BUILD-PROVENANCE.txt
head -40 /usr/share/polardb-postgresql/dependency-licences.txt

Licence and provenance

PolarDB for PostgreSQL is licensed under the Apache License 2.0, over PostgreSQL which is under the PostgreSQL Licence. Both texts ship on the image at /usr/share/polardb-postgresql/LICENSE and /usr/share/polardb-postgresql/COPYRIGHT, with upstream's NOTICE alongside them.

The image is built from the pinned upstream tag v17.10.1.0, commit accf02e2fda3d8e47e78afa61f42795e0c4065c4. That commit is compiled into the binary and is what SELECT version() reports, so you can verify on your own VM exactly which upstream source produced the engine you are running.

Every vendored extension's own licence artifact is preserved at /usr/share/polardb-postgresql/source-licences/, and the complete OS dependency licence inventory is at /usr/share/polardb-postgresql/dependency-licences.txt.

Further reading

Support

This image is published by cloudimg. For help with the image itself — deployment, first boot, credentials, the start time guard — contact cloudimg support. For questions about PolarDB the software, upstream's documentation and GitHub discussions are the right place.