Deployment guide

Unmanic

Overview

Unmanic is a self-hosted media library optimiser. Point it at a library of video files and it keeps the library in one consistent format, with no manual conversions. A scheduled scanner and a filesystem event monitor watch the library, and every file is tested against a configurable plugin flow. Files that do not match your target are queued.

A pool of workers then transcodes the queued files with ffmpeg and replaces each source with the optimised version. The web dashboard shows live worker progress, the pending queue and a full history of completed tasks. A REST API drives the same engine from scripts.

The SecureAnyCloud image installs the pinned Unmanic 0.4.0.post32 into a dedicated Python virtual environment under systemd. It also closes the gaps that make stock Unmanic unsuitable for a public cloud VM. Stock Unmanic has no authentication on the web UI or the API, and its default configuration listens on every network interface. Upstream distributes it mainly as a Docker image, where the container network hides this, but the documented pip install inherits it.

This image binds Unmanic to loopback only. An nginx reverse proxy in front of it requires the admin credential for the entire UI, the websocket and the whole API surface, including mutating routes such as settings/write and plugins/install. The password is generated uniquely on the first boot of your VM and is never baked into the image. An unauthenticated /healthz endpoint stays open for load balancer probes.

The image is also configured to do useful work on day one. Unmanic core does no transcoding by itself, because plugins do the work, and a clean install has no worker groups, so a stock deployment queues files and 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, created by ffmpeg itself with no third-party media, are seeded into the library on first boot. Your first login shows a completed transcode rather than an empty dashboard.

What you get:

  • 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) that protects 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 processed
  • ffmpeg from the Ubuntu 24.04 archive, with no third-party PPA and no non-free codec bundle
  • A dedicated 40 GiB data disk at /var/lib/unmanic that holds 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 with the latest patches and unattended security upgrades enabled
  • 24/7 SecureAnyCloud support

Prerequisites

  • An active Azure subscription
  • An SSH key pair
  • A VNet and subnet in the target region
  • A VM size of Standard_B2s (2 vCPU / 4 GiB RAM) or larger. Standard_B2s is the recommended starting point and is enough for the bundled demo library
  • NSG inbound rules that allow 22/tcp from your management network, 80/tcp for the UI and API, and 443/tcp if you add TLS

Transcoding is CPU-bound. For a real library, size the VM for the throughput you need and raise the worker count to match (see Maintenance). Unmanic serves plain HTTP on port 80, so 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, select Create a resource, search the Marketplace for Unmanic by SecureAnyCloud, and select Create.

On the Basics tab, 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 select Review + create -> Create.

Step 2 - Deploy from the Azure CLI

# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:unmanic-ubuntu-24-04:default:latest
az vm create \
  --resource-group <your-rg> \
  --name unmanic \
  --image sac1647283583153:unmanic-ubuntu-24-04:default:latest \
  --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

Connect over SSH as azureuser. Confirm that 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 in the image:

sudo cat /root/unmanic-credentials.txt

The nginx Basic Auth file stores only a bcrypt hash, so the plaintext password is not anywhere 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 show that the gate is enforced, including on a mutating API route. A positive control shows that 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 prompts 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.

Expand Completed Tasks to see the full history. The three seeded demo clips have each been transcoded 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. Both the periodic Library scanner and the File monitor are enabled, so new files are picked up 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. The plugin does the transcoding, because 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 do not play. Confirm what the pipeline 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'

Then read 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 show that the pipeline works. To use your own library, remove them and add your media.

Remove the demo clips:

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

Then copy your media into /var/lib/unmanic/library/. Copy to a temporary name on the same filesystem and rename the file into place, rather than copying directly into the library.

The file monitor reacts the moment 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 sees a complete file:

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

This is the pattern for each file, shown with the seeded demo media as an example:

sudo bash -c 'cp /opt/unmanic/demo-media/sac-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"'

To 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. The path must be readable and writable by the unmanic user, because Unmanic replaces each source file with its optimised version.

Trigger a scan now instead of 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. Workers beyond the available CPU do 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 much slower on CPU, so it suits larger VMs or overnight processing. Keep the destination container as Matroska unless you are sure your chosen container can carry the codec you select.

Where the data lives. All state is on the data disk at /var/lib/unmanic. That includes the library, 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 you size the disk. In-flight transcodes are written there before they replace the source, so it needs room for the largest file you process. You can snapshot or resize this disk independently of the OS disk.

df -h /var/lib/unmanic

Manage the services with systemctl:

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

Unmanic's own application log holds 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. In this image all of them sit behind the per-VM Basic Auth gate. 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 and do not expose Unmanic directly.

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

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

Support

SecureAnyCloud provides 24/7 support for this image. Contact sales@secureanycloud.com.

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.

Deploy on Azure

Launch Unmanic 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