CertGrid CertGrid
Hands-on Lab·Certified Kubernetes Application Developer

Generating YAML with --dry-run=client

Nobody passes CKAD by typing `apiVersion: apps/v1` from memory sixteen times. Every object on the exam has an imperative generator that will print a correct skeleton, and the skill being tested is editing that skeleton, not recalling indentation. This is the generator for each kind you will be asked for, including the two that need the object to exist first.

Working at Exam Speed Guide 3 of 44 Beginner

Written against the versions above. `--dry-run=client` renders locally and never contacts the API server for validation, which is why it is instant and why it will happily generate a manifest that the server would reject. `--dry-run=server` validates properly and is worth using when a manifest is refused and you cannot see why.

Runs on the single-node cka4001 rather than the shared cluster: this guide creates and deletes objects quickly and there is no reason to do that anywhere shared.
Server NameIP AddressOSRolesCPURAMHDD
CKA4001192.168.0.191Ubuntu 26.04 LTSSingle Node (control plane, untainted)2 Core4 GB50 GB

Before you start

  1. Pod and Deployment, the two you will type most

    kubectl run generates a Pod; kubectl create deployment generates a Deployment. Both with --dry-run=client -o yaml:

    apiVersion: v1
    kind: Pod
    metadata:
      name: web
    spec:
      containers:
      - image: nginx:alpine
        name: web

    That is the shape you edit. Add a volume, add an env var, add a probe - but never type the four lines above.

    The habit worth building now: always redirect to a file. kubectl run web --image=nginx:alpine --dry-run=client -o yaml > pod.yaml, edit, kubectl apply -f pod.yaml. Editing a file is recoverable; getting half-way through a heredoc is not.

    bash Example session
    kubectl create namespace ckad-gennamespace/ckad-gen createdkubectl -n ckad-gen run web --image=nginx:alpine --dry-run=client -o yamlapiVersion: v1kind: Podmetadata:  labels:    run: web  name: web  namespace: ckad-genspec:  containers:  - image: nginx:alpine    name: web    resources: {}  dnsPolicy: ClusterFirst  restartPolicy: Alwaysstatus: {}kubectl -n ckad-gen create deployment api --image=nginx:alpine --replicas=3 --dry-run=client -o yaml | head -20apiVersion: apps/v1kind: Deploymentmetadata:  labels:    app: api  name: api  namespace: ckad-genspec:  replicas: 3  selector:    matchLabels:      app: api  strategy: {}  template:    metadata:      labels:        app: api    spec:      containers:      - image: nginx:alpine

    Expected resultTwo valid manifests, printed instantly.

    Success conditionYou have the two skeletons the majority of tasks start from.

  2. Job, CronJob, ConfigMap, Secret

    These four are where hand-typing costs the most, because each has a nesting level people get wrong under pressure - a Job's spec.template.spec, a CronJob's spec.jobTemplate.spec.template.spec.

    Note how a command is passed: everything after -- becomes the container's args. And note the Secret generator does the base64 for you:

    data:
      token: czNjcjN0

    Four generators, four correct manifests, no nesting recalled from memory. The CronJob one in particular is worth practising until it is reflex - spec.jobTemplate.spec.template.spec.containers is four levels deep and it is where hand-written CronJobs fail.

    bash Example session
    kubectl -n ckad-gen create job pi --image=perl:5.34 --dry-run=client -o yaml -- perl -Mbignum=bpi -wle 'print bpi(200)'apiVersion: batch/v1kind: Jobmetadata:  name: pi  namespace: ckad-genspec:  template:    metadata: {}    spec:      containers:      - command:        - perl        - -Mbignum=bpi        - -wle        - print bpi(200)        image: perl:5.34        name: pi        resources: {}      restartPolicy: Neverstatus: {}kubectl -n ckad-gen create cronjob tick --image=busybox:1.36 --schedule='*/1 * * * *' --dry-run=client -o yaml -- /bin/sh -c dateapiVersion: batch/v1kind: CronJobmetadata:  name: tick  namespace: ckad-genspec:  jobTemplate:    metadata:      name: tick    spec:      template:        metadata: {}        spec:          containers:          - command:            - /bin/sh            - -c            - date            image: busybox:1.36            name: tick            resources: {}          restartPolicy: OnFailure  schedule: '*/1 * * * *'status: {}kubectl -n ckad-gen create configmap app --from-literal=MODE=prod --from-literal=TIMEOUT=30 --dry-run=client -o yamlapiVersion: v1data:  MODE: prod  TIMEOUT: "30"kind: ConfigMapmetadata:  name: app  namespace: ckad-genkubectl -n ckad-gen create secret generic api-key --from-literal=token=s3cr3t --dry-run=client -o yamlapiVersion: v1data:  token: czNjcjN0kind: Secretmetadata:  name: api-key  namespace: ckad-gen

    Expected resultFour manifests, with the Secret already base64-encoded.

    Success conditionYou can produce the four most awkward kinds without recalling their nesting.

  3. The two that need the object to exist first

    kubectl expose and kubectl create ingress both reference something. Create the Deployment first, then generate against it:

    spec:
      ports:
      - port: 80
        protocol: TCP
        targetPort: 80
      selector:
        app: api

    The selector was filled in for you from the Deployment's labels, which is the single most common thing to get wrong when writing a Service by hand - see the networking track for what a mismatched selector looks like.

    The Ingress generator takes a compact rule string:

    --rule='shop.example.com/*=api:80'

    and expands it into the nested rules[].http.paths[].backend.service structure, with pathType: Prefix filled in. That structure is genuinely hard to type correctly and you should never try.

    bash Example session
    kubectl -n ckad-gen create deployment api --image=nginx:alpine --replicas=3 --dry-run=client -o yaml | head -20apiVersion: apps/v1kind: Deploymentmetadata:  labels:    app: api  name: api  namespace: ckad-genspec:  replicas: 3  selector:    matchLabels:      app: api  strategy: {}  template:    metadata:      labels:        app: api    spec:      containers:      - image: nginx:alpinekubectl -n ckad-gen expose deployment api --port=80 --target-port=80 --dry-run=client -o yamlapiVersion: v1kind: Servicemetadata:  labels:    app: api  name: api  namespace: ckad-genspec:  ports:  - port: 80    protocol: TCP    targetPort: 80  selector:    app: apistatus:  loadBalancer: {}kubectl -n ckad-gen create ingress site --rule='shop.example.com/*=api:80' --dry-run=client -o yamlapiVersion: networking.k8s.io/v1kind: Ingressmetadata:  name: site  namespace: ckad-genspec:  rules:  - host: shop.example.com    http:      paths:      - backend:          service:            name: api            port:              number: 80        path: /        pathType: Prefixstatus:  loadBalancer: {}

    Expected resultA Service with the selector filled in, and a fully expanded Ingress.

    Success conditionYou never hand-write a Service selector or an Ingress path again.

  4. What this is actually worth

    kubectl ... --dry-run=client -o yaml >   0.02s user 0.01s system 35% cpu 0.088 total

    Eighty-eight milliseconds for fifteen lines of correct YAML. Typing those fifteen lines takes a competent person somewhere around a minute, and roughly one time in four they will produce something that does not parse.

    Strip the boilerplate the generator adds - creationTimestamp, status, empty resources - and eleven lines remain. Those eleven lines are what you would have typed anyway.

    Across sixteen tasks, this is the difference between finishing and not. It is the highest-value habit on this exam and it takes an afternoon to build.

    bash Example session
    time kubectl -n ckad-gen run t1 --image=nginx:alpine --dry-run=client -o yaml > /tmp/t1.yamlkubectl -n ckad-gen run t1 --image=nginx:alpine --dry-run=client -o yaml >   0.02s user 0.01s system 35% cpu 0.088 totalwc -l /tmp/t1.yaml15 /tmp/t1.yamlkubectl -n ckad-gen run t2 --image=nginx:alpine --dry-run=client -o yaml | grep -vE 'creationTimestamp|status|resources|dnsPolicy|restartPolicy|terminationGracePeriod|schedulerName|securityContext|serviceAccount' | wc -l11kubectl delete namespace ckad-gen --wait=falsenamespace "ckad-gen" deleted

    Expected resultSub-100ms generation, 15 lines down to 11 of substance.

    Success conditionYou have a measured reason to stop typing YAML.

Troubleshooting

Official sources