Part 8 – Mastering Configuration in Kubernetes

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:

InformationBest Location
Oracle PasswordSecret
Redis HostConfigMap
Payment TimeoutConfigMap
Holiday CalendarOracle Database
Currency Exchange RatesOracle Database
Customer PreferencesOracle 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.

earlier infrastructure sizing article while giving readers the practical knowledge needed to run Java workloads reliably in Kubernetes.

Leave a Reply

Your email address will not be published. Required fields are marked *