Skip to content

Spring Cloud Config Server vs Kubernetes ConfigMaps: When to Use Each

I was staring at my Spring Boot microservices deployment configuration, trying to decide whether to keep our Spring Cloud Config Server or migrate everything to Kubernetes ConfigMaps with GitOps. The team was split. Some argued Config Server is battle-tested, others said GitOps is the modern standard. I needed to figure out which approach made sense for our Kubernetes-native environment.

The Problem: Configuration Management Crossroads

We had been running Spring Cloud Config Server for years. It worked fine. But when we moved to Kubernetes and started using ArgoCD, I noticed something odd - we were managing configuration in two places. Config Server for Spring properties, and ConfigMaps for Kubernetes-native applications. This felt redundant.

The real question hit me during a Reddit discussion: “Do people still use config server in the age of gitops?”

The answer isn’t a simple yes or no. It depends on your environment, your team’s skills, and your operational requirements. Let me walk through what I learned from both approaches.

How Spring Cloud Config Server Works

I first tried Spring Cloud Config Server because our team was deeply invested in the Spring ecosystem. Here’s the architecture:

Config Server (Spring Boot)
|
v
Git/SVN Backend
|
v
Microservices poll or subscribe for changes

The setup is straightforward. You create a Spring Boot application that serves as the config server, pointing to a Git repository:

application.yml
server:
port: 8888
spring:
cloud:
config:
server:
git:
uri: https://github.com/myorg/config-repo
search-paths:
- '{application}'
default-label: main

Then your microservices connect to it:

bootstrap.yml
spring:
application:
name: my-service
cloud:
config:
uri: http://config-server:8888
fail-fast: true
retry:
initial-interval: 1000
max-attempts: 6

What I liked about Config Server:

  1. Dynamic refresh without restart - This was the killer feature. I could change a property, call the /actuator/refresh endpoint, and the application would pick up the new value without restarting.

  2. Works outside Kubernetes - When we needed to run some services on VMs, Config Server just worked. No Kubernetes dependency.

  3. Multiple backend support - Git, SVN, Vault, CredHub. We could even use the file system for local development.

What frustrated me:

  1. Extra service to maintain - Config Server became another thing to monitor, scale, and troubleshoot. When it went down, services couldn’t start.

  2. Not GitOps-native - While the config was in Git, the deployment model was push-based. Services had to actively fetch from Config Server. This didn’t fit our GitOps workflow where ArgoCD syncs everything from Git.

  3. Spring-centric - As we added Go and Python microservices, they couldn’t use Config Server without significant effort.

How Kubernetes ConfigMaps with GitOps Works

Then I tried the Kubernetes-native approach with ArgoCD and Helm. The architecture looks like this:

Git Repository
|
v
ArgoCD --> Sync to Cluster
|
v
Helm Charts --> Generate ConfigMaps/Secrets
|
v
Pods mount ConfigMaps as volumes or env vars

Here’s how I structured it. First, the Helm values file containing the configuration:

values.yaml
config:
database:
host: postgres.prod.svc.cluster.local
port: 5432
name: myapp
redis:
host: redis.prod.svc.cluster.local
port: 6379
featureFlags:
enableNewUI: true
maxConnections: 100

Then the ConfigMap template in the Helm chart:

templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-config
labels:
app: {{ .Release.Name }}
data:
application.yml: |
spring:
datasource:
url: jdbc:postgresql://{{ .Values.config.database.host }}:{{ .Values.config.database.port }}/{{ .Values.config.database.name }}
redis:
host: {{ .Values.config.redis.host }}
port: {{ .Values.config.redis.port }}
feature:
new-ui: {{ .Values.config.featureFlags.enableNewUI }}
max-connections: {{ .Values.config.featureFlags.maxConnections }}

The deployment mounts this ConfigMap:

templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}
spec:
template:
spec:
containers:
- name: app
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
volumeMounts:
- name: config
mountPath: /config
readOnly: true
volumes:
- name: config
configMap:
name: {{ .Release.Name }}-config

What I loved about this approach:

  1. Git as single source of truth - Every config change goes through Git. Full audit trail. Easy rollback with git revert.

  2. ArgoCD handles everything - I push to Git, ArgoCD syncs to the cluster. No manual deployment steps.

  3. Language agnostic - Works with Java, Go, Python, Node.js. Anything that can read from environment variables or files.

  4. Native Kubernetes - ConfigMaps are built into Kubernetes. No extra services to maintain.

The problem I hit:

ConfigMap changes don’t automatically propagate to running pods. I updated a config, ArgoCD synced the ConfigMap, but my pods kept using the old configuration. This was a critical issue.

Solving the ConfigMap Hot-Reload Problem

I found three solutions to this problem. Let me show you each one.

Solution 1: Immutable ConfigMap with Hash Annotation

The cleanest approach is to include a hash of the ConfigMap content in the deployment annotation. When the ConfigMap changes, the hash changes, triggering a new rollout.

templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}
spec:
template:
metadata:
annotations:
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
spec:
containers:
- name: app
# ... container spec

Now every time I change values.yaml, Helm generates a new hash, and Kubernetes rolls out new pods with the updated config. Simple and effective.

Solution 2: Stakater Reloader

For an automated approach, I installed Stakater Reloader. It watches ConfigMaps and Secrets, and triggers rollouts when they change.

deployment-with-reloader.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
reloader.stakater.com/auto: "true"
name: my-app
spec:
# ... deployment spec

I installed it with Helm:

install-reloader.sh
helm repo add stakater https://stakater.github.io/stakater-charts
helm repo update
helm install reloader stakater/reloader --namespace reloader --create-namespace

Now when I update a ConfigMap, Reloader detects the change and triggers a rolling restart of any deployment with the annotation.

Solution 3: Spring Cloud Kubernetes Config

If you’re staying in the Spring ecosystem but want Kubernetes-native config, Spring Cloud Kubernetes Config bridges the gap. It reads ConfigMaps directly and supports hot reload:

pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes-fabric8-config</artifactId>
</dependency>
bootstrap.yml
spring:
application:
name: my-service
cloud:
kubernetes:
config:
name: my-service-config
namespace: default
reload:
enabled: true
mode: event

This gives you hot-reload with ConfigMaps, combining the best of both worlds.

Decision Matrix: When to Use What

After testing both approaches extensively, I created this decision matrix:

CriteriaSpring Cloud Config ServerConfigMaps + GitOps
Kubernetes-nativeNoYes
Non-K8s supportYes (VMs, bare metal)No
GitOps workflowLimited (push-based)Excellent (pull-based)
Hot reload without restartYes (native)Requires tooling
Version controlGit backendGit native
Language agnosticNo (Spring-focused)Yes
Operational complexityHigher (extra service)Lower (native K8s)
Audit trailCustom loggingBuilt-in (Git history)
Mixed environmentsExcellentLimited

My Recommendation: Use ConfigMaps for Kubernetes-Native Environments

For teams running on Kubernetes with GitOps practices, ConfigMaps with ArgoCD/Helm is the better choice. Here’s why:

  1. Single source of truth - Git holds everything: application code, infrastructure, and configuration.

  2. Simpler operations - No Config Server to maintain, monitor, or troubleshoot.

  3. Better audit trail - Every config change is a Git commit with author, time, and reason.

  4. Works across languages - Your Go and Python services can use the same config system.

But keep Spring Cloud Config Server if:

  • You’re running on VMs or bare metal
  • You have legacy Spring Boot applications outside Kubernetes
  • You need hot-reload without pod restarts AND can’t add Reloader
  • Your team lacks Kubernetes expertise

Common Mistakes I Made

Mistake 1: Not handling ConfigMap propagation

I assumed ConfigMap changes would automatically reach running pods. They don’t. I had production incidents where config changes weren’t applied because pods were using cached configs.

Fix: Use hash-based annotations or Stakater Reloader.

Mistake 2: Over-engineering with both systems

I tried running Config Server alongside ConfigMaps. This created confusion about where to put configs and added operational overhead.

Fix: Pick one approach per environment. Don’t mix unless absolutely necessary.

Mistake 3: Ignoring GitOps benefits

I initially dismissed GitOps as “just another trend.” Then I accidentally deleted a config in Config Server with no audit trail. Recovery was painful.

Fix: Embrace GitOps. The audit trail and rollback capabilities alone are worth it.

Mistake 4: Not versioning ConfigMaps properly

I used plain ConfigMaps without any versioning strategy. When something broke, I couldn’t tell which config version caused it.

Fix: Include version in ConfigMap names or use Helm’s hash annotation for automatic versioning.

Migration Path: Config Server to GitOps

If you’re currently on Config Server and want to migrate to GitOps, here’s the path I recommend:

Phase 1: Parallel running

Deploy ArgoCD and start managing new services with ConfigMaps. Keep Config Server for existing services.

Phase 2: Convert configurations

Translate application-{profile}.yml files to Helm values:

convert-config.sh
# From Config Server repo
application-prod.yml -> values-prod.yaml
# Example conversion
spring.datasource.url=jdbc:postgresql://prod-db:5432/myapp
# becomes
config:
database:
url: jdbc:postgresql://prod-db:5432/myapp

Phase 3: Deploy with ArgoCD

Create ArgoCD applications for each service, pointing to your Helm charts.

Phase 4: Add hot-reload support

Install Stakater Reloader and add annotations to deployments.

Phase 5: Deprecate Config Server

Once all services are migrated, shut down Config Server.

GitOps Principles

GitOps is built on four principles:

  1. Declarative - Everything described declaratively in Git
  2. Versioned - Git provides version control and history
  3. Automated - Changes are automatically applied
  4. Reconciled - System continuously matches desired state

ConfigMap Best Practices

  • Keep ConfigMaps immutable (use immutable: true field)
  • Use separate ConfigMaps for different concerns (database, feature flags, etc.)
  • Never store sensitive data in ConfigMaps (use Secrets instead)
  • Include environment in ConfigMap names for clarity

Spring Cloud Config Alternatives

If you need config management outside Kubernetes but don’t want Spring Cloud Config:

  • Consul KV - HashiCorp’s service discovery with config storage
  • etcd - Distributed key-value store
  • HashiCorp Vault - Secrets management with dynamic config
  • AWS Parameter Store - Cloud-native config storage

Final Thoughts

The Reddit discussion that sparked my investigation had a clear consensus: GitOps has largely replaced Config Server in Kubernetes-native environments. The top comment summed it up:

“We use ArgoCD, helm for templating and mount configmaps and secrets. Where would the config server be a better option?”

The answer is simple: only when you’re not on Kubernetes, or when you have specific needs that ConfigMaps can’t meet.

For Kubernetes deployments in 2024 and beyond, use ConfigMaps with GitOps. Add Stakater Reloader or hash-based annotations for automatic rollouts. Your future self will thank you for the cleaner architecture, better audit trails, and simpler operations.

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments