Externalizing Configuration with ConfigMaps, Secrets, Spring Profiles, and Feature Flags
“The best deployment is the one where the application binary never changes. Only its configuration does.”
Wednesday Morning, 7:45 AM
The Digital Payments team at Global Trust Bank was preparing for another production deployment.
Unlike previous releases, there were no new features.
No code changes.
No bug fixes.
No new Docker image.
Yet a deployment was still required.
The business team had requested three operational changes.
- Switch to a new Oracle database cluster.
- Increase the Redis connection pool.
- Disable a recently introduced payment validation rule for corporate customers.
Sarah smiled.
A few years ago, these seemingly simple requests would have required:
- Updating
application.properties - Rebuilding the application
- Creating a new Docker image
- Running regression tests
- Deploying the entire application again
Today, none of those steps were necessary.
The application image remained exactly the same.
Only the configuration changed.
This is one of the most important principles of cloud-native application design.
The Problem with Traditional Configuration
Most Java developers have written applications like this.
spring.datasource.url=jdbc:oracle:thin:@dev-db:1521/PAYDEV
spring.datasource.username=payment
spring.datasource.password=password123
redis.host=localhost
payment.validation.enabled=true
Everything works during development.
Then the application moves to SIT.
Someone edits the file.
Next comes UAT.
Another edit.
Finally, Production.
Another edit.
Soon multiple versions of the same application begin to appear.
payment-service-dev.jar
payment-service-sit.jar
payment-service-uat.jar
payment-service-prod.jar
Although each application performs the same business function, they differ only because of configuration.
This approach creates several problems.
- Multiple artifacts to maintain.
- Configuration drift between environments.
- Difficult troubleshooting.
- Increased deployment risk.
- Unnecessary builds.
The application should never know whether it is running in Development or Production.
Only the environment should know.
Build Once, Deploy Everywhere
Modern deployment pipelines follow a very different philosophy.
Build
│
▼
payment-service.jar
│
Docker Image Built
│
▼
payment-service:2.5.0
│
┌───────────┼────────────┐
▼ ▼ ▼
DEV SIT PROD
Notice something important.
There is only one Docker image.
Every environment uses exactly the same application.
The only difference is the configuration supplied during deployment.
This dramatically reduces deployment risk.
If the application worked in SIT, the exact same binary reaches Production.
The Twelve-Factor Application Principle
One of the foundations of cloud-native development is the Twelve-Factor App methodology.
One of its core recommendations states:
Store configuration outside the application.
Configuration includes anything that changes between environments.
Examples include:
- Database URLs
- Redis hosts
- Kafka bootstrap servers
- Solace broker addresses
- API endpoints
- Logging levels
- Feature toggles
- Timeouts
- Thread pool sizes
These values belong to the deployment environment—not the application itself.
Understanding Configuration Categories
Not all configuration is equal.
A useful way to think about it is to classify configuration into three categories.
Configuration
┌──────────────┼──────────────┐
▼ ▼ ▼
Public Sensitive Dynamic
Let’s explore each category.
Category 1 – Public Configuration
Public configuration contains information that isn’t confidential.
Examples include:
- Service URLs
- Port numbers
- Connection pool sizes
- Logging levels
- Feature switches
- Retry counts
For example:
server.port=8080
logging.level.root=INFO
payment.retry.count=3
cache.ttl.minutes=15
These values are excellent candidates for Kubernetes ConfigMaps.
Category 2 – Sensitive Configuration
Sensitive configuration should never be stored inside source code.
Examples include:
- Oracle passwords
- Redis passwords
- API Keys
- OAuth client secrets
- Certificates
- Private keys
- JWT signing keys
Imagine accidentally committing this file.
spring.datasource.password=MySecretPassword
The password now exists forever in Git history.
Even if you delete it later, the secret has already been exposed.
This is why Kubernetes provides Secrets.
Secrets separate confidential information from application code and deployment manifests.
Category 3 – Dynamic Configuration
Some configuration changes frequently.
For example:
Maximum Daily Transfer
↓
$25,000
↓
Business Requests Change
↓
$50,000
Should developers rebuild the application?
Of course not.
These values often belong in:
- Database tables
- Feature Management Systems
- Distributed Configuration Services
Dynamic business configuration should remain independent of deployment configuration.
This distinction becomes increasingly important as applications grow.
Spring Boot Makes External Configuration Easy
One of Spring Boot’s greatest strengths is its flexible configuration model.
Configuration can come from many different sources.
Application Properties
↓
Environment Variables
↓
Command-Line Arguments
↓
System Properties
↓
ConfigMaps
↓
Secrets
Spring Boot automatically combines these sources according to a well-defined precedence order.
This makes applications remarkably portable across environments.
Spring Profiles
Consider our banking application.
Development uses:
- Local Oracle
- Local Redis
- Mock Fraud Service
Production uses:
- Oracle RAC
- Redis Cluster
- Live Fraud Platform
The business logic remains identical.
Only infrastructure changes.
Spring Profiles allow environment-specific configuration while keeping the application code unchanged.
For example:
application.yml
application-dev.yml
application-sit.yml
application-prod.yml
The correct profile is selected during deployment rather than at build time.
This keeps the application flexible without introducing multiple binaries.
Environment Variables
Containers were designed around environment variables.
Instead of embedding values inside configuration files, Kubernetes injects them when the container starts.
Conceptually:
Kubernetes
↓
DATABASE_URL
↓
Spring Boot
↓
DataSource
The application never knows where the value originated.
It simply consumes the configuration.
This decoupling makes applications significantly easier to deploy across multiple environments.
Feature Flags
One of Sarah’s favorite deployment techniques is using feature flags.
Suppose the team develops a new payment validation engine.
The code is complete.
The feature has passed testing.
But the business isn’t ready to enable it.
Instead of delaying deployment, the team ships the feature in a disabled state.
Feature Flag
↓
OFF
The application contains both implementations.
Old Validation
↓
Current Customers
---------------------
New Validation
↓
Disabled
When the business approves the rollout, only the configuration changes.
No deployment.
No restart.
No new Docker image.
The feature becomes available immediately.
Feature flags significantly reduce deployment risk because code deployment and feature activation become separate activities.
Configuration Is Not Business Data
A common mistake is storing business data inside ConfigMaps.
Imagine placing holiday calendars inside a ConfigMap.
Holiday Calendar
↓
ConfigMap
This may work initially.
But what happens when regulators declare an unexpected public holiday?
Should Operations modify Kubernetes resources every time?
Probably not.
Business data belongs in business systems.
For our banking platform:
| Information | Best Location |
|---|---|
| Oracle Password | Secret |
| Redis Host | ConfigMap |
| Payment Timeout | ConfigMap |
| Holiday Calendar | Oracle Database |
| Currency Exchange Rates | Oracle Database |
| Customer Preferences | Oracle Database |
Understanding this separation keeps deployments simple and applications maintainable.
Common Configuration Mistakes
Over the years, several mistakes appear repeatedly.
Hardcoding Values
String redisHost = "localhost";
Works locally.
Fails everywhere else.
Committing Secrets to Git
Perhaps the most dangerous mistake.
Source code repositories should never become password vaults.
Environment-Specific Business Logic
Avoid writing code like:
If Production
Do One Thing
Else
Do Something Different
Business behavior should remain consistent.
Only infrastructure configuration should vary.
Rebuilding the Application for Every Environment
If your CI/CD pipeline creates:
DEV Image
↓
SIT Image
↓
PROD Image
You’re losing one of the biggest advantages of containers.
One image.
Many environments.
Configuration in a Self-Healing World
Let’s connect configuration to everything we’ve learned so far.
Our application now:
- Is stateless.
- Starts gracefully.
- Shuts down gracefully.
- Reports meaningful health information.
Now it also becomes environment-independent.
When Kubernetes creates a replacement Pod after a failure:
New Pod
↓
Same Docker Image
↓
Configuration Injected
↓
Application Starts
↓
Ready
No manual intervention.
No rebuilding.
No copying property files.
The replacement Pod is configured exactly like the one it replaces.
This consistency is one of the reasons cloud-native platforms recover so effectively from failures.
Production Checklist
Before deploying any Spring Boot microservice, verify the following.
- One Docker image is used across all environments.
- Secrets are stored outside source code.
- Configuration is externalized.
- Feature flags are configurable.
- No environment-specific code exists.
- Business data is not stored in ConfigMaps.
- Sensitive values rotate without code changes.
- Spring Profiles are used appropriately.
- Environment variables override deployment-specific values.
- The application starts successfully regardless of the target environment.
Organizations that follow these principles dramatically reduce deployment risk while making configuration changes faster, safer, and easier to audit.
Looking Ahead
Our Payment Service has evolved considerably.
It is now stateless.
It shuts down gracefully.
It reports its health accurately.
Its configuration is completely externalized.
Yet another production issue still surprises many development teams.
The application works perfectly in Development.
It performs well in SIT.
But once deployed to Production, Pods begin restarting unexpectedly.
Sometimes they become slow under load.
Sometimes Kubernetes reports:
OOMKilled
Or:
CPU Throttling Detected
The application code hasn’t changed.
The infrastructure is healthy.
So why does performance suddenly degrade?
In the next article, we’ll explore one of the most misunderstood topics in Kubernetes: CPU, Memory, JVM Heap, Requests, Limits, and Why Spring Boot Applications Get OOMKilled. We’ll learn how Kubernetes manages resources, how the JVM behaves inside containers, and how to size Spring Boot microservices correctly for production.