Applications Azure

pg_timetable on Ubuntu 24.04 on Azure User Guide

| Product: pg_timetable PostgreSQL Job Scheduler on Ubuntu 24.04 LTS on Azure

Overview

This guide covers the deployment and use of pg_timetable on Ubuntu 24.04 on Azure using cloudimg Azure Marketplace images. pg_timetable is the advanced, database-driven job scheduler for PostgreSQL from CYBERTEC. Unlike cron, every schedule, task and execution log lives inside PostgreSQL: you define chains of SQL, shell (PROGRAM) and BUILTIN tasks, schedule them with full cron syntax or intervals, and pg_timetable executes them transactionally and records every run in the database.

To make the appliance useful the moment it boots, the image installs a local PostgreSQL 16 instance (from the official PostgreSQL PGDG repository) and wires pg_timetable to it. On first boot the scheduler bootstraps its timetable schema and seeds a demo chain that inserts a timestamped row every minute, so you can watch a real scheduled job firing immediately and then define your own.

Secure by design — loopback only, no public database port. The bundled PostgreSQL listens on 127.0.0.1 only. The image opens only TCP 22 in its Network Security Group; there is no public database port to attack. pg_timetable reaches PostgreSQL entirely over the loopback interface as an unprivileged scheduler role.

Secure by design — no baked credential. Nothing is baked into the image. On first boot the PostgreSQL superuser and the scheduler application role each receive an independent random password. The scheduler's connection string is written to /etc/pg_timetable/pg_timetable.env (mode 0600, read by systemd), and a root only summary is written to /root/pg-timetable-credentials.txt. The PostgreSQL data directory is reset when the image is built, so every deployed VM starts from a clean cluster with fresh credentials.

What is included:

  • pg_timetable v7.0.0 (the pinned upstream Go binary) running as the pg-timetable.service systemd daemon

  • A bundled local PostgreSQL 16 instance, listening on the loopback interface only, that backs the scheduler

  • A seeded demo chain proving the scheduler works: a per-minute SQL task appending a timestamped row to public.cloudimg_demo, plus a per-minute BUILTIN Log task

  • Full cron-style and interval scheduling, with schedules, tasks and execution history stored entirely inside PostgreSQL

  • Per VM PostgreSQL credentials generated on first boot, stored in the daemon's environment file and summarised in a root only file, never baked into the image

  • A prep-only first boot that mints credentials and seeds the demo chain, then hands off to a sentinel-gated daemon so the scheduler never starts with a default credential

  • Unattended security upgrades left enabled so the appliance keeps receiving patches

Prerequisites

  • Active Azure subscription, SSH public key, VNet + subnet in the target region

  • Subscription to the pg_timetable listing on Azure Marketplace

  • A Network Security Group rule allowing TCP 22 (admin) from your management network. No database port is exposed by this appliance.

Recommended virtual machine size: Standard_B2s (2 vCPU, 4 GB RAM) for the demo and light scheduling workloads. For busier schedules or larger backing databases, use Standard_D2s_v5 or larger.

Step 1: Deploy from the Azure Portal

Search pg_timetable in Marketplace, select the cloudimg publisher, and click Create. Configure the Network Security Group to allow TCP 22 for administration from your management network. No inbound database port is required — the bundled PostgreSQL is loopback only.

Step 2: Deploy from the Azure CLI

RG="scheduler-prod"; LOCATION="eastus"; VM_NAME="pgtt1"
GALLERY_IMAGE_ID="/subscriptions/<sub-id>/resourceGroups/azure-cloudimg/providers/Microsoft.Compute/galleries/cloudimgGallery/images/pg-timetable-ubuntu-24-04/versions/<version>"
SSH_KEY="$(cat ~/.ssh/id_rsa.pub)"
az group create --name "$RG" --location "$LOCATION"
az vm create \
  --resource-group "$RG" --name "$VM_NAME" \
  --image "$GALLERY_IMAGE_ID" \
  --size Standard_B2s \
  --admin-username azureuser \
  --ssh-key-values "$SSH_KEY" \
  --public-ip-sku Standard
# Open only the admin port on the VM's NSG:
az vm open-port --resource-group "$RG" --name "$VM_NAME" --port 22 --priority 1001

Step 3: First boot

On first boot the image starts PostgreSQL on the loopback interface, generates a per VM password for the PostgreSQL superuser and the scheduler role, creates the timetable database, bootstraps the pg_timetable schema, seeds the demo chain, writes /root/pg-timetable-credentials.txt, and then starts the scheduler daemon. This completes within a minute. SSH in as azureuser and read the details:

sudo cat /root/pg-timetable-credentials.txt

Step 4: Confirm the scheduler and its database are healthy

The scheduler daemon runs as pg-timetable.service and the bundled PostgreSQL is loopback only. Confirm both are active, and that PostgreSQL is bound to 127.0.0.1 with no public database port.

systemctl is-active postgresql.service pg-timetable.service
ss -tlnH | grep 5432

The postgresql and pg-timetable services both report active and enabled, and ss shows PostgreSQL listening only on 127.0.0.1:5432 with no public database port

Step 5: See the installed version and the registered chains

pg_timetable stores every chain in the timetable.chain table. The appliance ships v7.0.0 and two seeded demo chains, both live and scheduled every minute (* * * * *).

/usr/local/bin/pg_timetable --version
sudo -u postgres psql -d timetable -c \
  'SELECT chain_id, chain_name, run_at AS schedule, live FROM timetable.chain ORDER BY chain_id;'

pg_timetable prints version 7.0.0 and its database schema number, and the timetable.chain query lists the two live demo chains cloudimg_demo_tick and cloudimg_builtin_log, each scheduled every minute

Step 6: Watch the scheduled chain actually run

This is the heart of the appliance. The cloudimg_demo_tick chain runs an SQL task every minute that appends a timestamped row to public.cloudimg_demo. Query the table a few times and watch new rows appear, each with a real, minute-spaced timestamp — proof the scheduler is genuinely firing.

sudo -u postgres psql -d timetable -c \
  'SELECT id, message, ran_at FROM public.cloudimg_demo ORDER BY ran_at DESC LIMIT 8;'

The public.cloudimg_demo table shows rows accumulating with real per-minute timestamps, confirming the scheduled chain is firing every minute and writing data into PostgreSQL

Step 7: Inspect the execution log

pg_timetable records every task it runs in timetable.execution_log, including the start and finish time and the return code (0 = success). This is your audit trail for scheduled work.

sudo -u postgres psql -d timetable -c \
  'SELECT chain_id, task_id, last_run, finished, returncode FROM timetable.execution_log ORDER BY last_run DESC LIMIT 8;'

The timetable.execution_log query lists recent task executions for both demo chains, each with a start and finish timestamp and returncode 0, and the per-VM credentials file is shown to exist as 0600 root with its values masked

Step 8: Define your own scheduled job

Add your own jobs with the timetable.add_job() helper. The connection string for the scheduler role is in /etc/pg_timetable/pg_timetable.env; the simplest way to run SQL against the scheduler database as an admin is over the local peer connection as the postgres user. This example runs a VACUUM every night at 02:00:

sudo -u postgres psql -d timetable <<'SQL'
SELECT timetable.add_job(
    job_name     => 'nightly_vacuum',
    job_schedule => '0 2 * * *',
    job_command  => 'VACUUM',
    job_kind     => 'SQL'::timetable.command_kind
);
SQL

Chains can combine SQL, shell (PROGRAM) and BUILTIN tasks, run steps in sequence with per-task error handling, and use interval schedules such as @every 30 minutes as well as cron syntax. See the CYBERTEC documentation and the bundled examples under the release samples/ directory for the full task model.

Step 9: Manage the scheduler daemon

The daemon reads its connection string from /etc/pg_timetable/pg_timetable.env and runs as the unprivileged pgtt user. Standard systemd commands apply:

systemctl status pg-timetable.service
journalctl -u pg-timetable.service -n 50 --no-pager
sudo systemctl restart pg-timetable.service

Step 10: Security recommendations

  • Keep the database private. The bundled PostgreSQL is loopback only and no database port is exposed. If you connect additional databases, reach them over a private VNet, never a public address.

  • Restrict the NSG. Allow TCP 22 only from your management network.

  • Guard what your chains can do. PROGRAM (shell) tasks run commands on the host. Review any chains you add, and consider the --no-program-tasks daemon flag if you only need SQL and built-in tasks.

  • Protect the scheduler database. Schedules, task definitions and execution history live in the timetable database. Restrict access to the VM and back the database up as you would any production PostgreSQL.

  • Keep the OS and PostgreSQL patched. Unattended security upgrades remain enabled on the running VM.

Step 11: Support and Licensing

pg_timetable is developed by CYBERTEC PostgreSQL International GmbH and distributed under the PostgreSQL License (a permissive, BSD-style license). This cloudimg image ships the unmodified upstream pg_timetable v7.0.0 binary alongside the PGDG PostgreSQL 16 packages. cloudimg provides the packaging, the secure by default loopback only configuration, the prep-only first boot credential generation, the sentinel-gated daemon, and 24/7 support with a guaranteed 24 hour response SLA.

cloudimg is not affiliated with or endorsed by CYBERTEC. pg_timetable and PostgreSQL are marks of their respective owners and are used here only to identify the software.

Deploy on Azure

Find pg_timetable on Ubuntu 24.04 LTS on the Azure Marketplace, published by cloudimg. Deploy from the Portal or the Azure CLI as shown above.

Need Help?

Email support@cloudimg.co.uk for deployment help, configuration questions, or licensing enquiries.