Karaoke Eternal on Ubuntu 24.04 on Azure User Guide
Overview
Karaoke Eternal is a popular open source, self hosted karaoke party system. You point it at your own collection of karaoke files and it becomes a searchable library that everyone at the party can browse. Guests pick songs and join the queue from their own phones, while a browser player screen shows the lyrics and moves the party along automatically as each singer finishes.
The cloudimg image builds Karaoke Eternal Server 2.0.2 from the pinned upstream release under /opt/karaoke-eternal, runs the Node.js application server as the dedicated karaoke system user on 127.0.0.1:8080, and fronts it with nginx on port 80. The React client is compiled into the image at build time, so there is nothing for you to build. Its datastore is an embedded SQLite database at /var/lib/karaoke-eternal/data/database.sqlite3.
Your library ships empty, by design. cloudimg does not include, host or distribute any karaoke tracks, backing tracks, lyrics or artwork whatsoever. The image gives you an empty media folder at /srv/karaoke, already registered with the application, and you copy your own legally obtained karaoke files into it. Supported formats are MP3 with a matching CDG sidecar, ZIP archives of those pairs, and MP4 video.
Secure by default. No usable credential ships in the image. There is no database at all until your instance first boots, so no session signing key, no account and no room exist beforehand. On first boot a karaoke-eternal-firstboot.service oneshot creates the database (which mints a random JWT signing key unique to this VM), generates a strong administrator password and a strong room password, drives Karaoke Eternal's own one time first run setup to create exactly one administrator, records both secrets in a root only file, and only then enables the public nginx site. Until that has happened nothing is listening on port 80 at all, and the application server never accepts a connection from anywhere but 127.0.0.1.
Nobody can claim your server before you do. Karaoke Eternal's first run setup page is a genuine one shot: it creates the first administrator and then permanently closes. Because the image ships with no database and the application is unreachable from outside the instance until first boot has finished, that one shot is always used by your own instance and never by a passer by.
What is included:
- Karaoke Eternal Server 2.0.2 (ISC) under
/opt/karaoke-eternal, React client prebuilt - Embedded SQLite database at
/var/lib/karaoke-eternal/data/database.sqlite3 - An empty media library folder at
/srv/karaoke, pre registered with the application karaoke-eternal.servicerunning askaraoke, reachable only on127.0.0.1:8080- nginx reverse proxy on port 80 with correct websocket proxying, ready for TLS
karaoke-eternal-firstboot.servicefor per instance secret generation and admin seeding- Node.js 24 LTS runtime
- Ubuntu 24.04 LTS base, fully patched
- 24/7 cloudimg support, 24h response SLA
The sample song shown in the screenshots below is a short sound check clip that cloudimg generated for this guide: synthesised tones and cloudimg's own placeholder words. No commercial karaoke content appears anywhere in this image or this guide.
Prerequisites
An active Azure subscription, an SSH key pair, and a VNet with a subnet. Recommended VM size: Standard_B2s (2 vCPU, 4 GB). Karaoke Eternal is light; the sizing that matters is the OS disk, which has to hold your karaoke library, so pick a disk large enough for it. Your guests need nothing but a modern browser on their phone.
Step 1: Deploy from the Azure Portal
Search the Marketplace for Karaoke Eternal on Ubuntu 24.04, choose your VM size and OS disk size, and attach an NSG that allows TCP 22 (SSH) from your management network and TCP 80 / 443 (the web application, including the websockets that carry the live song queue) from the networks your guests connect from. On a home or venue network that usually means your own LAN. Front port 80 with TLS if the server is reachable from the internet, see the HTTPS section below.
Step 2: Deploy from the Azure CLI
RG="karaoke-prod"; LOCATION="eastus"; VM_NAME="karaoke-01"
GALLERY_IMAGE_ID="/subscriptions/<sub-id>/resourceGroups/azure-cloudimg/providers/Microsoft.Compute/galleries/cloudimgGallery/images/karaoke-eternal-ubuntu-24-04/versions/<version>"
SSH_KEY="$(cat ~/.ssh/id_rsa.pub)"
az group create --name "$RG" --location "$LOCATION"
az network vnet create -g "$RG" --name ke-vnet --address-prefix 10.91.0.0/16 --subnet-name ke-subnet --subnet-prefix 10.91.1.0/24
az network nsg create -g "$RG" --name ke-nsg
az network nsg rule create -g "$RG" --nsg-name ke-nsg --name allow-ssh --priority 100 \
--source-address-prefixes "<your-mgmt-cidr>" --destination-port-ranges 22 --access Allow --protocol Tcp
az network nsg rule create -g "$RG" --nsg-name ke-nsg --name allow-web --priority 110 \
--source-address-prefixes "<your-mgmt-cidr>" --destination-port-ranges 80 443 --access Allow --protocol Tcp
az vm create -g "$RG" --name "$VM_NAME" --image "$GALLERY_IMAGE_ID" \
--size Standard_B2s --storage-sku StandardSSD_LRS --os-disk-size-gb 256 \
--admin-username azureuser --ssh-key-values "$SSH_KEY" \
--vnet-name ke-vnet --subnet ke-subnet --nsg ke-nsg --public-ip-sku Standard
Give the OS disk enough room for your library: --os-disk-size-gb 256 holds roughly 20,000 MP3 plus CDG song pairs.
Step 3: Connect via SSH
ssh azureuser@<vm-ip>
Step 4: Verify the services are running
First boot takes about thirty seconds. When it has finished, both units are active and nginx answers on port 80.
sudo systemctl is-active karaoke-eternal nginx
sudo systemctl is-enabled karaoke-eternal nginx
curl -s -o /dev/null -w 'Karaoke Eternal: HTTP %{http_code}\n' http://127.0.0.1/
Expected output:
active
active
enabled
enabled
Karaoke Eternal: HTTP 200

Note what the listening sockets show: nginx holds port 80 for the outside world, and the application's own port 8080 is only ever reachable from inside the instance. That separation is enforced by systemd, not just by convention.
Step 5: Read your per instance credentials
Two secrets were generated on this VM at first boot: your administrator password, and the password for the default room that everyone joining the party needs.
sudo cat /root/karaoke-eternal-credentials.txt
Expected output (your values will differ; every instance gets its own):
# Karaoke Eternal on Ubuntu 24.04 - per-VM credentials (root only, chmod 600)
# ...
KARAOKE_ETERNAL_URL=http://<vm-ip>/
KARAOKE_ETERNAL_ADMIN_USER=kadmin
KARAOKE_ETERNAL_ADMIN_PASSWORD=<admin-password>
KARAOKE_ETERNAL_ROOM_PASSWORD=<room-password>
KARAOKE_ETERNAL_MEDIA_DIR=/srv/karaoke
The file is 0600 root:root, so only root can read it. Change the administrator password from the app's Account page once you are in, and keep the room password to hand: it is what you give your guests.

Step 6: Sign in
Browse to http://<vm-ip>/. Pick Room 1, enter the room password from Step 5, then sign in with the administrator username and password.

Step 7: Copy your own karaoke files into the library
The library folder is /srv/karaoke and it ships empty. Your instance's admin user is a member of the karaoke group, so you can copy straight into it over SSH from your own machine:
rsync -av --progress ~/my-karaoke-library/ azureuser@<vm-ip>:/srv/karaoke/
Karaoke Eternal recognises:
- MP3 + CDG — the classic MP3+G format. Keep the two files side by side with the same base name, for example
Some Artist - Some Song.mp3andSome Artist - Some Song.cdg. - ZIP — a zip containing one MP3 (or M4A) and its CDG sidecar.
- MP4 — karaoke videos with the lyrics burned in.
Name files Artist - Title and the scanner will split them into artist and title automatically.
On the instance you can confirm what has landed:
ls -la /srv/karaoke | sed -n '1,6p'
find /srv/karaoke -type f | wc -l
On a freshly deployed instance that reports an empty folder, which is exactly what should ship:
total 8
drwxrwsr-x 2 karaoke karaoke 4096 Aug 7 10:48 .
drwxr-xr-x 3 root root 4096 Aug 7 10:48 ..
0
Step 8: Scan the library
In the app, open the smiley menu, choose Preferences, and press Rescan next to /srv/karaoke. New files are also picked up automatically while the folder is being watched. You can trigger the same scan from the shell:
CF=/root/karaoke-eternal-credentials.txt
U=$(sudo grep '^KARAOKE_ETERNAL_ADMIN_USER=' "$CF" | cut -d= -f2-)
P=$(sudo grep '^KARAOKE_ETERNAL_ADMIN_PASSWORD=' "$CF" | cut -d= -f2-)
sudo node /usr/local/sbin/karaoke-eternal-ctl.mjs req http://127.0.0.1 "$U" "$P" - - GET /api/prefs/paths/scan
Expected output:
STATUS 200
Once the scan finishes, the library page lists your songs by artist.

Step 9: Optional, prove the whole chain with a clip you generate yourself
If you would rather confirm the appliance works before copying your real library across, generate a short test clip on the instance. Nothing here is downloaded: the video and the tone are synthesised locally by ffmpeg, which is not part of the image and is removed again at the end.
sudo apt-get -o DPkg::Lock::Timeout=120 update -qq
sudo apt-get -o DPkg::Lock::Timeout=120 install -y --no-install-recommends ffmpeg >/dev/null
ffmpeg -version 2>/dev/null | sed -n '1p'
command -v ffmpeg >/dev/null || { echo 'FAIL: ffmpeg did not install'; exit 1; }
sudo -u karaoke ffmpeg -nostdin -hide_banner -loglevel error -y \
-f lavfi -i "sine=frequency=440:duration=12" \
-f lavfi -i "color=c=0x101828:s=1280x720:d=12" \
-filter_complex "[1:v]drawtext=fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf:text='YOUR LIBRARY IS WORKING':fontcolor=white:fontsize=54:x=(w-tw)/2:y=(h-th)/2[v]" \
-map "[v]" -map 0:a -c:v libx264 -pix_fmt yuv420p -c:a aac -shortest \
"/srv/karaoke/cloudimg - Library Test.mp4"
ls -l "/srv/karaoke/cloudimg - Library Test.mp4"
[ -s "/srv/karaoke/cloudimg - Library Test.mp4" ] || { echo 'FAIL: test clip not generated'; exit 1; }
CF=/root/karaoke-eternal-credentials.txt
U=$(sudo grep '^KARAOKE_ETERNAL_ADMIN_USER=' "$CF" | cut -d= -f2-)
P=$(sudo grep '^KARAOKE_ETERNAL_ADMIN_PASSWORD=' "$CF" | cut -d= -f2-)
sudo node /usr/local/sbin/karaoke-eternal-ctl.mjs req http://127.0.0.1 "$U" "$P" - - GET /api/prefs/paths/scan
for i in $(seq 1 30); do
N=$(sudo sqlite3 /var/lib/karaoke-eternal/data/database.sqlite3 'SELECT COUNT(*) FROM songs;')
[ "$N" != "0" ] && break
sleep 2
done
sudo sqlite3 /var/lib/karaoke-eternal/data/database.sqlite3 \
'SELECT artists.name || " - " || songs.title FROM songs JOIN artists USING(artistId);'
[ "$(sudo sqlite3 /var/lib/karaoke-eternal/data/database.sqlite3 'SELECT COUNT(*) FROM songs;')" = "1" ] \
|| { echo 'FAIL: the test clip did not appear in the library'; exit 1; }
Expected output:
STATUS 200
cloudimg - Library Test
The song is now in the library and can be queued from any phone signed in to the room. When you are done, remove it and put the instance back to a clean state:
sudo rm -f "/srv/karaoke/cloudimg - Library Test.mp4"
CF=/root/karaoke-eternal-credentials.txt
U=$(sudo grep '^KARAOKE_ETERNAL_ADMIN_USER=' "$CF" | cut -d= -f2-)
P=$(sudo grep '^KARAOKE_ETERNAL_ADMIN_PASSWORD=' "$CF" | cut -d= -f2-)
sudo node /usr/local/sbin/karaoke-eternal-ctl.mjs req http://127.0.0.1 "$U" "$P" - - GET /api/prefs/paths/scan
sudo apt-get -o DPkg::Lock::Timeout=120 purge -y ffmpeg >/dev/null
sudo apt-get -o DPkg::Lock::Timeout=120 autoremove --purge -y >/dev/null
command -v ffmpeg >/dev/null && { echo 'FAIL: ffmpeg still installed'; exit 1; }
echo "ffmpeg removed again"
sleep 3
echo "songs now: $(sudo sqlite3 /var/lib/karaoke-eternal/data/database.sqlite3 'SELECT COUNT(*) FROM songs;')"
Step 10: Let your guests join from their phones
By default nobody can create their own account, which is the right posture for a server you can reach from a browser. For a party you almost certainly want the opposite, so open it deliberately:
- Sign in as the administrator and open Preferences from the smiley menu.
- Under Rooms, edit Room 1.
- Tick Allow new guest accounts (and Allow new user accounts if you want your regulars to keep a named account and their starred songs).
- Save.
Now anyone who can reach the server, enters the room password and picks a name can join. Give your guests the URL and the room password; the room password is what keeps the party to the people in the room.
Everyone browses the same library from their phone and taps a song to join the queue.

Step 11: Put the player on the big screen
On the machine wired to the TV or projector, sign in as the administrator, then open the player tab in the bottom bar (or browse to http://<vm-ip>/player). Press play, and the player screen takes over: lyrics for MP3+G songs are rendered live from the CDG stream, MP4 songs play as video, and a visualiser fills the background. Use the full screen control at the top right.

As each song ends the player automatically advances to whoever is next in the queue, so nobody has to touch the machine again all night.
Step 12: The websockets that carry the party
Every live action rides a socket.io websocket: a guest queueing a song, the queue reordering, the player advancing, the progress bar on everyone's phone. The nginx vhost in this image is already configured to proxy that upgrade, with long timeouts so a connection is never reaped mid song. You can watch the handshake succeed:
UP=$(curl -s -i -N -m 8 -H 'Connection: Upgrade' -H 'Upgrade: websocket' \
-H "Sec-WebSocket-Key: $(head -c 16 /dev/urandom | base64)" -H 'Sec-WebSocket-Version: 13' \
'http://127.0.0.1/socket.io/?EIO=4&transport=websocket' 2>/dev/null | sed -n '1,3p' || true)
printf '%s\n' "$UP"
printf '%s' "$UP" | grep -q '101 Switching Protocols' || { echo 'FAIL: nginx did not proxy the websocket upgrade'; exit 1; }
Expected output:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
101 Switching Protocols is the proof. A reverse proxy that answers 200 here has silently broken the live queue, which is why this image ships the proxy already configured rather than leaving it to you.
You can go one step further and confirm that an authenticated session really receives server pushed events over that websocket:
CF=/root/karaoke-eternal-credentials.txt
U=$(sudo grep '^KARAOKE_ETERNAL_ADMIN_USER=' "$CF" | cut -d= -f2-)
P=$(sudo grep '^KARAOKE_ETERNAL_ADMIN_PASSWORD=' "$CF" | cut -d= -f2-)
R=$(sudo grep '^KARAOKE_ETERNAL_ROOM_PASSWORD=' "$CF" | cut -d= -f2-)
sudo node /usr/local/sbin/karaoke-eternal-ctl.mjs wscheck http://127.0.0.1 "$U" "$P" 1 "$R"
Expected output:
OK:websocket-upgraded 'HTTP/1.1 101 Switching Protocols' authenticated=kadmin live-events=prefs-push+queue-push

Step 13: Rooms
Rooms let you run more than one party from the same library, or reset the evening with a clean queue. Under Preferences you can create a room, give it its own password, and open or close it. Closing a room stops anyone new joining without stopping the music; guests already in it stay put.
The default Room 1 was created for you at first boot with the room password from Step 5.
Step 14: The runtime stack
cat /opt/karaoke-eternal/CLOUDIMG_VERSION
node -v
nginx -v
ls /opt/karaoke-eternal/build/client | sed -n '1,4p'
Expected output:
2.0.2
v24.19.0
nginx version: nginx/1.24.0 (Ubuntu)
240.e30082e5be751ce05500.js
447.e30082e5be751ce05500.js
715.e30082e5be751ce05500.js
851.e30082e5be751ce05500.css
The client bundles under build/client are the compiled React app: they are built into the image, so a customer VM never needs a build toolchain, an internet connection or a CDN to serve the interface.

Step 15: Data and persistence
Everything that matters lives on the OS disk and is included in your normal VM backups or snapshots:
/srv/karaoke— your karaoke files/var/lib/karaoke-eternal/data/database.sqlite3— accounts, rooms, queues, stars and the media index/root/karaoke-eternal-credentials.txt— the credentials generated on this instance
To back up the database safely while the service is running, use SQLite's own online backup:
sudo sqlite3 /var/lib/karaoke-eternal/data/database.sqlite3 ".backup '/var/tmp/karaoke-eternal-backup.sqlite3'"
sudo ls -l /var/tmp/karaoke-eternal-backup.sqlite3
The media index is derived data, so if you ever lose the database you only lose accounts, rooms and stars; your library rebuilds itself with a rescan.
Step 16: Enable HTTPS
If the server is reachable from the internet, put TLS in front of it. Point a DNS record at the VM, open port 443 in the NSG, then:
sudo apt-get update && sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot --nginx -d your-domain.example.com
Certbot edits the shipped vhost in place and keeps the websocket and media proxy settings, so the live queue keeps working over wss://.
Managing the service
sudo systemctl status karaoke-eternal --no-pager | sed -n '1,5p'
sudo systemctl restart karaoke-eternal
for i in $(seq 1 30); do curl -sf -o /dev/null -m 5 http://127.0.0.1/ && break; sleep 2; done
sudo journalctl -u karaoke-eternal -n 20 --no-pager | sed -n '1,20p'
curl -s -o /dev/null -w 'back up: HTTP %{http_code}\n' http://127.0.0.1/
The application logs to the journal, and Karaoke Eternal also keeps its own logs under /var/lib/karaoke-eternal/data.
Support
This image is published and supported by cloudimg. Karaoke Eternal is open source software licensed under the ISC licence; cloudimg is not affiliated with or endorsed by its authors. Email support@cloudimg.co.uk, 24/7, with a 24 hour response SLA.
cloudimg does not supply karaoke content of any kind. You are responsible for holding the appropriate rights to the karaoke files you place in /srv/karaoke.