Skip to main content
CleanStart

Knowledge Hub

Kubernetes Manifests and Deployment Files: The Complete Guide

Watch the Lesson

A Kubernetes manifest is the source code of cloud infrastructure. Written in YAML, it describes the desired state of your applications, networks, storage, and security policies. When you submit a manifest, Kubernetes compares your declared intention against current reality and makes adjustments.

Manifests are not imperative instructions ("do this, then that"). They are declarative descriptions: "I want 3 replicas of this application running, with these resource limits, using this configuration, exposed via this service."

graph TB    YAML["Manifest.yaml<br/>Desired State"]    YAML -->|kubectl apply| API["Kubernetes<br/>API Server"]    API --> Compare["Compare with<br/>Current State"]    Compare --> Reconcile["Reconciliation<br/>Loop"]    Reconcile -->|Create/Update| Resources["Resources<br/>Deployments<br/>Services<br/>ConfigMaps"]    Resources -->|Monitor| Desired["Maintain<br/>Desired State"]    Desired --> Status["Status Update<br/>feedback to manifest"]

What Is a Manifest?

A Kubernetes manifest is a text file (YAML or JSON) that describes one or more Kubernetes resources.

Basic structure:

apiVersion: v1kind: Podmetadata:  name: my-podspec:  containers:  - name: app    image: myapp:latest

This describes a single resource: a Pod named "my-pod" running the "myapp:latest" container image.

apiVersion: The Kubernetes API version (v1, apps/v1, batch/v1, etc.) kind: The resource type (Pod, Deployment, Service, ConfigMap, etc.) metadata: Identifying information (name, namespace, labels) spec: The desired state (what to create, how to configure it)

A single manifest file can contain multiple resources separated by ---:

---apiVersion: v1kind: ConfigMapmetadata:  name: app-configdata:  setting1: value1---apiVersion: v1kind: Secretmetadata:  name: app-secretstype: Opaquedata:  api_key: c2tyLWFiYzEyMz==---apiVersion: v1kind: Servicemetadata:  name: app-servicespec:  selector:    app: myapp  ports:  - port: 80    targetPort: 8000---apiVersion: apps/v1kind: Deploymentmetadata:  name: myapp-deploymentspec:  replicas: 3  selector:    matchLabels:      app: myapp  template:    metadata:      labels:        app: myapp    spec:      containers:      - name: app        image: myapp:latest

This single file defines 4 resources that work together: configuration, secrets, service, and deployment.

Core Resource Types

Kubernetes has dozens of resource types. These are the most common and important.

Pod

The smallest deployable unit in Kubernetes. A pod is one or more containers (usually one) running together on the same node.

apiVersion: v1kind: Podmetadata:  name: app-pod  namespace: production  labels:    app: myapp    version: 1.0spec:  containers:  - name: app    image: myapp:latest    ports:    - containerPort: 8000  - name: sidecar    image: sidecar:latest

Pods are ephemeral: they're created, run, and deleted. You rarely create pods directly; instead, you create higher-level resources (Deployments) that manage pods for you.

Deployment

A Deployment manages multiple pod replicas, rolling updates, and rollbacks.

apiVersion: apps/v1kind: Deploymentmetadata:  name: web-appspec:  replicas: 3  # Keep 3 pods running at all times  selector:    matchLabels:      app: web  template:  # Template for creating pods    metadata:      labels:        app: web    spec:      containers:      - name: web        image: webapp:v2.1.0        ports:        - containerPort: 8000        resources:          requests:            memory: "256Mi"            cpu: "100m"          limits:            memory: "512Mi"            cpu: "500m"

When you create a Deployment, Kubernetes:

  1. Creates a ReplicaSet (which manages multiple pod copies)
  2. The ReplicaSet creates 3 Pods
  3. Watches continuously: if a pod dies, create a new one
  4. On image update: performs a rolling update (replace pods gradually)

Deployments are the standard way to run applications in Kubernetes.

Service

A Service exposes pods to the network. It provides a stable IP and DNS name even as pods are created and destroyed.

apiVersion: v1kind: Servicemetadata:  name: web-servicespec:  selector:    app: web  # Route traffic to pods with label app=web  type: ClusterIP  # Internal only (default)  ports:  - port: 80      # Incoming port    targetPort: 8000  # Pod container port    protocol: TCP

Types of Services exist in three main flavors. ClusterIP is the default, providing internal-only access where pods can reach each other. NodePort offers external access via the node IP and a port in the range 30000 and above. LoadBalancer provides an external IP from the cloud provider, though this option is slower and more expensive than the alternatives.

When you create this Service, Kubernetes:

  1. Assigns it a stable internal IP (e.g., 10.0.1.234)
  2. Creates DNS entry: web-service.default.svc.cluster.local
  3. Any pod can reach other pods via the service: curl http://web-service
  4. Traffic is load-balanced across all matching pods

ConfigMap

ConfigMaps store non-secret configuration data (strings, files, environment variables).

apiVersion: v1kind: ConfigMapmetadata:  name: app-configdata:  database_host: "postgres.default.svc.cluster.local"  database_port: "5432"  log_level: "debug"  nginx.conf: |    server {      listen 8000;      location / { ... }    }

Use in pods:

apiVersion: v1kind: Podmetadata:  name: appspec:  containers:  - name: app    image: myapp:latest    env:    - name: DB_HOST      valueFrom:        configMapKeyRef:          name: app-config          key: database_host    volumeMounts:    - name: config      mountPath: /etc/nginx  volumes:  - name: config    configMap:      name: app-config

This injects config values as environment variables and mounts the nginx.conf as a file.

Secret

Secrets store sensitive data (passwords, API keys, tokens, certificates).

apiVersion: v1kind: Secretmetadata:  name: db-credentialstype: Opaque  # Generic key-value secretdata:  username: dXNlcm5hbWU=  # base64 encoded "username"  password: cGFzc3dvcmQx  # base64 encoded "password1"---apiVersion: v1kind: Secretmetadata:  name: tls-certtype: kubernetes.io/tlsdata:  tls.crt: LS0tLS1CRUdJTi...  # Certificate (base64)  tls.key: LS0tLS1CRUdJTi...  # Private key (base64)

Important: Secrets are NOT encrypted by default (only base64-encoded). For production, enable encryption at rest:

# In /etc/kubernetes/manifests/kube-apiserver.yaml--encryption-provider-config=/etc/kubernetes/encryption.yaml

Use in pods:

spec:  containers:  - name: app    env:    - name: DB_PASSWORD      valueFrom:        secretKeyRef:          name: db-credentials          key: password    volumeMounts:    - name: tls      mountPath: /etc/tls      readOnly: true  volumes:  - name: tls    secret:      secretName: tls-cert

Namespace

Namespaces provide logical isolation. Resources in different namespaces can have the same name.

apiVersion: v1kind: Namespacemetadata:  name: production---apiVersion: v1kind: Namespacemetadata:  name: staging

Then create resources in a namespace:

kubectl apply -f manifest.yaml -n productionkubectl apply -f manifest.yaml -n staging

Each namespace can have its own quotas, network policies, and service accounts.

Ingress

An Ingress routes HTTP traffic to services based on hostname and path.

apiVersion: networking.k8s.io/v1kind: Ingressmetadata:  name: web-ingressspec:  rules:  - host: api.example.com    http:      paths:      - path: /v1        pathType: Prefix        backend:          service:            name: api-service            port:              number: 8000      - path: /v2        pathType: Prefix        backend:          service:            name: api-v2-service            port:              number: 8000  - host: admin.example.com    http:      paths:      - path: /        pathType: Prefix        backend:          service:            name: admin-service            port:              number: 3000  tls:  - hosts:    - api.example.com    - admin.example.com    secretName: tls-cert

Ingress requires an "ingress controller" (nginx, Traefik, etc.) to actually route traffic. Different cloud providers offer their own ingress implementations.

Deployment Anatomy: The Most Important Resource

Deployments are the workhorse of Kubernetes. Understanding their structure is critical.

apiVersion: apps/v1kind: Deploymentmetadata:  name: web-app  namespace: production  labels:    app: web    tier: frontendspec:  replicas: 3  strategy:    type: RollingUpdate    rollingUpdate:      maxSurge: 1  # Max 1 extra pod during update      maxUnavailable: 0  # Keep all pods available  selector:    matchLabels:      app: web  template:    metadata:      labels:        app: web        version: v2.1.0    spec:      serviceAccountName: web-sa      securityContext:        runAsNonRoot: true        runAsUser: 65532      containers:      - name: web        image: webapp:v2.1.0        imagePullPolicy: IfNotPresent        ports:        - containerPort: 8000          name: http        env:        - name: LOG_LEVEL          value: "info"        - name: DB_PASSWORD          valueFrom:            secretKeyRef:              name: db-secret              key: password        resources:          requests:            memory: "256Mi"            cpu: "100m"          limits:            memory: "512Mi"            cpu: "500m"        livenessProbe:          httpGet:            path: /health            port: 8000          initialDelaySeconds: 30          periodSeconds: 10          failureThreshold: 3        readinessProbe:          httpGet:            path: /ready            port: 8000          initialDelaySeconds: 5          periodSeconds: 5        volumeMounts:        - name: config          mountPath: /etc/config          readOnly: true      volumes:      - name: config        configMap:          name: app-config

Key sections:

replicas: Number of pod copies to maintain (3 copies of the application)

strategy: How to update. Rolling updates gradually replace old pods with new ones (zero downtime).

selector: Which pods this deployment manages (pods with label app: web)

template: Template for creating pods. This is a full Pod spec.

containers: The container(s) in each pod. Most pods have one container; some have multiple (sidecars).

env: Environment variables passed to the container

resources: CPU and memory requests (for scheduling) and limits (hard cap)

livenessProbe: Is the container still running? If probe fails 3 times in a row, restart the container.

readinessProbe: Is the container ready for traffic? If not, remove it from the load balancer temporarily (for live updates).

volumeMounts and volumes: Storage and config injection

Pod Spec Deep Dive

The pod spec (the spec: section inside template:) is where most configuration happens.

Init Containers

Init containers run before the main container, used for setup.

spec:  initContainers:  - name: wait-for-db    image: busybox:1.35    command: ['sh', '-c', "until nc -z postgres:5432; do sleep 1; done"]  containers:  - name: app    image: webapp:latest

The init container checks if PostgreSQL is listening on port 5432. Only after it succeeds does the main app container start.

Security Context

Security context defines privilege and access control settings.

spec:  securityContext:    runAsNonRoot: true    runAsUser: 65532    fsGroup: 65532  containers:  - name: app    image: webapp:latest    securityContext:      allowPrivilegeEscalation: false      readOnlyRootFilesystem: true      capabilities:        drop:        - ALL        add:        - NET_BIND_SERVICE

The runAsNonRoot setting enforces that the container must run as a non-root user. The runAsUser setting specifies a particular UID to run as, with 65532 being the standard choice. The allowPrivilegeEscalation setting prevents sudo and similar privilege escalation techniques from working. The readOnlyRootFilesystem setting makes the root filesystem read-only, with only /tmp and /var/run remaining writable. The capabilities setting drops all Linux capabilities and allows you to add back only the specific ones your application needs.

Resources: Requests and Limits

Resources tell Kubernetes how to schedule pods and when to kill them.

spec:  containers:  - name: app    resources:      requests:        memory: "256Mi"        cpu: "100m"      limits:        memory: "512Mi"        cpu: "500m"

Requests: Minimum guaranteed resources. Kubernetes won't schedule the pod on a node unless that much is available. Used for scheduling decisions.

Limits: Maximum resources allowed. If the container exceeds limits, Kubernetes terminates it (OOMKilled for memory, throttled for CPU).

Units for memory are expressed in mebibytes (Mi) and gibibytes (Gi), while CPU is measured in millicores (m) where 1000m equals 1 full core.

Rule of thumb: Set requests = expected usage, limits = 2x requests (allow for spikes but kill if runaway).

Probes: Health Checks

Kubernetes uses probes to determine if containers are healthy.

Liveness probe: Is the container still running? If probe fails repeatedly, restart it.

livenessProbe:  httpGet:    path: /health    port: 8000  initialDelaySeconds: 30  # Wait 30s before first check  periodSeconds: 10        # Check every 10s  failureThreshold: 3      # Restart after 3 failures

Readiness probe: Is the container ready to serve traffic? If probe fails, remove it from the load balancer.

readinessProbe:  httpGet:    path: /ready    port: 8000  initialDelaySeconds: 5  periodSeconds: 5  failureThreshold: 1

Startup probe: Is the container fully started (for slow-starting apps)?

startupProbe:  httpGet:    path: /startup    port: 8000  failureThreshold: 30  periodSeconds: 10  # Total: 5 minutes to start

Probe types include httpGet which calls an HTTP endpoint and expects a response code between 200-399, exec which runs a command in the container and expects an exit code of 0, and tcpSocket which opens a TCP connection and expects success. All three types are valid, and you should use whichever fits your application best.

Labels, Selectors, and Annotations

Labels are key-value pairs attached to resources. They're used for organization and selection.

apiVersion: v1kind: Podmetadata:  name: web-pod  labels:    app: web        # Label 1    version: v2     # Label 2    tier: frontend  # Label 3    env: production # Label 4

Selectors match labels:

# Service selector: route traffic to pods with app=webspec:  selector:    app: web # Or more complex:selector:  matchLabels:    app: web    env: production  matchExpressions:  - key: version    operator: In    values: [v2, v3]  # Match v2 OR v3

Annotations are like labels but for humans and tools (not used by Kubernetes for selection).

metadata:  annotations:    description: "Production web server"    contact: "team@example.com"    image.repository: "gcr.io/myproject/webapp"    security.scan: "passed"

Namespace Isolation

Namespaces provide logical separation. Resources in different namespaces can have the same name.

apiVersion: v1kind: Namespacemetadata:  name: production---apiVersion: v1kind: Namespacemetadata:  name: staging

Deploy to a specific namespace:

kubectl apply -f deployment.yaml -n productionkubectl apply -f deployment.yaml -n staging

Pods in production can reach each other: curl http://service.production.svc.cluster.local

Pods in production can reach pods in staging: curl http://service.staging.svc.cluster.local (but only if network policies allow)

Namespaces are useful for multiple purposes including separating environments such as prod, staging, and dev, isolating different teams so they can work independently, setting resource quotas per namespace to control resource consumption, and applying policies like network policies and RBAC at the namespace level.

Applying Manifests: From YAML to Running Containers

How does a manifest become running containers?

kubectl apply -f deployment.yaml

Kubernetes processes:

  1. Parse YAML: Convert to objects
  2. Validate: Check syntax and required fields
  3. Compare: Current state vs desired state
  4. Create/Update: Make changes as needed
  5. Watch: Monitor and maintain desired state

kubectl apply is declarative: it reconciles the desired state (from the manifest) with the current state (in the cluster). If the manifest changes, running kubectl apply again updates the cluster to match.

kubectl create is imperative: it fails if the resource already exists.

Always use kubectl apply for production. It's idempotent: applying the same manifest 10 times is the same as applying it once.

Common Manifest Patterns

Environment-Specific Configurations

Different configs for dev, staging, prod:

A typical Kustomize directory structure organizes files into a base directory containing the core manifests (deployment.yaml, service.yaml, and configmap.yaml), with separate overlay directories for each environment. The dev overlay contains its own kustomization.yaml and configmap.yaml overrides, the staging overlay has similar environment-specific kustomization and config, and the prod overlay applies production-specific configurations. This structure allows the base manifests to remain unchanged while environment-specific variations are applied via the overlays.

Deploy to different environments:

kubectl apply -k manifests/overlays/devkubectl apply -k manifests/overlays/stagingkubectl apply -k manifests/overlays/prod

(This uses Kustomize, a tool for overlaying manifest changes.)

Helm Charts

Helm is a package manager for Kubernetes. Instead of writing raw YAML, you define values and Helm generates manifests.

# values.yamlreplicaCount: 3image:  repository: webapp  tag: v2.1.0resources:  limits:    memory: 512Mi    cpu: 500m
helm install my-release ./webapp-chart -f values.yaml

Helm generates manifests from templates and applies them.

DaemonSet (One Pod Per Node)

Run a pod on every node (logging, monitoring):

apiVersion: apps/v1kind: DaemonSetmetadata:  name: filebeatspec:  selector:    matchLabels:      app: filebeat  template:    metadata:      labels:        app: filebeat    spec:      containers:      - name: filebeat        image: filebeat:latest

StatefulSet (Stateful Applications)

For applications requiring stable identities and persistent storage (databases, message queues):

apiVersion: apps/v1kind: StatefulSetmetadata:  name: postgresspec:  serviceName: postgres  replicas: 1  selector:    matchLabels:      app: postgres  template:    metadata:      labels:        app: postgres    spec:      containers:      - name: postgres        image: postgres:15        volumeMounts:        - name: data          mountPath: /var/lib/postgresql  volumeClaimTemplates:  - metadata:      name: data    spec:      accessModes: [ReadWriteOnce]      resources:        requests:          storage: 10Gi

Pods are numbered (postgres-0, postgres-1, etc.) and each gets its own persistent volume.

Common Mistakes in Manifests

Mistake 1: Using latest tag.

# BADimage: webapp:latest  # Unpredictable, rebuilds pull different versions # GOODimage: webapp:v2.1.0  # Specific versionimage: webapp@sha256:abc123...  # Specific digest

Mistake 2: No resource limits.

# BADcontainers:- name: app  image: webapp:latest  # No limits; app can consume all cluster resources # GOODcontainers:- name: app  image: webapp:latest  resources:    requests:      memory: 256Mi      cpu: 100m    limits:      memory: 512Mi      cpu: 500m

Mistake 3: No probes.

# BADcontainers:- name: app  image: webapp:latest  # No health checks; Kubernetes doesn't know if app is healthy # GOODcontainers:- name: app  image: webapp:latest  livenessProbe:    httpGet:      path: /health      port: 8000    initialDelaySeconds: 30    periodSeconds: 10  readinessProbe:    httpGet:      path: /ready      port: 8000    initialDelaySeconds: 5    periodSeconds: 5

Mistake 4: Hardcoded configuration and secrets.

# BADenv:- name: DB_PASSWORD  value: "password123"  # Secrets in manifest! # GOODenv:- name: DB_PASSWORD  valueFrom:    secretKeyRef:      name: db-secret      key: password

Mistake 5: Not setting requests (Kubernetes can't schedule properly).

# BADcontainers:- name: app  image: webapp:latest  # No requests; scheduler doesn't know memory/CPU needs # GOODresources:  requests:    memory: 256Mi    cpu: 100m

Summary: Manifests Are Code

Treat Kubernetes manifests like code by storing them in Git, code reviewing all pull requests before merging, using templating tools like Kustomize or Helm to reduce duplication across manifests, testing in staging environments before promoting to production, monitoring and alerting on deployment failures, and maintaining rollback procedures for when issues arise.

Manifests are the declarative specification of your infrastructure. They're as important as application code and should be treated with equal rigor.

The Kubernetes API has 60+ resource types and hundreds of configuration options. This guide covers the most important. For complete reference, consult the Kubernetes API documentation at kubernetes.io/docs.