Contents

Forward Compatibility for Mosquitto MQTT Broker with Docker Compose v2

Planning

While working on a personal project Komponist, it was due to update Mosquitto MQTT Broker due to a CVE and found some interesting changes that will impact me in the future when it comes to configuring the Broker. This post provides a solution to make the Broker compatible with future versions using new and less visited concepts in Docker Compose v2, namely:

  • Docker Init Containers
  • Using the include feature in Docker Compose files

Observations

This section provides information on what currently is observed post upgrading the Mosquitto Broker to v2.0.18.

Current Docker Compose File

This is my current docker-compose.mosquitto.yml with the following file structure generated by Komponist:

|- docker-compose.mosquitto.yml
|-- mosquitto
    |--- acl
    |--- users
    |--- mosquitto.conf

The Compose file content is:

services:
  mosquitto:
    image: docker.io/eclipse-mosquitto:2.0.18
    container_name: komponist_mosquitto
    configs:
      - mosquitto_conf
      - mosquitto_acl
      - mosquitto_users
    command: mosquitto -c /mosquitto_conf
    security_opt:
      - "no-new-privileges=true"
    volumes:
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro

configs:
  mosquitto_conf:
    file: ./mosquitto/mosquitto.conf
  mosquitto_acl:
    file: ./mosquitto/acl
  mosquitto_users:
    file: ./mosquitto/users

Bringing the container up using:

docker compose -f docker-compose.mosquitto.yml up

The logs will throw some warnings such as the following:

Warning: File /mosquitto_users has world readable permissions. Future versions will refuse to load this file.

Warning: File /mosquitto_users owner is not mosquitto. Future versions will refuse to load this file.
...
...

So for future versions the files need to have a specific user and file permissions to make the current configuration files valid.

Caveats

In systems where I do not have superuser privileges to create a new user mosquitto whose UID/GID should be 1883 and changing file permissions is not an option, the only way to tackle such a solution is through Docker Init Containers.

Docker Init Containers are like sidecar containers in Kubernetes or a patching container where it initializes a state (here specific configuration file) and then starts the core container.

This feature has existed for a while, but it is not well documented in the Compose Specifications with some practical examples. In essence, we will use the depends_on service dependency logic between a generic alpine container that will:

  1. change the user for the core configuration files
  2. change the file permissions for the core configuration files
  3. pass on the core files to the eclipse-mosquitto broker using shared volumes

Once this container is mounted with the files and is run successfully, the adapted configuration files will be passed on to the main docker container for the Mosquitto MQTT Broker.

Solution

We keep the file structure same as previously mentioned but we create a new Compose file called docker-compose.mosquitto-init.yml file as follows:

services:
  mosquitto_init:
    image: alpine:3.18
    container_name: mosquitto_init
    command: sh -c 'chmod 0400 -R /config/ && chown 1883:1883 -R /config/'
    volumes:
      - ./mosquitto/mosquitto.conf:/config/mosquitto_conf
      - ./mosquitto/users:/config/mosquitto_users
      - ./mosquitto/acl:/config/mosquitto_acl

We are mounting the configuration files into the init container under the /config directory and changing the file persmissions and ownership for the files in this init container.

NOTE: we will have to refactor out the Docker Configs section previously configured in original docker-compose.mosquitto.yml file into the docker-compose.mosquitto-init.yml file

We now refactor the mosquitto.conf file a bit to accept the new location for the files as follows:

# MQTT Port Listener
listener    1883
protocol    mqtt

# Authentication
allow_anonymous     false
password_file       /config/mosquitto_users

# Authorization
acl_file    /config/mosquitto_acl
# Logging Configuration
log_timestamp true
log_type all

Note the /config as prefix to the ACL and password file.

Final refactoring takes place in the docker-compose.mosquitto.yml file as follows:

include:
  - docker-compose.mosquitto-init.yml

services:
  mosquitto:
    image: docker.io/eclipse-mosquitto:2.0.18
    container_name: komponist_mosquitto
    entrypoint: mosquitto -c /config/mosquitto_conf
    depends_on:
      mosquitto_init:
        condition: service_completed_successfully
    security_opt:
      - "no-new-privileges=true"
    volumes_from:
      - mosquitto_init
    volumes:
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro

Upon bringing the stack up again:

docker compose -f docker-compose.mosquitto.yml up

the Warnings should be gone and your config files that were valid prior to version 2.0.18 will still be compatible for the upcoming versions (unless the development of mosquitto demands something more in the future).

In a Nutshell, the main mosquitto service is now dependent on the mosquitto-init service via the depends_on spec and core files are exchanged from the init container to the main container via volumes_from spec.

NOTE: this approach will change the file permissions / owner for the core configuration files on the host machine since they are volume mounted.

At the time of writing this post, this is the only approach I have tested out.

If you have read this post and have better approaches in mind, connect with me on LinkedIn and share your solutions or improve upon the method. I would love to hear your feedback.

GitHub Gist

the Gist of the code can be found here