27th dimension of the Internet

Installing koel in a Docker container.

by , on
last update by , on

Here is a simple tutorial to run koel, a free and open-source audio streaming plat-form, inside a Docker container.

koel logo (on blue background) — Copyright © Phan An
koel logo (on blue background) — Copyright © Phan An

We will talk about:

  1. koel—this is why we are here!
  2. Getting the Docker images
  3. Launching SQL server container
  4. Launching koel container
  5. Plugging the container to a reverse NGINX proxy
  6. Building the audio collection

This is not an official koel tutorial. It might contain useless steps, or even misguidance. These are the steps that worked for me. Use with caution, and think before you type ☺

1 About koel: listen to the bird’s song

Say you run a server and want to stream a large collection of music tracks, and that you wish to access and play them through a web interface. There are several applications able to so so. Subsonic (freemium) or Ampache (open source) seem to works well, but I wanted something simple and that I could display correctly on a smartphone. I stumbled upon koel (with a lowercase “k”), and decided to try it. It is open source software and built on recent technologies. It is actively developed on GitHub. The interface is simple and responsive. And the screenshot on top of its web page shows the player streaming The End of This Chapter by Sonata Arctica, which is a song I love—really, I had to try koele.

Under the hood, koel claims to rely on the following technologies: Vue, Laravel, ES6, HTML5, and Sass. I did not know what the first two ones were, so here is a short description:

Vue.js is a JavaScript library for building web interfaces. It runs on the client side to provide, well, all the interface of koel. There is also some part that runs on the serve side, with Node.js.
This object-oriented PHP framework provides the back-end needed to perform most of the server-side actions… I guess.
ECMAScript 6 and HTML 5
ECMAScript is a standard on which JavaScript is based; as of this writing, the 6th version has been out for some months (June 2015) and brings “new” features to JavaScript run in the browsers. HTML 5, well… I suppose you know HTML. 5th version includes media playing features, this is probably what is used here.
Sass is a language used to generate CSS. Because anyone who has already tried CSS knows that writing all the rules by hand is a pain!

Now koel also requires a MySQL-like database server (MySQL or MariaDB), and a web server—Apache or NGINX have been tested.

Good to know what’s inside, but there is no need to know much about it: thanks to Docker we do not have to manually install any of these, since it (nearly) all comes within the Docker image. Let’s review the steps! Note that all of the commands in this tutorial have to be executed as root.

2 Pulling the images

koel is a really recent project: first commit on GitHub is from December 2015 (last month!). As a consequence I found only two koel images on Docker Hub. One by “nampq”, with an embedded MySQL server, but I did not manage to make it work. So I chose another one after all: etopian/docker-koel, which embeds Alpine Linux, PHP-FPM, NGINX, NodeJS; and koel, of course. But it has no embedded SQL server, so I had to pull another image: I chose the official one from MariaDB project, as recommended by etopian.

Assuming you have Docker installed, let’s pull them both:

docker pull mariadb
docker pull etopian/docker-koel

3 Launching MariaDB container

We need to launch the MariaDB container first. Choose a SQL admin password, and launch the container with:

docker run -e MYSQL_ROOT_PASSWORD=My_MariaDB_Root_Password \
           -e MYSQL_DATABASE=forge --name mariadb -d mariadb

It seems that creating the forge database is required. I saw no indication of this in koel documentation, but I could not launch koel without it. You can change the --name of your container if you wish, though; just make sure to use the same name below.

4 Launching koel container

Launching the koel container by itself was easy; but picking the correct options to make it work took me a while. Indeed there are some parameters to select carefully the first time you run the container: first, we need to know the IP address of the SQL server host. Get it from the first container with:

DB_IP=$(docker exec mariadb ip addr | \
    sed -n 's/^ *inet \(172.17.[0-9]*.[0-9]*\)\/16.*/\1/p')

This runs ip addr command inside the container, parses the output to get the address, and assigns it to DB_IP shell variable. Now we can reuse this variable to launch koel container:

docker run \
    -e DB_HOST=$DB_IP \
    -e APP_DEBUG=false \
    -e AP_ENV="production" \
    -e DB_DATABASE="forge" \
    -e DB_USERNAME="root" \
    -e DB_PASSWORD="My_MariaDB_Root_Password" \
    -e ADMIN_EMAIL="whatever@domain.tld" \
    -e ADMIN_NAME="My_Koel_Admin_Name" \
    -e ADMIN_PASSWORD="My_Koel_Admin_Password" \
    --name koel \
    -v /PATH/TO/MUSIC/ON/THE/HOST/:/DATA/music/:ro \
    --link mariadb:mysql \
    -p 9876:80 \
    -d \


  • DB_HOST must be the IP address of the host for SQL server: this is where we reuse the result of the previous command.
  • APP_DEBUG is a bool to enable or disable debug logging (logs seem to be available in file /DATA/htdocs/storage/logs/laravel.log for Laravel, and in /DATA/logs/ for php and NGINX; I am not sure what files are impacted by this option).
  • AP_ENV can be production or some other term related to development. If you are following this tutorial, you most probably need production.
  • DB_DATABASE seems to need forge again.
  • DB_USERNAME is the user name for the database server (no kidding). Not good to work with root, but we can allow it as we are in a Docker container with no sensitive data in the base.
  • DB_PASSWORD is the password we set when launching the MariaDB container, obviously.
  • ADMIN_EMAIL is whatever mail address you want. We will need as a login name, but it does not need to be a valid address.
  • ADMIN_NAME is whatever name will be displayed once we are connected.
  • ADMIN_PASSWORD associated with email and name for the admin user for koel.
  • -v /PATH/TO/MUSIC/ON/THE/HOST/:/DATA/music/:ro means that you share the directory on the left side of the first column (:) between the host and the container. Edit this path to point to the directory where your music is. You may edit the path between the two columns, but this is not necessary: this is where your directory will be mounted as a volume in the container, and also where koel will have to look for the music. The :ro part at the end means that we mount it read only. We need no more than that, and in this way, even if the container got compromised, an attacker could not edit the audio collection.
  • --link mariadb:mysql is used to link the two containers, making the exposed port from the MariaDB container accessible to the koel container. I am not sure this is actually needed with current configuration, is might be useful only if we forbid containers to talk between each other; but I have not tested without it.
  • -p 9876:80 maps port 80 of the container to port 9876 on the host machine. The NGINX server inside the container is listening on port 80, do not change this port; but we can map any port we want on the host, this is just a random example.

Once the container is running, initialize koel with:

docker exec koel su nginx -c "cd /DATA/htdocs && php artisan init"

5 Preparing your proxy

To expose my koel installation on the web, I use a reverse NGINX proxy. I use a TLS secured connection to protect my credentials, and even the data transiting on the web. My setup is as follows:

  • koel on a subdomain of qoba.lt;
  • TLS connexion that needs a X.509 certificate;
  • Certificate Authority (CA) to assert the certificate is correct.

You may want to use only a self-signed certificate that the user will have to accept on their first connexion to the website; I can’t, because I already have a certificate for qoba.lt and www.qoba.lt domains and configured in a way that prevents me to use self-signed certificates for other subdomains. Thus I had to create my own CA (not used for this blog), and to load it inside my browser before being able to connect on my new subdomain. So let’s see how we can do it. I assume you have NGINX installed, possibly with a running website. The setup provided here is a possible configuration for running koel interface.

5.1 Generating a Certificate Authority and a certificate for the website

There are a lot of detailed tutorials on the web presenting those steps in details. I am only providing the commands here, but if you run this on your server, you should really dig to see what happens here.

mkdir ssl && cd ssl/
openssl genrsa -out ca.key 4096
openssl req -new -x509 -nodes -days 3650 -key ca.key \
    -sha512 -out ca.pem -extensions v3_ca

Second command waits for the user to answer to a bunch of questions. The only important field is the common-name, that you should set to your site domain (e.g. koel.domain.tld).

The ca.pem file that was created contains the certificate of the CA, and will have to be sent to clients. The ca.key is the key that will be used to sign certificates issued by the CA, and MUST be kept secret!


openssl genrsa -out koel.key 4096
openssl req -new -key koel.key -out koel.csr
openssl x509 -req -in koel.csr -CA ca.pem -CAkey ca.key \
    -CAcreateserial -out koel.crt -days 3640 -sha512

Again, pay attention to the common-name and fill it with the domain to protect on second step.

5.2 Setting up the proxy

Now let us configure NGINX. We will use it to proxy all the requests coming from the web and targeting our subdomain towards the koel container. Add (or link) a configuration file on the host under /etc/nginx/sites-enabled with something like this:

# We do not serve on port 80; instead we redirect automatically
# towards the secured TLS connection
server {
  listen 80;
  listen [::]:80;
  server_name koel.domain.tld; # EDIT HERE with your own domain name
  return 301 https://$host$request_uri;

server {
  # Listen on port 443 and enable SSL
  listen 443 ssl;
  listen [::]:443 ssl;

  # EDIT with path to your certificate and key
  ssl_certificate /root/ssl/koel_cert/koel.crt;
  ssl_certificate_key /root/ssl/koel_cert/koel.key;

  # EDIT with your domain
  server_name koel.domain.tld;

  # We redirect all commands to the port on which the NGINX server
  # inside the koel container is listening.
  location / {
    # EDIT line below with the port mapped on port 80 of container
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    # I needed the 3 lines below to install Ampache, not sure they are
    # still required here for koel
    proxy_set_header X-Forwarded-Proto $scheme;
    # EDIT below with correct port and domain
    proxy_redirect $scheme://koel.domain.tld;
    add_header Front-End-Https on;

And do not forget to reload your NGINX configuration. Under Debian:

systemctl reload nginx.service

Note that this NGINX proxy runs on the server, not in a container; so we want it and its connexions with the clients to be secured. This is not a tutorial to secure a web server and I am not an expert in this, so I will not give additional details here; but if you have sensitive stuff to serve with NGINX, you should search for tips to improve this configuration (ciphers restrictions, Diffie-Hellman parameter in use, Strict-Transport-Security, session cache and timeout, and so on).

Next, we need to load the CA certificate (ca.pem file) inside the browser. The easiest way to do so is to make it accessible from the webserver and to open it with the browser: Firefox, at least, automatically opens it and proposes to use it (choose to use it to authenticate websites). Otherwise, download it from the server any way you want, and import it through the preferences menu of your web client.

6 Building the audio collection

We are now able to access to the login page of koel, and to log in. Great! Time to scan the library. Before we do that, we can change the scan timeout value (recommended for big libraries). It is ten minutes by default, which appears to be far too short in my case.

docker exec -it koel \
    sed -i 's/\(APP_MAX_SCAN_TIME\)=600/\1=18000/' /DATA/htdocs/.env

Then we need to indicate the path to scan. I did not find how to do that from the command line—I could not find the path I set in any config file, I suppose it is registered in the SQL database. So I had to indicate the path on the web interface. Remember that this is the directory we mounted as a volume on the koel container, in my example it was /DATA/music/. At last we can launch the scan: if you indicated the directory to scan through the graphical interface in a browser (in Settings menu), then it was triggered when you clicked the Scan button. Otherwise (if you have found another way to indicate the path! or if the scan in the web interface fails), we can launch the scan from the command line by executing a command inside the container:

docker exec koel su nginx -c "cd /DATA/htdocs && php artisan koel:sync -v"

I had some last issues with malformed “artist” ID3 tags for some songs. It was not explicit, though, and sadly I did not copy it. Something about a violation of the uniqueness of an “artist_uniq_something” key, with some misprinted characters, when trying to insert metadata inside the database. In this case, try to edit and correct the tags. I do not know what caused the issue, but overwriting the tags with EasyTAG software did the trick. But I had to launch again the scan from the beginning… I saw a GitHub issue for koel about pursuing the scan in case of error, so this may be improved in the future.

After the collection is scanned, you can enjoy your music everywhere you go. Let’s party!