Deployment guide

DuckDB 1.5

Overview

DuckDB is an in-process analytical database engine. It reads Parquet, CSV, JSON, Iceberg and Delta Lake tables directly, with no extract-and-load step. The 1.5 series includes a built-in Web UI with a SQL editor, schema browser, query plan visualiser and notebook-style cell layout, served by the DuckDB process itself.

This SecureAnyCloud image installs the official DuckDB 1.5.2 CLI binary at /usr/local/bin/duckdb and runs the Web UI on Ubuntu 24.04 LTS on Azure. The UI is served as duckdb-ui.service, a long-lived Python wrapper that holds the persistent database open and serves the SPA on 127.0.0.1:4213.

nginx fronts the UI on port 80 with HTTP basic auth. The user is duckdb. The password is rotated on first boot to a 32-character random value and written to /stage/scripts/duckdb-credentials.log (mode 0600, root only). The persistent database lives at /var/lib/duckdb/main.duckdb, owned by the duckdb system user.

The DuckDB UI extension is pre-fetched and warmed during the build, so the first boot has no outbound dependency on community.duckdb.org or the extension CDN.

What you get:

  • The official DuckDB 1.5.2 CLI binary at /usr/local/bin/duckdb (linux amd64 release tarball, SHA256 verified at install time)
  • The DuckDB UI extension pre-warmed into /var/lib/duckdb/.duckdb/extensions/v1.5.2/linux_amd64/ui.duckdb_extension
  • duckdb-ui.service, a systemd unit running as the duckdb system user, holding the persistent database open and serving the UI on 127.0.0.1:4213
  • An nginx 1.24 reverse proxy on port 80 with HTTP basic auth, WebSocket upgrade for live query results, and an unauthenticated /healthz endpoint for load balancer health checks
  • duckdb-firstboot.service, a systemd oneshot that rotates the UI basic-auth password on first boot and writes the credentials to /stage/scripts/duckdb-credentials.log (mode 0600, root only)
  • A persistent database at /var/lib/duckdb/main.duckdb, owned by duckdb:duckdb
  • A Python 3.12 venv at /opt/duckdb-venv with the duckdb Python module pinned to 1.5.2, used by the UI wrapper and available for your own Python analytics
  • Bundled extensions on first launch: httpfs, parquet, json, icu, autocomplete, core_functions, jemalloc, shell, ui
  • An Ubuntu 24.04 LTS (Noble Numbat) base with the latest security patches applied at build time
  • The Azure Linux Agent for cloud integration and SSH key injection
  • 24/7 SecureAnyCloud support with a 24-hour response SLA

Prerequisites

You need:

  • An active Azure subscription
  • A subscription to the DuckDB 1.5 on Ubuntu 24.04 listing on Azure Marketplace
  • An SSH public key for VM authentication
  • A virtual network and subnet in the target region

Recommended VM size: Standard_D2s_v5 (2 vCPU, 8 GB RAM) for production analytical workloads. DuckDB benefits from L2/L3 cache and consistent single-thread performance, and the D-series provides both. Standard_B2s (2 vCPU, 4 GB RAM) is fine for evaluation and development.

For datasets over a few GB, attach a Premium SSD data disk and move /var/lib/duckdb/main.duckdb onto it before you populate the database.

1

Deploy from the Azure Portal

Open Marketplace in the Azure Portal, search for DuckDB, select the SecureAnyCloud publisher entry and click Create.

On the Networking tab, attach a network security group that allows inbound TCP 22 from your management IP range. Allow TCP 80 from your application VPN, jump host or an Azure Application Gateway / Front Door.

Do not expose port 80 directly to the public internet without TLS. The UI uses HTTP basic auth, which is fine over a private network or behind a TLS-terminating front door. Never send a basic-auth password over plain HTTP across the public internet.

Click Review + create, wait for validation, then click Create. Deployment takes around two minutes. The first-boot service rotates the UI password and writes the credentials file before the VM is reported as ready.

2

Deploy from the Azure CLI

RG="duckdb-prod" LOCATION="eastus" VM_NAME="duckdb-01" ADMIN_USER="azureuser" IMAGE_URN="sac1647283583153:duckdb-1-5-ubuntu-24-04:default:latest" SSH_KEY="$(cat ~/.ssh/id_rsa.pub)" az group create --name "$RG" --location "$LOCATION" az network vnet create --resource-group "$RG" --name duckdb-vnet \
  --address-prefix 10.74.0.0/16 --subnet-name duckdb-subnet --subnet-prefix 10.74.1.0/24 az network nsg create --resource-group "$RG" --name duckdb-nsg az network nsg rule create --resource-group "$RG" --nsg-name duckdb-nsg \
  --name allow-ssh --priority 100 \
  --source-address-prefixes "<your-mgmt-cidr>" \
  --destination-port-ranges 22 --access Allow --protocol Tcp az network nsg rule create --resource-group "$RG" --nsg-name duckdb-nsg \
  --name allow-ui --priority 110 \
  --source-address-prefixes 10.74.0.0/16 \
  --destination-port-ranges 80 --access Allow --protocol Tcp
# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:duckdb-1-5-ubuntu-24-04:default:latest
az vm create \
  --resource-group "$RG" --name "$VM_NAME" \
  --image "$IMAGE_URN" \
  --size Standard_D2s_v5 --storage-sku StandardSSD_LRS \
  --admin-username "$ADMIN_USER" --ssh-key-values "$SSH_KEY" \
  --vnet-name duckdb-vnet --subnet duckdb-subnet --nsg duckdb-nsg \
  --public-ip-sku Standard
3

Connect via SSH

ssh azureuser@<vm-ip>

On arrival, duckdb-ui.service and nginx.service are already running. duckdb-firstboot.service has already rotated the UI basic-auth password and written the credentials file.

4

Verify the DuckDB UI service

sudo systemctl status duckdb-ui.service --no-pager

The status should read active (running). Confirm the first-boot sentinel:

sudo test -f /var/lib/sac/data/duckdb-firstboot.done && echo FIRSTBOOT_DONE

Confirm the UI listener on 127.0.0.1:4213 and the nginx front door on :80:

sudo ss -tln | grep -E ':(80|4213) '

Expected output:

LISTEN 0 511 0.0.0.0:80 0.0.0.0:* LISTEN 0 5 127.0.0.1:4213 0.0.0.0:* LISTEN 0 511 [::]:80 [::]:*

Check the unauthenticated /healthz endpoint used by load balancer health probes:

curl -fsS http://127.0.0.1/healthz

It should return ok.

5

Retrieve the UI basic-auth password

Each VM rotates its own UI password on first boot and writes it to a root-only file. Read the file with:

sudo cat /stage/scripts/duckdb-credentials.log

The file contains lines similar to:

# DuckDB 1.5 — Per-VM Credentials
# Generated: Fri May 1 08:07:07 UTC 2026
#
# Web UI:
# URL: http://10.0.1.14/
# Username: duckdb
# Password: <DUCKDB_UI_PASSWORD>
# DUCKDB_UI_USER=duckdb DUCKDB_UI_PASSWORD=<DUCKDB_UI_PASSWORD> DUCKDB_UI_URL=http://<vm-ip>/ DUCKDB_DB_PATH=/var/lib/duckdb/main.duckdb LISTEN_PORT=80 UI_UPSTREAM_PORT=4213

The DUCKDB_UI_URL line records the IP that Azure IMDS reported at first boot. For Standard SKU public IPs, IMDS does not expose the public address, so the file falls back to the private IP. When you open the UI from outside the VNet, use the VM's public IP, the one you SSH to. Store the password in your secret store.

6

Open the DuckDB Web UI

Browse to http://<vm-ip>/. When nginx prompts for HTTP basic auth, enter username duckdb and the password from the credentials file. The DuckDB UI single-page app then loads. The default layout shows:

  • A SQL editor across the left and centre of the screen, with syntax highlighting and autocomplete
  • A schema browser on the right, listing attached databases, schemas, tables and columns (main is the default catalog, backed by /var/lib/duckdb/main.duckdb)
  • A query results pane below the editor, which renders tables, plots and query plans depending on the active tab
  • A notebook-style cell layout for chained queries, where each cell holds its own SQL statement

The UI talks to the HTTP server embedded in the running DuckDB process over WebSockets, so query results stream live as they execute. Drop a Parquet, CSV, JSON, Excel or Arrow file into the upload area at the top of the schema browser to attach it as a table instantly.

7

Run a test query in the Web UI

In the SQL editor, paste and run:

SELECT version(); CREATE OR REPLACE TABLE hello AS SELECT range AS i, range * 2 AS d FROM range(1000); SELECT count(*), sum(d) FROM hello;

The version cell returns v1.5.2, and the count and sum cell returns 1000 / 999000. The hello table is persisted in /var/lib/duckdb/main.duckdb and survives reboots.

8

Use the DuckDB CLI

Over SSH, you can open a DuckDB CLI session against any DuckDB file. Do not open /var/lib/duckdb/main.duckdb from the CLI while the UI service is running. DuckDB enforces a single-writer lock, and the UI process holds it. Either stop the service first or use a separate database file for CLI work:

# Use a scratch database — leaves the UI's main.duckdb alone sudo -u duckdb /usr/local/bin/duckdb /tmp/scratch.duckdb \
  -c "INSTALL httpfs; LOAD httpfs;" \
  -c "SELECT count(*) AS rows FROM read_parquet('https://blobs.duckdb.org/data/taxi_2019_04.parquet') LIMIT 1;"

The expected output is:

┌───────────┐ │ rows │ │ int64 │ ├───────────┤ │ 7433139 │ └───────────┘

To open the same main.duckdb that the UI uses, stop the service first:

sudo systemctl stop duckdb-ui.service
sudo -u duckdb /usr/local/bin/duckdb /var/lib/duckdb/main.duckdb -c "SHOW TABLES;"
sudo systemctl start duckdb-ui.service
9

Read Parquet from Azure Blob Storage

The httpfs and azure extensions let DuckDB read Parquet, CSV and JSON directly from Azure Blob Storage, with no intermediate copy. Run the following from the Web UI SQL editor or the CLI:

INSTALL azure; LOAD azure; -- For a public container, no credentials needed: SELECT count(*) FROM read_parquet('azure://<container>/<path>/*.parquet'); -- For a private container, set the connection string first: CREATE SECRET azure_blob ( TYPE AZURE, CONNECTION_STRING 'DefaultEndpointsProtocol=https;AccountName=<acct>;AccountKey=<key>;EndpointSuffix=core.windows.net' ); SELECT count(*) FROM read_parquet('azure://<container>/<path>/*.parquet');

Alternatively, authenticate with a Managed Identity, which is the recommended approach for production:

CREATE SECRET azure_mi ( TYPE AZURE, PROVIDER CREDENTIAL_CHAIN ); SELECT count(*) FROM read_parquet('azure://<container>/<path>/*.parquet');

The credential chain provider tries Managed Identity, Azure CLI, environment variables and Workload Identity. Use whichever fits your deployment. Grant the VM's system-assigned identity Storage Blob Data Reader on the target storage account.

10

Back up the persistent database

DuckDB's EXPORT DATABASE writes a portable directory of CSV or Parquet files plus a load script. Stop the UI service first to release the write lock, then run the export:

sudo systemctl stop duckdb-ui.service
sudo -u duckdb /usr/local/bin/duckdb /var/lib/duckdb/main.duckdb \
  -c "EXPORT DATABASE '/var/lib/duckdb/backup-$(date +%Y%m%d)' (FORMAT PARQUET);"
sudo systemctl start duckdb-ui.service

To restore, run the generated load.sql against a new database:

sudo -u duckdb /usr/local/bin/duckdb /var/lib/duckdb/restored.duckdb \
  -c ".read /var/lib/duckdb/backup-20260501/load.sql"

For frequent backups, schedule the export with a systemd timer or push the backup directory to Azure Blob Storage with azcopy. If you prefer a single file, DuckDB also supports ATTACH 'backup.duckdb' AS bk; COPY FROM DATABASE main TO bk; for whole-database copies.

11

Put TLS in front of the UI

The image ships nginx with HTTP basic auth on port 80. For internet-facing deployments, terminate TLS in front of the VM rather than reconfiguring nginx on the VM. Choose one of these options:

  • Azure Application Gateway with WAF v2 terminates TLS and uses a managed certificate from Key Vault. It lets you add rate limiting and IP allow-listing without touching the VM.
  • Azure Front Door Standard / Premium provides a global anycast endpoint, a managed certificate and a WAF policy.
  • Your own Caddy or nginx on a separate VM redirects 443 to port 80 on the DuckDB VM and handles the certificate through Let's Encrypt.

If you must terminate TLS on the VM itself, install a real certificate with certbot. Change the listen 80; lines in /etc/nginx/sites-available/duckdb to listen 443 ssl;, add the ssl_certificate and ssl_certificate_key directives, and add a 301-redirect server block on port 80.

12

Rotate the UI password

Rotate the UI basic-auth password at any time, without rebuilding the image:

NEW_PASS=$(openssl rand -hex 16)
sudo htpasswd -bc /etc/nginx/.duckdb-htpasswd duckdb "${NEW_PASS}"
sudo systemctl reload nginx echo "New UI password: ${NEW_PASS}"

Update /stage/scripts/duckdb-credentials.log to match. The htpasswd file is owned root:www-data with mode 0640. nginx reads it on each request, so the reload is all that is needed.

Support

SecureAnyCloud provides 24/7/365 technical support for this image. Email sales@secureanycloud.com for help with deployment, configuration, performance tuning or DuckDB questions. Non-critical tickets receive a response within 24 hours, and critical incidents within one hour on average.

For DuckDB documentation, see the upstream project at duckdb.org/docs, starting with the SQL reference and the Python API. The DuckDB community Discord and GitHub Discussions are also available.

Deploy on Azure

Launch DuckDB 1.5 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