pg_timetable on Ubuntu 24.04 on Azure User Guide
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.servicesystemd 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-minuteBUILTINLog 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

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;'

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;'

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;'

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-tasksdaemon flag if you only need SQL and built-in tasks. -
Protect the scheduler database. Schedules, task definitions and execution history live in the
timetabledatabase. 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.