Self-hosted service
blackcandy-org/blackcandy avatar
blackcandy-org/blackcandy

Black Candy: a self-hosted music streaming server in a single Docker container

A self hosted music streaming server

4,420 stars224 forksRubyMIT

At a glance

What is it?
Black Candy is a Ruby on Rails music server you run yourself, shipped as a Docker image with SQLite by default. The setup is short; the interesting parts are the storage layout, the upgrade warnings, and the licensing question around the mobile apps.
Who is it for?
Adopt Black Candy if you already run Docker, want a browser-accessible library, and are willing to read docs/upgrade.md before every version bump. Skip it if you need the mobile apps under a free licence or want a documented HTTP API for third-party clients.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 14 days ago.
What is it written in?
Mainly Ruby, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 28, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What Black Candy actually replaces

Black Candy is a music streaming server you host yourself. The README calls it "your personal music center", and the shape of the thing is familiar: you point it at a directory of audio files, it indexes them, and you play them through a browser or one of the mobile apps listed in the README. The default database is SQLite, which the README justifies on the grounds that it "can simplify the process of installation, and it's an ideal choice for self-hosted small server". That sentence is the whole design brief. This is not a service aimed at a household of heavy concurrent listeners with a NAS cluster behind it. It is aimed at one person, or a small group, who wants their own files reachable from a browser without paying a subscription.

The target user is someone who already runs Docker. There is no supported non-Docker install path in the README: no gem install, no source build instructions, no package for any distribution. The Dockerfile builds a production Rails image on ruby:4.0.2-alpine with ffmpeg and vips installed, so transcoding and artwork work out of the box. If you do not already have Docker running, the README gives you nothing to work with. That is a deliberate narrowing of scope, and it is worth knowing before you start.

How the Rails app, Hotwire front end and media path fit together

The repository is a Rails application with a Hotwire front end. package.json lists @hotwired/turbo-rails, @hotwired/stimulus and @hotwired/hotwire-native-bridge, plus howler for audio playback and idiomorph for DOM morphing. Assets are bundled with esbuild through esbuild.config.mjs, invoked by npm run build. There is no separate JavaScript application server: the browser gets HTML over the wire, and howler handles the audio element.

The data flow is straightforward. Media files live wherever you mount them and are pointed at with the MEDIA_PATH environment variable. Anything that has to survive a restart lives under /rails/storage, which the README describes as the location of "all the data that need to persist". That includes the SQLite database by default. Logs go to STDOUT, so Docker's own logging drivers handle rotation; the README points at the Docker logging configuration documentation rather than offering a log file.

Two environment variables carry real weight. MEDIA_PATH decides what the server can see. SECRET_KEY_BASE protects sessions, and the README is explicit about the failure mode: if it is not set, Black Candy generates a new one on each startup, "which will invalidate all existing sessions". The README suggests generating one with openssl rand -hex 64. The docker-compose example ships with the literal string fake_secret_key, which is a sample value, not something to deploy. If you copy that file and forget to change it, every container restart logs everyone out.

Installing Black Candy with Docker and mounting your library

The README's install path is one command. It maps container port 80 to port 80 on the host, which is the part worth changing before you run it on a machine that already serves something.

bash
docker run -p 80:80 ghcr.io/blackcandy-org/blackcandy:latest

After that, the README says you can reach the server at http://localhost or your host IP and log in with the initial admin user, [email protected] with password foobar. Change that password. The image is also published to Docker Hub as blackcandy/blackcandy:latest if you prefer that registry.

A more realistic run maps a different host port, mounts a media directory, and sets a secret. The README's own example for media mounts uses -v /media_data:/media_data with MEDIA_PATH=/media_data, and its persistence example mounts ./storage_data to /rails/storage.

bash
mkdir storage_data
docker run -p 3000:80 \
  -v ./storage_data:/rails/storage \
  -v /media_data:/media_data \
  -e MEDIA_PATH=/media_data \
  -e SECRET_KEY_BASE=$(openssl rand -hex 64) \
  ghcr.io/blackcandy-org/blackcandy:latest

One caveat on that command: generating the secret inline means a new value on every run, which is exactly the behaviour the README warns against. Generate it once, store it, and pass the same value each time.

If you would rather use Compose, the README provides a docker-compose.yml example with a storage_data volume, a ./media_data bind mount, and SECRET_KEY_BASE and MEDIA_PATH set under environment. Note the mismatch to watch for: the Compose example mounts the volume at /app/storage, while the README's persistence section says data lives in /rails/storage. Confirm which path your image version uses before you rely on either.

Permission problems between host and container are handled by running as an arbitrary user. The README's example passes --user 2000:2000 alongside the storage mount. If you skip this on a bind mount owned by your host user, the container's app user may not be able to write.

Upgrades are a manual, read-the-notes operation

The README puts an important notice above the upgrade section: read the upgrade guide carefully, because "there are may some breaking changes in a new version". The guide lives at docs/upgrade.md in the repository. This is the single biggest operational cost of running Black Candy. There is no migration runner that inspects your database and fixes itself, and no versioned upgrade path documented in the README itself. You stop the container, remove it, pull, and run again with your original options.

bash
docker stop <your_blackcandy_container>
docker rm <your_blackcandy_container>
docker pull ghcr.io/blackcandy-org/blackcandy:latest
docker run <OPTIONS> ghcr.io/blackcandy-org/blackcandy:latest

The Compose equivalent is down, pull, up. The release history supports the warning: v3.1.0 landed in June 2025, v3.2.0 in May 2026, and v3.2.1 in August 2026. Roughly a year between minor versions means each one can carry a lot of change, and the project has had a major version boundary before. If you pin :latest, you are opting into that risk on every restart. Pinning a specific tag is the obvious mitigation, though the README does not document a rollback procedure and the upgrade guide is described only as a forward path.

Where Black Candy is the wrong choice

The README lists mobile apps for iOS on the App Store, Android on Google Play and F-Droid, and a downloadable APK from a GitHub release. Those apps live in a separate repository, blackcandy-org/app, and that repository is not covered by the MIT licence on this one. The README says nothing about the apps' licensing or pricing. If you are choosing a self-hosted music server partly to avoid paid clients, verify the app situation on your platform before you migrate a library. F-Droid distribution suggests at least one free build exists, but the README does not confirm it.

The second gap is programmatic access. The README documents environment variables, port mapping, media mounts, persistence and logging. It does not document an HTTP API, an authentication scheme for third-party clients, or a CLI. If your plan involves a custom client, a home automation integration, or a script that talks to the server, the README gives you nothing to build against. The official mobile apps presumably use something, but it is not described here.

Finally, SQLite is the default for good reasons, and it is also a ceiling. The README frames it as suitable for a "self-hosted small server" and offers PostgreSQL as the escape hatch via DB_ADAPTER=postgresql and DB_URL. If you expect concurrent writes at any volume, plan for Postgres from the start rather than migrating later. And if you want a server that federates with other instances or exposes a documented API for a broad ecosystem of clients, this is not that project.

Funkwhale and Gonic take different routes to the same goal

Funkwhale is the closest well-known alternative, and the difference is architectural rather than cosmetic. Funkwhale is built around federation: instances can share libraries and follow each other using ActivityPub, so the unit of design is a network of servers, not a single one. Black Candy has no federation concept anywhere in the README. It is one server, your files, your users. If you want to publish a library to a wider network, Funkwhale is the tool for that job and Black Candy is not. If you want a private library with no social surface at all, Black Candy's scope is the smaller and simpler one, and the single-container install reflects that.

Gonic sits at the other end of the spectrum. It is a lightweight music streaming server, commonly run in Docker, and it is often chosen by people who want a small daemon serving a Subsonic-compatible API so existing Subsonic clients work against it. That is a genuinely different trade: Gonic's value is compatibility with a client ecosystem that already exists, while Black Candy's value is a first-party web interface and first-party mobile apps built with Hotwire. If your requirement is "my existing Subsonic client must keep working", Black Candy's README does not claim to satisfy it, and you should not assume it does.

Licence, maintenance and what the MIT grant does not cover

Black Candy is MIT licensed. That permits commercial use, modification and redistribution with the licence and copyright notice retained. It is a permissive licence, and the practical implication for a self-hoster is that running a modified copy on your own hardware raises no questions. The repository is not archived, and the last push was on 2026-09-09, eight days before this writing, with v3.2.1 released on 2026-08-26. That is a recent enough signal that the project is being worked on, though the README does not describe a support policy, a release cadence commitment, or a security reporting process.

The licence boundary is the thing to be careful about. The MIT licence in this repository covers the server. The mobile apps are maintained in a separate repository, blackcandy-org/app, and the README does not state their licence. Nothing here suggests the server's MIT grant extends to them. If you are evaluating Black Candy for an organisation, that distinction belongs in your review, and it is not legal advice: read the actual licence files in both repositories.

Upgrade cost is the recurring expense. Every version bump means reading docs/upgrade.md, stopping and removing the container, pulling a new image, and recreating it with your original options. There is no automated migration step documented in the README, and no rollback path documented either. Budget the time for each release, or pin a tag and accept that you are running an older version.

Editorial conclusion

Adopt Black Candy if you already run Docker, want a browser-accessible library, and are willing to read docs/upgrade.md before every version bump. Skip it if you need the mobile apps under a free licence or want a documented HTTP API for third-party clients. Before committing, check what the mobile apps actually cost on your platform, confirm whether SECRET_KEY_BASE is set in your run command, and read the upgrade guide for the version you are moving to.

Frequently asked questions

What is Black Candy?

It is a self-hosted music streaming server, described in its README as "your personal music center". You run it yourself, point it at a directory of audio files, and play them through a browser or the mobile apps listed in the README.

How do I install Black Candy?

The README's install path is a single Docker command that maps port 80 and pulls ghcr.io/blackcandy-org/blackcandy:latest. You then log in with the initial admin user [email protected] and password foobar, which you should change.

Where does Black Candy store its data?

The README states that all data that needs to persist is stored in /rails/storage, which is why it recommends mounting that directory to the host. The docker-compose example in the README mounts its volume at /app/storage instead, so check which path your version uses.

Can Black Candy use PostgreSQL instead of SQLite?

Yes. SQLite is the default, but the README documents DB_ADAPTER=postgresql together with DB_URL pointing at your database, which it suggests for cloud hosting or when SQLite is not enough.

What happens if I do not set SECRET_KEY_BASE in Black Candy?

The README says Black Candy will generate a new secret on each startup, which invalidates all existing sessions. It suggests generating a fixed value with openssl rand -hex 64 and setting it in the SECRET_KEY_BASE environment variable.

Official sources

  1. blackcandy-org/blackcandy on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/blackcandy-org-blackcandy.svg)](https://hysenlabs.com/projects/blackcandy-org-blackcandy)