SapotaCorp

Properties and secure configuration: running one Mule app across environments

One Mule application has to run in DEV, UAT, and PROD without anyone touching the code between environments. The trick is to externalize everything that changes into properties and to encrypt anything sensitive so it can live safely in Git. Here is how a banking customer-sync integration handles configuration and secrets in production, and the gotchas that bite teams who get it half-right.

Properties and secure configuration: running one Mule app across environments

Key takeaways

  • Externalize everything that differs between environments into YAML property files and select the right one at runtime with a single system property like -Dmule.env=prod, so the same artifact promotes from DEV to PROD without a code change or a rebuild.
  • Never put real passwords or API keys as plain text in committed YAML; encrypt them with the Secure Configuration Properties module (AES/CBC), store only the ![ciphertext] in Git, and supply the master key separately at runtime via -Dmule.key.
  • Understand property precedence: a stray -D system property override beats your YAML file silently, which is exactly how a developer's localhost override can follow an artifact into production and break it.
  • Give each environment its own master key and keep the PROD key out of developers' hands entirely; a shared key means one leak compromises every environment, and Mule never hot-reloads config, so every change requires a redeploy.

A while back I was building a Mule application for a bank that synchronized customer records between their core-banking system and Salesforce CRM. Nothing exotic on the surface: a scheduler pulls changed rows out of an Oracle database every few minutes, transforms them, and posts them to a core-banking web service. The interesting part was never the flow logic. It was the fact that this one application had to run, unchanged, in three environments with three completely different sets of endpoints, credentials, connection pools, and timeouts.

The DEV environment talked to a core-banking host on the internal *.local network over plain HTTP with a 30-second timeout. UAT pointed at a different host and a different Oracle instance. PROD talked to a public HTTPS endpoint backed by a database cluster, with a 120-second timeout and feature flags that were off everywhere else. If any of that lives in the code, every promotion means editing source, rebuilding, and re-reviewing — and that is precisely how accidents happen.

The cautionary tale I keep in mind: a team that hardcoded endpoints once fired a test run against the production core-banking host because someone forgot to swap a URL, and it generated over a thousand bogus transactions. The whole point of externalizing configuration is to make that class of mistake structurally impossible, not merely unlikely.

Externalize what changes, keep the code stable

The mechanism in Mule 4 is configuration properties. You put the per-environment values in files under src/main/resources — I keep dev.yaml, uat.yaml, and prod.yaml, plus a common.yaml for the handful of values that never change, like the bank code, SWIFT code, and timezone. YAML over flat .properties files, every time: nested structure reads far better when you have t24.retry.max and db.pool.max sitting under their parents instead of as a wall of dotted keys.

The declaration that ties it together is a single line:

<configuration-properties file="${mule.env}.yaml" />

Notice that the filename itself is a property. At startup you pass -Dmule.env=prod (or dev, or uat) and Mule expands ${mule.env}.yaml into the right file before loading it. Locally in Studio that goes into the VM arguments as -M-Dmule.env=dev; on CloudHub you set mule.env=uat in the Runtime Manager properties tab; on a standalone server it goes on the launch command. The deployed artifact is byte-for-byte identical across all three. One system property decides which world it wakes up in.

Inside the flows, anything configurable becomes a ${key} reference. The HTTP request connector reads host="${t24.endpoint}" and responseTimeout="${t24.timeout}"; the database config reads host="${db.host}" and maxPoolSize="${db.pool.max}". In DataWeave you reach the same values with the p() function — p('app.bank_code') or p('t24.retry.max') as Number — and it resolves nested keys with dot notation just like the XML does.

One detail about loading order that trips people up: when you declare two property files, the later one overrides the earlier one on conflicting keys. I load common.yaml first and the environment file second, so if prod.yaml and common.yaml both define app.log_level, the environment-specific value wins. Get that ordering backwards and your "shared" defaults will silently stomp on your per-environment settings.

Precedence is where the silent failures live

Mule resolves a property from several sources, and the priority order matters more than it first appears. A system property passed with -D beats an OS environment variable, which beats whatever is in your <configuration-properties> files, which beats an inline default written as ${t24.endpoint:http://localhost} — the bit after the colon being the fallback.

That ordering is genuinely useful day to day. If I want to point a local run at a mock service without editing dev.yaml, I just add -M-Dmule.env=dev -M-Dt24.endpoint=http://localhost:8080/mock and the -D override wins. But the same feature is a loaded gun. I have seen a developer set -Dt24.endpoint=localhost to test something, forget to remove it, and let it ride along into a CloudHub deployment, at which point the production app cheerfully tried to call localhost and failed. Before promoting anything, list the system properties actually in effect — CloudHub shows them all in the UI — because a stray override will never show up in a code review of your YAML.

Encrypting the secrets that have to live in Git

Externalizing endpoints and timeouts is the easy half. The hard half is that prod.yaml also needs the real core-banking password, the real Oracle password, and the real Salesforce client secret — and that file is committed to Git, where anyone with repository access can read it. Plain-text secrets in a committed YAML file fail every audit and compliance check that matters, and rightly so.

The answer is the Mule Secure Configuration Properties module. You add mule-secure-configuration-property-module to the pom.xml, then the model becomes a clean split: encrypted ciphertext lives in Git where developers can see it, and the master key that decrypts it lives only at runtime, known only to operations. You encrypt each sensitive value ahead of time with the secure-properties-tool.jar:

java -cp secure-properties-tool.jar com.mulesoft.tools.SecurePropertiesTool \
    encrypt AES CBC '<master-key>' '<the-real-password>'

The tool prints a ciphertext string. In the YAML you wrap it in ![...] so Mule knows it is encrypted:

t24:
  password: "![qH7vK2pL9mN3rX4tY8wZ5cV6bA1sD0eF]"
db:
  password: "![fE7gH3iJ8kL2mN6oP9qR4sT1uV5wX0yZaB]"

You declare a <secure-properties:config> with file="${mule.env}.yaml" and key="${mule.key}", and then the one rule that everyone forgets at least once: encrypted values are referenced with the secure:: prefix. A plain endpoint stays ${t24.endpoint}, but the encrypted password must be ${secure::t24.password}. Without the prefix Mule hands the raw ![...] ciphertext straight to your connector and authentication fails in a way that looks nothing like an encryption problem.

The master key arrives at runtime exactly like mule.env did — -Dmule.key=... in VM args locally, a hidden property in CloudHub's Runtime Manager, or injected from a vault on a standalone server. On the on-premise banking deployment we pulled the key from HashiCorp Vault in the startup script rather than parking it in wrapper.conf. Mule does offer a newer Anypoint Secrets Manager with centralized audit logging and rotation, but it only works on the Anypoint Platform, so for an on-prem runtime the encrypt-and-inject approach remains the practical choice.

The gotchas worth tattooing on the wall

A few hard-won rules. Give every environment its own master key. It is tempting to reuse one key for convenience, but then a leak of the DEV key — which developers obviously have — lets anyone decrypt PROD secrets if the key is shared. The PROD key should be known only to operations, full stop.

Keep the algorithm and mode consistent between encryption and runtime. If you encrypt with AES/CBC but the XML config says ECB, you get a BadPaddingException at startup that tells you nothing useful unless you already know to look. Pick one pair, write it in the ops README, and never deviate.

Remember that Mule reads properties once, at startup. Edit prod.yaml and the running app will not notice — there is no hot reload. Every configuration change means a redeploy, so plan promotions accordingly. And put a gitleaks or git-secrets pre-commit hook in front of the repository, because the single worst failure mode here is committing the master key itself — in a .env file, in a comment, anywhere — which instantly undoes the entire scheme.

The principle underneath all of it is simple: the artifact you build should be inert and identical everywhere, and the only thing that distinguishes a harmless test run from a live production run should be two values handed in at launch — which environment, and which key. Once configuration and secrets are truly external, "deploy to PROD" stops being an edit-and-pray exercise and becomes what it should be: flipping two switches you can read off a checklist.


Building or operating MuleSoft integrations? Our Salesforce team designs API-led architectures, builds Mule flows, and runs them in production. Get in touch ->

See our full platform services for the stack we cover.

Engineering certifications

Sapota engineers hold credentials on MuleSoft. Each badge links to the individual engineer's credly profile.

Browse MuleSoft certs

Need this on your team?

Sapota engineers ship the patterns you read here. Two-week paid trial, direct pricing from $1,800/ engineer/month, no agency markup.

Get a quote
Contact Us Now

Share Your Story

We build trust by delivering what we promise – the first time and every time!

We'd love to hear your vision. Our IT experts will reach out to you during business hours to discuss making it happen.

WHY CHOOSE US

"Collaborate, Elevate, Celebrate where Associates - Create Project Excellence"

SapotaCorp beyond the IT industry standard, we are

  • Certificated
  • Assured quality
  • Extra maintenance

Tell us about your project