Aim ML Experiment Tracking on Ubuntu 24.04 on Azure User Guide
Overview
Aim is an open-source ML experiment tracker: your training code logs metrics, hyperparameters and other artifacts as it runs, and Aim's fast web UI lets you browse, filter and compare those runs afterwards - a self-hosted alternative to hosted experiment trackers. The cloudimg image installs the pinned official Aim 3.29.1 pip package into a dedicated virtual environment, runs the built-in web UI/API server (aim up) under systemd, and locks it down for a marketplace appliance: Aim itself is bound to loopback only, and nginx does all customer-facing work on port 80, behind a per-VM HTTP Basic Auth gate. Aim has no user accounts of its own, so the nginx Basic Auth credential (user admin, unique password generated on first boot) is the gate on the UI and REST API. The Aim repository - the actual experiment database - lives on a dedicated 20 GiB Azure data disk and is genuinely durable across restarts and reboots. A small demo experiment with three runs is seeded on first boot so the UI shows real data immediately. Backed by 24/7 cloudimg support.
What is included:
- Aim 3.29.1 installed via pip into a dedicated virtual environment, running as the
aimsystemd service - The Aim web UI and full REST API on
:80, fronted by nginx with Aim bound to loopback only - Per-VM HTTP Basic Auth (user
admin) protecting the UI and API, with a unique password generated on first boot - A dedicated Azure data disk at
/var/lib/aimholding the Aim repository (RocksDB-backed, durable across restarts/reboots) - A seeded demo experiment (
cloudimg-demo, 3 runs with varying hyperparameters and metric curves) so the runs table, metrics chart, run detail and hyperparameters views all show real data on first login aim.service+nginx.serviceas systemd units, enabled and active- An unauthenticated
/healthzendpoint on:80for Azure Load Balancer health probes - 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 a comfortable starting point for a small team's experiment tracking; size up the VM for larger repositories or heavier UI concurrency. NSG inbound: allow 22/tcp from your management network, 80/tcp for the UI and API, and 443/tcp if you add TLS. Aim serves plain HTTP; for production use, terminate TLS in front of it with your own domain.
Step 1 - Deploy from the Azure Marketplace
Sign in to the Azure Portal, choose Create a resource, search the Marketplace for Aim 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). Review the dedicated data disk on the Disks tab, then Review + create -> Create.
Step 2 - Deploy from the Azure CLI
az vm create \
--resource-group <your-rg> \
--name aim \
--image <marketplace-image-urn> \
--size Standard_B2s \
--admin-username azureuser \
--ssh-key-values ~/.ssh/id_ed25519.pub \
--vnet-name <your-vnet> --subnet <your-subnet> \
--public-ip-sku Standard
az vm open-port --resource-group <your-rg> --name aim --port 80 --priority 1010
Step 3 - Connect to your VM
ssh azureuser@<vm-public-ip>
Step 4 - Confirm the services are running
systemctl is-active aim.service nginx.service
Both report active. Aim serves its web UI and full REST API on the loopback address 127.0.0.1:43800 only; nginx fronts port 80 with the per-VM HTTP Basic Auth gate. The Aim repository lives on the dedicated data disk at /var/lib/aim - this is the actual, durable experiment database (RocksDB-backed), not just a log file, so it survives service restarts and VM reboots.

Step 5 - Retrieve your web UI password
nginx protects the Aim UI and API with HTTP Basic Auth. The username is admin and a unique password is generated on the first boot of your VM and written to a root-only file:
sudo cat /root/aim-credentials.txt
This file contains AIM_USERNAME, AIM_PASSWORD, the AIM_URL to open in a browser, and AIM_REPO_PATH for pointing your own training scripts at this server's repository. The password is stored on disk only as a bcrypt hash in /etc/nginx/.aim.htpasswd, so no plaintext password ships in the image. Store the password somewhere safe.

Step 6 - Confirm the health endpoint
nginx serves an unauthenticated health endpoint for load balancers and probes:
curl -s http://localhost/healthz
It returns ok. This endpoint never requires authentication, so it is safe for an Azure Load Balancer health probe.
Step 7 - Confirm authentication and browse the seeded demo experiment
Because a password is set on first boot, an unauthenticated request to the UI returns HTTP 401, so nobody reaches your experiment data without the password. The following reads the per-VM password from the credentials file, proves the auth round-trip, and lists the demo experiment seeded on first boot via the authenticated API:
PW=$(sudo grep '^AIM_PASSWORD=' /root/aim-credentials.txt | cut -d= -f2-)
echo "unauth : $(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1/)"
echo "wrongpw : $(curl -s -o /dev/null -w '%{http_code}' -u admin:wrong-pw http://127.0.0.1/)"
echo "authed : $(curl -s -o /dev/null -w '%{http_code}' -u admin:$PW http://127.0.0.1/)"
curl -s -u admin:$PW http://127.0.0.1/api/experiments/ | python3 -m json.tool
It prints unauth : 401, wrongpw : 401, authed : 200, and the API response lists the cloudimg-demo experiment with run_count: 3. Only the per-VM password reaches the Aim UI and API, because Aim itself is bound to loopback and nginx port 80 is the only way in.

Step 8 - Sign in and browse the runs table
Browse to http://<vm-public-ip>/. Your browser prompts for a username and password: enter admin and the password from Step 5. Aim opens on the Runs explorer, listing the three demo runs seeded on first boot (baseline-lr1e-3, higher-lr3e-3, sgd-momentum), each logged against the cloudimg-demo experiment with its own hyperparameters and metric history.

Step 9 - Compare metrics across runs
Open Metrics from the left navigation, click + Metrics, and select the tracked metrics you want to compare (for example loss subset="train" and accuracy subset="train"), then Search. Aim plots each selected metric as a chart with one line per run, so you can see at a glance which hyperparameter combination trained faster or reached a better result.

Step 10 - Inspect a single run's detail
Click any run to open its detail view. The Overview tab shows the run's hyperparameters (hparams.learning_rate, hparams.batch_size, hparams.optimizer), description and metadata; the Metrics, System and other tabs along the top show that run's individual metric curves, system resource usage during training, and any logged images, text or figures.

Step 11 - Compare hyperparameters across runs
Open Params from the left navigation, click + Run Params, select the hyperparameters you want to compare (hparams.batch_size, hparams.learning_rate, hparams.optimizer), then Search. Aim renders a parallel-coordinates plot alongside a table (with a Tags column) so you can see which combinations of hyperparameters were used across your runs at a glance.

Step 12 - Log your own training runs
Point your own training code at this server's repository. If your training job runs on the same VM (or has the repository mounted over a shared filesystem), use the Aim Python SDK directly:
from aim import Run
run = Run(repo="/var/lib/aim", experiment="my-experiment")
run["hparams"] = {"learning_rate": 0.0003, "batch_size": 64, "optimizer": "adamw"}
for step, (loss, acc) in enumerate(your_training_loop()):
run.track(loss, name="loss", step=step, context={"subset": "train"})
run.track(acc, name="accuracy", step=step, context={"subset": "train"})
run.close()
pip install aim==3.29.1 in your training environment to match the server's SDK version. For training that runs on a different host and cannot reach this server's filesystem directly, use Aim's remote tracking server (aim server, a separate feature not enabled by default in this appliance) - see the upstream Aim documentation for the remote-tracking setup.
Maintenance
- Password: the UI/API password is set on first boot and stored as a bcrypt entry in
/etc/nginx/.aim.htpasswd. To change it, runsudo htpasswd -B /etc/nginx/.aim.htpasswd adminand thensudo systemctl reload nginx. - Durable storage: the Aim repository (
/var/lib/aim/.aim) is genuinely durable - it survivesaimservice restarts and VM reboots. Back up the data disk (or snapshot it) like you would any production database if your experiment history matters. - Restrict access: Aim serves plain HTTP on port 80. For production, restrict access to trusted IP ranges in your Network Security Group, and front it with TLS (for example certbot with your own domain) terminating on
:443. - Loopback binding: Aim is bound to
127.0.0.1:43800in/etc/systemd/system/aim.service(--host 127.0.0.1 --port 43800), so nginx is the only path in. Keep it that way - do not change the bind address to a public interface. - Disk space: the Aim repository grows with the number of runs and metrics you log. Monitor
/var/lib/aimusage (df -h /var/lib/aim) and resize the data disk if you log at high volume or for long-running projects. - Logs:
journalctl -u aimshows recent Aim server output;journalctl -u nginxshows the web server's own logs. - Security patches: unattended-upgrades remains enabled so the OS continues to receive security updates automatically.
Support
cloudimg provides 24/7 expert support for this image. Contact support@cloudimg.co.uk.