Introduction: The morning I forgot to back‑up the database
A few years ago, around 7 a.m., I spilled coffee on my keyboard while frantically trying to kick‑off a manual database dump before the workday stampede. I succeeded—but only after the caffeine trickled into the space bar. That was the day I muttered: “Never again. Let the robots remember the schedule.”

If you’ve ever shared that panic, Kubernetes CronJobs are your new best friend. In essence, a CronJob is the Kubernetes‑native way to run a Job—a one‑off Pod that exits when work is finished—on a recurring schedule you define with familiar cron syntax. CronJobs graduated to the stable batch/v1 API back in v1.21 and have been quietly gaining creature comforts (like native time‑zone support) ever since.
Why CronJobs matter (even if you already know crontab)
Traditional crontab entries live on a single node. If that box restarts—or you rebuild your infrastructure—your tasks vanish. CronJobs, by contrast, live in etcd along with the rest of your cluster state. They survive node failures, cluster upgrades, and even a coffee tsunami on your laptop. Kubernetes handles when to start each Job, tracks history, enforces concurrency rules, and lets you scale the underlying Pods like any other workload.
Core concepts in plain English
| Setting | What it does | Real‑world analogy |
|---|---|---|
schedule |
Five‑field cron expression that tells Kubernetes when to run. | Setting an alarm on your phone. |
concurrencyPolicy |
Allow, Forbid, or Replace—Controls what happens if a new run collides with one already in progress. |
Do you brew a second pot of coffee if the first pot isn’t finished? |
startingDeadlineSeconds |
Time window to catch up if the cluster misses a start time. | Snooze window after a missed alarm. |
successfulJobsHistoryLimit / failedJobsHistoryLimit |
How many past runs Kubernetes keeps. | Cleaning out yesterday’s to‑do list. |
suspend |
Pauses future runs without deleting the CronJob. | Sliding the alarm toggle off while on vacation. |
timeZone (v1.27+) |
Let's you pin the schedule to a specific zone. | Ensuring a meeting invite shows up at 10 a.m. your time, not UTC. |
(We’ll peek at each of these in action in a minute.)
A quick YAML tour
Below is a trimmed example I use in workshops to prune old log files. Copy‑paste it into a file named log‑cleanup.yaml, tweak the image, and apply with kubectl apply -f log‑cleanup.yaml.
apiVersion: batch/v1
kind: CronJob
metadata:
name: log-cleanup
spec:
schedule: "0 2 * * *" # 2 a.m. every day
timeZone: "Asia/Kolkata" # new in v1.27+
startingDeadlineSeconds: 300 # give it 5 min if it slips
concurrencyPolicy: Forbid # skip if yesterday is still running
successfulJobsHistoryLimit: 5
failedJobsHistoryLimit: 10
jobTemplate:
spec:
template:
spec:
restartPolicy: Never
containers:
- name: cleanup
image: alpine:3.20
command: ["sh","-c","find /var/log -type f -mtime +7 -delete"]
Notice how the Time‑Zone line eliminates mental gymnastics when daylight‑saving rules change.
How the controller thinks
- Schedules are calculated in advance, and CronJobs don’t poll every second; they pre‑compute the next run.
- At tick‑time, a Job is created; the Job controller then spins up Pods.
- If a prior run is still active,
concurrencyPolicydecides the outcome:Allow:start anyway (default).Forbid:Skip this run.Replace:Terminate the old Job and launch a fresh one.
- Missed windows—If the cluster hiccups and your Job starts late, Kubernetes checks
startingDeadlineSeconds. Exceeded? The run is abandoned and recorded as “missed.” - Garbage collection—Once the run ends, Kubernetes prunes old Job objects based on your history limits, keeping your API clean.

Join the conversation! Your thoughts help the community grow.