From 96593c41a6c01e7d4eb2adab9ffdeb7f3c7afe6b Mon Sep 17 00:00:00 2001 From: Marc Hefter Date: Sun, 31 Aug 2025 16:00:19 +0200 Subject: [PATCH 1/4] feature: image appuser with PUID/PGID env support PUID/PGID supporting image 2.1-appuser --- docker/2.1-appuser/Dockerfile | 115 ++++++++++++++++++++++++ docker/2.1-appuser/README.md | 104 +++++++++++++++++++++ docker/2.1-appuser/docker-entrypoint.sh | 36 ++++++++ 3 files changed, 255 insertions(+) create mode 100644 docker/2.1-appuser/Dockerfile create mode 100644 docker/2.1-appuser/README.md create mode 100644 docker/2.1-appuser/docker-entrypoint.sh diff --git a/docker/2.1-appuser/Dockerfile b/docker/2.1-appuser/Dockerfile new file mode 100644 index 0000000000..7487275396 --- /dev/null +++ b/docker/2.1-appuser/Dockerfile @@ -0,0 +1,115 @@ +FROM alpine:3.23 + +ENV VERSION=2.1.2 \ + DOWNLOAD_SHA256=fd905380691ac65ea5a93779e8214941829e3d6e038d5edff9eac5fd74cbed02 \ + GPG_KEYS=A0D6EEA1DCAE49A635A3B2F0779B22DFB3E717B7 + +LABEL \ + org.opencontainers.image.authors="Roger Light " \ + org.opencontainers.image.title="eclipse-mosquitto" \ + org.opencontainers.image.description="Eclipse Mosquitto MQTT Broker" \ + org.opencontainers.image.url="https://mosquitto.org/" \ + org.opencontainers.image.documentation="https://mosquitto.org/documentation/" \ + org.opencontainers.image.source="https://github.com/eclipse-mosquitto/mosquitto" \ + org.opencontainers.image.licenses="EPL-2.0 OR BSD-3-Clause" \ + org.opencontainers.image.version=${VERSION} + +# UID/GID will set the mosquitto UID/GID at build time (Dockerfile) +ARG UID=1883 +ARG GID=1883 +# PUID/PGID will set the mosquitto UID/GID at run time (docker-entrypoint.sh) +# and run mosquitto under the changed user +ENV PUID=${UID} +ENV PGID=${GID} + +RUN set -x && \ + apk --no-cache add \ + daemontools-encore \ + shadow \ + && \ + apk --no-cache add --virtual build-deps \ + argon2-dev \ + build-base \ + cjson-dev \ + cmake \ + cunit-dev \ + gnupg \ + gtest-dev \ + libedit-dev \ + libmicrohttpd-dev \ + linux-headers \ + openssl-dev \ + samurai \ + sqlite-dev \ + uthash-dev \ + util-linux-dev && \ + wget https://mosquitto.org/files/source/mosquitto-${VERSION}.tar.gz -O /tmp/mosq.tar.gz && \ + echo "$DOWNLOAD_SHA256 /tmp/mosq.tar.gz" | sha256sum -c - && \ + wget https://mosquitto.org/files/source/mosquitto-${VERSION}.tar.gz.asc -O /tmp/mosq.tar.gz.asc && \ + export GNUPGHOME="$(mktemp -d)" && \ + found=''; \ + for server in \ + hkps://keys.openpgp.org \ + hkp://keyserver.ubuntu.com:80 \ + pgp.mit.edu \ + ; do \ + echo "Fetching GPG key $GPG_KEYS from $server"; \ + gpg --keyserver "$server" --keyserver-options timeout=10 --recv-keys "$GPG_KEYS" && found=yes && break; \ + done; \ + test -z "$found" && echo >&2 "error: failed to fetch GPG key $GPG_KEYS" && exit 1; \ + gpg --batch --verify /tmp/mosq.tar.gz.asc /tmp/mosq.tar.gz && \ + gpgconf --kill all && \ + rm -rf "$GNUPGHOME" /tmp/mosq.tar.gz.asc && \ + mkdir -p /build/mosq && \ + tar --strip=1 -xf /tmp/mosq.tar.gz -C /build/mosq && \ + rm /tmp/mosq.tar.gz && \ + cmake -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=/usr \ + -DHTTP_API_DIR='\"/usr/share/mosquitto/dashboard\"' \ + -DWITH_DOCS=OFF \ + -S /build/mosq \ + -B /build/mosq/build && \ + cmake --build /build/mosq/build && \ + addgroup -S -g "${PGID}" mosquitto 2>/dev/null && \ + adduser -S -u "${PUID}" -D -H -h /var/empty -s /sbin/nologin -G mosquitto -g mosquitto mosquitto 2>/dev/null && \ + mkdir -p /mosquitto/config /mosquitto/data /mosquitto/log && \ + install -d /usr/sbin/ && \ + install -s -m755 /build/mosq/build/client/mosquitto_pub /usr/bin/mosquitto_pub && \ + install -s -m755 /build/mosq/build/client/mosquitto_rr /usr/bin/mosquitto_rr && \ + install -s -m755 /build/mosq/build/client/mosquitto_sub /usr/bin/mosquitto_sub && \ + install -s -m644 /build/mosq/build/lib/libmosquitto.so.1 /usr/lib/libmosquitto.so.1 && \ + install -s -m755 /build/mosq/build/src/mosquitto /usr/sbin/mosquitto && \ + install -s -m755 /build/mosq/build/apps/mosquitto_ctrl/mosquitto_ctrl /usr/bin/mosquitto_ctrl && \ + install -s -m755 /build/mosq/build/apps/mosquitto_passwd/mosquitto_passwd /usr/bin/mosquitto_passwd && \ + install -s -m755 /build/mosq/build/apps/mosquitto_signal/mosquitto_signal /usr/bin/mosquitto_signal && \ + install -s -m755 /build/mosq/build/plugins/acl-file/mosquitto_acl_file.so /usr/lib/mosquitto_acl_file.so && \ + install -s -m755 /build/mosq/build/plugins/dynamic-security/mosquitto_dynamic_security.so /usr/lib/mosquitto_dynamic_security.so && \ + install -s -m755 /build/mosq/build/plugins/password-file/mosquitto_password_file.so /usr/lib/mosquitto_password_file.so && \ + install -s -m755 /build/mosq/build/plugins/persist-sqlite/mosquitto_persist_sqlite.so /usr/lib/mosquitto_persist_sqlite.so && \ + install -s -m755 /build/mosq/build/plugins/sparkplug-aware/mosquitto_sparkplug_aware.so /usr/lib/mosquitto_sparkplug_aware.so && \ + install -m644 /build/mosq/docker/2.1-alpine/mosquitto.conf /mosquitto/config/mosquitto.conf && \ + install -m644 /build/mosq/docker/2.1-ubuntu/mosquitto.conf /mosquitto-no-auth.conf && \ + install -d /usr/share/mosquitto && \ + cp -r /build/mosq/dashboard/src /usr/share/mosquitto/dashboard && \ + install -Dm644 /build/mosq/epl-v20 /usr/share/licenses/mosquitto/epl-v20 && \ + install -Dm644 /build/mosq/edl-v10 /usr/share/licenses/mosquitto/edl-v10 && \ + chown -R mosquitto:mosquitto /mosquitto && \ + apk --no-cache add \ + argon2-libs \ + ca-certificates \ + cjson \ + libedit \ + libmicrohttpd \ + sqlite-libs \ + tzdata && \ + apk del build-deps && \ + rm -rf /build + +VOLUME ["/mosquitto/data", "/mosquitto/log"] + +# Set up the entry point script and default command +COPY --chmod=0755 docker-entrypoint.sh / +EXPOSE 1883 +ENTRYPOINT ["/docker-entrypoint.sh"] +CMD ["/usr/sbin/mosquitto", "-c", "/mosquitto/config/mosquitto.conf"] diff --git a/docker/2.1-appuser/README.md b/docker/2.1-appuser/README.md new file mode 100644 index 0000000000..271872f6be --- /dev/null +++ b/docker/2.1-appuser/README.md @@ -0,0 +1,104 @@ +# Eclipse Mosquitto Docker Image +Containers built with this Dockerfile build as source from published tarballs. + +## Mount Points +A docker mount point has been created in the image to be used for configuration. +``` +/mosquitto/config +``` + +Two docker volumes have been created in the image to be used for persistent storage and logs. +``` +/mosquitto/data +/mosquitto/log +``` + +## User/Group + +The image runs mosquitto under the mosquitto user and group. +Default uid and gid are 1883. + +- uid and gid can be specified at build time with `--build-arg UID={uid}` and`--build-arg GID={gid}` +- uid and gid can be specified at runtime with `--env PUID={uid}` and `--env PGID={gid}` +- changing of uid/gid will fail, if uid/gid already used + +The `docker-entrypoint.sh` script modifies +group mosquitto's gid and user mosquitto's uid. +Those will fail silently, if uid or gid is already occupied. +The filesystem ownership will be changed, if group/user modification succeded. +After modifications, the root privileges will be dropped +and the process will be started under user account mosquitto. + +## Running without a configuration file +Mosquitto 2.0 and up requires you to configure listeners and authentication +before it will allow connections from anything other than the loopback +interface. In the context of a container, this means you would normally need to +provide a configuration file with your settings. + +However, this container provides a default configuration which listens on port +1883 for unauthenticated access, and port 9883 for the local http dashboard. +If you wish to run mosquitto without any authentication, and without setting +any other configuration options, you can run without a configuration by binding +the appropriate network ports: +``` +docker run -it -p 1883:1883 -p localhost:9883:9883 eclipse-mosquitto: +``` + +## Configuration +To use a custom configuration file, create a **local** config directory with a +mosquitto.conf inside, then mount this directory to `/mosquitto/config` + +``` +docker run -it -p 1883:1883 -v :/mosquitto/config eclipse-mosquitto: +``` + +Your configuration file must include a `listener`, and you must configure some +form of authentication or allow unauthenticated access. If you do not do this, +clients will be unable to connect. + + +File based authentication and authorisation: +``` +listener 1883 +plugin /usr/lib/mosquitto_password_file.so +plugin_opt_password_file /mosquitto/data/mosquitto.password_file + +plugin /usr/lib/mosquitto_acl_file.so +plugin_opt_acl_file /mosquitto/data/mosquitto.aclfile +``` + +Plugin based authentication and authorisation: +``` +listener 1883 +plugin /usr/lib/mosquitto_dynamic_security.so +plugin_opt_config_file /mosquitto/data/mosquitto-dynsec.json +``` + +Unauthenticated access: +``` +listener 1883 +allow_anonymous true +``` + +:boom: if the mosquitto configuration (mosquitto.conf) was modified +to use non-default ports, the docker run command will need to be updated +to expose the ports that have been configured, for example: + +``` +docker run -it -p 1883:1883 -p 8080:8080 -v :/mosquitto/config eclipse-mosquitto: +``` + +Configuration can be changed to: + +* persist data to `/mosquitto/data` +* log to `/mosquitto/log/mosquitto.log` + +i.e. add the following to `mosquitto.conf`: +``` +persistence_location /mosquitto/data/ +plugin /usr/lib/mosquitto_persist_sqlite.so + +log_dest file /mosquitto/log/mosquitto.log +``` + +**Note**: For any volume used, the data will be persistent between containers. diff --git a/docker/2.1-appuser/docker-entrypoint.sh b/docker/2.1-appuser/docker-entrypoint.sh new file mode 100644 index 0000000000..770a615cf4 --- /dev/null +++ b/docker/2.1-appuser/docker-entrypoint.sh @@ -0,0 +1,36 @@ +#!/bin/ash +### docker-entrypoint.sh for alpine linux +set -e + +# get current uid/gid for user mosquitto +CURRENT_UID=$(/usr/bin/id -u mosquitto) +CURRENT_GID=$(/usr/bin/id -g mosquitto) + +# prepare user/group and permissions +if [ "$(/usr/bin/id -u)" != '0' ]; then + # we are an unprivileged user, don't modify system + echo "running as: $(/usr/bin/id)" +else + # change user and/or group to PUID/PGID + if [[ "${PGID}" != "${CURRENT_GID}" ]]; then + /usr/sbin/groupmod --gid "${PGID}" mosquitto 2>/dev/null && \ + /bin/chgrp --recursive "${PGID}" /mosquitto 2>/dev/null || true + fi + if [[ "${PUID}" != "${CURRENT_UID}" ]]; then + # if modification of gid failed, the user's primary group will no longer be mosquitto + /usr/sbin/usermod --uid "${PUID}" --gid "${PGID}" --groups mosquitto mosquitto 2>/dev/null && \ + /bin/chown --recursive "${PUID}" /mosquitto 2>/dev/null || true + fi + # modify filesystem ownership, otherwise /mosquitto will be inaccessible (and mode=0750) + #[ -d "/mosquitto" ] && /bin/chown --recursive "${PUID}:${PGID}" /mosquitto 2>/dev/null || true +fi + +# execute CMD +if [ "$(/usr/bin/id -u)" != '0' ]; then + # already running as unprivileged user + exec "$@" +else + [ -x /usr/bin/setuidgid ] || apk --no-cache add daemontools-encore + # drop from root to mosquitto + /usr/bin/setuidgid mosquitto "$@" +fi From 09867cae02a95f4507e08929ea5d7b297725e665 Mon Sep 17 00:00:00 2001 From: Marc Guenevere Hefter Date: Tue, 18 Aug 2026 13:54:39 +0200 Subject: [PATCH 2/4] chown only image files, not host files --- docker/2.1-appuser/docker-entrypoint.sh | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/docker/2.1-appuser/docker-entrypoint.sh b/docker/2.1-appuser/docker-entrypoint.sh index 770a615cf4..33d40cca85 100644 --- a/docker/2.1-appuser/docker-entrypoint.sh +++ b/docker/2.1-appuser/docker-entrypoint.sh @@ -13,16 +13,15 @@ if [ "$(/usr/bin/id -u)" != '0' ]; then else # change user and/or group to PUID/PGID if [[ "${PGID}" != "${CURRENT_GID}" ]]; then - /usr/sbin/groupmod --gid "${PGID}" mosquitto 2>/dev/null && \ - /bin/chgrp --recursive "${PGID}" /mosquitto 2>/dev/null || true + /usr/sbin/groupmod --gid "${PGID}" mosquitto 2>/dev/null || true fi if [[ "${PUID}" != "${CURRENT_UID}" ]]; then # if modification of gid failed, the user's primary group will no longer be mosquitto - /usr/sbin/usermod --uid "${PUID}" --gid "${PGID}" --groups mosquitto mosquitto 2>/dev/null && \ - /bin/chown --recursive "${PUID}" /mosquitto 2>/dev/null || true + /usr/sbin/usermod --uid "${PUID}" --gid "${PGID}" --groups mosquitto mosquitto 2>/dev/null || true fi # modify filesystem ownership, otherwise /mosquitto will be inaccessible (and mode=0750) - #[ -d "/mosquitto" ] && /bin/chown --recursive "${PUID}:${PGID}" /mosquitto 2>/dev/null || true + #/bin/chown --recursive "mosquitto:mosquitto" /mosquitto 2>/dev/null || true + /usr/bin/find /mosquitto -xdev -exec chown mosquitto:mosquitto {} \; fi # execute CMD From f03eadbdcb95e0bd1db5f4cedbc1d53dcf59a34b Mon Sep 17 00:00:00 2001 From: Marc Guenevere Hefter Date: Wed, 26 Aug 2026 11:17:48 +0200 Subject: [PATCH 3/4] ensure consistency with non-unique PUID/PGID --- docker/2.1-appuser/docker-entrypoint.sh | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/docker/2.1-appuser/docker-entrypoint.sh b/docker/2.1-appuser/docker-entrypoint.sh index 33d40cca85..b7b6d8ddac 100644 --- a/docker/2.1-appuser/docker-entrypoint.sh +++ b/docker/2.1-appuser/docker-entrypoint.sh @@ -13,11 +13,17 @@ if [ "$(/usr/bin/id -u)" != '0' ]; then else # change user and/or group to PUID/PGID if [[ "${PGID}" != "${CURRENT_GID}" ]]; then - /usr/sbin/groupmod --gid "${PGID}" mosquitto 2>/dev/null || true + /usr/sbin/groupmod --non-unique --gid "${PGID}" mosquitto 2>/dev/null || true fi if [[ "${PUID}" != "${CURRENT_UID}" ]]; then - # if modification of gid failed, the user's primary group will no longer be mosquitto - /usr/sbin/usermod --uid "${PUID}" --gid "${PGID}" --groups mosquitto mosquitto 2>/dev/null || true + # split to multiple usermod calls, ensure consistency + # modify uid + /usr/sbin/usermod --non-unique --uid "${PUID}" mosquitto || true + # modify primary group + /usr/sbin/usermod --gid "${PGID}" mosquitto || true + # modify additional group membership (this should be unneccessary, groupmod was called with --non-unique) + [[ "${PGID}" != "$(/bin/grep -e "^mosquitto:" /etc/group | /usr/bin/cut -d ":" -f3)" ]] && /usr/sbin/usermod --groups mosquitto mosquitto || true + fi # modify filesystem ownership, otherwise /mosquitto will be inaccessible (and mode=0750) #/bin/chown --recursive "mosquitto:mosquitto" /mosquitto 2>/dev/null || true From b19356e02d1d63488ea03e211ce0ac2d8bb19f22 Mon Sep 17 00:00:00 2001 From: Marc Guenevere Hefter Date: Tue, 1 Sep 2026 11:09:13 +0200 Subject: [PATCH 4/4] entrypoint won't drop root privileges using tini for signal handling --- docker/2.1-appuser/Dockerfile | 4 ++-- docker/2.1-appuser/docker-entrypoint.sh | 9 +-------- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/docker/2.1-appuser/Dockerfile b/docker/2.1-appuser/Dockerfile index 7487275396..e83a90ec31 100644 --- a/docker/2.1-appuser/Dockerfile +++ b/docker/2.1-appuser/Dockerfile @@ -24,8 +24,8 @@ ENV PGID=${GID} RUN set -x && \ apk --no-cache add \ - daemontools-encore \ shadow \ + tini \ && \ apk --no-cache add --virtual build-deps \ argon2-dev \ @@ -111,5 +111,5 @@ VOLUME ["/mosquitto/data", "/mosquitto/log"] # Set up the entry point script and default command COPY --chmod=0755 docker-entrypoint.sh / EXPOSE 1883 -ENTRYPOINT ["/docker-entrypoint.sh"] +ENTRYPOINT ["/sbin/tini","-g", "--", "/docker-entrypoint.sh"] CMD ["/usr/sbin/mosquitto", "-c", "/mosquitto/config/mosquitto.conf"] diff --git a/docker/2.1-appuser/docker-entrypoint.sh b/docker/2.1-appuser/docker-entrypoint.sh index b7b6d8ddac..6505f11cf4 100644 --- a/docker/2.1-appuser/docker-entrypoint.sh +++ b/docker/2.1-appuser/docker-entrypoint.sh @@ -31,11 +31,4 @@ else fi # execute CMD -if [ "$(/usr/bin/id -u)" != '0' ]; then - # already running as unprivileged user - exec "$@" -else - [ -x /usr/bin/setuidgid ] || apk --no-cache add daemontools-encore - # drop from root to mosquitto - /usr/bin/setuidgid mosquitto "$@" -fi +exec "$@"