Application Infrastructure Azure

uTask on Ubuntu 24.04 on Azure User Guide

| Product: uTask 1.34.0 on Ubuntu 24.04 LTS on Azure

Overview

This guide covers the deployment and configuration of uTask on Ubuntu 24.04 on Azure using cloudimg Azure Marketplace images. uTask is an open source automation engine from OVH for turning operational runbooks into auditable, repeatable, self service tasks. You describe a process once as a YAML task template, with typed and validated inputs, steps that declare their dependencies, conditions, loops and schema checks on each step's output, and uTask executes it as a resumable, fully audited task: it retries what can be retried, pauses for human validation where you ask it to, and keeps a complete history of everything that ran, who requested it and what it returned.

The image installs uTask 1.34.0 from the official upstream release (a single self contained Go binary, SHA-256 verified during the build) with its PostgreSQL database on the same machine, and the official prebuilt Angular dashboard. A web dashboard and a REST API expose your templates to the people and pipelines that need them.

Authentication follows the shape uTask itself prescribes. uTask deliberately ships with no built in login: upstream documents that it "is meant to be placed behind a reverse proxy that provides a username through the x-remote-user http header". In this image nginx is that reverse proxy on port 80 and the sole network facing surface. It authenticates every request with HTTP Basic against a per instance credential and passes the verified username on to uTask, overwriting anything a client tries to claim for itself. uTask itself listens on 8081 and is bound away from the network by a host firewall (nftables), so the proxy cannot be sidestepped.

Secure by default, no shared credentials. Nothing secret is baked into the image, and the image ships with no database at all. On the first boot of every instance, uTask generates the PostgreSQL role password, the dashboard/API password and the storage encryption key that protects every task input and result at rest, then creates the database, loads the schema and writes the credentials to a root only file. No build time data and no shared key can follow you into production.

What is included:

  • uTask 1.34.0 (BSD-3-Clause) from the official upstream release binary, run under systemd (utask.service) as an unprivileged utask user, bound to 127.0.0.1:8081

  • PostgreSQL (postgresql.service), uTask's only dependency, bound to loopback and created fresh with a per instance role password on first boot

  • nginx (nginx.service) on port 80 as the only network facing service: it authenticates with HTTP Basic against a per instance credential and injects the authenticated username as x-remote-user

  • An nftables rule (nftables.service) that drops inbound 8081 from anywhere except the loopback interface, so the reverse proxy cannot be bypassed

  • Two self contained example templates (hello-world and http-healthcheck) preloaded so the dashboard is useful the moment you log in

  • A per instance dashboard/API password, database password and storage encryption key, generated fresh on first boot, with the values written to /root/utask-credentials.txt

Prerequisites

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

  • Subscription to the uTask listing on Azure Marketplace

  • Network Security Group rules allowing TCP 22 (admin) and TCP 80 (the dashboard and REST API) from the operators and pipelines that use uTask

Recommended virtual machine size: Standard_B2s (2 vCPU, 4 GB RAM) is ample for a team automation engine. Increase the size when you run many concurrent tasks or store a large task history.

Step 1: Deploy from the Azure Portal

Search uTask in Marketplace, select the cloudimg publisher, and click Create. Configure the Network Security Group to allow TCP 80 from the workstations and pipelines that will use uTask, and TCP 22 for administration. Keep 80 restricted to the networks you serve; put your own TLS certificate and domain in front for production.

Step 2: Deploy from the Azure CLI

RG="utask-prod"; LOCATION="eastus"; VM_NAME="utask1"
GALLERY_IMAGE_ID="/subscriptions/<sub-id>/resourceGroups/azure-cloudimg/providers/Microsoft.Compute/galleries/cloudimgGallery/images/utask-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
az vm open-port --resource-group "$RG" --name "$VM_NAME" --port 80 --priority 1001
az vm open-port --resource-group "$RG" --name "$VM_NAME" --port 22 --priority 1002

Step 3: First boot and per instance credentials

On first boot the image generates the per instance secrets, creates the PostgreSQL database and role, loads the schema, starts uTask and writes everything to /root/utask-credentials.txt. This completes within seconds. SSH in as azureuser and read the credentials file:

sudo cat /root/utask-credentials.txt

The file is 0600 root:root and records the dashboard URL, the admin username and password for the dashboard and API, and the database name, role and password. These values are unique to this instance. The storage encryption key that protects task data at rest lives in /etc/utask/utask.env and is likewise unique per instance.

Step 4: Confirm the services are running

SSH to the instance and confirm the four services are active and that nginx on port 80 is the only network facing listener, while uTask and PostgreSQL are bound to loopback:

systemctl is-active postgresql nftables utask nginx
ss -tlnp | grep -E ':80 |:8081 |:5432 '

Expected output:

active
active
active
active
LISTEN 0.0.0.0:80
LISTEN *:8081
LISTEN 127.0.0.1:5432

nginx listens on port 80 on all interfaces; PostgreSQL is bound to loopback. uTask binds 8081 on all interfaces because it has no bind address option of its own, which is exactly why the nftables rule below matters: it is what keeps 8081 unreachable from the network.

systemctl reports postgresql, nftables, utask and nginx all active, and ss shows nginx listening on port 80 on all interfaces while PostgreSQL is bound to loopback 127.0.0.1 on port 5432 and uTask listens on 8081 where it is firewalled off the network

Step 5: uTask is secure by default

uTask has no authentication of its own and trusts the x-remote-user header, so this image places it behind an authenticating reverse proxy and firewalls it off the network. Confirm that the dashboard and API require the per instance credential, that a client cannot reach uTask directly to spoof its identity, and that nginx sets the authenticated username itself:

PW=$(sudo grep -E '^UTASK_ADMIN_PASSWORD=' /root/utask-credentials.txt | cut -d= -f2-)
HOSTIP=$(hostname -I | awk '{print $1}')

# public liveness routes
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1/healthz
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1/unsecured/mon/ping

# the API is rejected without the per instance credential, accepted with it
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1/template
curl -s -o /dev/null -w '%{http_code}\n' -u admin:"$PW" http://127.0.0.1/template

# uTask on :8081 is unreachable off loopback, so x-remote-user cannot be spoofed
curl -s -o /dev/null -w '%{http_code}\n' -m 5 -H 'x-remote-user: admin' http://"$HOSTIP":8081/meta

# nginx reports the AUTHENTICATED username, overwriting any client supplied value
curl -s -u admin:"$PW" -H 'x-remote-user: not-this-user' http://127.0.0.1/meta

Expected output:

200
200
401
200
000
{"application_name":"uTask","user_is_admin":true,"username":"admin","user_groups":[],"version":"v1.34.0",...}

The /template API returns 401 with no credential and 200 with the per instance credential. The direct request to 8081 returns nothing (000) because the firewall dropped it. The /meta response reports admin, the authenticated user, not the not-this-user value the client tried to inject.

the health and liveness routes return HTTP 200 without credentials, the template API returns HTTP 401 without the per instance credential and HTTP 200 with it, a direct request to uTask on port 8081 off the loopback interface with a spoofed x-remote-user header is dropped, and the meta endpoint reports the authenticated username admin overwriting the client supplied value

Step 6: Run a task through the engine

This is what uTask exists to do. Create a task from the preloaded hello-world template through the REST API with the per instance credential, then watch the engine resolve it to state DONE and return a result:

PW=$(sudo grep -E '^UTASK_ADMIN_PASSWORD=' /root/utask-credentials.txt | cut -d= -f2-)

# create a task from the hello-world template
TID=$(curl -s -u admin:"$PW" -H 'Content-Type: application/json' \
  -d '{"template_name":"hello-world","input":{"name":"cloudimg"}}' \
  http://127.0.0.1/task | python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])')
echo "created task: $TID"

# poll until the engine resolves it
curl -s -u admin:"$PW" http://127.0.0.1/task/"$TID" | python3 -m json.tool | grep -A3 '"result"'

Expected output:

created task: 47ac2de2-f738-4d97-888e-71e1df1c2007
    "result": {
        "greeting": "Hello cloudimg, this task ran on uTask by cloudimg"
    },

The greeting field was produced by the template's echo step: the engine actually executed the workflow and recorded its output. A real task, not a page that merely loaded.

a task is created from the hello-world template through the REST API with the per instance credential, the engine resolves it to state DONE, and the task result contains the greeting produced by the template's echo step, Hello cloudimg this task ran on uTask by cloudimg

Step 7: Open the dashboard

Open http://<vm-public-ip>/ui/dashboard/ in a browser. Your browser will prompt for HTTP Basic credentials: enter admin and the UTASK_ADMIN_PASSWORD from the credentials file. The Tasks page lists every task and its state; the tasks you ran through the API in Step 6 appear here in green DONE state.

The uTask dashboard Tasks page on a freshly deployed cloudimg VM, listing several tasks all in green DONE state after the engine ran them

Click any task to see its detail: the task's status, the request that created it, the execution, and the Result panel with the exact output the engine produced.

A completed uTask task detail page showing the DONE status and the Result panel containing the engine-produced greeting JSON, with the request and execution sections below

The Templates page lists the automations available to run. The image ships two self contained example templates so the dashboard is useful immediately.

The uTask dashboard Templates page listing the two bundled example templates, hello-world and http-healthcheck, each with a description

Click New task to launch an automation from the browser: pick a template, fill in its typed inputs and submit. This is the self service path your team uses without touching the API.

The uTask dashboard New task form with the template selector open, offering the hello-world and http-healthcheck templates for a self service task run

Step 8: Author your own task templates

Templates live in /opt/utask/templates as YAML files. A template declares typed inputs, then steps with dependencies, conditions and action blocks. uTask ships builtin actions for HTTP calls, SSH commands, scripts, email, notifications and subtasks, so a template can drive the systems a real procedure touches. Copy the bundled hello-world.yaml as a starting point:

sudo cp /opt/utask/templates/hello-world.yaml /opt/utask/templates/my-first-template.yaml
sudo nano /opt/utask/templates/my-first-template.yaml
sudo systemctl restart utask

uTask loads templates from that directory at startup, so restart the service after adding or editing a template. Confirm it was picked up over the API:

PW=$(sudo grep -E '^UTASK_ADMIN_PASSWORD=' /root/utask-credentials.txt | cut -d= -f2-)
curl -s -u admin:"$PW" http://127.0.0.1/template | python3 -c 'import json,sys; print([t["name"] for t in json.load(sys.stdin)])'

The full template authoring reference — value templating, step conditions, loops, JSON schema validation and the builtin action catalogue — is documented upstream at github.com/ovh/utask.

Step 9: Per instance credentials and the encryption key

Every secret on the instance is unique to that instance and generated at first boot:

sudo cat /root/utask-credentials.txt
sudo -u postgres psql -tAc 'SELECT name FROM task_template ORDER BY name' utask

The credentials file is 0600 root:root. The storage encryption key in /etc/utask/utask.env is a 32 byte aes-gcm key that encrypts every task input and result at rest — back it up before you rotate it, because previously stored task data becomes unreadable without the key it was written with. The key rotation procedure is documented upstream.

the per instance credentials file is listed as 0600 root:root and shows the dashboard URL, admin username and database identity with the passwords redacted, the storage encryption key is a per instance 32 byte aes-gcm key, and the two seeded templates hello-world and http-healthcheck are listed from the instance PostgreSQL database

Step 10: Security recommendations

  • Front uTask with your own TLS certificate and domain. nginx serves plain HTTP on port 80; put your own certificate and a real hostname in front for production so credentials and task data are encrypted in transit.

  • Restrict port 80 to the networks you serve. A private automation engine rarely needs to be reachable from the whole internet; scope the Network Security Group to your operators and pipelines.

  • Integrate your own identity provider. For teams, replace the single HTTP Basic credential by having your reverse proxy or identity provider set x-remote-user (and optionally group headers) from real authenticated users; uTask uses that username for authorization and its admin_usernames list.

  • Rotate the dashboard credential and back up the encryption key. The per instance dashboard password lives in the nginx htpasswd; rotate it with htpasswd -B. Always back up the storage encryption key before rotating it.

  • Keep the base image patched. Unattended security upgrades are enabled; reboot after kernel updates.

Step 11: Support and Licensing

This is a repackaged open source software product with additional charges for cloudimg support services. uTask is licensed under the BSD 3 Clause License. All product and company names are trademarks or registered trademarks of their respective holders. Use of them does not imply any affiliation with or endorsement by them.

Deploy on Azure

The cloudimg uTask image is available on the Azure Marketplace. Search uTask by cloudimg, deploy into your subscription, and follow this guide.

Need Help?

cloudimg provides 24/7 technical support for this uTask product. We help with deployment and first boot configuration, authoring and debugging task templates, fronting the dashboard with your own TLS certificate and custom domain, integrating your identity provider, wiring notification backends, encryption key rotation, PostgreSQL sizing and backup, and uTask version upgrades. Critical issues receive a one hour average response time. Contact support@cloudimg.co.uk.