Writing Better Kubernetes YAML
A practical guide to writing clean and maintainable Kubernetes YAML, covering structure, naming, labels, namespaces, resource requests, probes, configuration, security, validation, and common mistakes.
Kubernetes resources are usually defined with YAML manifests. A small manifest can be easy to read, but real applications quickly produce configuration containing Deployments, Services, ConfigMaps, Secrets, probes, resource limits, security settings, volumes, labels, and environment-specific values.
Writing better Kubernetes YAML is therefore more than making indentation look neat. Good manifests should be predictable, easy to review, safe to change, and clear enough that another developer can understand what a resource is supposed to do.
This guide covers practical Kubernetes YAML practices that help keep manifests readable and maintainable as a project grows.
Why Kubernetes YAML Quality Matters
Kubernetes YAML is declarative configuration. Instead of describing every individual operation needed to start an application, the manifest describes the desired state of a resource.
Because these files often become part of Git repositories and deployment pipelines, their readability directly affects development and operations. A confusing manifest can lead to incorrect changes, difficult code reviews, deployment failures, and configuration drift.
- Use consistent formatting and indentation.
- Give resources meaningful names.
- Use namespaces deliberately.
- Apply consistent labels and selectors.
- Specify important configuration explicitly.
- Set appropriate resource requests and limits.
- Use health probes for long-running applications.
- Keep secrets separate from ordinary configuration.
- Avoid unnecessary duplication.
- Validate YAML and Kubernetes manifests before deployment.
- Keep environment-specific configuration manageable.
- Review manifests before applying them to production.
Start with Valid YAML
The first requirement is simple: the file must be valid YAML. Kubernetes cannot interpret a manifest correctly if the YAML structure itself is invalid.
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
APP_ENV: production
API_URL: https://api.example.comYAML relies heavily on indentation and structure. A single incorrect indentation level can change the meaning of a document or make it invalid.
# Incorrect structure
metadata:
name: appThe name field should be nested under metadata, so the correct structure is:
metadata:
name: appUse Consistent Indentation
Two spaces per indentation level is a common convention for YAML. The important part is consistency: do not mix indentation styles inside the same file.
spec:
containers:
- name: api
image: example/api:1.0
ports:
- containerPort: 8080Consistent indentation makes deeply nested Kubernetes specifications significantly easier to review.
Use One Logical Resource per Document
Kubernetes YAML files can contain multiple resources separated by the YAML document separator. This is useful when several related resources are deployed together.
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
---
apiVersion: v1
kind: Service
metadata:
name: apiThere is no requirement that every Kubernetes resource must have its own file. The best organization depends on the project, deployment tooling, and team workflow.
For small applications, a single manifest containing several related resources can be convenient. Larger repositories often separate resources into logical files or directories so individual components are easier to review.
Use Meaningful Resource Names
Names should communicate what a resource represents. Avoid generic names such as app, test, thing, or resource when a more descriptive name is available.
metadata:
name: payments-apiConsistent naming becomes particularly important when a cluster contains many applications and environments.
| Less descriptive | More descriptive |
|---|---|
| app | payments-api |
| service1 | checkout-api |
| config | payments-api-config |
| deployment | frontend |
Use Namespaces Deliberately
Namespaces provide logical scope for many Kubernetes resources. Including the intended namespace in manifests can make resource placement explicit.
apiVersion: apps/v1
kind: Deployment
metadata:
name: payments-api
namespace: productionFor environment-specific deployments, the namespace can instead be supplied by deployment tooling such as Helm or Kustomize. The important point is to have a clear strategy rather than accidentally relying on the current kubectl context.
Keep apiVersion and kind Clear
Every Kubernetes object should clearly identify its API version and resource kind. These fields determine how Kubernetes interprets the object.
apiVersion: apps/v1
kind: DeploymentAvoid copying old examples without checking whether the API version is appropriate for the Kubernetes version and resource type used by your cluster.
Organize metadata Consistently
Metadata is one of the most important parts of a Kubernetes manifest because names, namespaces, labels, and annotations are used by Kubernetes and external tooling.
metadata:
name: payments-api
namespace: production
labels:
app: payments-api
environment: production
team: paymentsA consistent metadata structure makes resources easier to search, group, monitor, and manage.
Use Labels Consistently
Labels are key-value metadata used for identification and selection. They are especially important because resources such as Services and Deployments use selectors to connect related objects.
metadata:
labels:
app: payments-api
component: backend
environment: productionThe labels used by a Deployment's Pod template should match the Deployment selector.
spec:
selector:
matchLabels:
app: payments-api
template:
metadata:
labels:
app: payments-apiUse Standard Kubernetes Labels Where Appropriate
Kubernetes projects commonly use the app.kubernetes.io label convention for identifying application-related metadata. Consistent standard labels can make resources easier for humans and tools to understand.
labels:
app.kubernetes.io/name: payments
app.kubernetes.io/instance: payments-production
app.kubernetes.io/component: backend
app.kubernetes.io/part-of: commerceYou do not need to use every possible label. Choose a small, consistent set that provides useful information for your project.
Make Selectors Easy to Understand
Selectors connect Kubernetes resources. A Service, for example, uses a selector to identify the Pods to which it sends traffic.
spec:
selector:
app: payments-apiAvoid creating unnecessarily complicated selectors. If a single stable label clearly identifies the intended Pods, a simple selector is often easier to maintain.
Separate Configuration from the Container Image
Application configuration that changes between environments should generally not require rebuilding the container image. Kubernetes ConfigMaps, Secrets, environment variables, and other configuration mechanisms can keep deployment configuration separate from application binaries.
env:
- name: APP_ENV
valueFrom:
configMapKeyRef:
name: payments-config
key: APP_ENVThis allows the same image to be deployed with different configuration in development, staging, and production.
Do Not Put Secrets in Ordinary ConfigMaps
ConfigMaps are intended for non-confidential configuration. Sensitive values such as passwords, API credentials, and private keys should be handled using appropriate secret-management mechanisms.
apiVersion: v1
kind: Secret
metadata:
name: payments-db
type: Opaque
stringData:
DATABASE_USER: app
DATABASE_PASSWORD: change-meAvoid Hardcoding Sensitive Values
Even when a Secret manifest is technically valid, committing real credentials to Git can expose them to anyone who can access the repository history. Production workflows should separate secret values from ordinary source-controlled configuration.
Set Resource Requests and Limits
Containers can specify CPU and memory requests and limits. Requests influence scheduling, while limits constrain resource usage according to the configured Kubernetes behavior.
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512MiChoosing values should be based on the application's actual behavior and workload requirements. Arbitrary values copied from examples can be counterproductive.
Add Readiness and Liveness Probes
Health probes allow Kubernetes to make better decisions about application availability. Readiness indicates whether a container is ready to receive traffic, while liveness can help detect an application that needs to be restarted.
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 15
periodSeconds: 20Probe configuration should reflect the application's startup and recovery behavior. An overly aggressive liveness probe can restart an application that is simply taking time to initialize.
Use Startup Probes for Slow-Starting Applications
Applications with long initialization periods can use a startup probe to give Kubernetes an explicit startup window before liveness checking becomes relevant.
startupProbe:
httpGet:
path: /health
port: 8080
failureThreshold: 30
periodSeconds: 10The correct probe type and timing depend on how the application behaves during startup and normal operation.
Keep Container Definitions Focused
A container specification can contain many fields, but adding configuration simply because it is available can make manifests harder to understand.
containers:
- name: api
image: example/api:1.4.0
ports:
- containerPort: 8080
env:
- name: APP_ENV
value: production
resources:
requests:
cpu: 100m
memory: 128MiKeep fields that are required for the workload or provide meaningful operational behavior. Avoid copying large blocks of configuration that are not relevant to the application.
Prefer Explicit Image Versions
Container images should normally use a predictable version or immutable reference instead of an ambiguous moving tag.
image: example/api:1.4.0Explicit versions make deployments easier to reproduce and understand. For workflows requiring stronger immutability, image digests can be used.
Avoid Unnecessary YAML Duplication
When the same configuration is copied into many manifests, it becomes easy for the files to drift apart. If several environments share most of their configuration, consider a templating or overlay approach.
Helm and Kustomize are common solutions for managing reusable Kubernetes configuration. They allow shared configuration to be separated from environment-specific changes.
Keep Environment Differences Small
Development, staging, and production often need different replica counts, images, resource settings, hostnames, or configuration values. The goal should be to represent those differences explicitly without maintaining three completely unrelated copies of every manifest.
| Configuration | Often environment-specific? |
|---|---|
| Namespace | Yes |
| Container image version | Often |
| Replica count | Often |
| Resource requests | Often |
| Resource limits | Often |
| Application labels | Usually stable |
| Container port | Usually stable |
| Health endpoint | Usually stable |
Use YAML Anchors Carefully
YAML supports anchors and aliases for reusing values within YAML documents. Although they can reduce duplication, they may make Kubernetes configuration less obvious to readers and are not always the best choice for reusable deployment configuration.
For substantial environment-specific reuse, dedicated configuration tools such as Helm or Kustomize can provide a clearer project structure than increasingly complex YAML anchors.
Use Comments for Important Context
Comments are useful when a configuration choice is not obvious from the manifest itself. A good comment explains why something is configured a particular way rather than simply repeating what the YAML already says.
resources:
requests:
cpu: 250m
memory: 256Mi
# API performs an in-memory cache warmup during startup.
# Keep enough memory available to avoid startup failures.
limits:
memory: 512MiAvoid filling manifests with comments that merely translate YAML into English. The configuration should remain understandable without excessive annotation.
Use Consistent Ordering
Kubernetes does not generally require fields inside objects to appear in a particular order, but consistent ordering improves readability.
For example, a container can consistently place name and image first, followed by ports, environment variables, resources, and probes. The exact convention can vary between teams as long as it is applied consistently.
containers:
- name: api
image: example/api:1.4.0
ports:
- containerPort: 8080
env:
- name: APP_ENV
value: production
resources:
requests:
cpu: 100m
memory: 128Mi
readinessProbe:
httpGet:
path: /health
port: 8080Keep Services Simple
A Service should expose the application using a clear selector and the ports it actually needs.
apiVersion: v1
kind: Service
metadata:
name: payments-api
spec:
selector:
app: payments-api
ports:
- port: 80
targetPort: 8080Avoid adding unnecessary ports, annotations, or networking configuration unless the application or infrastructure actually requires them.
Make Port Configuration Understandable
Port names can make larger manifests easier to understand, particularly when an application exposes multiple protocols or ports.
ports:
- name: http
port: 80
targetPort: http
- name: metrics
port: 9090
targetPort: metricsNamed ports can also make references more expressive than relying exclusively on numeric values.
Use Security Contexts Where Appropriate
Container and Pod security settings can reduce unnecessary privileges. The exact configuration depends on the application and image.
securityContext:
runAsNonRoot: true
allowPrivilegeEscalation: falseDo not blindly copy security settings into every workload. Verify that the application and container image support the selected configuration.
Avoid Running Containers as Root When It Is Not Needed
If an application does not require root privileges, configuring it to run as a non-root user can reduce the impact of certain container-level security problems.
securityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefaultUse Volumes Only When Necessary
Volumes are essential for many workloads, but unnecessary storage configuration can make a manifest difficult to understand. Clearly define what data needs persistence and what data can remain ephemeral.
volumes:
- name: config
configMap:
name: payments-config
containers:
- name: api
volumeMounts:
- name: config
mountPath: /etc/appThe volume name should communicate what is being mounted and should match its volumeMounts reference.
Do Not Treat YAML Formatting as Validation
A manifest can be perfectly formatted YAML and still be invalid Kubernetes configuration. YAML syntax validation checks the document structure, but Kubernetes also needs to validate resource kinds, fields, types, selectors, and other API requirements.
kubectl apply --dry-run=client -f deployment.yamlDepending on your workflow, server-side validation and dry-run operations can provide additional confidence before making changes to a cluster.
Format and Validate Before Deployment
A useful workflow is to format the YAML, validate its syntax, inspect the resulting structure, and then use Kubernetes-aware validation or dry-run capabilities before applying it.
- Format the YAML.
- Check indentation and nesting.
- Validate YAML syntax.
- Review apiVersion and kind.
- Check names and namespaces.
- Verify labels and selectors.
- Check resource references such as ConfigMaps and Secrets.
- Review resource requests and limits.
- Check probes and ports.
- Run an appropriate dry-run or validation command.
- Review the final diff before production deployment.
Review YAML Diffs
When modifying Kubernetes manifests, reviewing the difference between the old and new versions is often more useful than reading the entire file again.
git diff -- deployment.yamlA YAML-aware diff tool can make structural changes easier to recognize, especially when indentation or field ordering creates a large textual diff.
Keep Manifests in Version Control
Kubernetes manifests are infrastructure configuration and should normally be treated as code. Keeping them in Git provides history, review, rollback support, and a record of configuration changes.
Pull requests can then be used to review changes before they reach shared or production environments.
Avoid Editing Production Resources Manually Without a Reason
kubectl edit and kubectl patch can be useful operational tools, but manually changing a live resource without updating the source manifest can create configuration drift.
If a manual change is necessary, update the source configuration afterward when appropriate so the desired state remains documented.
Use Separate Configuration for Secrets
A clean Kubernetes repository should make it obvious which files contain ordinary configuration and which mechanisms provide sensitive credentials.
For production systems, teams often use secret-management integrations rather than storing plaintext credentials directly in source-controlled manifests.
A Clean Kubernetes Deployment Example
The following example combines several of the practices discussed above: meaningful names, namespace placement, labels, an explicit image version, resources, a readiness probe, and a matching Service selector.
apiVersion: apps/v1
kind: Deployment
metadata:
name: payments-api
namespace: production
labels:
app.kubernetes.io/name: payments-api
app.kubernetes.io/component: backend
app.kubernetes.io/part-of: commerce
spec:
replicas: 3
selector:
matchLabels:
app.kubernetes.io/name: payments-api
template:
metadata:
labels:
app.kubernetes.io/name: payments-api
app.kubernetes.io/component: backend
spec:
containers:
- name: payments-api
image: example/payments-api:1.4.0
ports:
- name: http
containerPort: 8080
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
readinessProbe:
httpGet:
path: /health
port: http
---
apiVersion: v1
kind: Service
metadata:
name: payments-api
namespace: production
spec:
selector:
app.kubernetes.io/name: payments-api
ports:
- name: http
port: 80
targetPort: httpThe example is intentionally explicit without trying to configure every possible Kubernetes field. It provides the information needed to understand how the application is deployed and exposed while leaving unnecessary defaults alone.
Organizing Kubernetes YAML Files
There are many valid ways to organize Kubernetes manifests. A small application might use a few files such as deployment.yaml, service.yaml, and configmap.yaml. A larger application may use directories for base configuration and environment-specific overlays.
deployment.yaml
service.yaml
configmap.yaml
ingress.yamlThe important principle is discoverability. A developer should be able to find the configuration for a workload without searching through a huge collection of unrelated YAML.
Use Helm When Templating Becomes Necessary
Helm can be useful when several deployments share a common Kubernetes structure but need configurable values. Instead of maintaining many nearly identical manifests, a chart can define reusable templates and values.
replicas: {{ .Values.replicaCount }}
image:
repository: {{ .Values.image.repository }}
tag: {{ .Values.image.tag }}Helm adds its own templating syntax, so it should be introduced when the benefits of reuse and configuration outweigh the additional complexity.
Use Kustomize for Declarative Overlays
Kustomize is another option for managing environment-specific Kubernetes configuration. A base can contain common manifests while overlays modify selected fields for development, staging, or production.
The choice between plain YAML, Kustomize, Helm, or another deployment system depends on project complexity and team requirements. The underlying manifests should still remain understandable.
Avoid Overengineering Small Manifests
Not every Kubernetes application needs Helm, Kustomize, multiple overlays, generated manifests, or a large abstraction layer. For a small project, a few clear YAML files may be easier to maintain.
Introduce additional tooling when duplication, environment differences, or deployment complexity becomes difficult to manage with plain YAML.
Common Kubernetes YAML Mistakes
- Using inconsistent indentation.
- Putting resources into an unintended namespace.
- Using selectors that do not match Pod labels.
- Using vague resource names.
- Hardcoding production secrets into Git.
- Using moving image tags when reproducibility matters.
- Copying arbitrary CPU and memory values.
- Adding probes without considering application startup behavior.
- Maintaining several large duplicated manifests.
- Treating formatted YAML as automatically valid Kubernetes configuration.
- Making manual production changes without updating the source configuration.
- Adding unnecessary fields simply because examples contain them.
A Practical Kubernetes YAML Checklist
- Is the YAML syntactically valid?
- Is indentation consistent?
- Are apiVersion and kind correct?
- Does the resource have a meaningful name?
- Is the namespace intentional?
- Are labels consistent?
- Do selectors match the intended Pods?
- Are image references predictable?
- Are configuration values separated from the image where appropriate?
- Are sensitive values handled securely?
- Are CPU and memory requests appropriate?
- Are limits appropriate for the workload?
- Are readiness, liveness, or startup probes needed?
- Are Service ports and targetPorts correct?
- Are security settings appropriate for the container?
- Is duplication under control?
- Has the manifest been validated before deployment?
- Has the change been reviewed in version control?
Frequently Asked Questions
What makes Kubernetes YAML good?
Good Kubernetes YAML is valid, readable, predictable, and maintainable. It uses meaningful names, consistent labels and selectors, deliberate namespaces, appropriate resources and probes, clear configuration, and a validation workflow.
How many spaces should Kubernetes YAML use?
Two spaces per indentation level is a common YAML convention. Kubernetes does not require that exact style, but consistent indentation is important for readability and correct YAML structure.
Should every Kubernetes resource have its own YAML file?
No. Related resources can be stored in the same multi-document YAML file. The best organization depends on application size, repository structure, deployment tooling, and team workflow.
Should I always specify the namespace in Kubernetes YAML?
Not necessarily. Explicit namespaces can make resource placement clear, while tools such as Helm or Kustomize can manage namespace configuration. What matters is having a deliberate and consistent namespace strategy.
Are Kubernetes Secrets safe to commit to Git?
Real credentials should generally not be committed to Git simply because they are stored in a Kubernetes Secret manifest. Production environments often use dedicated secret-management solutions and strict access controls.
Do Kubernetes YAML formatters validate Kubernetes resources?
A YAML formatter primarily improves formatting and readability. It does not necessarily verify that a manifest is valid for a particular Kubernetes API. Kubernetes-aware validation or dry-run commands provide additional checks.
Should Kubernetes YAML use Helm or Kustomize?
Neither is required for every project. Plain YAML is often sufficient for small applications, while Helm or Kustomize can help when environments, reuse, or configuration differences make plain manifests difficult to maintain.
Helpful Kubernetes YAML Tools
Several types of web tools can make Kubernetes configuration easier to work with. Kubernetes YAML formatters can improve the readability of manifests, general YAML formatters can normalize indentation and structure, and YAML validators can catch syntax problems. YAML tree viewers are useful for inspecting deeply nested documents, while YAML diff tools can make configuration changes easier to review.
Conclusion
Writing better Kubernetes YAML means creating manifests that are easy to understand, validate, review, and safely modify. Consistent formatting is only the starting point. Namespaces, labels, selectors, resources, health probes, image versions, security settings, and configuration management all contribute to maintainable Kubernetes configuration.
For small projects, a few clean YAML files may be enough. As applications grow, Helm, Kustomize, Git-based workflows, validation, and structured environment configuration can reduce duplication and configuration errors. The goal is not to make manifests more complicated, but to make the desired Kubernetes state clear and predictable.