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.
| Platform | How |
|---|---|
| Windows | SoundStorm-Setup.cmd, from the latest GitHub release, which runs install.ps1 |
| Mac, Linux | curl -fsSL .../install.sh | sh |
| Anything with Docker | The 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.
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:
- One thread, kept painted. The window runs on the setup's own thread, and every place the script waits keeps it repainting. A second thread would have to marshal every dialog; a compiled helper would be blocked by Smart App Control on exactly these PCs.
- Shown twice, on purpose. A process started hidden applies "hidden" to the first window it shows, which turned out to be the setup's own window, not the console. The first full run sat on its error screen invisibly.
- No message tells anybody to type a command. A failure offers a button that opens the log file, with SoundStorm's own recent log lines appended and the setup code removed, so it can be sent to whoever helps.
Virtualization, WSL, then Docker
Docker on Windows runs Linux in a virtual machine, so the order of checks matters:
- 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
HypervisorPresentbeforeVirtualizationFirmwareEnabled: 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. - 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-distributionfirst.--no-distributionskips Ubuntu, a gigabyte and a prompt for a Linux username nobody will use. - 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:
- Windows makes every new Wi-Fi network Public, and Public lets nothing in.
- The firewall alert for Docker's backend appears during the first start and ticks Private only; anything unticked gets a Block rule, and Block beats Allow.
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.
| Option | What it does |
|---|---|
--library PATH | Keep the media library somewhere else, such as an external drive |
--no-https | Plain http only (https with a real certificate is the default) |
--remote | Reach it from anywhere over the internet (off by default; also a switch in the app) |
--tailscale | Also reach it away from home over a tailnet |
--export PATH, --import PATH | Move to another computer |
--uninstall | Remove 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.
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.
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.
- Bundled, not downloaded: fetching on install would spend donated services' bandwidth on every installation, and the first run works without internet.
- No film. A Blender open movie was bundled for exactly one commit; it was 60% of the bundle. Everybody already has a film; the credit points at Blender's free films instead.
- Once, ever. A flag in the state file records that it has unpacked, so someone
who deletes the samples does not get them back at the next restart.
SOUNDSTORM_STARTER_LIBRARY=falseturns it off entirely.
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.