RomM on Ubuntu 24.04 on Azure User Guide
Overview
This image runs RomM 5.0.0, the open source self-hosted ROM and retro-game library manager, on Ubuntu 24.04 LTS. RomM scans a folder of ROMs, organizes them by platform, enriches each game with cover art and metadata, and lets you browse, manage saves and states, and play many titles directly in the browser through the built-in EmulatorJS and Ruffle players.
The pinned upstream application is run as the official rommapp/romm container under a systemd service that starts it on boot and restarts it on failure, backed by a local MariaDB database on the same VM. The upstream image bundles its own Valkey instance for background tasks. RomM serves its web UI and REST API on port 8080. MariaDB is bound to loopback only and holds all library metadata.
What is included:
- RomM 5.0.0 (official
rommapp/rommimage, pinned by digest), run by systemd - The RomM web UI and REST API on
:8080 - A per-VM administrator account generated on first boot and recorded in a root-only file
- A per-VM MariaDB password and a per-VM application secret key generated on first boot
- MariaDB, bound to loopback, holding platforms, games, users, saves and states
mariadb.service,docker.serviceandromm.serviceas systemd units, enabled and active- An empty library — no game content ships; you add your own legally owned ROMs
- 24/7 cloudimg support
RomM is licensed under the GNU AGPL-3.0 and is shipped here unmodified. The exact upstream Corresponding Source for the pinned release is published at github.com/rommapp/romm (tag 5.0.0).
Prerequisites
An active Azure subscription, an SSH key pair, and a VNet plus subnet in the target region. Standard_B2s (2 vCPU / 4 GiB RAM) is a sensible starting point; size up for larger libraries and in-browser play. NSG inbound: allow 22/tcp from your management network and 8080/tcp for the web UI. RomM serves plain HTTP on port 8080; for production, front it with your own domain and TLS (see the final step).
You are responsible for supplying only game content you are legally entitled to use. The image ships with an empty library and no ROMs of any kind.
Step 1 - Deploy from the Azure Marketplace
Sign in to the Azure Portal, choose Create a resource, search the Marketplace for RomM 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 8080. Then Review + create then Create.
Step 2 - Deploy from the Azure CLI
az vm create \
--resource-group <your-rg> \
--name romm \
--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 romm --port 8080 --priority 1010
Step 3 - Connect to your VM
ssh azureuser@<vm-public-ip>
Step 4 - Confirm the services are running
RomM, MariaDB and Docker start automatically on first boot. Confirm the three units are active and the RomM container is up:
systemctl is-active mariadb docker romm
docker ps --format 'table {{.Names}}\t{{.Status}}'
ss -tln | grep -E ':8080 |:3306 '
You should see all three services active, the pinned rommapp/romm:5.0.0 container Up, and RomM listening on :8080 with MariaDB on loopback :3306.

Step 5 - Retrieve your per-VM credentials
On the first boot of every instance, RomM generates a unique administrator account, a unique MariaDB password and a unique application secret key. The administrator credentials are written to a root-only file:
sudo cat /root/romm-credentials.txt
Note the ROMM_URL, ROMM_ADMIN_USERNAME and ROMM_ADMIN_PASSWORD — you will use them to sign in.

Step 6 - Confirm the API health and administrator sign-in
RomM exposes an unauthenticated health endpoint and an OAuth2 token endpoint. Confirm the service is healthy and that the per-VM administrator credential authenticates (while a wrong password is rejected):
curl -s -o /dev/null -w 'heartbeat: %{http_code}\n' http://127.0.0.1:8080/api/heartbeat
U=$(sudo grep '^ROMM_ADMIN_USERNAME=' /root/romm-credentials.txt | cut -d= -f2-)
P=$(sudo grep '^ROMM_ADMIN_PASSWORD=' /root/romm-credentials.txt | cut -d= -f2-)
echo -n 'wrong password rejected: '
curl -s -o /dev/null -w '%{http_code}\n' -X POST http://127.0.0.1:8080/api/token \
--data-urlencode grant_type=password --data-urlencode "username=$U" --data-urlencode 'password=wrong'
echo -n 'admin token accepted: '
curl -s -o /dev/null -w '%{http_code}\n' -X POST http://127.0.0.1:8080/api/token \
--data-urlencode grant_type=password --data-urlencode "username=$U" --data-urlencode "password=$P" \
--data-urlencode 'scope=me.read'
The health check returns 200, the wrong password is rejected with 401, and the per-VM administrator credential is accepted with 200.

Step 7 - Sign in to the web UI
Open http://<vm-public-ip>:8080 in a browser. Sign in with the ROMM_ADMIN_USERNAME and ROMM_ADMIN_PASSWORD from Step 5.

Step 8 - Add your ROMs and scan the library
The library ships empty. Copy your own legally owned ROM files onto the VM, placing each system's files under its platform folder in the library:
/var/lib/romm/library/roms/<platform>/<your-rom-file>
For example, Nintendo Entertainment System files go under roms/nes/, Game Boy under roms/gb/, and so on (RomM recognizes the standard platform slugs). Then open the Scan page in the web UI, choose All platforms and Quick scan, and click Scan. RomM detects the new files, creates the platforms, and imports each game, pulling metadata where a provider is configured.

You can confirm the imported library from the API as well:
U=$(sudo grep '^ROMM_ADMIN_USERNAME=' /root/romm-credentials.txt | cut -d= -f2-)
P=$(sudo grep '^ROMM_ADMIN_PASSWORD=' /root/romm-credentials.txt | cut -d= -f2-)
AT=$(curl -s -X POST http://127.0.0.1:8080/api/token \
--data-urlencode grant_type=password --data-urlencode "username=$U" --data-urlencode "password=$P" \
--data-urlencode 'scope=platforms.read' | python3 -c 'import sys,json;print(json.load(sys.stdin)["access_token"])')
curl -s -H "Authorization: Bearer $AT" http://127.0.0.1:8080/api/platforms \
| python3 -c 'import sys,json;[print(p["name"],"->",p.get("rom_count"),"roms") for p in json.load(sys.stdin)]'

Step 9 - Browse your organized library
Back on the Home page, RomM shows your library stats, a recently-added shelf and your platforms. The Platforms page lists each system with its game count, and clicking through opens the games for that platform where you can view details, manage saves and states, and launch supported titles in the browser.


Step 10 - Production hardening
RomM serves plain HTTP on port 8080 and points browsers at ROMM_BASE_URL, which is set to this VM's public address on every boot. For production:
- Put RomM behind your own domain and a TLS-terminating reverse proxy or Azure Application Gateway, and restrict
8080/tcpin the NSG to your proxy. - Set
ROMM_BASE_URLin/etc/romm/romm.envto your public HTTPS URL and restart withsudo systemctl restart romm. - Keep SSH (
22/tcp) restricted to your management network. - Configure metadata providers (IGDB, ScreenScraper, SteamGridDB, RetroAchievements) in the web UI to enrich your library with cover art and metadata.
Maintenance
- Service control:
sudo systemctl restart romm(the application),sudo systemctl status romm. - Logs:
sudo docker logs rommandsudo journalctl -u romm. - The OS applies security updates automatically via unattended-upgrades.
- Your library, saves and states live under
/var/lib/romm; back this path up, along with a MariaDB dump, to preserve your collection.
Source and licensing
RomM is free and open source under the GNU AGPL-3.0 and is shipped here unmodified. The complete Corresponding Source for the exact pinned release is available at github.com/rommapp/romm (tag 5.0.0). The cloudimg charge covers packaging, security patching, image maintenance and 24/7 support.
Support
cloudimg provides 24/7 support for this image. Contact us at support@cloudimg.co.uk.