Installing

The install is a Docker Compose file plus an installer script. On Windows it is a file to double-click, which installs Docker itself and shows a window rather than a console; on Mac and Linux it is one command. Either way it ends with a setup code and an address, and the first person to sign up with that code owns the server.

The pieces

docker-compose.yml names a published image and contains no build: key, so it works on its own in an empty folder for somebody who never cloned anything. Developers add docker-compose.dev.yml, which puts the build back. The images are built for linux/amd64 and linux/arm64, because many home servers are a Raspberry Pi, a Synology box or an Apple silicon Mac.

PlatformHow
WindowsSoundStorm-Setup.cmd, from the latest GitHub release, which runs install.ps1
Mac, Linuxcurl -fsSL .../install.sh | sh
Anything with DockerThe compose file alone

Windows

The Windows installer is aimed at somebody who has never opened a terminal, because that is who a self-hosted media server usually gets given to. It installs Docker Desktop itself, starts it, and leaves desktop, Start menu and start-up shortcuts instead of an address to remember.

The setup file

The download link points at a release asset, /releases/latest/download/SoundStorm-Setup.cmd, not at the raw file in the repository. A raw link is served as plain text with no download header, so the browser shows the script instead of saving it. A release asset is a real download, and the link always follows the newest release.

Windows Smart App Control blocks any script downloaded from the web, by extension, whatever it contains: "Dangerous file extension from the web", with no Run anyway. It cannot be fixed inside the file. Right-click, Properties, Unblock clears the download mark, which is why that is the first step in the instructions. The real fixes, a signed program or a winget package, are still open.

The setup file hands straight to a hidden PowerShell and closes. That PowerShell downloads install.ps1 to a file and runs the file - never piping a download into PowerShell from memory, which is the classic malware pattern that Windows Defender blocks on sight. It downloads from the newest commit's address rather than the branch's, because GitHub's raw file host caches a branch address for five minutes and ignores any cache-busting query string.

A window, not a console

Numbered steps and coloured boxes made the console legible, but the black window itself put people off. So the setup shows a Windows Forms window: the four steps ticking off (Getting Docker ready, Preparing the SoundStorm folder, Downloading the media servers, Starting SoundStorm), a status line, a progress bar, callouts in a coloured panel, and at the end the setup code, the address for phones and an Open SoundStorm button. The console text survives under "Show details" and in a log file in the temp folder.

A few details of how that window works, because each was learned the hard way:

Virtualization, WSL, then Docker

Docker on Windows runs Linux in a virtual machine, so the order of checks matters:

  1. Is virtualization on? A PC with VT-x or SVM switched off in its firmware cannot run any of this. The first person to try the installer found out only after half a gigabyte of Docker had installed, from a Docker error offering to fix it by signing in - which fixes nothing. Now the installer asks Windows first. The trick is to ask HypervisorPresent before VirtualizationFirmwareEnabled: once a hypervisor is running, Windows can no longer see the firmware and reports the second as false or blank on a perfectly working PC. When it cannot tell, it assumes yes; refusing a working machine is the worse mistake.
  2. WSL. Docker Desktop runs its engine in WSL2. On a machine without it, Docker installs and then asks the person to install WSL by hand, as administrator, with a restart. So the installer runs wsl --install --no-distribution first. --no-distribution skips Ubuntu, a gigabyte and a prompt for a Linux username nobody will use.
  3. Docker Desktop, through winget, elevated for that one step only. Its dashboard is turned off in Docker's settings file before it is installed, because it launches itself the moment its installer finishes.

Before Docker's first start, a window explains its first-run screens - Accept, Skip, Skip, no account needed - with one button, I understand. A description alone was not enough: people walked away expecting everything to happen by itself, leaving setup waiting on a Docker window nobody clicked. Downloads then count up ("Downloaded 3 of 12: jellyfin"), and a registry rate limit is retried after 30, 60 and 120 seconds rather than reported as a broken connection.

The home network

A server people can only reach from the computer it runs on is not much use, and two Windows defaults stop phones reaching it:

After starting, Set-LanAccess checks the profile of the network holding the LAN address. If it is Public, the installer asks whether this is the home network, because marking a café's network Private is the wrong default. With consent, one elevated step marks it Private, adds an Allow rule for the port on Private only, and takes Private out of Docker's Block rules. A work (domain) network is left alone.

The installer also works out the computer's LAN address, preferring 192.168., then 10., then 172. (where Docker and WSL put virtual adapters that reach nothing), and writes it into .env as SOUNDSTORM_TLS_HOSTS. The container cannot work this out: inside Docker, the only addresses visible are the container's own. On every launch the address is updated if the old one has gone from every adapter, so a laptop that moves keeps working.

One install per computer

The compose project name is fixed, so a second install in a second folder would adopt the first one's containers and point them at an empty library. Both installers check the running container's working folder and refuse, naming the other folder.

Mac and Linux

curl -fsSL https://raw.githubusercontent.com/GabrielHollberg/soundstorm/main/install.sh | sh

install.sh is written for /bin/sh, not bash, because a stock Debian's /bin/sh is dash - and the cheap home server it is aimed at is exactly where a bash-only script would fail. It downloads the compose file into a soundstorm folder, picks a free port, writes .env with owner-only permissions (it holds the setup code and any Tailscale key), starts the stack and waits until it answers. Every failure says what to do next; "Docker is installed but not running" has a branch of its own because it is the most common by far.

OptionWhat it does
--library PATHKeep the media library somewhere else, such as an external drive
--no-httpsPlain http only (https with a real certificate is the default)
--remoteReach it from anywhere over the internet (off by default; also a switch in the app)
--tailscaleAlso reach it away from home over a tailnet
--export PATH, --import PATHMove to another computer
--uninstallRemove it, keeping the library

On macOS and Linux with Avahi, the computer's .local name is offered as well as the IP address. Windows prints only the IP address: it does not reliably advertise its name over mDNS, and an earlier version that "verified" the name by resolving it on the same machine proved nothing - a machine always answers for its own name.

Compose alone

Anybody can put the compose file in a folder and run docker compose up -d. Two differences from the installers: with no installer to generate a setup code, SoundStorm makes one up at each start and writes it to its log until an account exists; and https stays off by default, because without an installer nothing records the LAN address that the secure name would point at.

The setup code and the first sign-up

Sign-up is a first-boot action. It closes the moment the first account exists, and that account is the owner; after that only the owner adds people. Until then, whoever reaches the port could claim the server, so the first sign-up needs a setup code.

"Only allow sign-up from the home network" was the first idea, and it cannot be checked. Under Docker Desktop every connection - from the same computer, the LAN, or the internet through a forwarded port - arrives from Docker's own internal address. That was measured, not assumed. A code is the only thing that tells the person who installed it from a stranger.

The installers generate it into .env (SOUNDSTORM_SETUP_CODE, eighty random bits) and open the browser at an address carrying it; the page removes it from the address once it is staying, so it is not left in history. Because it can still go astray - a closed tab, the desktop icon opened instead - it is also shown in full at the end of setup, grouped in fours (case, spaces and dashes are ignored), and the desktop icon opens with it while no account exists. The sign-up form says where to look: the setup window, then .env in Notepad, then, for compose alone, the logs.

A real bug lived here for a while: the page stripped the code from the address, then moved itself to the secure https name carrying an address that no longer had it. On a fresh install the first screen asked for a code the person had never seen. The code is now only removed on paths that stay put.

Where the library lives

The installer creates library/ in the install folder with a subfolder per shelf: music, movies, tv, audiobooks, ebooks, documents and pictures, named for what people call things. To keep it on another drive, run the installer with -Library (Windows; or the Start menu's Move SoundStorm library) or --library. That writes SOUNDSTORM_LIBRARY_PATH, which every mount in the compose file reads, so the shelves cannot drift apart.

It is an installer option and not an app setting because which folders a container can see is fixed when it starts; changing it from inside would mean SoundStorm driving Docker. The installer never moves existing media - it says where the old files are.

On Linux, mount an external library drive at boot. An unmounted drive leaves an empty folder that looks, to every media server, like a library somebody emptied. On Windows a missing drive simply stops the stack from starting, which is the safe failure.

The starter library

A fresh install arrives with one item per stocked shelf, about 17MB embedded in the binary and unpacked once into empty shelves: a public-domain ebook from Wikisource, a public-domain LibriVox audiobook, and a CC0 recording of a Goldberg Variations aria. It answers the question a first run has to answer - does each kind of media work? - and the credits point people at LibriVox and Wikisource.

After installing

From another device, use the computer's address with the port, for example http://192.168.1.50:8099; Settings → Use on your phone or TV shows it with a Copy button. Within a minute the server also gets a secure https://<id>.home.soundstorm.dev:8099 name with a real certificate, and pages move there by themselves once they have checked they can reach it. That is the address to install the app from on Android. See Away from home for how the name works.