Configuration #
One of the main pillars that made Kubernetes so successful and widely adopted in the industry is its decision to design Declarative Configuration management. We no longer manage servers by sending a series of line-by-line commands; instead, we define our entire infrastructure in written manifest files.
Understanding how Kubernetes reads, stores, applies, and distributes this configuration to application containers is a fundamental skill for building clean, secure, and easily reproducible deployment architectures.
The Declarative Philosophy: How kubectl apply Works
#
In traditional (Imperative) server management, we give instructions as an ordered sequence of commands: “Create a new server, download Docker, run the container, open the port”. If one step fails, we have to track the system state manually.
Kubernetes flips this paradigm with the Declarative model: “I want 3 replicas of the nginx application running on port 80”. We write a YAML manifest describing that end state, then submit it to the API Server using:
# CORRECT: Applying configuration declaratively
kubectl apply -f deployment.yaml
Behind the scenes, kubectl apply isn’t just sending a file. Kubernetes uses a smart mechanism called the Three-Way Merge Patch to update the system. This mechanism compares three data sources simultaneously:
- Local Manifest File: The YAML file we’re currently submitting from our machine.
- Live Configuration in the Cluster: The real condition of the object currently stored in the cluster’s etcd.
- The Last-Applied-Configuration Annotation: Special metadata Kubernetes inserts into cluster objects to remember what the local manifest looked like at the previous
applyexecution.
By comparing all three, Kubernetes can determine which fields we intentionally added, changed, or removed in the local file, and then update only those differences (diff) on the live cluster. This ensures ad-hoc modifications in the cluster don’t conflict and stay safe.
Kubernetes YAML Manifest Format #
All objects in Kubernetes are configured through YAML (or JSON) manifest documents. Every manifest, without exception, must have these four main fields at its top level:
apiVersion: apps/v1 # 1. API version of the object group
kind: Deployment # 2. Type of object to create
metadata: # 3. Object identity data
name: api-server
namespace: production
labels:
app: api
spec: # 4. Technical object spec (Desired State)
replicas: 3
template:
# ... Pod configuration details
Explanation of the 4 Main Fields: #
apiVersion: Determines the API schema version used to read the manifest. Kubernetes splits the API into several groups (e.g. corev1for Pod/Service,apps/v1for Deployment/StatefulSet,networking.k8s.io/v1for Ingress).kind: Determines what type of object is being declared (Pod, Service, ConfigMap, Secret, Ingress, etc.).metadata: Holds information to uniquely identify the object, such asname(object name),namespace(logical location), andlabels(grouping tags).spec: The technical spec block describing the end state we want. The contents of thisspecblock vary dramatically depending on the object type defined inkind.
Separating Code and Configuration: ConfigMap vs Secret #
Based on modern application development methodology (Twelve-Factor App), one absolute principle is separating application code from runtime environment configuration. We must be able to deploy the same container image to Development, Staging, and Production environments without recompiling code.
Kubernetes provides two special objects for separating configuration from containers:
1. ConfigMap (Non-Sensitive Configuration) #
Used to store general configuration parameters that contain no secrets. Examples: database host names, application ports, feature flags, or logging configuration.
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
namespace: production
data:
DB_HOST: "postgres-prod.company.local"
DB_PORT: "5432"
LOG_LEVEL: "info"
2. Secret (Sensitive Credentials) #
Used to store secret data like database passwords, API keys, OAuth tokens, SSL certificates, or SSH credentials.
apiVersion: v1
kind: Secret
metadata:
name: app-secret
namespace: production
type: Opaque
data:
# Data values must be encoded to Base64 format
DB_PASSWORD: cG9zdGdyZXMxMjM0NTY= # base64 representation of "postgres123456"
ANTI-PATTERN: Storing Base64 Secrets Directly in a Git Repository
// WHAT WE DO:
- Write a `kind: Secret` manifest like the example above.
- Push that YAML file to the team's Github/Gitlab repository.
// THE CONSEQUENCES IN PRODUCTION:
- Illusion of Security: Base64 is not encryption — it's just character encoding. Anyone with read access to our git repository can decode that password in 1 second with `echo cG9zdGdyZXMxMjM0NTY= | base64 --decode`.
- Massive production credential leaks.
✓ THE RIGHT SOLUTION:
- Use git manifest encryption tools like [Mozilla SOPS](https://github.com/mozilla/sops) or [Sealed Secrets](https://github.com/bitnami-labs/sealed-secrets). Sealed Secrets encrypts Secret manifests with the cluster's public key, so they're safe to store in git. Only the target cluster holds the private key to decrypt them.
- Even better: use an **External Secrets Operator** that securely syncs sensitive data from a cloud secret vault (like HashiCorp Vault, AWS Secrets Manager, or GCP Secret Manager) directly into cluster memory at runtime.
Configuration Injection Methods into Pods #
Once we create ConfigMap and Secret objects, we can inject their values into Pod containers through two main methods:
1. Injection as Environment Variables #
The value of a specific key in the ConfigMap/Secret is read and turned into an environment variable inside the container OS.
# Example Injection via Environment Variable
spec:
containers:
- name: api
image: my-app:v1
env:
- name: DB_HOST
valueFrom:
configMapKeyRef:
name: app-config
key: DB_HOST
- name: DB_PASS
valueFrom:
secretKeyRef:
name: app-secret
key: DB_PASSWORD
Characteristics: Simple and supported by almost all application frameworks. However, variable values are static. If we change a ConfigMap value, the container must be restarted to read the new environment variable value.
2. Injection as Files (Volume Mount) #
Kubernetes mounts the ConfigMap or Secret object as a virtual file directory inside the container filesystem. Each key in the data becomes a plain text file, with the file contents being the key’s value.
# Example Injection via Volume Mount
spec:
containers:
- name: api
image: my-app:v1
volumeMounts:
- name: config-volume
mountPath: /etc/app-config # The ConfigMap will be mounted as a file directory here
volumes:
- name: config-volume
configMap:
name: app-config
Characteristics: Ideal for large configuration (like Spring Boot application.yaml files, Nginx config, or Prometheus config). Its biggest advantage is supporting dynamic updates (Hot Reload). When the ConfigMap content changes, the Kubelet automatically updates the virtual files inside the container within seconds without restarting the Pod. Your application just needs to be designed to detect those file changes (file watch).
The Immutable Infrastructure Principle #
Kubernetes strongly emphasizes compliance with the Immutable Infrastructure principle. In short: once cluster servers and containers are deployed to production, we must not make configuration changes, library updates, or direct manual modifications to the running system.
If you want to change an application port or update an HTML file, you’re forbidden from SSHing/exec-ing into the container to change it.
The Correct Configuration Change Flow:
1. Edit the local manifest file (YAML) or modify the code pipeline.
2. Commit the changes to Git (GitOps).
3. Run `kubectl apply -f deployment.yaml` or let a GitOps controller detect the change.
4. Kubernetes creates new Pods with the new configuration gradually.
5. Old Pods carrying stale configuration are removed safely.
By following this principle, we guarantee that the production cluster state is always identical to what’s written in the Git repository (Infrastructure as Code). This eliminates the classic “it works on my server, but fails on staging” problem and makes disaster recovery much easier.
Summary #
- Declarative Configuration Model — We only define the system’s final desired state through YAML manifests, and let Kubernetes (API Server & Control Loops) do the work to achieve it.
- Three-Way Merge Patch —
kubectl apply’s smart mechanism comparing the local manifest, live cluster state, and last applied configuration to minimize update conflicts.- ConfigMap vs Secret — ConfigMap is for regular non-sensitive configuration parameters, while Secret is specifically for sensitive credentials.
- Encryption Is Not Encoding — Kubernetes Secrets are only Base64-encoded by default. Protect cluster secret data with git encryption tools (like Sealed Secrets) or external vault integrations.
- Environment Variables vs Volume Mount — Environment variables are static and need a Pod restart when changed. Mounted ConfigMap files can be updated dynamically (hot-reload) at cluster runtime without restarting the Pod.