Skip to main content

YAML manifests and kubectl: the declarative workflow

Summary: in Kubernetes, you never say what to do — you describe the desired state in a YAML file, and Kubernetes continuously converges towards that state. This lesson explains the convergent declarative model, the essential kubectl commands, the concept of idempotence, and how Git + kubectl naturally become a GitOps practice.


1. Imperative vs declarative — the paradigm shift

Kubernetes imposes a mental revolution on many developers coming from the bash script or Docker world.

In one sentence: imperative = "do this now". Declarative = "always maintain this".


2. YAML — the language of Kubernetes

Kubernetes uses YAML for all its manifests. Here are the 5 rules to absolutely remember to avoid the traps.

The #1 beginner mistake: mixing tabs and spaces. Configure your editor (VS Code, Vim, IntelliJ) to always convert Tab to 2 spaces on .yaml files.


3. Anatomy of a Kubernetes manifest

All manifests share the same basic structure.

apiVersion: apps/v1        # Kubernetes API version
kind: Deployment # Object type
metadata: # Object metadata
name: my-app
namespace: production
labels:
app: my-app
tier: backend
spec: # Specification · the desired state
replicas: 3
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: my-app
image: my-app:v2.5

The 4 universal sections:

SectionRole
apiVersionWhich Kubernetes API is targeted (v1, apps/v1, batch/v1, etc.)
kindThe object type (Pod, Deployment, Service, etc.)
metadataName, namespace, labels, annotations
specThe description of the desired state (varies by kind)

Productivity tip: kubectl explain deployment.spec lists all the available fields with their meaning.


4. A complete concrete example — a web application in Kubernetes

Here is a complete, real manifest that deploys a web application with a PostgreSQL database.

# ----------- 1. Namespace -----------
apiVersion: v1
kind: Namespace
metadata:
name: production

---
# ----------- 2. ConfigMap · non-sensitive configuration -----------
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
namespace: production
data:
DATABASE_HOST: "postgres-svc"
DATABASE_PORT: "5432"
LOG_LEVEL: "info"

---
# ----------- 3. Secret · database password -----------
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
namespace: production
type: Opaque
stringData:
DATABASE_USER: "app_user"
DATABASE_PASSWORD: "super-secret-generated"

---
# ----------- 4. Deployment · the web application -----------
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: my-app
image: my-registry/my-app:v2.5
ports:
- containerPort: 8080
envFrom:
- configMapRef:
name: app-config
- secretRef:
name: db-credentials
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5

---
# ----------- 5. Service · stable access -----------
apiVersion: v1
kind: Service
metadata:
name: my-app-svc
namespace: production
spec:
type: ClusterIP
selector:
app: my-app
ports:
- port: 80
targetPort: 8080

---
# ----------- 6. Ingress · public exposure -----------
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app-ingress
namespace: production
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
ingressClassName: nginx
tls:
- hosts:
- api.my-site.com
secretName: my-site-tls
rules:
- host: api.my-site.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-app-svc
port:
number: 80

What this manifest does in one kubectl apply command:

  1. Creates the production namespace.
  2. Injects the non-sensitive config via a ConfigMap.
  3. Injects the DB password via a Secret (encrypted at rest).
  4. Launches 3 replicas of the app with health probes.
  5. Creates an internal Service to balance the traffic.
  6. Exposes the app publicly over HTTPS via Ingress + Let's Encrypt.

Result: your application is in production, exposed on api.my-site.com, over HTTPS, with auto-renewed certificates, in less than 60 seconds.

This same file can be applied on any Kubernetes cluster (EKS, GKE, AKS, on-premise) without modification. That is Kubernetes portability.


5. kubectl — the command-line tool

kubectl is the command every Kubernetes engineer types dozens of times a day. It communicates with the API Server.

5.1 · The 15 commands that cover 90% of cases

Tips that double your kubectl productivity:

  • Alias: alias k=kubectl — you type k get pods instead of kubectl get pods.
  • Autocompletion: source <(kubectl completion bash) — Tab completes everything.
  • Context: kubectl config use-context prod — switch between clusters.
  • Default namespace: kubectl config set-context --current --namespace=production — avoids typing -n production everywhere.

5.2 · kubectl vs direct curl — what does kubectl really do?

kubectl is an HTTP client that talks to the API Server over HTTPS with authentication.

You can do the same thing with curl (useful for advanced scripting), but kubectl makes your life much easier.


6. Idempotence — the declarative superpower

Idempotence means applying the same command N times gives the same result. Kubernetes is fully idempotent by design.

This property is revolutionary — you can automate your deployments without fear. A CI/CD pipeline that applies the same manifest 100 times a day does nothing dangerous — Kubernetes converges intelligently.


7. GitOps — the logical consequence

Logical question: if your YAML manifests are the source of truth of your infrastructure, where to store them?

Obvious answer: in Git. This is what is called GitOps.

GitOps advantages:

  • Complete history — every deployment is a traced Git commit.
  • Trivial rollbackgit revert of the faulty commit.
  • Audit — who deployed what, when, why?
  • Multi-cluster — a single Git repo can drive 5 clusters (dev, staging, prod-EU, prod-US, prod-DR).
  • No direct access to the cluster for developers — everything goes through PRs.

Popular GitOps tools in 2026: Argo CD (the most used), Flux CD (CNCF graduated), Fleet (Rancher).


8. Helm — Kubernetes packages

Writing all these YAML files by hand quickly becomes painful for complex applications. Helm is Kubernetes' package manager — like apt for Ubuntu or npm for Node.js.

Typical use case: instead of writing 50 YAML manifests yourself to deploy Prometheus + Grafana + Alertmanager, you do:

helm install monitoring prometheus-community/kube-prometheus-stack

And the entire monitoring stack is deployed, correctly configured, with the best practices.


Remember in 30 seconds

  • Kubernetes uses the convergent declarative model — you describe, K8s executes.
  • YAML is the language — beware of indentation with spaces, not tabs.
  • A manifest has 4 sections: apiVersion, kind, metadata, spec.
  • kubectl is the HTTP client that talks to the API Server.
  • kubectl apply is idempotent — applying 10 times gives the same result.
  • GitOps = Kubernetes + Git + Argo CD / Flux — the source of truth is Git.
  • Helm = the Kubernetes package manager (charts, values, install/upgrade).

Next: Ecosystem and alternatives: EKS, GKE, AKS, K8s vs Docker Swarm vs Nomad →