Streaming & Messaging Azure

Unmanic on Ubuntu 24.04 on Azure User Guide

| Product: Unmanic on Ubuntu 24.04 LTS on Azure

Overview

Unmanic is a self-hosted media library optimiser. You point it at a library of video files and it keeps that library converging on a single, consistent format without anyone running conversions by hand. A scheduled scanner and a filesystem event monitor watch the library, every file is tested against a configurable plugin flow, and anything that does not already match your target is queued. A pool of workers then transcodes the queued files with ffmpeg, replacing each source with the optimised version. The web dashboard shows live worker progress, the pending queue and a full history of completed tasks, and a REST API drives the same engine from scripts.

The cloudimg image installs the pinned Unmanic 0.4.0.post32 into a dedicated Python virtual environment under systemd, then closes the gaps that make stock Unmanic unsuitable for a public cloud VM. Unmanic ships with no authentication of any kind - not on the web UI and not on the API - and its default configuration listens on every network interface. Upstream distributes it primarily as a Docker image, where the container network hides that, but the documented pip install inherits it. This image binds Unmanic to loopback only and puts an nginx reverse proxy in front of it that requires the admin credential - a password generated uniquely on the first boot of your VM, never baked into the image - for the entire UI, the websocket and the whole API surface, including mutating routes such as settings/write and plugins/install. An unauthenticated /healthz endpoint is left open for load balancer probes.

The image is also configured to actually do work on day one. Unmanic core performs no transcoding by itself - the work is done by plugins - and a clean install has no worker groups, so a stock deployment queues files and then processes none of them. This image pre-installs and enables the official Transcode Video Files plugin, creates a worker group, and targets H.264 in a Matroska container. Three short synthetic demo clips (generated by ffmpeg itself, so there is no third-party media involved) are seeded into the library on first boot, so your first login shows a real completed transcode rather than an empty dashboard. Backed by 24/7 cloudimg support.

What is included:

  • Unmanic 0.4.0.post32 in a dedicated virtualenv at /opt/unmanic/venv, running as the unmanic systemd service
  • The Unmanic web UI, websocket and REST API on :80, fronted by nginx, with Unmanic bound to 127.0.0.1:8888 only
  • Per-VM HTTP Basic Auth (user admin) protecting the entire UI and API, with a unique password generated on first boot
  • The official video_transcoder plugin (v0.1.20) pre-installed and enabled, targeting H.264/libx264 into Matroska
  • A worker group with one worker, so queued files are actually processed
  • ffmpeg from the Ubuntu 24.04 archive - no third-party PPA and no non-free codec bundle
  • A dedicated 40 GiB data disk at /var/lib/unmanic holding the library, the transcode cache and all application state
  • Three synthetic demo clips seeded into the library on first boot so the queue and history show real work
  • An unauthenticated /healthz endpoint for Azure Load Balancer health probes
  • Ubuntu 24.04 LTS base, latest patches, unattended security upgrades enabled
  • 24/7 cloudimg support

Prerequisites

An active Azure subscription, an SSH key pair, and a VNet + subnet in the target region. Standard_B2s (2 vCPU / 4 GiB RAM) is the tested starting point and is sufficient for the bundled demo library, but transcoding is CPU-bound: if you are optimising a real library, size the VM for the throughput you need and raise the worker count to match (see Maintenance). NSG inbound: allow 22/tcp from your management network, 80/tcp for the UI and API, and 443/tcp if you add TLS. Unmanic serves plain HTTP on port 80; for production use, terminate TLS in front of it with your own domain and restrict access to trusted IP ranges.

Codec licensing. ffmpeg in this image comes from Ubuntu's own archive, so the image contains only what Canonical already distributes for Ubuntu 24.04. The H.264/H.265 encoders available are the open-source x264/x265 builds. Patent licensing for using or distributing those codecs in your jurisdiction remains your responsibility - this image grants no codec licence.

Step 1 - Deploy from the Azure Marketplace

Sign in to the Azure Portal, choose Create a resource, search the Marketplace for Unmanic by cloudimg, and select Create. On Basics pick your subscription, resource group, region and size; under Administrator account choose SSH public key and paste your key; under Inbound port rules allow SSH (22) and HTTP (80). Then Review + create -> Create.

Step 2 - Deploy from the Azure CLI

az vm create \
  --resource-group <your-rg> \
  --name unmanic \
  --image <marketplace-image-urn> \
  --size Standard_B2s \
  --admin-username azureuser \
  --generate-ssh-keys \
  --public-ip-sku Standard \
  --os-disk-delete-option Delete \
  --nic-delete-option Delete

Then open the UI port:

az vm open-port --resource-group <your-rg> --name unmanic --port 80 --priority 1010

Step 3 - Connect and confirm the services are running

SSH in as azureuser and confirm both services are active, that Unmanic is listening on loopback only, and that the data disk is mounted:

systemctl is-active unmanic.service nginx.service
ss -tln | grep -E ':8888|:80 '

Unmanic must appear only on 127.0.0.1:8888 and nginx on :80. Unmanic is never exposed to the network directly - nginx is the only public path, and it requires a password.

findmnt -no SOURCE,TARGET,FSTYPE /var/lib/unmanic

Unmanic and nginx service status, loopback listener and the mounted data disk

The data disk is mounted by UUID from /etc/fstab, not by device node - device names such as /dev/sdc vary with VM size and disk layout, so never key a mount on them:

grep unmanic /etc/fstab

Step 4 - Retrieve your per-VM password

The web UI password is generated on the first boot of your VM by unmanic-firstboot.service and written to a root-only file. It is unique to your VM and is not present in the image:

sudo cat /root/unmanic-credentials.txt

The nginx Basic Auth file stores only a bcrypt hash, so the plaintext password exists nowhere in the image:

sudo cut -c1-32 /etc/nginx/.unmanic.htpasswd

Per-VM credentials file, the bcrypt Basic Auth entry and the seeded media library

Step 5 - Confirm the authentication gate

Every route is behind Basic Auth except the health probe. The checks below prove the gate is real - including on a genuinely mutating API route, with a positive control showing the rejections are an authentication decision and not a missing endpoint:

curl -s -o /dev/null -w 'unauth /healthz            -> HTTP %{http_code}\n' http://127.0.0.1/healthz
curl -s -o /dev/null -w 'unauth dashboard           -> HTTP %{http_code}\n' http://127.0.0.1/unmanic/ui/dashboard/
curl -s -o /dev/null -w 'unauth POST settings/write -> HTTP %{http_code}\n' \
  -X POST -H 'Content-Type: application/json' -d '{"settings":{"debugging":false}}' \
  http://127.0.0.1/unmanic/api/v2/settings/write

The health probe returns 200, and both the dashboard and the mutating API route return 401. Now repeat the mutating call with the per-VM password - it must return 200:

PW=$(sudo grep '^UNMANIC_PASSWORD=' /root/unmanic-credentials.txt | cut -d= -f2-)
curl -s -o /dev/null -w 'authenticated POST         -> HTTP %{http_code}\n' \
  -u "admin:$PW" -X POST -H 'Content-Type: application/json' \
  -d '{"settings":{"debugging":false}}' \
  http://127.0.0.1/unmanic/api/v2/settings/write

Basic Auth round trip showing the health probe open, the dashboard and mutating API rejected, and the per-VM password accepted

Step 6 - Sign in to the dashboard

Browse to http://<your-vm-public-ip>/. Your browser will prompt for credentials: enter admin and the password from Step 4. Unmanic redirects to the dashboard.

The dashboard has three panels. Workers shows the worker pool and what each worker is doing right now, updating live over a websocket. Pending Tasks is the queue of files the scanner has flagged. Completed Tasks is the history of finished transcodes.

Unmanic dashboard showing the worker pool, the pending queue and completed transcode tasks

Expanding Completed Tasks shows the full history. The three seeded demo clips have each been transcoded successfully on your VM:

Expanded completed tasks list showing three successfully transcoded demo clips with start and completion times

Step 7 - Review the library configuration

Go to Settings -> Library. The bundled library is named Media Library and points at /var/lib/unmanic/library on the dedicated data disk, with both the periodic Library scanner and the File monitor enabled - so new files are picked up either when they appear or on the next scheduled scan.

Unmanic library settings showing the media library on the data disk with the scanner and file monitor enabled

Step 8 - Review the transcoding plugin

Go to Settings -> Plugins. The official Transcode Video Files plugin is pre-installed and enabled. This is what does the actual work: Unmanic core only scans and schedules.

Unmanic plugins page showing the Transcode Video Files plugin installed and enabled

The plugin is configured to encode to H.264 (libx264) and write to a Matroska (.mkv) container. Writing to MKV rather than keeping the source container matters: a container such as AVI cannot carry H.264/H.265 cleanly, and keeping it produces files that will not play. You can confirm what the pipeline actually produced:

sudo bash -c 'for f in /var/lib/unmanic/library/*.mkv; do printf "%-34s " "$(basename $f)"; ffprobe -v error -select_streams v:0 -show_entries stream=codec_name -of default=nw=1:nk=1 "$f"; done'

And the task history over the authenticated API:

PW=$(sudo grep '^UNMANIC_PASSWORD=' /root/unmanic-credentials.txt | cut -d= -f2-)
curl -s -u "admin:$PW" -X POST -H 'Content-Type: application/json' \
  -d '{"start":0,"length":5}' http://127.0.0.1/unmanic/api/v2/history/tasks \
  | python3 -c "import json,sys;d=json.load(sys.stdin);print('success:',d['successCount'],'failed:',d['failedCount'])"

Completed transcode tasks from the API and the resulting h264 Matroska files on disk

Step 9 - Point Unmanic at your own media

The demo clips exist only to prove the pipeline. To use your own library, remove them and add your media.

Remove the demo clips:

sudo rm -f /var/lib/unmanic/library/cloudimg-demo-*.mkv /var/lib/unmanic/library/cloudimg-demo-*.avi

Then copy your media into /var/lib/unmanic/library/. Copy to a temporary name on the same filesystem and rename it into place, rather than copying directly into the library. The file monitor reacts the instant a file appears, so a direct copy can be probed while it is still being written - the probe fails and the task is recorded as failed until the next scan retries it. A rename within the same filesystem is atomic, so the monitor only ever sees a complete file:

sudo install -d -o unmanic -g unmanic /var/lib/unmanic/library

The pattern for each file, using the seeded demo media as a stand-in example:

sudo bash -c 'cp /opt/unmanic/demo-media/cloudimg-demo-smpte.avi /var/lib/unmanic/library/.incoming-example.avi && chown unmanic:unmanic /var/lib/unmanic/library/.incoming-example.avi && mv /var/lib/unmanic/library/.incoming-example.avi /var/lib/unmanic/library/example.avi && echo "staged and renamed into place"'

If you would rather keep your media on a separate volume or an Azure Files share, mount it and set the library path to that mount point under Settings -> Library. Whatever path you choose must be readable and writable by the unmanic user, because Unmanic replaces each source file with its optimised version.

Trigger a scan immediately rather than waiting for the schedule:

PW=$(sudo grep '^UNMANIC_PASSWORD=' /root/unmanic-credentials.txt | cut -d= -f2-)
curl -s -o /dev/null -w 'rescan -> HTTP %{http_code}\n' -u "admin:$PW" \
  -X POST -H 'Content-Type: application/json' -d '{}' \
  http://127.0.0.1/unmanic/api/v2/pending/rescan

Maintenance

Scale the worker pool. One worker is configured, which suits the 2 vCPU starting size. Transcoding is CPU-bound, so on a larger VM raise the count under Settings -> Workers; a reasonable rule of thumb is one worker per 2 vCPUs. Adding workers beyond available CPU will not increase throughput.

Change the transcode target. Under Settings -> Plugins, open the settings for Transcode Video Files. H.265/libx265 produces noticeably smaller files than the configured H.264 default but is substantially slower on CPU, so it suits larger VMs or overnight processing. Keep the destination container as Matroska unless you are certain your chosen container can carry the codec you select.

Where the data lives. Everything stateful is on the data disk at /var/lib/unmanic: the library itself, the worker transcode cache at /var/lib/unmanic/cache, and the application state (configuration database, logs and installed plugins) under /var/lib/unmanic/.unmanic. The cache matters when sizing - in-flight transcodes are written there before replacing the source, so it needs room for the largest file you process. Snapshot or resize this disk independently of the OS disk.

df -h /var/lib/unmanic

Service management.

sudo systemctl restart unmanic.service
sudo journalctl -u unmanic.service -n 30 --no-pager

Unmanic's own application log carries the scanner, worker and post-processor detail:

sudo tail -n 20 /var/lib/unmanic/.unmanic/logs/unmanic.log

Add TLS. For anything beyond a trial, put a domain and a certificate in front of nginx and restrict port 80/443 to trusted ranges in your NSG. The Basic Auth gate protects the application, but without TLS the password crosses the network in base64.

Security posture. Unmanic's API includes routes that change the library path, install plugin code and browse the host filesystem. These are all behind the per-VM Basic Auth gate in this image, and Unmanic itself is bound to loopback so the gate cannot be bypassed. If you place your own reverse proxy in front of this VM, keep an authentication layer - do not expose Unmanic directly.

Patching. The image ships fully patched with unattended security upgrades enabled. Apply the latest updates at any time:

sudo apt-get update && sudo apt-get -y upgrade

Support

cloudimg provides 24/7 support for this image. Contact support@cloudimg.co.uk.

Unmanic is open-source software licensed under the GNU General Public License v3.0. Upstream project documentation is at docs.unmanic.app and the source is at github.com/Unmanic/unmanic.