name: demo-scada-hivemq services: auth-init: image: eclipse-mosquitto:2.0.22@sha256:199ea8ef2e35ec2b1b37e59cfd1dbae538ed4dfa4a2251a121a52215a6248a21 user: "${DEMO_HOST_UID:-1000}:${DEMO_HOST_GID:-1000}" entrypoint: ["/bin/sh", "/scripts/init-auth.sh"] volumes: - ./.runtime/secrets:/run/demo-secrets - ./scripts/init-auth.sh:/scripts/init-auth.sh:ro read_only: true tmpfs: - /tmp:size=8m,mode=1777 cap_drop: ["ALL"] security_opt: ["no-new-privileges:true"] restart: "no" tls-init: image: hivemq/hivemq-ce:2025.5@sha256:7ae39e84654a41ced6e946dd84f131d508bc2b862ec96265b7f23d241db214da user: "${DEMO_HOST_UID:-1000}:${DEMO_HOST_GID:-1000}" entrypoint: ["/bin/bash", "/scripts/init-tls.sh"] volumes: - ./.runtime/tls:/run/hivemq-tls - ./scripts/init-tls.sh:/scripts/init-tls.sh:ro read_only: true tmpfs: - /tmp:size=16m,mode=1777 cap_drop: ["ALL"] security_opt: ["no-new-privileges:true"] restart: "no" edge-data-init: build: context: . dockerfile: edge-init/Dockerfile user: "65534:65534" entrypoint: - /usr/local/bin/busybox - chown - -R - 65532:65532 - /var/lib/expanso volumes: - expanso-data:/var/lib/expanso read_only: true cap_drop: ["ALL"] cap_add: ["CHOWN", "DAC_OVERRIDE", "FOWNER"] restart: "no" mqtt-source: image: eclipse-mosquitto:2.0.22@sha256:199ea8ef2e35ec2b1b37e59cfd1dbae538ed4dfa4a2251a121a52215a6248a21 user: "1883:1883" entrypoint: - /usr/sbin/mosquitto - -c - /mosquitto/config/mosquitto.conf depends_on: auth-init: condition: service_completed_successfully volumes: - ./mqtt-source/mosquitto.conf:/mosquitto/config/mosquitto.conf:ro - mqtt-source-data:/mosquitto/data secrets: - source-password - source-passwords healthcheck: test: - CMD-SHELL - >- mosquitto_pub -h 127.0.0.1 -u source-publisher -P "$$(cat /run/secrets/source-password)" -t health/source -m ready -q 1 interval: 3s timeout: 2s retries: 20 start_period: 3s read_only: true tmpfs: - /tmp:size=8m,mode=1777 cap_drop: ["ALL"] security_opt: ["no-new-privileges:true"] networks: [data-plane] mqtt-broker: build: context: . dockerfile: hivemq/Dockerfile depends_on: auth-init: condition: service_completed_successfully tls-init: condition: service_completed_successfully user: "10000:0" volumes: - hivemq-home:/opt/hivemq - ./hivemq/config.xml:/opt/hivemq/conf/config.xml:ro - ./.runtime/tls:/run/hivemq-tls:ro - hivemq-data:/opt/hivemq/data - hivemq-log:/opt/hivemq/log secrets: - hivemq-credentials.xml ports: - "${MQTT_TLS_PORT}:8883" healthcheck: test: ["CMD-SHELL", "bash -ec 'echo >/dev/tcp/127.0.0.1/8883'"] interval: 5s timeout: 3s retries: 30 start_period: 20s read_only: true tmpfs: - /tmp:size=32m,mode=1777,exec - /opt/hivemq/extensions/hivemq-file-rbac-extension/credentials-archive:size=8m,mode=0750,uid=10000,gid=0 cap_drop: ["ALL"] security_opt: ["no-new-privileges:true"] networks: [data-plane] expanso-edge: image: ghcr.io/expanso-io/expanso-edge:v2.1.21@sha256:2a9ec67c90075b0bf33811232971cd4ee03326181ed4186c799986cf9d371b38 depends_on: edge-data-init: condition: service_completed_successfully mqtt-source: condition: service_healthy mqtt-broker: condition: service_healthy entrypoint: ["/bin/sh", "/scripts/run-local-edge.sh"] user: "65532:65532" working_dir: /schemas environment: SOURCE_MQTT_URL: tcp://mqtt-source:1883 SOURCE_MQTT_USER: source-publisher HIVEMQ_URL: ssl://mqtt-broker:8883 HIVEMQ_USER: expanso-edge HIVEMQ_CA_FILE: /run/hivemq-tls/ca.crt volumes: - ./scripts/run-local-edge.sh:/scripts/run-local-edge.sh:ro - ./sparkplug_b.proto:/schemas/sparkplug_b.proto:ro - ./.runtime/tls:/run/hivemq-tls:ro - expanso-data:/var/lib/expanso secrets: - source-password - hivemq-password read_only: true tmpfs: - /tmp:size=32m,mode=1777 cap_drop: ["ALL"] security_opt: ["no-new-privileges:true"] networks: [data-plane] pipeline-loader: image: ghcr.io/expanso-io/expanso-cli:v2.1.21@sha256:d46fc6f21bbb1e53df800b2f89bca6a20b9cb116984fec336a47f7490161189d depends_on: expanso-edge: condition: service_started entrypoint: ["/bin/sh", "/scripts/deploy-local-pipeline.sh"] user: "65532:65532" volumes: - ./pipelines/scada-hivemq.yaml:/pipelines/scada-hivemq.yaml:ro - ./scripts/deploy-local-pipeline.sh:/scripts/deploy-local-pipeline.sh:ro read_only: true tmpfs: - /tmp:size=8m,mode=1777 cap_drop: ["ALL"] security_opt: ["no-new-privileges:true"] networks: [data-plane] restart: "no" scada-sim: build: context: . dockerfile: scada-sim/Dockerfile depends_on: pipeline-loader: condition: service_completed_successfully entrypoint: ["/bin/sh", "/scripts/run-scada-sim.sh"] user: "65532:65532" environment: MQTT_HOST: mqtt-source MQTT_PORT: "1883" MQTT_USER: source-publisher SPARKPLUG_TOPIC: spBv1.0/Expanso/DDATA/edge1/sensors UPDATE_INTERVAL_SEC: "0.25" volumes: - ./scripts/run-scada-sim.sh:/scripts/run-scada-sim.sh:ro secrets: - source-password read_only: true tmpfs: - /tmp:size=16m,mode=1777 cap_drop: ["ALL"] security_opt: ["no-new-privileges:true"] networks: [data-plane] profiles: ["live"] fixture-check: build: context: . dockerfile: scada-sim/Dockerfile depends_on: pipeline-loader: condition: service_completed_successfully entrypoint: - /bin/sh - -c - >- exec uv run --active --no-sync python /scripts/verify_fixture.py user: "${DEMO_HOST_UID:-1000}:${DEMO_HOST_GID:-1000}" environment: SOURCE_MQTT_USER: source-publisher HIVEMQ_USER: fixture-observer PYTHONPATH: /app RESULT_PATH: /runtime/acceptance.json UV_CACHE_DIR: /tmp/uv-cache volumes: - ./scripts/verify_fixture.py:/scripts/verify_fixture.py:ro - ./fixtures:/fixtures:ro - ./.runtime/tls:/run/hivemq-tls:ro - ./.runtime/results:/runtime secrets: - source-password - hivemq-observer-password read_only: true tmpfs: - /tmp:size=16m,mode=1777 cap_drop: ["ALL"] security_opt: ["no-new-privileges:true"] networks: [data-plane] profiles: ["verify"] dashboard: image: nginx:1.27.5-alpine@sha256:65645c7bb6a0661892a8b03b89d0743208a18dd2f3f17a54ef4b76fb8e2f2a10 depends_on: pipeline-loader: condition: service_completed_successfully user: "101:101" volumes: - ./dashboard:/usr/share/nginx/html:ro - ./dashboard/nginx.conf:/etc/nginx/conf.d/default.conf:ro - ./fixtures:/usr/share/nginx/html/fixtures:ro - ./pipelines:/usr/share/nginx/html/pipelines:ro ports: - "127.0.0.1:${DASHBOARD_PORT}:8080" read_only: true tmpfs: - /var/cache/nginx:size=16m,mode=1777 - /var/run:size=4m,mode=1777 - /tmp:size=8m,mode=1777 cap_drop: ["ALL"] security_opt: ["no-new-privileges:true"] networks: [presentation] volumes: expanso-data: hivemq-home: hivemq-data: hivemq-log: mqtt-source-data: secrets: source-password: file: ./.runtime/secrets/source-password source-passwords: file: ./.runtime/secrets/source-passwords hivemq-password: file: ./.runtime/secrets/hivemq-password hivemq-observer-password: file: ./.runtime/secrets/hivemq-observer-password hivemq-credentials.xml: file: ./.runtime/secrets/hivemq-credentials.xml networks: data-plane: internal: true presentation: