Eu
Media & Entertainment Azure

Euterpe on Ubuntu 24.04 on Azure User Guide

| Product: Euterpe on Ubuntu 24.04 LTS on Azure

Overview

Euterpe is a self hosted music streaming server. It indexes a music library you already own, reads the tags and cover art out of your own files, and streams them back through a clean browser player, a documented REST API and a Subsonic compatible API that works with a wide range of third party mobile clients. Browse and search by artist, album or track, play in the browser, build playlists, download a whole album as a zip, and pair devices with a QR code.

The cloudimg image builds Euterpe v1.6.3 from the pinned upstream release tag against this operating system's own audio tagging and Unicode libraries, and puts it behind an nginx reverse proxy. The appliance is secure by default and there is no password to look up: nothing at all listens on the public port in the shipped image, and the server will not start until euterpe-firstboot.service has generated this machine's own administrator password and its own token signing secret. Only then is the public site opened.

The music library ships EMPTY, on purpose. This is a server for music you already own. No recordings of any kind are included in or distributed with this image. Copy your own files into /var/lib/euterpe/music, or point the configuration at a disk you attach, and Euterpe indexes them automatically.

What is included:

  • Euterpe v1.6.3, a single Go binary at /usr/local/bin/euterpe with the web player, the SQL migrations and the HTML templates embedded inside it
  • SQLite library index at /var/lib/euterpe/euterpe.db, created on first scan, with no separate database to maintain
  • An empty music library directory at /var/lib/euterpe/music, ready for your own files
  • euterpe.service running as the euterpe user, bound to loopback 127.0.0.1:9996
  • nginx.service reverse proxy on TCP 80, TLS ready, streaming Range requests straight through so seeking works
  • euterpe-firstboot.service generating a per VM administrator password and a per VM token signing secret on first boot
  • A REST API with HTTP Basic and bearer token authentication, and a Subsonic compatible API for third party mobile players
  • The GNU GPL v3 licence, a written offer for the corresponding source and full third party notices in /usr/share/doc/euterpe
  • Ubuntu 24.04 LTS base, latest patches, unattended security upgrades enabled
  • 24/7 cloudimg support, 24h response SLA

Prerequisites

An active Azure subscription, an SSH key, and a VNet with a subnet. Recommended VM size: Standard_B2s. Euterpe is very light; the size you need is driven by how much disk your music library takes, not by CPU or memory.

Step 1: Deploy from the Azure Portal

Search the Azure Marketplace for Euterpe, choose the plan, and create the VM. In the networking step attach an NSG that allows inbound TCP 22 (SSH) and TCP 80 (the web player) from your client networks only. Put a TLS certificate in front of port 80 before you use it over the internet (Step 13).

If your library is large, add a data disk at create time and mount it, then point Euterpe at that mount in Step 6.

Step 2: Deploy from the Azure CLI

RG="euterpe-prod"; LOCATION="eastus"; VM_NAME="euterpe-01"
GALLERY_IMAGE_ID="/subscriptions/<sub-id>/resourceGroups/azure-cloudimg/providers/Microsoft.Compute/galleries/cloudimgGallery/images/euterpe-ubuntu-24-04/versions/<version>"
az vm create \
  --resource-group "$RG" --name "$VM_NAME" --location "$LOCATION" \
  --image "$GALLERY_IMAGE_ID" --size Standard_B2s \
  --admin-username azureuser --generate-ssh-keys \
  --public-ip-sku Standard
# then open TCP 80 to your own client network only:
az vm open-port --resource-group "$RG" --name "$VM_NAME" --port 80 --priority 900

Step 3: Connect via SSH

ssh azureuser@<vm-ip>

euterpe-firstboot.service, euterpe.service and nginx.service all come up automatically on the first boot, in that order.

Step 4: Verify the Service

sudo systemctl is-active euterpe nginx euterpe-firstboot
sudo test -f /var/lib/cloudimg/euterpe-firstboot.done && echo FIRSTBOOT_DONE
sudo ss -tlnH | awk '$4 ~ /:80$|:9996$/ {print $1, $4}' | sort

Expected output. All three units are active, the firstboot sentinel is present, and the listeners show the split that matters: nginx is the only thing on the public port 80, while Euterpe itself is reachable only on loopback 127.0.0.1:9996.

active
active
active
FIRSTBOOT_DONE
LISTEN 0.0.0.0:80
LISTEN 127.0.0.1:9996
LISTEN [::]:80

Euterpe, nginx and the firstboot service all reporting active, the firstboot sentinel present, nginx bound to public port 80 and Euterpe bound to loopback 127.0.0.1:9996 only, and the server reporting version v1.6.3

Step 5: Get Your Login Credentials

There is no default password. On first boot this machine generated its own administrator password and its own token signing secret, wrote them into the configuration, and only then opened the public site.

sudo cat /root/euterpe-info.txt

The note holds the username, the password and the URL, all unique to this VM and none of them baked into the image:

# Euterpe on Ubuntu 24.04 - Per-VM Instance Info
#
EUTERPE_ADMIN_USERNAME=admin
EUTERPE_ADMIN_PASSWORD=<EUTERPE_ADMIN_PASSWORD>
EUTERPE_URL=http://10.0.0.10/
EUTERPE_LIBRARY_PATH=/var/lib/euterpe/music

Now prove that every way in really is gated. An anonymous browser request is bounced to the login page, an anonymous API call is refused, and only this machine's own credentials are accepted:

curl -s -o /dev/null -w 'web UI, no credentials -> HTTP %{http_code}\n' -H 'Accept: text/html' http://127.0.0.1/
curl -s -o /dev/null -w 'REST API, no credentials -> HTTP %{http_code}\n' -H 'Accept: application/json' http://127.0.0.1/v1/about
P=$(sudo grep '^EUTERPE_ADMIN_PASSWORD=' /root/euterpe-info.txt | cut -d= -f2-)
curl -s -o /dev/null -w 'REST API, this VM login -> HTTP %{http_code}\n' -u "admin:$P" http://127.0.0.1/v1/about
web UI, no credentials -> HTTP 302
REST API, no credentials -> HTTP 401
REST API, this VM login -> HTTP 200

The EUTERPE_URL line uses the address the VM sees for itself. Browse to the player using your VM's own public IP address.

The per VM credential note with the password masked, showing that an anonymous web request is redirected to the login page, an anonymous API call and a guessed admin:admin both return 401, and only this VM's own credential returns 200

Step 6: Add Your Own Music

The library directory ships empty. It is owned by the euterpe service user and is where Euterpe looks for your files:

ls -ld /var/lib/euterpe/music
sudo -u euterpe test -w /var/lib/euterpe/music && echo "writable by the euterpe service user"
drwxr-xr-x 2 euterpe euterpe 4096 Aug  7 13:16 /var/lib/euterpe/music
writable by the euterpe service user

Copy your own audio in from your workstation. Run these on your own machine, not on the VM:

# a single album
scp -r "/path/to/your/music/Some Album" azureuser@<vm-ip>:/tmp/
# or a whole library, resumable
rsync -av --progress "/path/to/your/music/" azureuser@<vm-ip>:/tmp/incoming/

Then, on the VM, move the files into the library and hand them to the service user:

sudo mkdir -p /var/lib/euterpe/music
# sudo mv /tmp/incoming/* /var/lib/euterpe/music/
sudo chown -R euterpe:euterpe /var/lib/euterpe/music
sudo find /var/lib/euterpe/music -type d -exec chmod 0755 {} +
sudo find /var/lib/euterpe/music -type f -exec chmod 0644 {} +

Euterpe watches the library directories, so new files are picked up without a restart. Confirm what is indexed through the API:

P=$(sudo grep '^EUTERPE_ADMIN_PASSWORD=' /root/euterpe-info.txt | cut -d= -f2-)
curl -s -u "admin:$P" 'http://127.0.0.1/v1/browse?by=artist'

On a freshly deployed VM the library is empty, exactly as shipped, and the API says so:

{"data":null,"next":"","previous":"","pages_count":0}

Once your files are in place the same call lists your artists. If you would rather keep your music somewhere else, for example on a mounted data disk, add that path to the libraries list in /etc/euterpe/config.json and restart the service. Euterpe reads MP3, FLAC, OGG, M4A, WAV and Opus, and takes the artist, album, track number and embedded cover art from the tags in your files.

The music library directory shipping empty with zero artists indexed, the installed component paths, and the GPL-3.0 licence, written source offer and third party notices in /usr/share/doc/euterpe

Step 7: Sign In to the Web Player

Open http://<vm-ip>/ in a browser. Any anonymous request is redirected to the login page. Enter the username admin and the password from Step 5.

The Euterpe login page, reached automatically because an anonymous visitor is redirected there, with fields for the username and the per VM password

Step 8: Browse Your Library

After signing in you get the player: a search box across the top, an artist list and an album list, an artwork panel, the transport controls, and the playlist underneath. Choose Load All Music to pull the whole library into the playlist, or filter down with the artist and album lists first.

The screenshots in this guide were taken against a library holding nothing but placeholder audio tones that cloudimg generated for documentation purposes, so that the interface can be shown populated. Your VM ships with an empty library and will show your own music once you copy it in.

The Euterpe player after signing in, with the artist and album lists filled in and the playlist showing every track in the library, ready to play

Step 9: Play a Track

Click any track in the playlist and it starts streaming immediately. The progress bar fills, the elapsed and total time count along, and the transport switches to a pause button. Use the previous, next and stop buttons, the volume slider, and the shuffle and repeat toggles to control playback. Because nginx passes Range requests straight through, dragging the progress bar seeks within the track rather than restarting it.

A track playing in Euterpe: the progress bar is partly filled, the clock reads five seconds into a thirty second track, the transport is showing the pause button and the playing track is highlighted in the playlist

Step 10: Search and Filter

Type into the search box to match across track, album and artist names, or narrow the playlist by selecting an artist and then an album. The artwork panel shows the cover art embedded in your own files, and falls back to a placeholder when a track has none.

Euterpe filtered by album with a search term applied, showing only the matching tracks in the playlist while the selected track keeps playing

Step 11: Use the REST API

Euterpe has a documented, stable REST API behind the same login. It accepts HTTP Basic credentials, or you can exchange them once for a bearer token and use that instead, which is what you want for scripts and long lived device sign ins.

P=$(sudo grep '^EUTERPE_ADMIN_PASSWORD=' /root/euterpe-info.txt | cut -d= -f2-)
curl -s -u "admin:$P" http://127.0.0.1/v1/about
TOK=$(curl -s -H 'Content-Type: application/json' \
  --data "{\"username\":\"admin\",\"password\":\"$P\"}" \
  http://127.0.0.1/v1/login/token/ | python3 -c 'import json,sys;print(json.load(sys.stdin)["token"])')
echo "token length: ${#TOK}"
curl -s -o /dev/null -w 'with the token -> HTTP %{http_code}\n' -H "Authorization: Bearer $TOK" http://127.0.0.1/v1/about
curl -s -o /dev/null -w 'with a forged token -> HTTP %{http_code}\n' -H 'Accept: application/json' -H 'Authorization: Bearer forged' http://127.0.0.1/v1/about

A real token is accepted and anything else is refused, because tokens are signed with the secret this VM generated for itself on first boot:

{"server_version":"v1.6.3"}
token length: 128
with the token -> HTTP 200
with a forged token -> HTTP 401

The full endpoint reference ships on the VM at /usr/share/doc/euterpe/API.md. It covers search, browse, streaming a track, downloading an album as a zip, album artwork, artist images and playlists.

Requesting the server version, exchanging the per VM credentials for a bearer token, that token returning 200 while a forged token returns 401, and the Subsonic API refusing an anonymous call but accepting the per VM credentials

Step 12: Connect a Subsonic Mobile Player

Euterpe also speaks the Subsonic API, so most third party Subsonic clients on phones, tablets and desktops can play your library. Point the client at http://<vm-ip>/rest/ with the username and password from Step 5. Check the endpoint from the VM first:

P=$(sudo grep '^EUTERPE_ADMIN_PASSWORD=' /root/euterpe-info.txt | cut -d= -f2-)
curl -s "http://127.0.0.1/rest/ping.view?v=1.16.1&c=cloudimg&f=json&u=admin&p=$P"
{
  "subsonic-response": {
    "status": "ok",
    "version": "1.16.1",
    "type": "euterpe",
    "serverVersion": "v1.6.3",
    "openSubsonic": true
  }
}

This surface is authenticated too. Without credentials, or with the wrong ones, the same call returns a failed status. Add TLS (Step 13) before pointing a phone at it over the internet, since Subsonic clients send the credentials with every request.

Step 13: Server Components and Configuration

Everything the image installs lives in a handful of predictable paths:

/usr/local/bin/euterpe -v
ls -1 /usr/local/bin/euterpe /etc/euterpe/config.json /etc/systemd/system/euterpe.service \
  /usr/local/sbin/euterpe-firstboot.sh /etc/nginx/sites-available/cloudimg-euterpe
ls -1 /usr/share/doc/euterpe/
Euterpe Media Server v1.6.3
Build with go1.24.13
/etc/euterpe/config.json
/etc/nginx/sites-available/cloudimg-euterpe
/etc/systemd/system/euterpe.service
/usr/local/bin/euterpe
/usr/local/sbin/euterpe-firstboot.sh
API.md
CHANGELOG.md
LICENSE
LICENSE.go-taglib
README.md
SOURCE-OFFER.txt
THIRD-PARTY-NOTICES.txt

The configuration this VM generated for itself looks like this, with the two secrets redacted:

{
    "listen": "127.0.0.1:9996",
    "ssl": false,
    "basic_authenticate": true,
    "authentication": {
        "user": "admin",
        "password": "<EUTERPE_ADMIN_PASSWORD>",
        "secret": "<128-hex-signing-secret-generated-on-this-vm>"
    },
    "libraries": [
        "/var/lib/euterpe/music"
    ],
    "sqlite_database": "/var/lib/euterpe/euterpe.db",
    "log_file": "/var/log/euterpe/euterpe.log",
    "download_artwork": false
}

basic_authenticate must stay true: it is what puts the login in front of the web player, the REST API and the Subsonic API. Set download_artwork to true, and optionally add a discogs_auth_token, if you want Euterpe to fetch missing cover art and artist images from the Cover Art Archive and Discogs. It is off by default so a fresh appliance makes no outbound requests.

Step 14: Change the Administrator Password

The password lives in /etc/euterpe/config.json. Set a new one and restart:

sudo python3 - <<'PY'
import json, getpass
p = "/etc/euterpe/config.json"
c = json.load(open(p))
c["authentication"]["password"] = "<new-password>"
json.dump(c, open(p, "w"), indent=4)
PY
sudo systemctl restart euterpe

Replace <new-password> with your own value before running it, update /root/euterpe-info.txt if you rely on that note, and store the new password in your own secret manager. Existing API tokens keep working, because they are signed with the separate signing secret; to invalidate every issued token, change the secret value in the same file and restart.

Step 15: Managing the Service

sudo systemctl status euterpe --no-pager
sudo systemctl restart euterpe
sudo journalctl -u euterpe -n 50 --no-pager

Euterpe is a simple systemd service with Restart=always, so it comes back automatically after a crash or a reboot. It runs as the unprivileged euterpe user with ProtectSystem=strict, and can write only to /var/lib/euterpe and /var/log/euterpe.

If the service refuses to start, check that /etc/euterpe/config.json and /var/lib/euterpe/.bootstrap-ready both exist. The unit deliberately will not run without them, which is what stops the image ever serving before it has generated a credential of its own.

Step 16: Add TLS

For anything reachable from the internet, terminate TLS in front of port 80. The reverse proxy is already in place at /etc/nginx/sites-available/cloudimg-euterpe; add a certificate for your own domain name with certbot:

sudo apt-get update && sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot --nginx -d music.example.com

Certbot edits the existing server block in place and sets up automatic renewal. After that, browse to https://music.example.com/ and point any Subsonic client at the same HTTPS URL.

Step 17: Backup

Two things are worth backing up, and they are separate concerns:

sudo systemctl stop euterpe
sudo cp /var/lib/euterpe/euterpe.db /var/backups/euterpe.db.$(date +%F)
sudo cp /etc/euterpe/config.json /var/backups/euterpe-config.json.$(date +%F)
sudo systemctl start euterpe
ls -1 /var/backups/ | head

euterpe.db holds the library index, your playlists and any artwork you uploaded through the interface; config.json holds your credentials and signing secret. Your actual music files in /var/lib/euterpe/music are your own master copies and should be backed up by whatever you already use for your media, or kept on a data disk you snapshot.

Licensing

Euterpe is free software distributed under the GNU General Public License version 3. This image ships an unmodified build of the tagged upstream release; cloudimg has not patched or forked it. The full licence text is at /usr/share/doc/euterpe/LICENSE, a written offer for the corresponding source with the exact tag, commit and build settings is at /usr/share/doc/euterpe/SOURCE-OFFER.txt, and every bundled third party component is listed in /usr/share/doc/euterpe/THIRD-PARTY-NOTICES.txt.

This is a repackaged open source software product with additional charges for cloudimg support services. cloudimg is not affiliated with, endorsed by or sponsored by the Euterpe project. All product and company names are trademarks or registered trademarks of their respective holders, and their use does not imply any affiliation with or endorsement by them.

No music is included with this image. Euterpe is a server for a library you already own, and nothing in this image grants any rights in any recording. You are responsible for holding the rights to whatever you put in your library.

Support

24/7 support with a 24h response SLA is included with this image. Contact support@cloudimg.co.uk quoting the offer name and your VM's resource ID.