Unmanic on Ubuntu 24.04 on Azure User Guide
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 theunmanicsystemd service - The Unmanic web UI, websocket and REST API on
:80, fronted by nginx, with Unmanic bound to127.0.0.1:8888only - Per-VM HTTP Basic Auth (user
admin) protecting the entire UI and API, with a unique password generated on first boot - The official
video_transcoderplugin (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/unmanicholding 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
/healthzendpoint 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

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

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

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.

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

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.

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.

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'])"

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.