linkedin
Careers
Talk To Us

Running an OpenTelemetry Collector on Azure Container Apps for MuleSoft Logging

MuleSoft | Nov 5, 2026 | 12 min read | Sandeep Krishnan

TECHNICAL SERIES | PART 2 OF 2

Why put the Collector in a container?

An OpenTelemetry Collector on Azure Container Apps gives you the same MuleSoft-to-Application Insights logging path as a VM, without a server to patch.

In an earlier post, we sent MuleSoft logs to Azure Monitor through a self-managed Collector on an Ubuntu VM. It worked well, but someone still had to look after that VM. This post moves the same Collector onto Azure Container Apps, Microsoft’s serverless container service.

The log path stays the same: MuleSoft Log4j2 Socket Appender → TCP port 5170 → OpenTelemetry Collector → Azure Monitor exporter → Application Insights; only the host changes.

In our deployment, logs reached Application Insights end to end. Here’s how to build it, using OpenTelemetry Collector Contrib 0.162.0 on the Consumption workload profile.

In Part 1, we explored how to send MuleSoft logs to Azure Monitor using a self-managed OpenTelemetry Collector on an Ubuntu VM. This article builds on that approach by deploying the Collector on Azure Container Apps.

Link Part 1 to:
https://njclabs.com/blog/mulesoft-azure-monitor-opentelemetry/

How it fits together

The Collector runs as a single Container App inside a VNet-connected environment, pulling its image from your own registry.

Azure layout · Mule app, Container App, registry and Application Insights

Mule sends logs to the app’s public TCP endpoint on port 5170. The app reads its secrets from environment variables and exports to Application Insights. The Collector also opens OTLP receivers on 4317 (gRPC) and 4318 (HTTP) for apps that speak OpenTelemetry directly.

Step by step: deploying it

The build has six stages: package the Collector as an image, push it to a registry, then create the network, the environment, the app and its ingress.

You’ll need:

  • Azure permissions to create a VNet and subnet, a Container Apps environment, a Container App and role assignments.
  • Docker Desktop and the Azure CLI (az login) on your machine.
  • Your Application Insights connection string.
  • A strong random ingest key, shared between the Collector and your Mule app.
  • A working config.yaml (the full file is at the end of this post).

The examples use these generic names. Swap in your own:

ResourceExample name
Resource grouporg-monitoring-prod
RegionEast US 2
Container registryorgotelregistry.azurecr.io
Imageorg-otel-collector:1.0.0
Virtual networkorg-otel-vnet-eus2 (10.0.0.0/16)
Subnetaca-infrastructure-subnet (10.0.1.0/27)
Container Apps environmentorg-otel-env-eus2
Container Apporg-otel-collector
Application Insightsorg-mulesoft-prod

1. Build the Collector image

In your project folder, create a Dockerfile that copies your config into the official image:

FROM otel/opentelemetry-collector-contrib:0.162.0
COPY config.yaml /etc/otelcol-contrib/config.yaml
ENTRYPOINT ["/otelcol-contrib"]
CMD ["--config=/etc/otelcol-contrib/config.yaml"]

Check the config with the same Collector version before building. No error means it’s valid:

docker run --rm \
  -v "$PWD/config.yaml:/etc/otelcol-contrib/config.yaml:ro" \
  otel/opentelemetry-collector-contrib:0.162.0 \
  validate --config=/etc/otelcol-contrib/config.yaml

Then build for linux/amd64. This matters if you’re on an Apple Silicon Mac, because Azure runs Linux on AMD64:

docker buildx build --platform linux/amd64 \
  -t orgotelregistry.azurecr.io/org-otel-collector:1.0.0 \
  --load.

2. Push it to Azure Container Registry

az login
az acr login --name orgotelregistry
docker push orgotelregistry.azurecr.io/org-otel-collector:1.0.0
az acr repository show-tags --name orgotelregistry --repository org-otel-collector --output table

3. Create the virtual network

In the Azure portal, open Virtual networks:

  1. Create the VNet in your resource group and region, with address space 10.0.0.0/16.
  2. Add a subnet with range 10.0.1.0/27.
  3. Delegate the subnet to Microsoft.App/environments.
  4. Leave “Enable private subnet (no default outbound access)” unchecked.

We didn’t add a NAT gateway, network security group, or route table for the first deployment. Decide whether you need them for production.

One lesson learned: our first attempt in East US hit a regional capacity error, so we deployed in East US 2. If you see the same error, try a nearby region.

4. Create the Container Apps environment

  1. Create a Container Apps environment in the same resource group and region.
  2. Choose the Workload profiles environment type.
  3. Turn on VNet integration and pick the VNet and subnet from step 3.
  4. Connect a Log Analytics workspace (the portal can create a new one).
  5. Create it and wait for the provisioning state to read Succeeded.

If you already have monitoring resources, such as an existing Application Insights or Log Analytics workspace, leave them alone. Don’t delete them during cleanup.

5. Create the Container App

  1. Create a Container App in the new environment.
  2. Use the image orgotelregistry.azurecr.io/org-otel-collector:1.0.0.
  3. For registry login, choose the system-assigned managed identity and give it the AcrPull role. If the image won’t pull, check this role first.
  4. Choose the Consumption workload profile. We started with 0.5 vCPU and 1 GiB memory.
  5. Leave the container command and arguments blank. The Dockerfile already tells the Collector where its config is.

Then add two secrets, referenced as environment variables. The names must match the config exactly:

NameValueWhat it’s for
APPLICATIONINSIGHTS_CONNECTION_STRINGCopied from Application Insights → OverviewWhere the Collector sends logs
MULE_LOG_INGEST_KEYA strong random secretLets the Collector reject logs that don’t carry it

Never paste these values into email, tickets, screenshots or source control.

6. Turn on TCP ingress

Open the Container App and go to Ingress:

  1. Enable ingress and choose TCP as the type.
  2. Set both the target port and the exposed port to 5170.
  3. Save, then copy the full TCP endpoint the portal shows. It looks like org-otel-collector–<revision-id>.<environment-id>.eastus2.azurecontainerapps.io:5170.

Always re-check this endpoint in the portal before using it, because it can change if the app or revision is recreated. Also note that the environment shows a static IP address; Mule should point to the endpoint name, not that IP.

For our first test, ingress accepted traffic from anywhere. That’s fine for a proof of concept, but not for production. See the production tips below.

Connecting MuleSoft and testing

Point the Mule app’s Socket Appender at the new endpoint, send a marked test log, and look for it in Application Insights.

In your Mule app’s properties, set the host to the endpoint you copied from the Ingress page:

org.logs.host=org-otel-collector--<revision-id>.<environment-id>.eastus2.azurecontainerapps.io
org.logs.port=5170
org.logs.protocol=TCP

Keep the ingest key in the Mule deployment’s secure properties, not in source code. Each log record must include the fields config.yaml expects: time, level, app, env, instance, ingestKey, and message. Then redeploy or restart the app through your normal release process.

To test it:

  1. In the portal, open Container Apps → your app → Revisions and replicas, and confirm the active revision is running.
  2. Open the app’s Log stream and look for startup, parsing, or export errors.
  3. Trigger a test log from Mule with a unique message, such as LOG4J2-TEST-<timestamp>.
  4. In Application Insights, open Logs and run:
traces
| where timestamp > ago(30m)
| where message contains "LOG4J2-TEST"
| order by timestamp desc

No results? Depending on the exporter and schema, logs may land in a different table, such as AppTraces. Check the table list in Logs.

A visible log proves the basic path works. Restart recovery, sustained volume, security restrictions and alerting still need their own tests.

Troubleshooting and production tips

Most problems come down to the endpoint, the registry role, or a mismatched secret; most production work is about locking down the open ingress.

SymptomWhat to check
Container App won’t startRevision events and container logs; that the image tag exists, the identity has AcrPull, and the config parses
Image pull failsRegistry name, repository, and tag; the managed identity and its ACRPull role scope
No logs reach the CollectorThe current ingress endpoint and port 5170; Mule’s outbound connectivity; the Log4j2 host and port
Logs reach the Collector but not AzureThat APPLICATIONINSIGHTS_CONNECTION_STRING is set and points at the right resource; exporter errors
Logs are filtered outThat MULE_LOG_INGEST_KEY matches the app’s ingestKey exactly
Wrong time or severityThe time format and that level values map to severity
Endpoint stopped workingRe-copy it from the Ingress page; don’t trust an old one
Unexpected costsReplica scaling, workload profile, Log Analytics ingestion and retention, registry storage. Consumption is usage-based, not free

Before you call it production-ready:

  • Restrict ingress. Allow only your approved MuleSoft/CloudHub egress IP ranges. Don’t leave “allow all” without a signed-off risk acceptance.
  • Encrypt the log path. Enable TLS on the TCP receiver if your setup supports it, or put a secured proxy in front. An ingest key alone isn’t enough protection.
  • Manage the ingest key. Make it strong and unique, rotate it regularly, and keep it in managed secrets.
  • Close unused ports. The config also opens OTLP on 4317 and 4318. These skip the ingest-key check, so don’t expose them publicly unless you need them and protect them separately.
  • Review the debug exporter. It helps during setup but produces verbose logs.
  • Set up alerts for missing logs, Collector restarts, export failures and unusual volume.
  • Test resilience. Restart a revision and confirm logs resume. Run a realistic volume test.
  • Keep a rollback. Validate every config change locally, and keep a known-good image tag ready.

Wrapping up

Azure Container Apps makes a tidy, serverless home for the Collector: the MuleSoft-to-Application-Insights path works with no VM to maintain. Finish the security, alerting, and cost reviews above before trusting it with production traffic.

The full config.yaml

This is the complete config baked into the image. Secrets come from environment variables, so never put real values in this file. Keep it in version control.

receivers:
  tcp_log/mule:
    listen_address: 0.0.0.0:5170
    max_log_size: 1MiB
    # For production, enable TLS and mount the certificate/key securely.
    # tls:
    #   cert_file: /etc/otelcol-contrib/tls/fullchain.pem
    #   key_file: /etc/otelcol-contrib/tls/privkey.pem
    operators:
      - type: json_parser
        parse_from: body
        on_error: drop_quiet
      - type: move
        from: attributes.message
        to: body
      - type: move
        from: attributes.app
        to: resource["service.name"]
      - type: move
        from: attributes.instance
        to: resource["service.instance.id"]
      - type: move
        from: attributes.env
        to: resource["deployment.environment.name"]
      - type: remove
        if: 'attributes.time != nil'
        field: attributes.time
      - type: remove
        if: 'attributes.level != nil'
        field: attributes.level
      - type: remove
        if: 'attributes.exception == ""'
        field: attributes.exception
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

processors:
  filter/ingest_key:
    error_mode: ignore
    logs:
      log_record:
        - 'log.attributes["ingestKey"] != "${env:MULE_LOG_INGEST_KEY}"'
  transform/remove_key:
    error_mode: ignore
    log_statements:
      - context: log
        statements:
          - delete_key(log.attributes, "ingestKey")
  batch:
    timeout: 10s
    send_batch_size: 512

exporters:
  azure_monitor:
    connection_string: "${env:APPLICATIONINSIGHTS_CONNECTION_STRING}"
  debug:
    verbosity: basic

service:
  pipelines:
    # MuleSoft Log4j2 JSON over TCP: validate the key, remove it, then export.
    logs/mule:
      receivers: [tcp_log/mule]
      processors: [filter/ingest_key, transform/remove_key, batch]
      exporters: [azure_monitor, debug]
    # OTLP pipelines for clients that send OpenTelemetry directly.
    logs:
      receivers: [otlp]
      processors: [batch]
      exporters: [azure_monitor, debug]
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [azure_monitor, debug]
    metrics:
      receivers: [otlp]
      processors: [batch]
      exporters: [azure_monitor, debug]

Validate it against the exact Collector Contrib image (0.162.0) before every rebuild, especially after editing processor expressions or receiver settings.

Read Part 1 of This Series

Haven’t explored the initial setup yet? Start with Part 1 to understand how to send MuleSoft logs to Azure Monitor using a self-managed OpenTelemetry Collector on an Ubuntu VM, without upgrading your Anypoint subscription.

Read Part 1: Sending MuleSoft Logs to Azure Monitor with OpenTelemetry – Without a Subscription Upgrade

Further reading

Knowledge is just the beginning.

Let NJC Labs help you put these insights into action for your organisation.

Start the Conversation