Documentation

fsds/iot-emqx-production-hardening.md

EMQX Production Hardening Plan

Problem Summary

Your EMQX container is running correctly, but has two issues:

  1. Prometheus push gateway errors - EMQX tries to push metrics to 127.0.0.1:9091 which doesn't exist
  2. Insecure Erlang cookie - Using default cookie (security risk)

Root Cause: The Bicep module is out of sync with the Aspire orchestration code:

  • Aspire code (line 590): Has EMQX_PROMETHEUS__PUSH_GATEWAY_SERVER=""
  • Bicep module: Missing this env var, only has EMQX_PROMETHEUS__ENABLE=true

Files to Modify

File Changes
infra/emqx/emqx-containerapp.module.bicep Add missing env vars + Erlang cookie secret
infra/emqx/emqx.tmpl.bicepparam Add Erlang cookie parameter binding
infra/main.bicep Add Erlang cookie parameter + wire emqx module
infra/main.parameters.json Add Erlang cookie env var mapping
DynaplexInfrastructureExtensions.cs Add Erlang cookie parameter to Aspire

Implementation Steps

Stage 1: Fix Prometheus Errors (Immediate)

File: projects/bbu-rfid/src/Acsis.Dynaplex.Projects.BbuRfid/infra/emqx/emqx-containerapp.module.bicep

Add after line 74 (EMQX_PROMETHEUS__ENABLE):

{
  name: 'EMQX_PROMETHEUS__PUSH_GATEWAY_SERVER'
  value: ''
}

2a. Update Bicep Module

File: infra/emqx/emqx-containerapp.module.bicep

Add parameter:

@secure()
param emqx_erlang_cookie_value string

Add to secrets array:

{
  name: 'emqx-node--cookie'
  value: emqx_erlang_cookie_value
}

Add to env array:

{
  name: 'EMQX_NODE__COOKIE'
  secretRef: 'emqx-node--cookie'
}

2b. Update Bicep Parameters Template

File: infra/emqx/emqx.tmpl.bicepparam

Add:

param emqx_erlang_cookie_value = '{{ securedParameter "emqx_erlang_cookie" }}'

2c. Update main.bicep

File: infra/main.bicep

Add parameter (near other emqx params):

@secure()
param emqx_erlang_cookie string

Add emqx module call (if not already present - verify first):

module emqx 'emqx/emqx-containerapp.module.bicep' = {
  name: 'emqx'
  scope: rg
  params: {
    aspire_env_outputs_azure_container_apps_environment_default_domain: aspire_env.outputs.azure_container_apps_environment_default_domain
    aspire_env_outputs_azure_container_apps_environment_id: aspire_env.outputs.azure_container_apps_environment_id
    emqx_admin_password_value: emqx_admin_password
    emqx_erlang_cookie_value: emqx_erlang_cookie
    location: location
  }
}

2d. Update Parameters JSON

File: infra/main.parameters.json

Add:

"emqx_erlang_cookie": {
  "value": "${AZURE_EMQX_ERLANG_COOKIE}"
}

2e. Update Aspire Orchestration

File: strata/orchestration/src/Acsis.Dynaplex.Strata.Orchestration/DynaplexInfrastructureExtensions.cs

In AddDynaplexMqttBroker() method, add parameter and env var:

var emqxAdminPassword = builder.AddParameter("emqx-admin-password", secret: true);
var emqxErlangCookie = builder.AddParameter("emqx-erlang-cookie", secret: true);

var emqx = builder.AddContainer("emqx", "emqx/emqx", "5.8")
    // ... existing config ...
    .WithEnvironment("EMQX_NODE__COOKIE", emqxErlangCookie)
    // ... rest of config ...

Stage 3: Production Logging Settings (Optional)

Add to both Bicep and Aspire:

EMQX_LOG__CONSOLE__LEVEL=warning
EMQX_LOG__FILE__ENABLE=false

Deployment Steps

  1. Generate strong Erlang cookie:

    openssl rand -base64 48 | tr -d '\n'
    
  2. Set azd environment variable:

    azd env set AZURE_EMQX_ERLANG_COOKIE "<generated-cookie>"
    
  3. Deploy:

    azd up
    

Expected Outcome

After deployment:

  • No more Prometheus "connection refused" errors in logs
  • Erlang cookie warning resolved
  • Prometheus metrics available at /api/v5/prometheus/stats on port 18083 for future Grafana integration

Notes

  • Keep 10MB max packet size (matches your emqx.conf)
  • Keep EMQX_MQTT__MAX_MQUEUE_LEN=0 (unlimited queue)
  • Keep 2592000s (30 days) session expiry - matches Aspire code
  • emqx.conf file is NOT mounted - all config via environment variables (simpler, version-controlled)