Roon and Sonos made easy with Docker

13 Nov 2016

I have amassed a large collection of CDs and ripped all of them to FLAC, and recently started using Tidal to listen to expand my lossless music collection further. Sonos made it easy to stream my local files throughout the house, but the Sonos apps only let you browse the local and Tidal collections independently. Sonos also lacks support for my high-res audio and can’t take advantage of the good DACs I use on my PCs. Roon helps address both of those, combining local & Tidal collections into a single database and streaming it to any device. However, it needs a central server to hold all that metadata and provide a consistent experience across all devices. I’m tired of maintaining separate VMs for each server app, so it sounds like a job for Docker!

Parts needed

Setting up AirSonos

sudo docker run -d --restart=always --net="host" --name="airsonos" \
  -p 5000-5050:5000-5050/tcp justintime/airsonos

Right away, it found all 3 of my Sonos devices:

patrick@coldbrew:~$ docker logs 90
*** Running /etc/my_init.d/00_regen_ssh_host_keys.sh...
No SSH host key available. Generating one...
Creating SSH2 RSA key; this may take some time ...
Creating SSH2 DSA key; this may take some time ...
Creating SSH2 ECDSA key; this may take some time ...
*** Running /etc/my_init.d/10_dbus.sh...
*** Running /etc/rc.local...
*** Booting runit daemon...
*** Runit started as PID 79
ok: run: avahi: (pid 88) 0s
ok: run: dbus: (pid 87) 0s
Searching for Sonos devices on network...

Living Room (@ 192.168.1.241:1400, RINCON_B8E937A863E801400:46)
Studio (@ 192.168.1.111:1400, RINCON_B8E937A863E801400:49)
Master Bedroom (@ 192.168.1.107:1400, RINCON_000E583F00AC01400:132)

Search complete. Set up 3 device tunnels.

Once I figured out how to use AirPlay in iOS 10, it worked. My mistake was that the AirPlay button is now for display only. Once something is playing on the phone, swipe up from the bottom then left.

There’s a bit of delay, but hey, it works.

iOS Airplay to Sonos

Setting up Roon Server

I decided to build mikedickey’s image myself, rather than pulling it to make sure I had the latest version.

git clone https://github.com/mikedickey/RoonServer
cd RoonServer
docker build -t roonserver .

His instructions said to run docker run --name RoonServer --net=host -d -v /home/roon:/var/roon -v /home/music:/music mikedickey/roonserver, but had a few concerns about it:

  1. I don’t have my music on the same server as the container, and I want to mount it read-only. I use J.River Media Center for all tagging instead, and don’t want any other apps modifying or retagging files.
  2. It seems unreasonable to map a whole home directory from the server.

Instead, I want to use a Docker volume to store /var/roon, and map in my existing file server read only.

I created a new user ‘roon’, and gave it read only permissions to my file share.

On the Linux container host, I followed the steps for MountWindowsSharesPermanently to mount my music to /mnt/musicarchive. The user I created on the Windows side only has read only access to the share.

Creating a container volume is easy, just put a unique name after -v for the server side of the path such as -v roondata:/var/roon.

This changed my command line to docker run --name RoonServer --net=host -d -v roondata:/var/roon -v /mnt/musicarchive:/music roonserver

Setting up Roon on Windows

I’m not going to use my laptop as a server, but I would like to use it for control and output. I downloaded and installed the Windows 64 bit version from the download site

One of the first steps was Choose your Core, which showed the right IP for my Linux server running the container. I chose it, then logged in using my Roon login and password.

It had my Tidal collection there immediately, but didn’t have my local collection.

Adding the local collection

I clicked the hamburger menu, went to Settings, Storage, Watched Folders then clicked “Add Folder”. There’s a local folder button, so I clicked it and entered /music/.

Roon - Storage tab

CPU usage peaked on the server for a bit - docker stats showed that the Roon server container was busy indeed: none CONTAINER CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS a55f30b3fbab 25.23% 800.1 MiB / 1.945 GiB 40.17% 0 B / 0 B 1.36 MB / 12.89 MB 77 90a23eba41d4 7.76% 94.77 MiB / 1.945 GiB 4.76% 0 B / 0 B 24.57 MB / 1.321 GB 20 f7a429a746a9 0.15% 88.54 MiB / 1.945 GiB 4.45% 136.8 kB / 14.9 kB 66.54 MB / 307.4 MB 26 f3c0464a67f2 0.01% 40.09 MiB / 1.945 GiB 2.01% 156.2 kB / 64.95 kB 41.07 MB / 106.5 kB 10 e63e9267361c 0.13% 56.1 MiB / 1.945 GiB 2.82% 2.309 MB / 4.979 MB 24.87 MB / 280.3 MB 30 3c6d0cd0e929 0.01% 110.4 MiB / 1.945 GiB 5.54% 14.11 MB / 11.57 MB 66.84 MB / 20.55 MB 11 867b129d08e4 0.11% 19.09 MiB / 1.945 GiB 0.96% 149.2 kB / 27.91 kB 16.27 MB / 5.39 MB 26 1d3f3f46c3ce 0.01% 22.06 MiB / 1.945 GiB 1.11% 191.1 kB / 211.3 kB 12.75 MB / 49.15 kB 11

Adding audio devices

Next, I needed to configure audio devices for Roon. Roon is designed to give one consistent view of the music collection, but stream it anywhere. I started off with my laptop’s local “Realtek High Definition Audio” under “Connected to this PC”, which isn’t anything out of the ordinary. I scrolled down further to “Networked” - and the Sonos devices (via AirSonos) were already listed. I clicked “Enable” next to one of them, entered a name, and it was good to go.

Roon - Audio Tab, Networked

Notice - instead of showing the URL of my physical network (192.168.1.x), it actually showed the AirSonos container’s IP in the 172.x.x.x subnet.

And that’s it!

Roon Core is up and running in a container, and will automatically restart if stops unexpectedly or if the Docker Engine restarts. Because it’s using host networking, you can only run one instance per server. That’s not too much of a problem though since that would also need 2 Roon accounts.

Time to update

When a new update is available, you can simply build a new container and launch a new instance using the same data volume as before.

docker stop roonserver
docker rm a55 # the previous running container ID
docker build --no-cache -t roonserver .
docker run --name RoonServer --net=host -d -v roondata:/var/roon -v /mnt/musicarchive:/music roonserver

Once it’s up and running, you can see that your databases and history are preserved: Roon - History

Things to think about for later

I could probably run all of this in Windows containers and skip the Linux VM altogether. Given that others had tried it on Linux and succeeded, I thought I would start there first. I am concerned about being able to keep audio streaming under load which could be difficult when the fileserver VM is doing a backup. If I get those into a single VM (with Roon in a container), the IO & memory scheduling should work better.

Deploy with Docker-Compose - Move Roon Server’s configuration file out of the container into a container volume - Build directly from Docker-Compose to make sure it’s always up to date