Deployment guide

DBLab Engine

Overview

This guide covers the deployment and configuration of DBLab Engine on Ubuntu 24.04 on Azure, using SecureAnyCloud Azure Marketplace images.

DBLab Engine (Database Lab Engine, DLE) is postgres.ai's database-branching engine for PostgreSQL. It takes a snapshot of a source database and then hands out thin clones: full-size, fully writable PostgreSQL instances that are created in seconds and cost almost nothing on disk. They share their unchanged blocks with the snapshot and store only what you modify.

That single property changes how teams work with production-scale data. You can rehearse a destructive migration against real data and throw it away. You can tune a slow query against a real plan on a real dataset. Every engineer, and every CI job, can have their own copy of a database that would be far too large and far too slow to duplicate conventionally.

What you get is the complete appliance on a single virtual machine:

  • DBLab Engine 4.1.3: the engine and REST API on TCP 2345
  • The Community Edition web UI, served through nginx on TCP 80
  • A bundled demo source database: PostgreSQL 18 with a 50,000-row orders table, so the appliance clones something real the moment it boots
  • A dedicated ZFS pool, built at first boot on a separate 32 GiB data disk, which is what makes cloning copy-on-write
  • DBLab Engine 4.1.3 and the Community Edition UI (Apache License 2.0), pinned to immutable container image digests
  • ZFS from Ubuntu's kernel-shipped module, so a kernel upgrade can never leave the pool unimportable
  • A first-boot service that generates the verification token and the demo database password uniquely per virtual machine
  • A shipped self-test that proves copy-on-write cloning works, end to end

The appliance is self-demonstrating and standalone. It does not need an external database to be useful on day one. First boot creates the pool, seeds the demo source database, takes the first snapshot and starts the engine.

You can create your first thin clone minutes after the machine comes up. Repoint the engine at your own PostgreSQL when you are ready (Step 11).

The five appliance services, the ZFS pool that clones are branched from, and the only two ports reachable from off the machine.

Security model

A DBLab engine can clone entire databases, so an unauthenticated engine is a data-disclosure hole, not a mere misconfiguration. This image treats the API and the clones as privileged:

  • Only TCP 80 (the web UI) and TCP 22 (SSH) are reachable from the network.
  • The engine API (2345), the UI container (2346), the bundled source database (5432) and the entire thin-clone port range (6000-6099) bind 127.0.0.1 only. The clone range keeps upstream's cloneAccessAddresses: 127.0.0.1 default deliberately. A thin clone is a complete, writable copy of the source database, so exposing it on a public interface would publish the data it holds. You reach clones over an SSH tunnel (Step 8).
  • No default credential ships. The engine's verification token and the demo database password are generated per virtual machine at first boot and written to a 0600 root:root file. Neither the engine nor the source database can start until first boot has created them, because both units are gated on a bootstrap marker that first boot writes last.
  • The engine runs as Community Edition and never consults a billing path. The DLE_COMPUTING_INFRASTRUCTURE variable is deliberately never set on this image. Setting it switches the engine to Standard Edition, where snapshotting and cloning silently stop working without an active postgres.ai billing subscription.

Prerequisites

  • An Azure subscription with permission to create virtual machines
  • An SSH key pair for administrative access
  • A network security group allowing inbound TCP 22 (SSH) and 80 (DBLab web UI) from your own address ranges
  • Recommended size: Standard_B2ms (2 vCPU, 8 GiB) or larger. The engine, the source database and every running clone are PostgreSQL processes on one machine, so memory matters most as the clone count grows.
1

Deploy from the Azure Portal

  • In the Azure Portal, choose Create a resource and search for DBLab Engine on Ubuntu 24.04 LTS by SecureAnyCloud.
  • Select the offer and click Create.
  • Choose your subscription, resource group and region.
  • Set the virtual machine size to Standard_B2ms or larger.
  • Under Administrator account, select SSH public key and supply your key. The admin username is azureuser.
  • Under Inbound port rules, allow SSH (22) and HTTP (80).
  • Select Review + create, then Create.

The image carries a dedicated data disk for the ZFS pool. Azure provisions it automatically with the virtual machine, so you do not need to add or format a disk.

2

Deploy from the Azure CLI

# Create a resource group az group create --name dblab-rg --location eastus
# Create the virtual machine from the sac offer az vm image terms accept --urn sac1647283583153:dblab-engine:default:latest az vm create \
  --resource-group dblab-rg \
  --name dblab-vm \
  --image sac1647283583153:dblab-engine:default:latest \
  --size Standard_B2ms \
  --admin-username azureuser \
  --generate-ssh-keys \
  --public-ip-sku Standard
# Open the web UI port az vm open-port --resource-group dblab-rg --name dblab-vm --port 80 --priority 1010
# Retrieve the public IP address of the new virtual machine az vm show -d --resource-group dblab-rg --name dblab-vm --query publicIps -o tsv
3

Connect via SSH

ssh azureuser@<vm-ip>

First boot creates the ZFS pool, seeds the demo database, takes the first snapshot and starts the engine. That takes two to three minutes on a Standard_B2ms. The rest of this guide assumes that it has finished.

4

Verify the services

Five services make up the appliance. All five should report active:

systemctl is-active docker nginx dblab-pool dblab-source-db dblab-server

Expected output:

active active active active active

Confirm that the ZFS pool that clones are branched from is online and backed by the dedicated data disk:

zpool list zfs list -t all

In the expected output, the pool is ONLINE and its mount point is /var/lib/dblab/dblab_pool:

NAME SIZE ALLOC FREE CKPOINT EXPANDSZ FRAG CAP DEDUP HEALTH ALTROOT dblab_pool 31.5G 36.6M 31.5G - - 0% 0% 1.00x ONLINE -
5

Confirm only the web UI is exposed

Check what is actually listening. Only 22 and 80 hold a wildcard socket. Everything else is bound to loopback:

ss -tlnH | awk '{print $1, $4}' | grep -vE '127.0.0.5[34]' | sort -u

Expected output:

LISTEN 0.0.0.0:22 LISTEN 0.0.0.0:80 LISTEN 127.0.0.1:2345 LISTEN 127.0.0.1:2346 LISTEN 127.0.0.1:5432 LISTEN [::]:22 LISTEN [::]:80

Prove that the loopback binding is a deliberate choice and not a dead service. nginx answers on the machine's own routable address while the engine port is refused there, and the engine answers on loopback:

IP=$(hostname -I | awk '{print $1}')
curl -s -o /dev/null -w "http://$IP/ -> HTTP %{http_code}\n" -m 8 "http://$IP/" timeout 4 bash -c "exec 3<>/dev/tcp/$IP/2345" 2>/dev/null && echo "2345 REACHABLE" || echo "$IP:2345 -> connection refused"
curl -s -o /dev/null -w "127.0.0.1:2345 (no token) -> HTTP %{http_code}\n" http://127.0.0.1:2345/status

The web UI answers on the routable address, every other port refuses there, and the API rejects an unauthenticated caller. The image is patched at build time and ships with no swap.

6

Retrieve the verification token

Every virtual machine generates its own credentials on first boot. Read them:

sudo cat /stage/scripts/dblab-engine-credentials.log

The file is 0600 root:root. It holds the web UI address, the verification token, the API base URL, and the demo source database credentials.

The verification token grants full control of the engine, including cloning entire databases. Treat it as a secret.

Confirm that the engine is healthy and running as Community Edition:

curl -s -H 'Verification-Token: <DBLAB_VERIFICATION_TOKEN>' http://127.0.0.1:2345/status \
  | jq '{status: .status.code, version: .engine.version, edition: .engine.edition, pool: .pools[0].name, mode: .pools[0].mode, retrieval: .retrieving.status}'

Expected output:

{ "status": "OK", "version": "v4.1.3-20260508-1125", "edition": "community", "pool": "dblab_pool", "mode": "zfs", "retrieval": "finished" }

Per-VM credentials in a root-only file, and an engine reporting Community Edition on a ZFS pool.

7

Run the shipped self-test

The image ships an end-to-end self-test. It does more than check that the API returns 200. It takes the engine's snapshot, creates a real thin clone and confirms that the clone carries the source data. It then writes 1,000 rows into the clone, re-counts the source and asserts that the source is unchanged.

That copy-on-write independence is the product. Run the test:

sudo /usr/local/sbin/dblab-selftest.sh

The test creates a thin clone, writes to it, proves that the clone does not affect the source, and destroys it. It also confirms that the API refuses an unauthenticated call.

8

Create your first thin clone

Create clones through the REST API. Ask the engine which snapshots exist, then branch a clone from the newest one:

SNAP=$(curl -s -H 'Verification-Token: <DBLAB_VERIFICATION_TOKEN>' \
  http://127.0.0.1:2345/snapshots | jq -r '.[0].id')
echo "cloning from snapshot: $SNAP" curl -s -X POST -H 'Verification-Token: <DBLAB_VERIFICATION_TOKEN>' \
  -H 'Content-Type: application/json' \
  -d "{\"id\":\"my-clone\",\"snapshot\":{\"id\":\"$SNAP\"},\"protected\":false,\"db\":{\"username\":\"engineer\",\"password\":\"choose-a-password\"}}" \
  http://127.0.0.1:2345/clone | jq -c '{id: .id, status: .status.code}'

Poll until the clone reports OK and note the port it was given:

for i in $(seq 1 40); do
CJ=$(curl -s -H 'Verification-Token: <DBLAB_VERIFICATION_TOKEN>' http://127.0.0.1:2345/clone/my-clone)
ST=$(echo "$CJ" | jq -r '.status.code'); [ "$ST" = "OK" ] && break; sleep 3 done echo "$CJ" | jq -c '{id: .id, status: .status.code, port: .db.port, user: .db.username}'

Connect to the clone on the port that the engine assigned to it. The clone is a complete, writable PostgreSQL instance carrying every row that the source had:

PORT=$(curl -s -H 'Verification-Token: <DBLAB_VERIFICATION_TOKEN>' \
  http://127.0.0.1:2345/clone/my-clone | jq -r '.db.port') PGPASSWORD='choose-a-password' psql -h 127.0.0.1 -p "$PORT" -U engineer -d demo \
  -c "SELECT count(*) AS rows_in_clone FROM orders"

Write to it, then confirm the source is untouched:

PORT=$(curl -s -H 'Verification-Token: <DBLAB_VERIFICATION_TOKEN>' \
  http://127.0.0.1:2345/clone/my-clone | jq -r '.db.port') PGPASSWORD='choose-a-password' psql -h 127.0.0.1 -p "$PORT" -U engineer -d demo \
  -c "DELETE FROM orders WHERE id % 2 = 0" -c "SELECT count(*) AS rows_after_delete FROM orders" PGPASSWORD='<DEMO_PASSWORD>' psql -h 127.0.0.1 -p 5432 -U postgres -d demo \
  -c "SELECT count(*) AS rows_in_source FROM orders"

The clone loses half its rows, and the source still reports 50,000. Throw the clone away when you are done:

curl -s -X DELETE -H 'Verification-Token: <DBLAB_VERIFICATION_TOKEN>' \
  http://127.0.0.1:2345/clone/my-clone sleep 5
curl -s -o /dev/null -w 'clone after delete -> HTTP %{http_code} (404 = reaped)\n' \
  -H 'Verification-Token: <DBLAB_VERIFICATION_TOKEN>' http://127.0.0.1:2345/clone/my-clone

Reaching a clone from your laptop

Clone ports are bound to loopback on purpose, because a clone holds a full copy of the source data. Open an SSH tunnel from your own machine, and connect through it:

ssh -L 6000:127.0.0.1:6000 azureuser@<vm-ip>

Then point any PostgreSQL client at localhost:6000. If your clone is not the first one on the machine, substitute the port that the engine reported for it.

9

Use the web UI

Open http://<vm-ip>/ in a browser. The Community Edition UI asks for the verification token from Step 6.

The CE UI authenticates with the per-VM verification token. There is no default login to guess.

After you authenticate, the instance overview opens. It shows the cloning summary, engine version, retrieval status, and the ZFS pool with its free space.

The overview also shows average cloning time, live clone count, engine version and pool health, with the clone list underneath.

The Snapshots tab shows what makes thin cloning cheap. The snapshot below carries 55.476 MiB of logical data but occupies 64 KiB of physical space, and one clone is already branched from it.

Logical size versus physical size is the copy-on-write saving, made visible.

The Clones tab lists every live clone with the port and database user needed to connect, plus the diff size that each one has accumulated since it was branched.

The example shows a live thin clone on port 6000, with the data that it has diverged by since branching.

You can also create and destroy clones from this screen with Create clone, which makes the same API call as Step 8.

10

How snapshots are refreshed

The engine re-dumps and re-restores the source database on a schedule, then takes a fresh snapshot. On this image, the schedule is weekly:

sudo grep -A3 '^retrieval:' /etc/dblab/server.yml

Older snapshots are retained so that clones branched from them keep working.

To take a snapshot immediately rather than waiting for the schedule, use Full refresh on the instance overview, or the /full-refresh API endpoint.

11

Clone your own database

The bundled demo database exists so that the appliance can demonstrate itself. To clone your real data, repoint the engine's retrieval configuration at your own PostgreSQL server.

Look at the logicalDump job in /etc/dblab/server.yml. This is the part you change:

sudo sed -n '/logicalDump:/,/logicalRestore:/p' /etc/dblab/server.yml

Open that file in your editor of choice (sudo vi /etc/dblab/server.yml). Replace the bundled source with your server's connection details:

- name: logicalDump options: source: type: remote connection: host: your-postgres-host.example.com port: 5432 dbname: your_database username: your_readonly_user password: your_password

Then restart the engine and trigger a refresh:

sudo systemctl restart dblab-server

Keep these practical notes in mind:

  • Use a read-only role for the dump connection. DBLab only ever reads from your source.
  • The pool must hold the logical size of your database plus the divergence of every live clone. Resize the data disk before you point the engine at a database larger than the 32 GiB pool that this image ships with.
  • Larger databases take proportionally longer for the first dump and restore. Watch progress with journalctl -u dblab-server -f.
  • For very large databases, postgres.ai documents a physical (physicalRestore) retrieval mode that attaches to a replica instead of dumping. See the upstream configuration reference.
12

Grow the pool

The ZFS pool lives on a dedicated Azure data disk. To enlarge it, expand the disk in Azure. Then tell ZFS to use the extra space:

sudo zpool set autoexpand=on dblab_pool
sudo zpool online -e dblab_pool /dev/disk/azure/scsi1/lun0 zpool list

Always address the device through /dev/disk/azure/scsi1/lun0 rather than /dev/sdX, because Linux device letters can shift between boots on Azure.

13

Managing the services

# Status systemctl status dblab-server --no-pager
# Engine logs
sudo journalctl -u dblab-server -n 50 --no-pager
# Container logs from the engine itself
sudo docker logs --tail 50 dblab_server
# The bundled demo source database systemctl status dblab-source-db --no-pager

The engine, the UI and the source database all run as containers pinned to immutable image digests. The containers are pre-pulled into the image, so your virtual machine never needs to reach a container registry to start.

14

Troubleshooting

First boot did not complete. The engine and the source database are deliberately gated behind a marker that first boot writes only after it creates the credentials, so neither can start with a default secret. Check the first-boot service:

systemctl status dblab-engine-firstboot.service --no-pager sudo journalctl -u dblab-engine-firstboot.service -n 40 --no-pager

The engine reports no pools. First boot creates the pool on the dedicated data disk. Confirm that the disk is present and the pool is imported:

ls -l /dev/disk/azure/scsi1/lun0 zpool status findmnt /var/lib/dblab/dblab_pool

A clone will not start. Clones are PostgreSQL containers, and they fail to come up if the machine is out of memory. Check what the engine recorded:

curl -s -H 'Verification-Token: <DBLAB_VERIFICATION_TOKEN>' http://127.0.0.1:2345/status \
  | jq '{numClones: .cloning.numClones, clones: [.cloning.clones[] | {id, status: .status.code}]}' free -h

Each running clone is a full PostgreSQL process. Move to a larger virtual machine size before you run many clones concurrently.

Cloning or snapshotting silently stops working. Check that the engine still reports community:

curl -s -H 'Verification-Token: <DBLAB_VERIFICATION_TOKEN>' http://127.0.0.1:2345/status | jq -r '.engine.edition'

If it reports standard, the DLE_COMPUTING_INFRASTRUCTURE environment variable has been set somewhere. Standard Edition requires an active postgres.ai billing subscription and refuses to snapshot or clone without one.

This image never sets that variable, so leave it unset.

15

Security recommendations

  • Restrict inbound 80 and 22 to your own address ranges in the network security group.
  • Put TLS in front of the web UI before you expose it broadly. Terminate TLS at an Azure Application Gateway, or add a certificate to the nginx vhost at /etc/nginx/sites-available/dblab.
  • Treat the verification token as a root-equivalent secret. It can clone any database the engine can reach. Rotate it by editing server.verificationToken in /etc/dblab/server.yml and restarting dblab-server.
  • Leave the clone port range bound to loopback and reach clones over an SSH tunnel. A clone is a complete copy of your source data.
  • Use a read-only role for the engine's connection to your source database.
  • Keep the machine patched: sudo apt update && sudo apt upgrade.
16

Support and licensing

DBLab Engine and its Community Edition UI are distributed under the Apache License 2.0. PostgreSQL is distributed under the PostgreSQL License. There are no subscription or license fees for the software in this image.

postgres.ai sells DBLab Standard Edition, which adds the hosted postgres.ai console, organization-level access control and vendor support. It does not unlock engine functionality. Snapshotting, branching and thin cloning are all Community Edition features, and all of them work on this image.

SecureAnyCloud provides 24/7 support for the image itself, its first-boot provisioning and its configuration.

Deploy on Azure

Find DBLab Engine on Ubuntu 24.04 LTS in the Azure Marketplace.

Need Help?

Contact SecureAnyCloud support at sales@secureanycloud.com.

Deploy on Azure

Launch DBLab Engine on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.

View this image

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on Azure