Kubernetes: ConfigMaps, Secrets & Spring Boot Property Sources
Spring Boot reads configuration from many sources: environment variables, property files, and system properties. On Kubernetes, ConfigMaps provide non-sensitive config and Secrets provide sensitive values (passwords, API keys, TLS certificates). This article shows how to create both, inject them into your Spring Boot pods, enable live refresh without restarting, and manage secrets securely in production.
Contents
ConfigMap: Creating & Using
A ConfigMap holds configuration as key-value pairs. A pod can receive it in two ways: each key as an environment variable, or each key as a file. The two ways suit different kinds of settings, so this article uses one ConfigMap for each:
# configmap.yaml
# Settings passed as environment variables
apiVersion: v1
kind: ConfigMap
metadata:
name: payment-service-env
namespace: payments
data:
LOGGING_LEVEL_ROOT: "INFO"
FEATURE_NEW_CHECKOUT: "true"
APP_RETRY_MAX_ATTEMPTS: "3"
---
# A whole properties file, passed as a file
apiVersion: v1
kind: ConfigMap
metadata:
name: payment-service-config
namespace: payments
data:
application.properties: |
spring.jpa.show-sql=false
app.payment.timeout-seconds=30
You can also create ConfigMaps from the command line:
# From individual values
kubectl create configmap payment-service-env --from-literal=LOGGING_LEVEL_ROOT=INFO --from-literal=APP_RETRY_MAX_ATTEMPTS=3 -n payments
# From an existing file; the key becomes the file name
kubectl create configmap payment-service-config --from-file=application.properties=./k8s/application-prod.properties -n payments
# Show what is stored
kubectl get configmap payment-service-config -n payments -o yaml
Injecting as Environment Variables
envFromenvconfigMapKeyRef
# In the Deployment's pod template
spec:
containers:
- name: payment-service
image: myorg/payment-service:1.0.0
# Every key of the ConfigMap as an environment variable
envFrom:
- configMapRef:
name: payment-service-env
# Or one key, under a name of your choice
env:
- name: LOGGING_LEVEL_COM_EXAMPLE
valueFrom:
configMapKeyRef:
name: payment-service-env
key: LOGGING_LEVEL_ROOT
Spring Boot reads environment variables as properties. To find the variable for a property, replace dots and dashes with underscores and write it in capitals: spring.datasource.urlSPRING_DATASOURCE_URLfeature.new-checkoutFEATURE_NEW_CHECKOUTLOG_LEVEL
Injecting as Volume-Mounted Files
Mounting a ConfigMap as a volume creates one file per key. Point Spring Boot at the folder with SPRING_CONFIG_ADDITIONAL_LOCATIONapplication.properties
spec:
containers:
- name: payment-service
image: myorg/payment-service:1.0.0
env:
- name: SPRING_CONFIG_ADDITIONAL_LOCATION
value: "optional:file:/etc/config/"
volumeMounts:
- name: app-config
mountPath: /etc/config
readOnly: true
volumes:
- name: app-config
configMap:
name: payment-service-config
subPathKubernetes Secrets
A Secret works like a ConfigMap but is meant for passwords, keys and certificates. Values under datastringData
# secret.yaml — base64-encoded values (use Sealed Secrets or ESO in production)
apiVersion: v1
kind: Secret
metadata:
name: payment-service-secrets
namespace: payments
type: Opaque
data:
# Values must be base64-encoded
DB_PASSWORD: cGFzc3dvcmQxMjM= # echo -n "password123" | base64
STRIPE_API_KEY: c2tfbGl2ZV8uLi4=
stringData:
# stringData is plain text — Kubernetes base64-encodes it
REDIS_PASSWORD: "my-redis-password"
# Create from literals (Kubernetes does the base64 encoding)
kubectl create secret generic payment-service-secrets \
--from-literal=DB_PASSWORD=password123 \
--from-literal=STRIPE_API_KEY=sk_live_... \
-n payments
# Create TLS secret
kubectl create secret tls api-tls-cert \
--cert=tls.crt \
--key=tls.key \
-n payments
# Mount secrets as environment variables
spec:
containers:
- name: payment-service
envFrom:
- secretRef:
name: payment-service-secrets
# Or individual secret keys
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: payment-service-secrets
key: DB_PASSWORD
Spring Cloud Kubernetes Config
Spring Cloud Kubernetes reads ConfigMaps and Secrets directly from the Kubernetes API: no environment variable mapping needed. The ConfigMap name matches spring.application.name
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes-client-config</artifactId>
</dependency>
# application.yml
spring:
application:
name: payment-service # without "name" below, a ConfigMap named "payment-service" is loaded
config:
import: "kubernetes:" # needed since Spring Cloud 2020.0, which no longer uses bootstrap.yml
cloud:
kubernetes:
config:
enabled: true
name: payment-service-config # override name
namespace: payments
secrets:
enabled: true
name: payment-service-secrets
namespace: payments
# RBAC — give the pod permission to read ConfigMaps and Secrets
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: config-reader
namespace: payments
rules:
- apiGroups: [""]
resources: ["configmaps", "secrets", "pods"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: config-reader-binding
namespace: payments
subjects:
- kind: ServiceAccount
name: default
namespace: payments
roleRef:
kind: Role
name: config-reader
apiGroup: rbac.authorization.k8s.io
This Role lets the pod read every ConfigMap and Secret in the namespace. To follow the least-privilege advice at the end of this article, list the exact objects under resourceNamessecrets
Auto-Refresh with @RefreshScope
Spring Cloud Kubernetes can watch ConfigMaps for changes and trigger a context refresh without restarting the pod.
# application.yml
spring:
cloud:
kubernetes:
reload:
enabled: true
mode: polling # or "event" for watch-based (requires RBAC watch verb)
period: 15000 # poll every 15 seconds
strategy: refresh # refresh vs restart_context vs shutdown
With the refresh@RefreshScope@ConfigurationProperties@Value
// Beans annotated with @RefreshScope are recreated after a reload
@RefreshScope
@Service
public class FeatureFlagService {
@Value("${feature.new-checkout:false}")
private boolean newCheckoutEnabled;
public boolean isNewCheckoutEnabled() { return newCheckoutEnabled; }
}
External Secrets Operator (ESO)
The External Secrets Operator syncs secrets from AWS Secrets Manager, HashiCorp Vault, GCP Secret Manager, or Azure Key Vault into Kubernetes Secrets, keeping the source of truth outside the cluster. The examples use the current v1v1beta1
# SecretStore — points to AWS Secrets Manager
apiVersion: external-secrets.io/v1
kind: SecretStore
metadata:
name: aws-secrets-manager
namespace: payments
spec:
provider:
aws:
service: SecretsManager
region: us-east-1
auth:
serviceAccount: # use IRSA for RBAC
name: external-secrets-sa
---
# ExternalSecret — defines which secrets to sync
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: payment-service-secrets
namespace: payments
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets-manager
kind: SecretStore
target:
name: payment-service-secrets # resulting K8s Secret name
creationPolicy: Owner
data:
- secretKey: DB_PASSWORD # key in the K8s Secret
remoteRef:
key: payment-service/prod # path in AWS Secrets Manager
property: db_password # field within the secret JSON
- secretKey: STRIPE_API_KEY
remoteRef:
key: payment-service/prod
property: stripe_api_key
Secrets Best Practices
- Never commit secrets to Git: even base64-encoded K8s secret YAML. Use Sealed Secrets or the External Secrets Operator.
- Enable encryption at rest: configure
in your cluster to encrypt Secrets in etcd.EncryptionConfiguration - Least privilege RBAC: only grant
on specific secrets; neverget orlist all secrets cluster-wide.watch - Prefer files to environment variables: environment variables are passed on to every child process, readable in
, and often end up in error reports and diagnostic dumps. Files mounted from a Secret are less exposed, and are updated when the Secret is rotated./proc - Rotate regularly: use ESO's
to pull rotated secrets into Kubernetes automatically. The application still has to pick up the new value: files are updated in place, but environment variables only change when the pod restarts.refreshInterval - Audit access: enable Kubernetes audit logging for
secrets operations to detect unexpected access.get