Black Candy: a Rails music server you run from a single Docker image
A self hosted music streaming server
At a glance
- What is it?
- Black Candy is an MIT-licensed self-hosted music streaming server written in Ruby on Rails, shipped as a Docker image with SQLite by default. It is a good fit for a small personal library on one machine, and a poor fit if you expect a documented API contract or a painless major-version upgrade.
- Who is it for?
- Adopt Black Candy if you want a single-container music server for a personal library, you are comfortable with Docker volume permissions, and you will pin an image tag rather than track latest. Do not adopt it if you need a stable documented API for third-party clients or you cannot tolerate breaking changes between releases, since the README itself warns that a new version may contain them.
- 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 7 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 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
What Black Candy replaces, and for whom
The problem is narrow and concrete: you have audio files on a disk and you want to play them from a browser and a phone without uploading them to a streaming service. Black Candy is a Rails application that indexes a media directory you mount into its container and serves it as a web music player. The README calls it "your personal music center" and the repository topics list hotwire, rails and ruby, so the web interface is built with the Hotwire stack rather than a separate JavaScript application. Official iOS and Android apps exist in separate repositories and are linked from the README, with the Android build also distributed on F-Droid and as an APK on GitHub Releases. That matters because it means the project is not only a web page: there is a client story, even though the API those clients use is not documented in the README. The intended user is someone running a home server or a small VPS who wants one container, one volume for media and one volume for state.
The deployment shape: one container, two volumes, SQLite
The architecture visible in the material is a single Rails process listening on port 80 inside the container. Persistent state lives in /rails/storage, and the README says all data that needs to persist is stored there. Media is separate: you mount a host directory and point MEDIA_PATH at it inside the container. The database is SQLite by default, and the README gives the reasoning directly, saying SQLite "can simplify the process of installation, and it's an ideal choice for self-hosted small server." PostgreSQL is supported by setting DB_ADAPTER=postgresql and DB_URL to a connection string, which the README frames as the option for people running on a cloud service such as Heroku. Note the inconsistency worth knowing about before you copy config: the prose says data lives in /rails/storage, while the docker compose example mounts storage_data at /app/storage. The compose file is the one to trust for that example, but you should confirm the path against the image you actually pull rather than assuming either string.
Getting it running with the documented commands
The shortest path in the README is one command: docker run -p 80:80 ghcr.io/blackcandy-org/blackcandy:latest, or the same image from Docker Hub as blackcandy/blackcandy:latest. The initial admin account is documented as admin@admin.com with password foobar, which you should change immediately since it is published in the README. A more realistic invocation mounts media, persists storage and sets a secret. The README recommends generating one with openssl rand -hex 64 and passing it as SECRET_KEY_BASE. It states plainly what happens otherwise: if SECRET_KEY_BASE is not set, Black Candy generates a new one on each startup, which invalidates all existing sessions. That is the single most common way to end up with a server that logs everyone out on every restart. Port mapping is done with -p, media with -v /media_data:/media_data plus -e MEDIA_PATH=/media_data, and persistence with -v ./storage_data:/rails/storage. If the mounted volume produces permission errors, the README's answer is to pass --user 2000:2000 so the container runs with the same UID and GID as the host user. Logging goes to STDOUT by default, so log handling is delegated to Docker's own logging drivers rather than to an application config file.
Upgrades are the sharp edge
The README puts an important note above the upgrade instructions: if you upgrade to a new version, read the upgrade guide carefully first, "because there are may some breaking changes in a new version." The procedure itself is ordinary Docker work. Stop the container, remove it, pull the new image, run it again with the same options; with compose it is docker-compose down, docker pull, docker-compose up. What is not ordinary is the expectation that you read docs/upgrade.md before each move. The release history supports that caution. Three releases are listed, v3.1.0 in June 2025, v3.2.0 in May 2026 and v3.2.1 in August 2026, so the project ships roughly one minor release a year with occasional patches. That cadence is slow enough that a breaking change is a real event you plan around rather than something you absorb weekly. The practical consequence is that tracking :latest is a bad default here. Pin a version tag, keep the storage volume, and read the guide for the version you are jumping to before you pull.
Where it is the wrong tool
Two limitations are visible from the material alone. First, the README does not document an API. Mobile apps exist and are linked, so some interface exists, but nothing in the supplied text describes its stability, versioning or authentication. If you are choosing a server specifically so you can write your own client or script against it, this repository gives you no contract to build on, and the upgrade warning suggests interfaces can move between releases. Second, the security defaults are aimed at a trusted network. The admin password is published in the README and the demo instance uses the same credentials, which is fine for a demo and not fine for an instance reachable from the internet. Nothing in the material describes TLS termination, rate limiting or a reverse proxy setup; the container exports plain port 80, so you are expected to put something in front of it yourself. The SQLite default is a third boundary. The README frames it as suited to a small self-hosted server, and offers PostgreSQL for cloud hosting, so a large library with concurrent listeners is outside the case the documentation argues for.
How it differs from a general file-streaming setup
The obvious alternative is a generic media server such as Jellyfin or Navidrome, which you would run the same way, in a container against a mounted media directory. The difference is in what the application layer does. Black Candy is a Rails application with Hotwire in its topic list, and the repository is organized as a Rails app with app/assets and a /rails/storage path, so the web player and the server-side rendering are the same codebase. A generic media server typically exposes a documented, versioned API as its primary interface and treats the web UI as one client among several. That trade runs the other way too: Black Candy ships first-party iOS and Android apps from its own repositories, which is a level of client integration a general-purpose server usually leaves to third parties. If your priority is a stable protocol you can target from anything, the general-purpose server is the safer pick. If your priority is a coherent web and mobile experience maintained by the same project, Black Candy is the more direct answer, at the cost of an undocumented interface.
Licence and the cost of keeping it running
Black Candy is MIT licensed, which permits commercial use, modification and redistribution provided the copyright notice and permission notice are retained. That is a permissive licence with no copyleft obligation on your own code and no source-disclosure requirement if you fork it for internal use. This is a description of the licence text, not legal advice; check the LICENSE file in the repository for the exact terms. The ongoing cost is operational rather than financial. You maintain a Docker host, a media directory with correct ownership, a storage volume and a SECRET_KEY_BASE you generated and stored somewhere safe. Each upgrade means reading docs/upgrade.md, stopping the container, pulling a new image and starting it again, with the storage volume preserved across the swap. Because releases arrive roughly annually, that is a small amount of work concentrated into a few days a year, but it is work you cannot skip without risking a broken instance.
Editorial conclusion
Adopt Black Candy if you want a single-container music server for a personal library, you are comfortable with Docker volume permissions, and you will pin an image tag rather than track latest. Do not adopt it if you need a stable documented API for third-party clients or you cannot tolerate breaking changes between releases, since the README itself warns that a new version may contain them. Before committing, verify three things: that your media directory is readable under the UID you pass to --user, that you have set SECRET_KEY_BASE so sessions survive a restart, and that you have read docs/upgrade.md for the specific version you are moving to.
Community notes