DEV Community

Shan Desai
Shan Desai

Posted on • Originally published at shantanoo-desai.github.io

Forward Compatibility for Mosquitto MQTT Broker with Docker Compose v2

Planning

While working on a personal project Komponist, I 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Bringing the container up using:

docker compose -f docker-compose.mosquitto.yml up
Enter fullscreen mode Exit fullscreen mode

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.
...
...

Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

Upon bringing the stack up again:

docker compose -f docker-compose.mosquitto.yml up
Enter fullscreen mode Exit fullscreen mode

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

Top comments (0)