Black Candy
Overview
This guide covers deploying and configuring Black Candy on Ubuntu 24.04 on Azure, using SecureAnyCloud Azure Marketplace images. Black Candy is an open-source, self-hosted music streaming server. You point it at a folder of your own audio files, and it reads the metadata to build a library of artists, albums, songs and playlists. It streams that library to a clean web player you can use from a laptop or phone, transcoding on the fly when a track needs it. A token-authenticated JSON API is included for companion apps.
The SecureAnyCloud image ships the free, MIT-licensed Black Candy server (Ruby on Rails 8), built from source at the pinned upstream release and run behind nginx. Nothing ships with a known secret. The upstream project seeds a well-known default administrator account (admin@admin.com / foobar) and regenerates its signing key on every restart. On this image, that default account is never created. Instead, each VM generates a persistent signing key and an admin account with a random password on first boot, before the app is reachable.
Black Candy is a trademark of its respective owner. This image is produced by SecureAnyCloud and is not affiliated with, endorsed by, or sponsored by it.
What you get:
- Black Candy v3.2.0 (the MIT-licensed Ruby on Rails music streaming server), built from source at the pinned upstream tag
- Ruby 4.0.2 compiled from source to match the upstream
.ruby-version, with the app and its bundled gems under/opt/black-candy - SQLite databases (application, cache, queue and cable), created empty on first boot, with background jobs run in process (no Redis, no PostgreSQL, no separate worker)
- ffmpeg for on-the-fly audio transcoding and libvips for cover art processing
- puma bound to
127.0.0.1:3000only, fronted by nginx on port80as the single public front door black-candy-firstboot.service,black-candy.serviceandnginx.serviceas systemd units, enabled and active on boot- A per-instance Rails signing secret and a per-instance admin account with a random password, generated on first boot and never baked into the image
- No default login: the upstream
admin@admin.com/foobaraccount is never created, and the databases are empty on first boot - Login required for all content, with the application bound to loopback behind a single nginx front door
- A preloaded demo track, so the library and player are populated on first use
- Ubuntu 24.04 LTS base with the latest security patches applied at build time
- Azure Linux Agent for cloud integration and SSH key injection
- 24/7 SecureAnyCloud support with a guaranteed 24-hour response SLA
Prerequisites
- An active Azure subscription, an SSH public key, and a VNet with a subnet in the target region
- A subscription to the Black Candy listing on Azure Marketplace
Recommended virtual machine size: Standard_B2s (2 vCPU, 4 GB RAM) is a sensible starting size for a personal library. For large libraries or several concurrent listeners, use Standard_D2s_v5 or larger. If your collection is big, attach a data disk for your music.
Configure the network security group (NSG) to allow inbound 22/tcp from your management network and 80/tcp for the web player from the networks that use it.
Deploy from the Azure Portal
Sign in to the Azure Portal, choose Create a resource, search the Marketplace for Black Candy 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).
Select Review + create, then Create.
Deploy from the Azure CLI
RG="black-candy-prod"; LOCATION="eastus"; VM_NAME="black-candy-01" IMAGE_URN="sac1647283583153:black-candy-ubuntu-24-04:default:latest" SSH_KEY="$(cat ~/.ssh/id_rsa.pub)" az group create --name "$RG" --location "$LOCATION" az network vnet create -g "$RG" --name bc-vnet --address-prefix 10.100.0.0/16 --subnet-name bc-subnet --subnet-prefix 10.100.1.0/24 az network nsg create -g "$RG" --name bc-nsg az network nsg rule create -g "$RG" --nsg-name bc-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 bc-nsg --name allow-http --priority 110 \
--destination-port-ranges 80 --access Allow --protocol Tcp
# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:black-candy-ubuntu-24-04:default:latest
az vm create -g "$RG" --name "$VM_NAME" --image "$IMAGE_URN" \
--size Standard_B2s --storage-sku StandardSSD_LRS \
--admin-username azureuser --ssh-key-values "$SSH_KEY" \
--vnet-name bc-vnet --subnet bc-subnet --nsg bc-nsg --public-ip-sku StandardConnect to your VM
ssh azureuser@<vm-ip>Confirm the services are running
Black Candy runs as a puma application under black-candy.service, fronted by nginx. Background jobs, such as media sync, run in process. Confirm that the services are active and check the listeners:
sudo systemctl is-active black-candy-firstboot black-candy nginx
sudo ss -tlnp | grep -E ':80 |:3000 'Each service reports active. nginx listens on :80 as the single public front door. The puma application is bound to 127.0.0.1:3000 on the loopback interface only and is never exposed directly.
Read the per-instance credentials
On first boot, before the app was reachable, this VM generated a per-instance Rails signing secret and a per-instance admin account with a random password. It wrote them to a root-only file. Read the file:
sudo cat /root/black-candy-credentials.txtThe file (mode 0600 root:root) holds ADMIN_EMAIL and ADMIN_PASSWORD, the account you sign in to the web player with, and WEB_URL.
None of these values ship in the image. Every value is unique to this VM, so no two instances share a credential and there is no default login to change. The upstream admin@admin.com / foobar demo account is never created.
Verify the security model
Login is required for all content. Confirm three things. The well-known upstream default account does not exist. An unauthenticated request to a protected streaming endpoint is rejected with 401. The per-instance admin credential authenticates and streams real audio:
ADMIN_EMAIL=$(sudo grep '^ADMIN_EMAIL=' /root/black-candy-credentials.txt | cut -d= -f2-)
ADMIN_PASSWORD=$(sudo grep '^ADMIN_PASSWORD=' /root/black-candy-credentials.txt | cut -d= -f2-)
curl -s -o /dev/null -w 'stream (no token): %{http_code}\n' 'http://localhost/api/v1/stream/new?song_id=1'
TOKEN=$(curl -s -X POST http://localhost/api/v1/authentication -H 'Content-Type: application/json' \
-d "{\"session\":{\"email\":\"$ADMIN_EMAIL\",\"password\":\"$ADMIN_PASSWORD\"}}" \
| python3 -c 'import sys,json;print(json.load(sys.stdin)["user"]["api_token"])')
curl -s -o /dev/null -w 'stream (with token): %{http_code}\n' -H "Authorization: Bearer $TOKEN" 'http://localhost/api/v1/stream/new?song_id=1'The unauthenticated stream request is rejected with 401. The same request with the per-instance admin token returns 200 and streams the audio. This is the security model: content is gated behind login, and the API is authenticated with a token you obtain by signing in with the per-instance admin credentials.
The same proof is bundled as a single script on the image. It looks up a real song, authenticates, refuses an unauthenticated stream, then streams real audio bytes. Run it to check the credential and streaming end to end:
sudo /opt/black-candy/black-candy-selftest.shOpen the web player and sign in
Browse to http://<vm-ip>/ to reach the Black Candy web player. Sign in with the ADMIN_EMAIL and ADMIN_PASSWORD from Step 5.
Browse your library
After you sign in, you land on the player. The left navigation gives you Artists, Albums, Songs and Playlists.
The image ships with a preloaded demo track (SecureAnyCloud Test Tone on the SecureAnyCloud Demo Album), so the library is populated on first use. Open Albums to see your collection as a grid of album cards.
Play a track
Open Songs to see the track list, and click a song to start playback. The player bar at the bottom shows the current track, progress and playback controls. It streams the audio straight from your VM.
Add your own music
Copy your audio files onto the VM into the media directory, then trigger a library sync. The media directory is /var/lib/black-candy/media.
sudo install -d -o blackcandy -g blackcandy /var/lib/black-candy/media
# copy your files in, e.g. from your machine: scp -r ./music/* azureuser@<vm-ip>:/tmp/music/
sudo cp -r /tmp/music/. /var/lib/black-candy/media/
sudo chown -R blackcandy:blackcandy /var/lib/black-candy/mediaThen open Settings in the web player and click Sync to scan the media directory and import the new files. You configure the media path and the transcoding options (bitrate, lossless) on the same page.
Use the API from your own machine
A JSON API backs every feature. Authenticate to obtain a token, then stream or query with the VM address:
TOKEN=$(curl -s -X POST http://<vm-ip>/api/v1/authentication -H 'Content-Type: application/json' \
-d '{"session":{"email":"<ADMIN_EMAIL>","password":"<ADMIN_PASSWORD>"}}' \
| python3 -c 'import sys,json;print(json.load(sys.stdin)["user"]["api_token"])')
curl -s -o track.mp3 -H "Authorization: Bearer $TOKEN" 'http://<vm-ip>/api/v1/stream/new?song_id=1'The authentication call returns a token in user.api_token. Pass it as an Authorization: Bearer header to the streaming and library endpoints. Black Candy companion apps use this same API.
Server components
- Application: Black Candy v3.2.0 (Ruby on Rails 8, built from source at the pinned tag)
- Interpreter: Ruby 4.0.2 compiled from source, at /opt/ruby
- Web server: puma bound to 127.0.0.1:3000 (loopback only), background jobs run in process
- Front door: nginx on :80, reverse proxying to puma
- Databases: SQLite (application, cache, queue, cable), created empty on first boot
- Media and transcoding: ffmpeg and libvips, with media directory /var/lib/black-candy/media
- Signing secret: per instance, generated on first boot into /etc/black-candy/black-candy.env
- Admin account: per instance, random password created on first boot into /root/black-candy-credentials.txt
- Operating system: Ubuntu 24.04 LTS (patched at build)
- License: MIT (Black Candy)
Managing the service
sudo systemctl status black-candy --no-pager | head -12 sudo journalctl -u black-candy --no-pager | tail -40Restart the application with sudo systemctl restart black-candy. Follow its logs live with sudo journalctl -u black-candy -f. View the configuration in /etc/black-candy/black-candy.env.
After you change the environment file, restart the service to apply it.
Use your own domain and HTTPS
The image serves the web player over plain HTTP on port 80, which suits a setup behind a load balancer or on a private network. Because the sign-in password travels to the server, terminate TLS in front of the VM for any internet-facing use.
- Front the VM with Azure Application Gateway, a load balancer with a managed certificate, or a CDN, and forward to nginx on port
80. - Alternatively, install your own certificate. Add a TLS
serverblock to/etc/nginx/sites-available/sac-black-candythat references your certificate and key, open443/tcpon the NSG, then runsudo systemctl reload nginx. Once TLS is terminated in front, you can also setFORCE_SSL=truein/etc/black-candy/black-candy.env. - Point a DNS name you control at the VM public IP.
Security recommendations
- Restrict the NSG. Allow TCP 80, and 22 for administration, only from the networks that use the app.
- Terminate TLS in front of the app (Step 14) so that the admin password travels encrypted.
- Rotate the seeded admin password. Sign in and change it in the web player after you deploy.
- Keep the application private to the loopback. puma is bound to
127.0.0.1:3000and fronted by nginx. Keep it that way and never expose the application port directly. - Keep the OS patched. Unattended security upgrades remain enabled on the running VM.
Support and Licensing
Black Candy is distributed under the MIT License. This SecureAnyCloud image builds the unmodified official open-source release from source. SecureAnyCloud provides the packaging, the nginx front door, the per-instance signing key and admin generation, this deployment guide, and 24/7 support with a guaranteed 24-hour response SLA.
Black Candy is an independent open-source project, and this image is not affiliated with or endorsed by it.
Deploy on Azure
Launch Black Candy on Ubuntu 24.04 LTS by SecureAnyCloud from the Azure Marketplace and follow this guide to a working self-hosted music streaming server.
Deploy on Azure
Launch Black Candy 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