We have almost always self-hosted our music collection and, for a little over a decade now, have been streaming music from a self-hosted instance of Subsonic.
However, that install is now positively ancient: the most recent release of Subsonic was cut in November 2019 and is susceptible to things like the log4j vulnerability (I mitigated by locking down access at the reverse proxy).
I’ve periodically looked for alternatives but (until recently) have never quite found anything that we were able to make the jump to. Ever since Subsonic [went closed source](https://web.archive.org/web/20250208154945/http://forum.subsonic.org/fo…
We have almost always self-hosted our music collection and, for a little over a decade now, have been streaming music from a self-hosted instance of Subsonic.
However, that install is now positively ancient: the most recent release of Subsonic was cut in November 2019 and is susceptible to things like the log4j vulnerability (I mitigated by locking down access at the reverse proxy).
I’ve periodically looked for alternatives but (until recently) have never quite found anything that we were able to make the jump to. Ever since Subsonic went closed source, there have been various forks (Airsonic et al), but few seem to have stayed in development for very long (and are all, of course, heavy Java apps).
Recently, though, I stumbled across Gonic.
Rather than being a fork, Gonic is a Subsonic compatible server, which meant that I could still (more or less) just point our existing players at it.
This post talks about migrating our music collection from Subsonic to Gonic.
History of Our Music
The timeline of our digital music collection looks something like this:

I think that there was also a brief period where I was using Realplayer (RealOne at the time) instead of the original Windows Media Player.
Essentially, our collection started off as a bunch of locally stored audio files, courtesy of Kazaa Limewire ripping CDs. Our short stint of having to use (the awful) SonicStage for our MP3 players is best left buried in the sands of time.
As time went by, we moved from playing files stored on the same system to streaming those same files over a network - first from Google Play Music and then from a self-hosted Subsonic instance.
I never quite got onto the Spotify bandwagon: I tried (and liked) it when they first came to the UK, but was put off by them requiring a paid subscription to be able to use it on Linux (and/or my phone)1.
Alternatives
There are a bunch of self-hosted music solutions out there, but the following came particularly close to being chosen.
Navidrome

I first looked at Navidrome about a year ago.
Navidrome doesn’t use a folders heirachy and, instead, organises the collection by looking at how tracks are tagged (there are no plans to change this).
This seemed like a reasonable position to take, but after experimenting, I found that our collection needed more work than I was willing or able to invest to straighten all of the tags out (I kept running into annoying edge cases, like some tracks having Album Artist set to the name of the production company).
It’s a pity, because I really like the interface (have a look at the demo site, it’s nice!).
Funkwhale

Funkwhale is Fediverse enabled, meaning that users can also explore music that others have shared. I really like the idea and, so, was quite drawn to it.
There are Funkwhale instances with open registration, but I didn’t want to be tied to someone else’s infra: the whole point is that our collection should always be under our control.
Although it’s possible to self-host Funkwhale, it has some infra level dependencies - at a minimum it needs Postgres and Redis. I’ve been working to get Postgres back out of my LAN, so didn’t really want to add another dependency on it.
Gonic
After much exploration, I settled on Gonic.
Unlike some of the other options its web interface is quite simple and does not let you explore or play media:

I’ve a vague feeling that, when I was looking last year, I dismissed Gonic because it felt like a big step back from the functionality provided by Subsonic’s interface:

However, it turned out that the only person who actually uses that interface is me. Even then, I only really use it to trigger library updates (which Gonic’s interface does support).
Actual playback primarily occurs via the following:
Most importantly, Gonic exposes a Subsonic compatible API so we could continue to use the same players.
Deployment
Gonic is written in Go, so can easily be compiled and run natively. However, I now tend to deploy containers (primarily because it makes it easier to lift & shift between hardware).
I started by creating directories to use as persistent storage
mkdir gonic
mkdir gonic/playlists
mkdir gonic/cache
mkdir gonic/podcasts
mkdir gonic/data
I didn’t create a directory for music: that lives on an NFS share which was already mounted elsewhere on the host (the subsonic container lived on the same box).
I added the following to my docker-compose.yml:
gonic:
restart: always
image: sentriz/gonic:latest
container_name: gonic
ports:
- 4747:80
environment:
- GONIC_SCAN_AT_START_ENABLED=true
- GONIC_SCAN_INTERVAL=1440
- GONIC_MUSIC_PATH=/mnt/Music-NAS/Albums_Sorted
volumes:
- /mnt/Music-NAS/:/mnt/Music-NAS:ro
- /home/ben/docker_files/gonic/data:/data
- /home/ben/docker_files/gonic/playlists:/playlists
- /home/ben/docker_files/gonic/cache:/cache
- /home/ben/docker_files/gonic/podcasts:/podcasts
There’s quite an important note here: by default, Gonic expects to find music under /music, but I’ve overridden that.
I could have bound /mnt/Music-NAS to /music (in fact, I originally did) but the prefix became quite important when importing playlists from Subsonic (more on that below).
I started the container:
docker compose up -d
Gonic’s web interface came up almost immediately and, in the background, gonic began scanning our music library.
While waiting for the scan to finish, I changed the admin creds (the default is admin/admin) and created an unprivileged user.
After logging in as my user, I hooked Gonic up to Last.fm and ListenBrainz:

This means that the server will automatically scrobble - something that, apparently, I haven’t done since 2010!2

In order to provide external players with access, I configured my reverse proxy to point to Gonic and acquired a SSL cert from LetsEncrypt:
upstream gonic {
server 192.168.11.145:4747 weight=1;
keepalive 5;
}
server {
listen 443 ssl;
server_name gonic.example.com;
root /usr/share/nginx/letsencryptbase;
index index.php index.html index.htm;
ssl_certificate /etc/letsencrypt/live/gonic.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/gonic.example.com/privkey.pem;
ssl_session_timeout 5m;
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_pass http://gonic;
# Force use of upstream keepalives
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_redirect http:// https://;
proxy_buffers 16 16k;
proxy_buffer_size 16k;
add_header X-Clacks-Overhead "GNU Terry Pratchett";
add_header Strict-Transport-Security "max-age=31536000; includeSubdomains; preload" ;
}
}
Once the library index had finished (it took about 10 minutes), I reconfigured Dsub on my phone to point to the Gonic server and attempted to play some music - it worked!
Playlist Import
Although our music collection was there, our playlists still needed copying across.
Subsonic doesn’t expose a way to bulk export playlists, however the web interface does provide a way to do them one by one.
If you click into a playlist there’s an Export option at the top:

Clicking this downloads a m3u8 format playlist.
I had worried that playlists might rely on Subsonic specific track IDs, but they turned out to be much simpler than that:
#EXTM3U
/mnt/Music-NAS/Albums_Sorted/Semblant/Obscura (2020)/03 - Dethrone the Gods, Control the Masters (Legacy of Blood, Pt. IV).mp3
/mnt/Music-NAS/Albums_Sorted/Semblant/Lunar Manifesto/01 Incinerate.mp3
/mnt/Music-NAS/Albums_Sorted/Semblant/Lunar Manifesto/02 Dark of the Day.mp3
/mnt/Music-NAS/Albums_Sorted/The Agonist/Prisoners (2012)/05. Panophobia.mp3
/mnt/Music-NAS/Albums_Sorted/Alien Weaponry/Tu/05 Kai Tangata.mp3
/mnt/Music-NAS/Albums_Sorted/Arch Enemy/Will To Power (2017)/06. Reason to Believe.mp3
/mnt/Music-NAS/Albums_Sorted/Arch Enemy/Will To Power (2017)/04. The World Is Yours.mp3
This is why I needed to recreate my gonic container with music mounted under /mnt/Music-NAS.
Technically, I could have used sed (or similar) to replace the path prefix, but I was migrating enough playlists that I didn’t want to have to process them if it could be avoided.
To import the playlists into gonic, I needed to know gonic’s ID for the user that would own them.
There are two ways to figure this out
- Count on your fingers - the default
adminuser is1, the next is2etc - Query gonic’s database
The first is easier, but doesn’t quite scratch the geek itch:
sqlite3 gonic.db 'select id, name from users'
1|admin
2|ben
Note: if you’re setting up gonic yourself, don’t copy this database to random systems - gonic stores credentials in plaintext :(
Once I knew the user ID, I just needed to create a directory with the same name and move the playlists into it:
mkdir gonic/playlists/2
mv ~/playlists/*.m3u8 gonic/playlists/2
I didn’t even need to restart gonic, the playlists appeared in my player almost immediately and took their names from the playlists’s filename.
There was, however, one last thing to do.
Some of my Subsonic playlists were made available to other users on the server:

gonic also supports this, but it wasn’t immediately clear how to enable it.
After fiddling around in DSub, I found an Update Information option which allowed me to mark the playlist as shared with others.
After I’d done so, I opened the playlist file in less - new metadata had been added to the top:
#EXTM3U
#GONIC-NAME:"Heavy mix"
#GONIC-COMMENT:""
#GONIC-IS-PUBLIC:"true"
/mnt/Music-NAS/Albums_Sorted/Semblant/Obscura (2020)/03 - Dethrone the Gods, Control the Masters (Legacy of Blood, Pt. IV).mp3
/mnt/Music-NAS/Albums_Sorted/Semblant/Lunar Manifesto/01 Incinerate.mp3
Although I could have edited the relevant playlists, it seemed quicker (and less error prone) to use Dsub, so I worked through the (relative few) playlists that needed sharing and updated the setting.
Star Syncing
In hindsight, I’m not overly convinced that I needed this service.
The author of gonic has a second repo: gonic-lastfm-sync. This contains a small codebase which provides bi-directional syncing of "favourites" (or stars, depending on the player) between gonic and last.fm.
So, if I previously favourited something in last.fm, that state could be pulled down.
The repo contains a Dockerfile, but I wanted something a bit lighter and so wrote my own:
FROM cgr.dev/chainguard/wolfi-base AS builder
RUN apk add go git \
&& mkdir /build \
&& cd /build \
&& git clone --depth=1 https://github.com/sentriz/gonic-lastfm-sync.git \
&& cd gonic-lastfm-sync \
&& go build -o lastfm-gonic-sync .
FROM cgr.dev/chainguard/wolfi-base
COPY --from=builder /build/gonic-lastfm-sync/lastfm-gonic-sync /bin/
ENV GONIC_DB_PATH /data/gonic.db
CMD ["sh", "-c", "while true; do lastfm-gonic-sync; sleep 3600; done"]
Once I’d built the image, I added a section to my docker-compose.yml to run it, passing it the path to the gonic container’s database:
gonic_sync:
restart: always
image: gonic-lastfm-sync:b0d860
container_name: gonic-lastfm-sync
environment:
- GONIC_GONIC_USERNAME=ben
volumes:
- /home/ben/docker_files/gonic/data:/data
I brought the container up:
docker compose up -d
The software went to work.
In practice, though, it turned out that I’d only actually previously starred a few things anyway:
$ docker logs -f gonic-lastfm-sync
2025/12/01 17:36:12 no match for "dreamtheaterthisdyingsoul"
2025/12/01 17:36:12 saved lastfm->gonic stars, 7 of 8 matched
2025/12/01 17:36:12 saved gonic->lastfm stars, 0 new
Ah well

Cutover
The original plan was for me to use gonic for a while and then cut the rest of the family over once I was happy that things were working. But, everything seemed to be OK.
In Dsub, I deleted my temporary profile and then updated the URL of my existing Subsonic profile to see whether profile reuse caused any issues: it didn’t.
I couldn’t really think of anything else that needed checking, so, I decided to take a risk and just cut everyone over.
To prepare for the change, I need to create user accounts in gonic with the same credentials as those used for Subsonic.
I didn’t have those credentials, but Subsonic did: within it’s data directory is a file called subsonic.script - this is essentially a bunch of SQL statements that Subsonic uses to recreate it’s database at startup.
I used grep to extract the statements used to populate user records:
grep "INSERT INTO USER VALUES" subsonic.script
This returned lines like this:
INSERT INTO USER VALUES('admin','enc:6e65766572676f6e6e6167697665796f757570',149828415,0,0,FALSE,NULL)
From this, I needed two things: the username and a decoded password.
Although not in ASCII, Subsonic does store credentials in the clear - they’re only hex encoded and so can be decoded using xxd:
grep "INSERT INTO USER VALUES" subsonic.script | cut -d"(" -f2 | cut -d, -f1,2 | while read -r line
do
username=`echo "$line" | cut -d, -f1 | tr -d "'"`
password=`echo "$line" | cut -d, -f2 | tr -d "'" | cut -d: -f2 | xxd -r -p`
echo "${username}:${password}"
done
With this information in hand, I:
- Created corresponding users in
gonicwith the same passwords as in Subsonic - Logged into their subsonic accounts and exported playlists
- Imported playlists into
gonic
Once I was ready, I updated my reverse proxy for the Subsonic domain so that it would proxy onto gonic instead.
proxy_pass http://gonic;
One Nginx reload later, nothing broke.
Bonus: A New Desktop Player
Although the migration was transparent to the other users of my Subsonic instance, it did change things for me.
It was no longer possible to log into a web interface to manage our music collection and I now needed to do so through a front-end app.
DSub is perfectly capable, but I’m not a fan of any app-only workflow (looks pointedly towards the finance sector), if I don’t have the option of doing it from a desktop, I’m not doing it at all.
Jamstash is great for playing music but isn’t really suited to managing it. Whatever I landed on would, obviously, need to be able to talk to a Subsonic compatible API.
After a bit of looking around, I stumbled across Feishin:

It has pretty much everything that I’m likely to need and, after years of keeping a browser tab open, there’s something quite refreshing about going back to having a dedicated music app (even if, being an electron app, it is actually still just a wrapper around a browser).
Although I haven’t played around with it yet, it also seems that Amarok is back in development!3
Resource Usage
Back when first deploying Subsonic, I added the following comment to my ticket:

It should come as no surprise that a Go program is more RAM efficient than an aged Java beast, but just look at the difference:

Even idle, Subsonic still requires ten times the memory demanded by gonic (admittedly, I’m not sure that half a gig of RAM constitutes "RAM hungry" nowadays).
CPU usage graphs tell a similar story:

That brief spike in usage appears to be gonic scanning our music library for updates.
Conclusion
Because of my experiences with Navidrome last year, I expected that this was going to be a big, long running project.
That hasn’t proven to be the case though - in fact, it’s taken me substantially longer to write the first draft of this blog post than it did to perform the full migration.
The move was entirely transparent to my Subsonic users and our music is now served by an extremely lightweight container without any of the overhead associated with Java apps.
I’ve now been able to tear the Subsonic container down, greatly reducing the average age of the software running on our LAN, let alone that which is exposed to the internet.
We were skint, so I couldn’t really afford/justify a subscription. But, they also lost the future me that could afford to pay for subscriptions. ↩ 1.
Which, I think, probably puts a date on when we moved from Amarok to Google Play Music ↩ 1.
Though it’s not a given that the Subrok plugin will still work with it - the plugin was a bit long in the tooth a decade ago and seems to have been abandoned since. ↩ 1.
This was actually a drop from when I first looked - it had been closer to 800MiB the day before ↩