CKAD Certification Journey — Part 1: Application Design & Build

Achieving the Certified Kubernetes Application Developer (CKAD) certification has been a rewarding milestone in my journey as a developer. This blog series is structured into five parts, each covering a key domain of the CKAD curriculum:

  1. Application Design & Build (this article)
  2. Application Deployment
  3. Application Observability & Maintenance
  4. Application Environment, Configuration & Security
  5. Services & Networking

This first part focuses on the foundations of building applications for Kubernetes — understanding containers, how they are built and distributed, and how Kubernetes runs them efficiently.


🧱 Container Fundamentals

Docker Images — The Building Blocks

Every Kubernetes workload starts with a container image.

A Docker image is:

  • Immutable (does not change once built)
  • Layered (each instruction adds a layer)
  • Portable (can run anywhere Docker is supported)

👉 Think of it as a snapshot of your application + runtime + dependencies.


🐳 Building Custom Images

Creating your own image gives you full control over your application runtime.

Example Dockerfile:

FROM python:3.9-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install -r requirements.txt

COPY . .

ENTRYPOINT ["python"]
CMD ["app.py"]

🔍 Best Practices for Dockerfiles

  • Use small base images (e.g., alpine, slim)
  • Minimise layers (combine commands where possible)
  • Use .dockerignore to exclude unnecessary files
  • Avoid running containers as root
  • Pin dependency versions for reproducibility

⚙️ ENTRYPOINT vs CMD (Deep Dive)

These two instructions define how your container behaves at runtime.

ENTRYPOINT ["python"]
CMD ["app.py"]

How they work together:

  • ENTRYPOINT → defines the executable
  • CMD → provides default arguments

➡️ Default execution:

python app.py

➡️ Override CMD:

docker run my-image script.py

➡️ Result:

python script.py

💡 Important insight for CKAD: Use ENTRYPOINT when you want to enforce a specific executable, and CMD when you want flexibility.


📦 Docker Hub & Image Distribution

Once built, images need to be shared.

Typical workflow:

docker build -t username/my-app:v1 .
docker login
docker push username/my-app:v1

💡 Notes:

  • Use version tags (v1, v2) instead of latest
  • Private registries are common in production
  • Kubernetes pulls images using imagePullPolicy

☸️ Kubernetes Core Concepts

Kubernetes API — The Control Plane Brain

Everything in Kubernetes is an API object:

  • Pods
  • Deployments
  • Services
  • ConfigMaps

You define desired state in YAML → Kubernetes ensures it becomes reality.


🧩 Pods — The Smallest Unit

A Pod represents a running instance of your application.

Characteristics:

  • One or more containers
  • Shared network (same IP & port space)
  • Shared storage (volumes)

👉 In real-world usage:

You almost always run one container per Pod


⚠️ Single vs Multi-Container Pods

Single container per Pod

  • Easier scaling
  • Better isolation
  • Simpler debugging

❌ Multi-container drawbacks:

  • Cannot scale containers independently
  • Increased coupling

Multi-Container Patterns (When You REALLY Need Them)

1. Sidecar Pattern

Adds supporting functionality.

Example:

  • Logging agent
  • Monitoring exporter

2. Ambassador Pattern

Acts as a proxy.

Example:

  • Database proxy container

3. Adapter Pattern

Transforms output.

Example:

  • Convert app metrics into Prometheus format

4. Init Containers

Run before main container starts.

Example:

initContainers:
- name: init-db
  image: busybox
  command: ['sh', '-c', 'echo preparing environment']

💡 Key point: Init containers must complete successfully before the main container starts.


🛠️ kubectl — Your Main Tool

Generate YAML Automatically

Instead of writing YAML from scratch:

kubectl run redis-app --image=redis --dry-run=client -o yaml

This outputs a valid Pod manifest.


Why This Is Powerful

  • Saves time in exams (CKAD is time-constrained!)
  • Helps learn object structure
  • Reduces syntax errors

👉 You can also:

kubectl create deployment nginx --image=nginx --dry-run=client -o yaml

Essential Pod Commands

kubectl get pods
kubectl describe pod <name>
kubectl logs <pod>
kubectl exec -it <pod> -- /bin/sh
kubectl delete pod <name>

💡 CKAD tip: Memorise these — they are heavily used during the exam.


🔁 Jobs and CronJobs

Not all workloads should run forever.


🧩 Jobs — Run Once and Finish

A Job ensures a task completes successfully.

Example use cases:

  • Database migrations
  • Data processing
  • Backups

Example:

apiVersion: batch/v1
kind: Job
metadata:
  name: backup-job
spec:
  template:
    spec:
      containers:
      - name: backup
        image: busybox
        command: ["echo", "Running backup"]
      restartPolicy: Never

⏰ CronJobs — Scheduled Execution

A CronJob runs Jobs on a schedule.

apiVersion: batch/v1
kind: CronJob
metadata:
  name: backup-cron
spec:
  schedule: "0 2 * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: backup
            image: busybox
            command: ["echo", "Daily backup"]
          restartPolicy: Never

🔍 Job vs CronJob (Clear Distinction)

FeatureJobCronJob
ExecutionOnceRepeated
TriggerManualScheduled
Use caseMigrationDaily backups

💾 Storage & Volumes

Containers are ephemeral — data disappears when they stop.

👉 Volumes solve this.


Volume Types

emptyDir

  • Lives as long as the Pod exists
  • Good for temporary data (cache, scratch space)

hostPath

  • Uses node filesystem
  • Not portable (avoid in production)

📊 Data Classification

Understanding your data is critical:

TypeExampleSolution
TemporaryCacheemptyDir
PersistentDatabasePersistent Volume
SharedLogsNetwork storage
ConfigEnv variablesConfigMaps
SensitivePasswordsSecrets

🔗 Sharing Volumes Between Containers in the Same Pod

One of the most important Kubernetes concepts — and highly relevant for the CKAD exam — is that containers within the same Pod can share storage using volumes.

This is a key enabler for multi-container design patterns.


🧠 Core Idea

A volume is defined at the Pod level, which means:

👉 Any container inside that Pod can mount and access the same volume.

This allows containers to communicate and share data through the filesystem, without needing networking.


📦 Example: Shared Volume Between Containers

apiVersion: v1
kind: Pod
metadata:
  name: shared-volume-example
spec:
  containers:
  - name: writer
    image: busybox
    command: ["sh", "-c", "echo Hello from writer > /data/message.txt && sleep 3600"]
    volumeMounts:
    - name: shared-data
      mountPath: /data

  - name: reader
    image: busybox
    command: ["sh", "-c", "sleep 3600"]
    volumeMounts:
    - name: shared-data
      mountPath: /data

  volumes:
  - name: shared-data
    emptyDir: {}

🔍 What Happens Here?

  • A volume (emptyDir) is created when the Pod starts
  • The writer container writes a file into /data
  • The reader container mounts the same volume and can read that file

👉 Both containers see the same filesystem content


📌 Why This Is Important

This pattern is widely used in real-world Kubernetes architectures:

1. Sidecar Pattern

  • Main container writes logs to a shared volume
  • Sidecar container reads and ships logs (e.g., to Elasticsearch)

2. Data Processing Pipelines

  • One container produces data
  • Another consumes and processes it

3. Init Containers

  • Init container prepares files/configuration
  • Main container uses them after startup

⚠️ Important Notes

  • Volumes are ephemeral if using emptyDir

    • Data is lost when the Pod is deleted
  • All containers must reference the same volume name

  • Mount paths can be different across containers

Example:

volumeMounts:
- name: shared-data
  mountPath: /app/data   # container A

- name: shared-data
  mountPath: /var/data   # container B

👉 Different paths, same underlying data


🧠 Mental Model

Think of it like:

  • Volume → shared disk
  • Containers → different users accessing that disk
  • mountPath → where each user sees the disk

🚀 CKAD Exam Tip

If you see a question involving:

  • multiple containers in a Pod
  • file sharing between them

👉 The answer is almost always: Use a shared volume (usually emptyDir)


✅ Key Takeaway

Containers in the same Pod can share data efficiently by mounting the same volume, enabling powerful patterns like sidecars, init containers, and inter-container communication without networking.

This is one of the most fundamental building blocks of Kubernetes application design.


🔐 ConfigMaps & Secrets

ConfigMaps

Store non-sensitive config:

env:
- name: APP_MODE
  valueFrom:
    configMapKeyRef:
      name: app-config
      key: mode

Secrets

Store sensitive data (base64 encoded):

env:
- name: DB_PASSWORD
  valueFrom:
    secretKeyRef:
      name: db-secret
      key: password

💡 Important:

  • Secrets are not encrypted by default
  • Use RBAC + encryption at rest in production

🧠 Key Takeaways

  • Docker images are the foundation of Kubernetes workloads
  • A well-structured Dockerfile makes everything easier downstream
  • Pods should be simple and single-purpose
  • Multi-container Pods are advanced — use only when necessary
  • kubectl is essential — speed matters in CKAD
  • Jobs and CronJobs handle non-continuous workloads
  • Understanding data types helps you choose the right storage strategy

🚀 What’s Next?

In Part 2, we’ll dive into:

👉 Application Deployment

  • Deployments
  • ReplicaSets
  • Rolling updates
  • Scaling strategies

This is where Kubernetes truly starts to shine.

Stay tuned!