Icecast SSL with docker ? Is it ready. Does someone has an example config?

TLDR: I can’t make icecast ssl working with Docker install and the doc describe the installer based setup mainly for that part. Does anyone has an example working config to help me ?

I’ve been installing libretime for the first time recently and wanted to use Terraform to achieve fully automated install so I can reup a clean libretime instance anytime in one command. So I used the docker installation method to have a template based non interactive install.
I managed to do that until the icecast SSL part : everything works fine with a http://domain:8000/main stream, but I can’t make icecast working with ssl, so the mixed content error prevent usage with chrome basically.

I use nginx reverse and generate the certificates with classic certbot and bundle as described in the doc in one bundle.pem file that I mount in the icecast container, also changing icecast.xml and libretime config.yml accordingly. My conf files are below.

But I still get : INFO connection/get_ssl_certificate No SSL capability on any configured ports in icecast and no secure stream working.

I read about icecast not necessarily ready for SSL if not compiled correctly and stuff. But I am not sure about the reason of this INFO message and how to fix this.
The default docker install from the doc is really oriented toward non ssl icecast. Is docker install ready for SSL ? Can anyone give me some insights on the way to go from here.

libretime 3.1.0

here is my docker-compose.yml:

version: "3.9"

services:

  nginx:
    image: nginx
    ports:
      - 8080:80
    depends_on:
      - legacy
    volumes:
      - libretime_assets:/var/www/html:ro
      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro

  postgres:
    image: postgres:15
    volumes:
      - postgres_data:/var/lib/postgresql/data
    environment:
      POSTGRES_USER: libretime
      POSTGRES_PASSWORD: "${postgres-password}"
    healthcheck:
      test: pg_isready -U libretime

  rabbitmq:
    image: rabbitmq:3.12-alpine
    environment:
      RABBITMQ_DEFAULT_VHOST: /libretime
      RABBITMQ_DEFAULT_USER: libretime
      RABBITMQ_DEFAULT_PASS: "${rabbitmq-password}"
    healthcheck:
      test: rabbitmq-diagnostics -q ping

  playout:
    image: ghcr.io/libretime/libretime-playout:${libretime-version}
    init: true
    ulimits:
      nofile: 1024
    depends_on:
      - rabbitmq
    volumes:
      - ./config.yml:/etc/libretime/config.yml:ro
      - libretime_playout:/app
    environment:
      LIBRETIME_GENERAL_PUBLIC_URL: "http://nginx"

  liquidsoap:
    image: ghcr.io/libretime/libretime-playout:${libretime-version}
    command: /usr/local/bin/libretime-liquidsoap
    init: true
    ulimits:
      nofile: 1024
    ports:
      - 8001:8001
      - 8002:8002
    depends_on:
      - rabbitmq
    volumes:
      - ./config.yml:/etc/libretime/config.yml:ro
      - libretime_playout:/app
    environment:
      LIBRETIME_GENERAL_PUBLIC_URL: "http://nginx"

  analyzer:
    image: ghcr.io/libretime/libretime-analyzer:${libretime-version}
    init: true
    ulimits:
      nofile: 1024
    depends_on:
      - rabbitmq
    volumes:
      - ./config.yml:/etc/libretime/config.yml:ro
      - libretime_storage:/srv/libretime
    environment:
      LIBRETIME_GENERAL_PUBLIC_URL: "http://nginx"

  worker:
    image: ghcr.io/libretime/libretime-worker:${libretime-version}
    init: true
    ulimits:
      nofile: 1024
    depends_on:
      - rabbitmq
    volumes:
      - ./config.yml:/etc/libretime/config.yml:ro
    environment:
      LIBRETIME_GENERAL_PUBLIC_URL: "http://nginx"

  api:
    image: ghcr.io/libretime/libretime-api:${libretime-version}
    init: true
    ulimits:
      nofile: 1024
    depends_on:
      - postgres
      - rabbitmq
    volumes:
      - ./config.yml:/etc/libretime/config.yml:ro
      - libretime_storage:/srv/libretime

  legacy:
    image: ghcr.io/libretime/libretime-legacy:${libretime-version}
    init: true
    ulimits:
      nofile: 1024
    depends_on:
      - postgres
      - rabbitmq
    volumes:
      - ./config.yml:/etc/libretime/config.yml:ro
      - libretime_assets:/var/www/html
      - libretime_storage:/srv/libretime


  icecast:
    image: ghcr.io/libretime/icecast:2.4.4
    ports:
      # - 8000:8000
      - 8443:8443
    volumes:
      - ./icecast.xml:/etc/icecast2/icecast.xml
      - type: bind
        source: ./bundle.pem
        target: /etc/icecast2/bundle.pem
    environment:
      ICECAST_SOURCE_PASSWORD: "${icecast-source-password}"
      ICECAST_ADMIN_PASSWORD: "${icecast-admin-password}"
      ICECAST_RELAY_PASSWORD: "${icecast-relay-password}"

volumes:
  postgres_data: {}
  libretime_storage: {}
  libretime_assets: {}
  libretime_playout: {}

then config.yml:

# See https://libretime.org/docs/admin-manual/setup/configuration/

general:
  # The public url.
  # > this field is REQUIRED
  public_url: "https://${public-domain}"
  # The internal API authentication key.
  # > this field is REQUIRED
  api_key: "${api-key}"
  # The Django API secret key. If not defined, the value of [general.api_key] will be
  # used as fallback.
  # > this field will be REQUIRED starting with LibreTime 4.0.0
  secret_key: "${secret-key}"

  # List of origins allowed to access resources on the server, the public url
  # origin is automatically included.
  # > default is []
  allowed_cors_origins: []

  # The server timezone, should be a lookup key in the IANA time zone database,
  # for example Europe/Berlin.
  # > default is UTC
  timezone: Europe/Paris

  # How many hours ahead Playout should cache scheduled media files.
  # > default is 1
  cache_ahead_hours: 1

  # Authentication adaptor to use for the legacy service, specify a class like
  # LibreTime_Auth_Adaptor_FreeIpa to replace the built-in adaptor.
  # > default is local
  auth: local

storage:
  # Path of the storage directory.
  # > default is /srv/libretime
  path: /srv/libretime

database:
  # The hostname of the PostgreSQL server.
  # > default is localhost
  host: postgres
  # The port of the PostgreSQL server.
  # > default is 5432
  port: 5432
  # The name of the PostgreSQL database.
  # > default is libretime
  name: libretime
  # The username of the PostgreSQL user.
  # > default is libretime
  user: libretime
  # The password of the PostgreSQL user.
  # > default is libretime
  password: "${postgres-password}"

rabbitmq:
  # The hostname of the RabbitMQ server.
  # > default is localhost
  host: rabbitmq
  # The port of the RabbitMQ server.
  # > default is 5672
  port: 5672
  # The virtual host of RabbitMQ server.
  # > default is /libretime
  vhost: "/libretime"
  # The username of the RabbitMQ user.
  # > default is libretime
  user: libretime
  # The password of the RabbitMQ user.
  # > default is libretime
  password: "${rabbitmq-password}"

playout:
  # Liquidsoap connection host.
  # > default is localhost
  liquidsoap_host: liquidsoap
  # Liquidsoap connection port.
  # > default is 1234
  liquidsoap_port: 1234

  # The format for recordings.
  # > must be one of (ogg, mp3)
  # > default is ogg
  record_file_format: mp3
  # The bitrate for recordings.
  # > default is 256
  record_bitrate: 256
  # The samplerate for recordings.
  # > default is 44100
  record_samplerate: 44100
  # The number of channels for recordings.
  # > default is 2
  record_channels: 2
  # The sample size for recordings.
  # > default is 16
  record_sample_size: 16

liquidsoap:
  # Liquidsoap server listen address.
  # > default is 127.0.0.1
  server_listen_address: 0.0.0.0
  # Liquidsoap server listen port.
  # > default is 1234
  server_listen_port: 1234

  # Input harbor listen address.
  # > default is ["0.0.0.0"]
  harbor_listen_address: ["0.0.0.0"]

  # Input harbor tls certificate path.
  harbor_ssl_certificate:
  # Input harbor tls certificate private key path.
  harbor_ssl_private_key:
  # Input harbor tls certificate password.
  harbor_ssl_password:

stream:
  # Inputs sources.
  inputs:
    # Main harbor input.
    main:
      # Harbor input public url. If not defined, the value will be generated from
      # the [general.public_url] hostname, the input port and mount.
      public_url:
      # Mount point for the main harbor input.
      # > default is main
      mount: main
      # Listen port for the main harbor input.
      # > default is 8001
      port: 8001
      # Whether the input harbor is secured with the tls certificate.
      # > default is false
      secure: false

    # Show harbor input.
    show:
      # Harbor input public url. If not defined, the value will be generated from
      # the [general.public_url] hostname, the input port and mount.
      public_url:
      # Mount point for the show harbor input.
      # > default is show
      mount: show
      # Listen port for the show harbor input.
      # > default is 8002
      port: 8002
      # Whether the input harbor is secured with the tls certificate.
      # > default is false
      secure: false

  # Output streams.
  outputs:
    # Default icecast output
    # This can be reused to define multiple outputs without duplicating data
    .default_icecast_output: &default_icecast_output
      host: icecast
      # port: 8000
      source_password: "${icecast-source-password}"
      admin_password: "${icecast-admin-password}"
      name: LibreTime!
      description: LibreTime Radio!
      website: https://libretime.org
      genre: various

    # Icecast output streams.
    # > max items is 3
    icecast:
      # The default Icecast output stream
      - <<: *default_icecast_output
        enabled: true
        public_url: https://${public-domain}:8443/main.mp3
        mount: main
        port: 8443
        audio:
          format: mp3
          bitrate: 256

      # You can define extra outputs by reusing the default output using a yaml anchor
      - <<: *default_icecast_output
        enabled: false
        mount: main-ssl
        public_url:
        port: 8443
        audio:
          format: mp3
          bitrate: 256

      - # Whether the output is enabled.
        # > default is false
        enabled: false
        # Output public url, If not defined, the value will be generated from
        # the [general.public_url] hostname, the output port and mount.
        public_url:
        # Icecast server host.
        # > default is localhost
        host: localhost
        # Icecast server port.
        # > default is 8000
        port: 8000
        # Icecast server mount point.
        # > this field is REQUIRED
        mount: main
        # Icecast source user.
        # > default is source
        source_user: source
        # Icecast source password.
        # > this field is REQUIRED
        source_password: hackme
        # Icecast admin user.
        # > default is admin
        admin_user: admin
        # Icecast admin password. If not defined, statistics will not be collected.
        admin_password: hackme

        # Icecast output audio.
        audio:
          # Icecast output audio format.
          # > must be one of (aac, mp3, ogg, opus)
          # > this field is REQUIRED
          format: mp3
          # Icecast output audio bitrate.
          # > must be one of (32, 48, 64, 96, 128, 160, 192, 224, 256, 320)
          # > this field is REQUIRED
          bitrate: 256

          # format=ogg only field: Embed metadata (track title, artist, and show name)
          # in the output stream. Some bugged players will disconnect from the stream
          # after every songs when playing ogg streams that have metadata information
          # enabled.
          # > default is false
          enable_metadata: false

        # Icecast stream name.
        name: LibreTime!
        # Icecast stream description.
        description: LibreTime Radio!
        # Icecast stream website.
        website: https://libretime.org
        # Icecast stream genre.
        genre: various

    # Shoutcast output streams.
    # > max items is 1
    shoutcast:
      - # Whether the output is enabled.
        # > default is false
        enabled: false
        # Output public url. If not defined, the value will be generated from
        # the [general.public_url] hostname and the output port.
        public_url:
        # Shoutcast server host.
        # > default is localhost
        host: localhost
        # Shoutcast server port.
        # > default is 8000
        port: 8000
        # Shoutcast source user.
        # > default is source
        source_user: source
        # Shoutcast source password.
        # > this field is REQUIRED
        source_password: hackme
        # Shoutcast admin user.
        # > default is admin
        admin_user: admin
        # Shoutcast admin password. If not defined, statistics will not be collected.
        admin_password: hackme

        # Shoutcast output audio.
        audio:
          # Shoutcast output audio format.
          # > must be one of (aac, mp3)
          # > this field is REQUIRED
          format: mp3
          # Shoutcast output audio bitrate.
          # > must be one of (32, 48, 64, 96, 128, 160, 192, 224, 256, 320)
          # > this field is REQUIRED
          bitrate: 256

        # Shoutcast stream name.
        name: LibreTime!
        # Shoutcast stream website.
        website: https://libretime.org
        # Shoutcast stream genre.
        genre: various

    # System outputs.
    # > max items is 1
    system:
      - # Whether the output is enabled.
        # > default is false
        enabled: false
        # System output kind.
        # > must be one of (alsa, ao, oss, portaudio, pulseaudio)
        # > default is alsa
        kind: alsa

then icecast.xml:

<icecast>
    <!-- location and admin are two arbitrary strings that are e.g. visible
         on the server info page of the icecast web interface
         (server_version.xsl). -->
    <location>Paris</location>
    <admin>machin@confmail.test</admin>

    <!-- IMPORTANT!
         Especially for inexperienced users:
         Start out by ONLY changing all passwords and restarting Icecast.
         For detailed setup instructions please refer to the documentation.
         It's also available here: http://icecast.org/docs/
    -->

    <limits>
        <clients>100</clients>
        <sources>2</sources>
        <queue-size>524288</queue-size>
        <client-timeout>30</client-timeout>
        <header-timeout>15</header-timeout>
        <source-timeout>10</source-timeout>
        <!-- If enabled, this will provide a burst of data when a client
             first connects, thereby significantly reducing the startup
             time for listeners that do substantial buffering. However,
             it also significantly increases latency between the source
             client and listening client.  For low-latency setups, you
             might want to disable this. -->
        <burst-on-connect>1</burst-on-connect>
        <!-- same as burst-on-connect, but this allows for being more
             specific on how much to burst. Most people won't need to
             change from the default 64k. Applies to all mountpoints  -->
        <burst-size>65535</burst-size>
    </limits>

    <authentication>
        <!-- Sources log in with username 'source' -->
        <source-password>${icecast-source-password}</source-password>
        <!-- Relays log in with username 'relay' -->
        <relay-password>${icecast-relay-password}</relay-password>

        <!-- Admin logs in with the username given below -->
        <admin-user>admin</admin-user>
        <admin-password>${icecast-admin-password}</admin-password>
    </authentication>

    <!-- set the mountpoint for a shoutcast source to use, the default if not
         specified is /stream but you can change it here if an alternative is
         wanted or an extension is required
    <shoutcast-mount>/live.nsv</shoutcast-mount>
    -->

    <!-- Uncomment this if you want directory listings -->
    <!--
    <directory>
        <yp-url-timeout>15</yp-url-timeout>
        <yp-url>http://dir.xiph.org/cgi-bin/yp-cgi</yp-url>
    </directory>
    -->

    <!-- This is the hostname other people will use to connect to your server.
         It affects mainly the urls generated by Icecast for playlists and yp
         listings. You MUST configure it properly for YP listings to work!
    -->
    <hostname>${public-domain}</hostname>

    <!-- You may have multiple <listen-socket> elements -->
    <listen-socket>
        <port>8000</port>
        <!-- <bind-address>127.0.0.1</bind-address> -->
        <!-- <shoutcast-mount>/stream</shoutcast-mount> -->
    </listen-socket>

    <listen-socket>
        <port>8443</port>
        <ssl>1</ssl>
    </listen-socket>


    <!-- Global header settings
         Headers defined here will be returned for every HTTP request to Icecast.

         The ACAO header makes Icecast public content/API by default
         This will make streams easier embeddable (some HTML5 functionality needs it).
         Also it allows direct access to e.g. /status-json.xsl from other sites.
         If you don't want this, comment out the following line or read up on CORS.
    -->
    <http-headers>
        <header name="Access-Control-Allow-Origin" value="*" />
    </http-headers>


    <!-- Relaying
         You don't need this if you only have one server.
         Please refer to the documentation for a detailed explanation.
    -->
    <!--<master-server>127.0.0.1</master-server>-->
    <!--<master-server-port>8001</master-server-port>-->
    <!--<master-update-interval>120</master-update-interval>-->
    <!--<master-password>hackme</master-password>-->

    <!-- setting this makes all relays on-demand unless overridden, this is
         useful for master relays which do not have <relay> definitions here.
         The default is 0 -->
    <!--<relays-on-demand>1</relays-on-demand>-->

    <!--
    <relay>
        <server>127.0.0.1</server>
        <port>8080</port>
        <mount>/example.ogg</mount>
        <local-mount>/different.ogg</local-mount>
        <on-demand>0</on-demand>

        <relay-shoutcast-metadata>0</relay-shoutcast-metadata>
    </relay>
    -->


    <!-- Mountpoints
         Only define <mount> sections if you want to use advanced options,
         like alternative usernames or passwords
    -->

    <!-- Default settings for all mounts that don't have a specific <mount type="normal">.
    -->
    <!--
    <mount type="default">
        <public>0</public>
        <intro>/server-wide-intro.ogg</intro>
        <max-listener-duration>3600</max-listener-duration>
        <authentication type="url">
                <option name="mount_add" value="http://auth.example.org/stream_start.php"/>
        </authentication>
        <http-headers>
                <header name="foo" value="bar" />
        </http-headers>
    </mount>
    -->
    <mount type="default">
        <charset>UTF-8</charset>
    </mount>

    <!-- Normal mounts -->
    <!--
    <mount type="normal">
        <mount-name>/example-complex.ogg</mount-name>

        <username>othersource</username>
        <password>hackmemore</password>

        <max-listeners>1</max-listeners>
        <dump-file>/tmp/dump-example1.ogg</dump-file>
        <burst-size>65536</burst-size>
        <fallback-mount>/example2.ogg</fallback-mount>
        <fallback-override>1</fallback-override>
        <fallback-when-full>1</fallback-when-full>
        <intro>/example_intro.ogg</intro>
        <hidden>1</hidden>
        <public>1</public>
        <authentication type="htpasswd">
                <option name="filename" value="myauth"/>
                <option name="allow_duplicate_users" value="0"/>
        </authentication>
        <http-headers>
                <header name="Access-Control-Allow-Origin" value="http://webplayer.example.org" />
                <header name="baz" value="quux" />
        </http-headers>
        <on-connect>/home/icecast/bin/stream-start</on-connect>
        <on-disconnect>/home/icecast/bin/stream-stop</on-disconnect>
    </mount>
    -->

    <!--
    <mount type="normal">
        <mount-name>/auth_example.ogg</mount-name>
        <authentication type="url">
            <option name="mount_add"       value="http://myauthserver.net/notify_mount.php"/>
            <option name="mount_remove"    value="http://myauthserver.net/notify_mount.php"/>
            <option name="listener_add"    value="http://myauthserver.net/notify_listener.php"/>
            <option name="listener_remove" value="http://myauthserver.net/notify_listener.php"/>
            <option name="headers"         value="x-pragma,x-token"/>
            <option name="header_prefix"   value="ClientHeader."/>
        </authentication>
    </mount>
    -->

    <fileserve>1</fileserve>

    <paths>
        <!-- basedir is only used if chroot is enabled -->
        <basedir>/usr/share/icecast2</basedir>

        <!-- Note that if <chroot> is turned on below, these paths must both
             be relative to the new root, not the original root -->
        <logdir>/var/log/icecast2</logdir>
        <webroot>/usr/share/icecast2/web</webroot>
        <adminroot>/usr/share/icecast2/admin</adminroot>
        <!-- <pidfile>/usr/share/icecast2/icecast.pid</pidfile> -->

        <!-- Aliases: treat requests for 'source' path as being for 'dest' path
             May be made specific to a port or bound address using the "port"
             and "bind-address" attributes.
          -->
        <!--
        <alias source="/foo" destination="/bar"/>
        -->
        <!-- Aliases: can also be used for simple redirections as well,
             this example will redirect all requests for http://server:port/ to
             the status page
        -->
        <alias source="/" destination="/status.xsl" />
        <ssl-certificate>/etc/icecast2/bundle.pem</ssl-certificate>
        <!-- The certificate file needs to contain both public and private part.
             Both should be PEM encoded.
        <ssl-certificate>/usr/share/icecast2/bundle.pem</ssl-certificate>
        -->
    </paths>

    <logging>
        <accesslog>access.log</accesslog>
        <errorlog>error.log</errorlog>
        <!-- <playlistlog>playlist.log</playlistlog> -->
        <loglevel>3</loglevel> <!-- 4 Debug, 3 Info, 2 Warn, 1 Error -->
        <logsize>10000</logsize> <!-- Max size of a logfile -->
        <!-- If logarchive is enabled (1), then when logsize is reached
             the logfile will be moved to [error|access|playlist].log.DATESTAMP,
             otherwise it will be moved to [error|access|playlist].log.old.
             Default is non-archive mode (i.e. overwrite)
        -->
        <!-- <logarchive>1</logarchive> -->
    </logging>

    <security>
        <chroot>0</chroot>
        <!--
        <changeowner>
            <user>nobody</user>
            <group>nogroup</group>
        </changeowner>
        -->
    </security>
</icecast>

Check out the conversation HTTPS on libretime, that might help

Hey @cderuiter; thanks for the answer and the link but itsn’t really is about the dockerised config if I read correctly.

My error (preceding message) in the icecast container, when I google it suggest that the icecast version inside the container is not ssl ready or I miss something and I think I got the certificate part right already. I bundled the two certificates files into one, mounted it inside the container and adjusted the icecast config. Still icecast seems not ssl ready…

I think I read somewhere in the forum that you use a dockerised configuration. Is there anything specific regarding the icecast SSL ? Can I have an example docker-compose if you have some time for that ?

Within the other post you reference you will find the changes I made to the docker-compose file to externalize several configuration files and the db.
the icecast XML does need the SSL files in a specific format - order to work, this took me a little fiddling.
cat “private key” “new certificate file” DigiCertCA.crt My_CA_Bundle.crt TrustedRoot.crt > myicecastssl.crt
hope that helps