Ctrl + K
Kubernetes19 min read

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.

Published: 2026-10-05

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.com

YAML 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: app

The name field should be nested under metadata, so the correct structure is:

metadata:
  name: app
💡 Use a YAML formatter while editing large Kubernetes manifests. Formatting tools can make indentation and nesting problems much easier to spot.

Use 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: 8080

Consistent 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: api

There 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-api

Consistent naming becomes particularly important when a cluster contains many applications and environments.

Less descriptiveMore descriptive
apppayments-api
service1checkout-api
configpayments-api-config
deploymentfrontend

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: production

For 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.

⚠️ Be especially careful with commands that use the current namespace implicitly. Applying a manifest to the wrong namespace can create a resource successfully while putting it somewhere you did not intend.

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: Deployment

Avoid 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: payments

A 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: production

The labels used by a Deployment's Pod template should match the Deployment selector.

spec:
  selector:
    matchLabels:
      app: payments-api
  template:
    metadata:
      labels:
        app: payments-api
⚠️ Do not casually change a Deployment selector. Selectors are part of the identity relationship between a controller and its Pods, and an incompatible selector can prevent a Deployment from being updated as intended.

Use 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: commerce

You 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-api

Avoid 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_ENV

This 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-me
⚠️ Kubernetes Secret objects should not be treated as automatically secure just because they are called Secrets. Protect access to them with RBAC and consider an external secret-management solution for sensitive production credentials.

Avoid 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: 512Mi

Choosing values should be based on the application's actual behavior and workload requirements. Arbitrary values copied from examples can be counterproductive.

💡 Start with reasonable resource settings, observe actual usage, and adjust them as you collect production or staging data.

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: 20

Probe 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: 10

The 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: 128Mi

Keep 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.0

Explicit 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.

ConfigurationOften environment-specific?
NamespaceYes
Container image versionOften
Replica countOften
Resource requestsOften
Resource limitsOften
Application labelsUsually stable
Container portUsually stable
Health endpointUsually 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: 512Mi

Avoid 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: 8080

Keep 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: 8080

Avoid 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: metrics

Named 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: false

Do 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: RuntimeDefault

Use 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/app

The 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.yaml

Depending 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.yaml

A 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: http

The 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.yaml

The 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.

Found an issue?

Found an error, outdated information, or something missing from this article? Let me know through the Contact page.

Your feedback helps improve our articles and keep them accurate and useful.