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:
- Application Design & Build (this article)
- Application Deployment
- Application Observability & Maintenance
- Application Environment, Configuration & Security
- 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
.dockerignoreto 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 executableCMD→ 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 oflatest - 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
✅ Recommended:
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)
| Feature | Job | CronJob |
|---|---|---|
| Execution | Once | Repeated |
| Trigger | Manual | Scheduled |
| Use case | Migration | Daily 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:
| Type | Example | Solution |
|---|---|---|
| Temporary | Cache | emptyDir |
| Persistent | Database | Persistent Volume |
| Shared | Logs | Network storage |
| Config | Env variables | ConfigMaps |
| Sensitive | Passwords | Secrets |
🔗 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
kubectlis 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!