Deployment guide

uTask

Overview

uTask is an open source automation engine from OVH that turns operational runbooks into auditable, repeatable, self-service tasks. You describe a process once as a YAML task template, with typed and validated inputs and steps that declare their dependencies, conditions, loops and schema checks on each step's output. uTask then runs 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 what ran, who requested it and what it returned.

This SecureAnyCloud image runs uTask on Ubuntu 24.04 on Azure. It 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 model uTask prescribes. uTask 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 only network-facing surface. It authenticates every request with HTTP Basic against a per-instance credential and passes the verified username to uTask, overwriting anything a client tries to claim. uTask listens on 8081 and is kept off the network by a host firewall (nftables), so the proxy cannot be bypassed.

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. It then creates the database, loads the schema and writes the credentials to a root-only file. No build-time data and no shared key carry over into your deployment.

What you get:

  • 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, authenticating with HTTP Basic against a per-instance credential and injecting 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 from your first login
  • A per-instance dashboard/API password, database password and storage encryption key, generated on first boot, with the values written to /root/utask-credentials.txt

Prerequisites

You need an active Azure subscription, an SSH public key, and a VNet with a subnet in the target region. Subscribe to the uTask listing on Azure Marketplace.

Configure Network Security Group rules that allow TCP 22 (administration) 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. Choose a larger size if you run many concurrent tasks or store a large task history.

1

Deploy from the Azure Portal

Search Marketplace for uTask, select the SecureAnyCloud publisher, and choose 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 port 80 restricted to the networks you serve. For production, put your own TLS certificate and domain in front of it.

2

Deploy from the Azure CLI

RG="utask-prod"; LOCATION="eastus"; VM_NAME="utask1" IMAGE_URN="sac1647283583153:utask-ubuntu-24-04:default:latest" SSH_KEY="$(cat ~/.ssh/id_rsa.pub)" az group create --name "$RG" --location "$LOCATION"
# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:utask-ubuntu-24-04:default:latest
az vm create \
  --resource-group "$RG" --name "$VM_NAME" \
  --image "$IMAGE_URN" \
  --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
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. It 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. It is also unique per instance.

4

Confirm the services are running

SSH to the instance. Confirm that the four services are active and that nginx on port 80 is the only network-facing listener, while PostgreSQL is 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, and PostgreSQL is bound to loopback. uTask binds 8081 on all interfaces because it has no bind address option of its own. The nftables rule is what keeps 8081 unreachable from the network.

5

Verify the authentication model

uTask has no authentication of its own and trusts the x-remote-user header. This image therefore places it behind an authenticating reverse proxy and firewalls it off the network. Run the checks below to confirm three things. The dashboard and API require the per-instance credential. A client cannot reach uTask directly to spoof its identity. 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 drops it. The /meta response reports admin, the authenticated user, not the not-this-user value the client tried to inject.

6

Run a task through the engine

Create a task from the preloaded hello-world template through the REST API, using 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":"sac"}}' \
  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 sac, this task ran on uTask by sac" },

The greeting field was produced by the template's echo step. The engine executed the workflow and recorded its output.

7

Open the dashboard

Open http://<vm-public-ip>/ui/dashboard/ in a browser. When the browser prompts 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 task you ran through the API in Step 6 appears in green DONE state. Select any task to see its status, the request that created it, the execution, and the Result panel with the exact output the engine produced.

The Templates page lists the automations available to run. The image ships two self-contained example templates.

Select New task to launch an automation from the browser. Pick a template, fill in its typed inputs and submit. Your team can use this self-service path without touching the API.

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 built-in actions for HTTP calls, SSH commands, scripts, email, notifications and subtasks. 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 you add or edit a template. Confirm that it was picked up through 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 is documented upstream at github.com/ovh/utask. It covers value templating, step conditions, loops, JSON schema validation and the built-in action catalogue.

9

Per-instance credentials and the encryption key

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

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 up the key before you rotate it. Previously stored task data becomes unreadable without the key it was written with. The key rotation procedure is documented upstream.

10

Security recommendations

Front uTask with your own TLS certificate and domain. nginx serves plain HTTP on port 80. For production, put your own certificate and a real hostname in front 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. Have 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 for its admin_usernames list.

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

Keep the base image patched. Unattended security upgrades are enabled. Reboot after kernel updates.

11

Support and Licensing

This is a repackaged open source software product, with additional charges for SecureAnyCloud 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. Their use does not imply any affiliation with or endorsement by them.

Deploy on Azure

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

Need Help?

SecureAnyCloud 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 sales@secureanycloud.com.

Deploy on Azure

Launch uTask 1.34.0 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