Kubernetes Jobs and CronJobs Explained with Examples: Complete Hands-on Guide for DevOps Engineer
Keywords
Kubernetes Jobs
Kubernetes CronJobs
Kubernetes Job example
Kubernetes CronJob example
Kubernetes batch workloads
kubectl create job
kubectl create cronjob
Kubernetes scheduled jobs
Kubernetes background tasks
Kubernetes batch processing
1. Introduction
In Kubernetes, not every workload is a long-running application like a web server, API service, or microservice. Many real-world tasks are short-lived. They start, perform some work, complete, and then stop.
Examples include:
Database backup
Report generation
Data cleanup
File processing
Batch import/export
Email notification batch
Log archival
Certificate renewal
One-time migration
Scheduled health check
Machine learning batch job
Code language: JavaScript (javascript)
For these use cases, Kubernetes provides two important workload resources:
Job
CronJob
A Job runs one or more Pods until the task completes successfully. A CronJob creates Jobs repeatedly according to a time-based schedule. Kubernetes official documentation defines Jobs as one-off tasks that run to completion, while CronJobs create Jobs on a repeating schedule.
As of the current Kubernetes documentation baseline, Kubernetes v1.36.2 is the latest stable patch release in the Kubernetes 1.36 line, released on June 9, 2026. The examples in this tutorial use the stable batch/v1 APIs for Jobs and CronJobs.
2. Job vs CronJob — Quick Comparison
| Feature | Job | CronJob |
|---|---|---|
| Purpose | Run a task once until completion | Run tasks repeatedly on a schedule |
| API version | batch/v1 | batch/v1 |
| Creates Pods directly? | Yes, Job controller creates Pods | No, CronJob creates Jobs, then Jobs create Pods |
| Common use | One-time migration, batch processing, data import | Backups, reports, cleanup, scheduled sync |
| Scheduling | Runs immediately after creation unless suspended | Runs based on cron schedule |
| Retry support | Yes, using backoffLimit | Yes, through Job template |
| Parallel execution | Yes, using parallelism and completions | Yes, inside jobTemplate |
| Automatic cleanup | Yes, using ttlSecondsAfterFinished | Uses Job history limits and/or Job TTL |
| Time zone support | Not applicable | Yes, using .spec.timeZone |
| Concurrency control | Controlled by Job parallelism | Controlled by concurrencyPolicy |
| Best for | One-time run-to-completion workloads | Scheduled run-to-completion workloads |
Simple rule:
Use Job when the task must run once.
Use CronJob when the task must run repeatedly on a schedule.
Code language: PHP (php)
3. How Kubernetes Job Works
A Kubernetes Job creates one or more Pods and tracks successful completions. The Job is considered complete when the required number of Pods finish successfully. For a simple non-parallel Job, Kubernetes usually starts one Pod, and the Job completes when that Pod exits successfully.
Basic flow:
User creates Job
|
v
Job controller creates Pod
|
v
Container runs command
|
v
Command exits with code 0
|
v
Pod becomes Completed
|
v
Job becomes Complete
Code language: JavaScript (javascript)
If the container exits with a non-zero code, Kubernetes can retry according to the Job configuration. By default, the Job uses .spec.backoffLimit, which defaults to 6, unless per-index backoff is configured for Indexed Jobs.
4. How Kubernetes CronJob Works
A CronJob is like a Linux crontab entry inside Kubernetes. It does not run the container directly. Instead, it creates a Job at the scheduled time, and that Job creates the Pod. Kubernetes documentation defines CronJob as a resource that starts one-time Jobs on a repeating schedule.
Basic flow:
User creates CronJob
|
v
CronJob controller watches schedule
|
v
At scheduled time, CronJob creates Job
|
v
Job creates Pod
|
v
Container runs command
|
v
Pod completes
|
v
Job completes
Example:
CronJob schedule: every day at 2 AM
|
v
Creates a new Job daily at 2 AM
|
v
Each Job runs backup script
Code language: JavaScript (javascript)
5. Prerequisites
You need access to a Kubernetes cluster.
Check client and server version:
kubectl version
For OpenShift:
oc version
Create a namespace for practice:
kubectl create namespace jobs-demo
kubectl config set-context --current --namespace=jobs-demo
Code language: JavaScript (javascript)
For OpenShift:
oc new-project jobs-demo
Code language: JavaScript (javascript)
Verify:
kubectl get ns jobs-demo
Code language: JavaScript (javascript)
Or:
oc project
6. Basic Kubernetes Job Example
Let us create a simple Job that prints a message and exits.
Create file:
cat > job-hello.yaml <<'EOF'
apiVersion: batch/v1
kind: Job
metadata:
name: hello-job
spec:
template:
spec:
restartPolicy: Never
containers:
- name: hello
image: busybox:1.36
command:
- /bin/sh
- -c
- |
echo "Hello from Kubernetes Job"
echo "Current date is:"
date
echo "Job completed successfully"
EOF
Code language: PHP (php)
Apply:
kubectl apply -f job-hello.yaml
Code language: CSS (css)
For OpenShift:
oc apply -f job-hello.yaml
Code language: CSS (css)
Check Job:
kubectl get jobs
Code language: JavaScript (javascript)
Expected:
NAME STATUS COMPLETIONS DURATION AGE
hello-job Complete 1/1 5s 10s
Check Pods:
kubectl get pods
Code language: JavaScript (javascript)
Expected:
NAME READY STATUS RESTARTS AGE
hello-job-xxxxx 0/1 Completed 0 20s
View logs:
kubectl logs job/hello-job
Expected output:
Hello from Kubernetes Job
Current date is:
Sat Jul 4 10:00:00 UTC 2026
Job completed successfully
Code language: CSS (css)
The Pod remains after completion so that you can inspect status and logs. Kubernetes documentation notes that completed Job Pods are usually not deleted immediately, allowing you to inspect logs and diagnostics.
7. Important Job Fields
| Field | Meaning |
|---|---|
template | Pod template used by the Job |
restartPolicy | Must be Never or OnFailure |
backoffLimit | Number of retries before Job is marked failed |
activeDeadlineSeconds | Maximum time allowed for the whole Job |
completions | Number of successful Pods required |
parallelism | Maximum Pods running at the same time |
completionMode | NonIndexed or Indexed |
ttlSecondsAfterFinished | Auto-cleanup time after Job finishes |
suspend | Pause Job execution |
podFailurePolicy | Advanced failure handling |
successPolicy | Advanced success handling for Indexed Jobs |
The Kubernetes Job API states that restartPolicy inside the Job Pod template can only be Never or OnFailure. It also defines important fields such as activeDeadlineSeconds, backoffLimit, completionMode, parallelism, suspend, and ttlSecondsAfterFinished.
8. Job Restart Policy: Never vs OnFailure
A Job Pod can use:
restartPolicy: Never
restartPolicy: OnFailure
Code language: HTTP (http)
restartPolicy: Never
If the container fails, the Pod is marked failed and the Job controller creates a new Pod.
restartPolicy: OnFailure
If the container fails, Kubernetes restarts the container inside the same Pod.
Kubernetes documentation explains that when a container fails and restartPolicy is OnFailure, the Pod stays on the node and the container is rerun. If the Pod fails entirely, the Job controller starts a new Pod.
For clean learning, use:
restartPolicy: Never
Code language: HTTP (http)
For production, choose based on how your application handles retries.
9. Failed Job Example
Create a Job that always fails.
cat > job-fail.yaml <<'EOF'
apiVersion: batch/v1
kind: Job
metadata:
name: fail-job
spec:
backoffLimit: 3
template:
spec:
restartPolicy: Never
containers:
- name: fail
image: busybox:1.36
command:
- /bin/sh
- -c
- |
echo "This job will fail"
exit 1
EOF
Code language: PHP (php)
Apply:
kubectl apply -f job-fail.yaml
Code language: CSS (css)
Watch:
kubectl get jobs,pods -w
Code language: JavaScript (javascript)
You will see multiple failed Pods because backoffLimit: 3 allows retries before the Job is marked failed.
Check Job details:
kubectl describe job fail-job
Check failed Pods:
kubectl get pods -l job-name=fail-job
Code language: JavaScript (javascript)
Check logs from one failed Pod:
kubectl logs -l job-name=fail-job
Expected:
This job will fail
Clean up:
kubectl delete job fail-job
Code language: JavaScript (javascript)
10. backoffLimit — Retry Control
backoffLimit controls how many failures are allowed before Kubernetes marks the Job as failed. The default is 6, unless backoffLimitPerIndex is configured for an Indexed Job.
Example:
spec:
backoffLimit: 3
Meaning:
Kubernetes retries failed Pods.
After retry limit is reached, Job becomes Failed.
Use low values when:
Failure means a real bug
You do not want wasteful retries
The task is expensive
Code language: JavaScript (javascript)
Use higher values when:
Failures may be temporary
Network dependencies are unstable
External API may recover
11. activeDeadlineSeconds — Maximum Runtime
activeDeadlineSeconds limits the total runtime of the Job. Once the Job exceeds this duration, Kubernetes terminates running Pods and marks the Job as failed with reason DeadlineExceeded.
Example:
cat > job-deadline.yaml <<'EOF'
apiVersion: batch/v1
kind: Job
metadata:
name: deadline-job
spec:
activeDeadlineSeconds: 20
template:
spec:
restartPolicy: Never
containers:
- name: slow-task
image: busybox:1.36
command:
- /bin/sh
- -c
- |
echo "Starting long task"
sleep 60
echo "This line may never run"
EOF
Code language: PHP (php)
Apply:
kubectl apply -f job-deadline.yaml
Code language: CSS (css)
Watch:
kubectl get job deadline-job -w
Code language: JavaScript (javascript)
Describe:
kubectl describe job deadline-job
Expected reason:
DeadlineExceeded
Clean up:
kubectl delete job deadline-job
Code language: JavaScript (javascript)
12. Auto-Cleanup Finished Jobs with TTL
By default, finished Jobs and their Pods may stay in the cluster. For production, you should clean them up.
Kubernetes provides ttlSecondsAfterFinished for automatic cleanup of completed or failed Jobs. The TTL-after-finished controller is stable and removes the Job and its dependent objects after the configured TTL.
Example:
cat > job-ttl.yaml <<'EOF'
apiVersion: batch/v1
kind: Job
metadata:
name: ttl-job
spec:
ttlSecondsAfterFinished: 60
template:
spec:
restartPolicy: Never
containers:
- name: task
image: busybox:1.36
command:
- /bin/sh
- -c
- |
echo "This Job will be cleaned up after 60 seconds"
date
EOF
Code language: JavaScript (javascript)
Apply:
kubectl apply -f job-ttl.yaml
Code language: CSS (css)
Check:
kubectl get jobs,pods
Code language: JavaScript (javascript)
After about 60 seconds from completion, Kubernetes becomes eligible to delete the Job and its Pods.
13. Parallel Jobs
Some tasks can run in parallel.
Example use cases:
Process 100 files
Send 10,000 emails
Run 50 test cases
Process data partitions
Run simulations
Kubernetes supports parallel Jobs using:
completions: 6
parallelism: 2
Code language: HTTP (http)
Meaning:
Need 6 successful Pods total.
Run maximum 2 Pods at a time.
Create:
cat > job-parallel.yaml <<'EOF'
apiVersion: batch/v1
kind: Job
metadata:
name: parallel-job
spec:
completions: 6
parallelism: 2
template:
spec:
restartPolicy: Never
containers:
- name: worker
image: busybox:1.36
command:
- /bin/sh
- -c
- |
echo "Worker started on $(hostname)"
sleep 10
echo "Worker completed on $(hostname)"
EOF
Code language: PHP (php)
Apply:
kubectl apply -f job-parallel.yaml
Code language: CSS (css)
Watch:
kubectl get pods -l job-name=parallel-job -w
Code language: JavaScript (javascript)
Check Job:
kubectl get job parallel-job
Code language: JavaScript (javascript)
Expected:
COMPLETIONS
6/6
Kubernetes documentation states that .spec.parallelism controls the requested number of Pods running at any instant, and .spec.completions controls how many successful Pods are required for fixed-completion Jobs.
14. Indexed Jobs
An Indexed Job gives each Pod a unique completion index.
This is useful when each Pod must process a specific shard or partition:
Pod index 0 processes file group 0
Pod index 1 processes file group 1
Pod index 2 processes file group 2
Kubernetes supports completionMode: Indexed. For Indexed Jobs, each Pod gets an index from 0 to .spec.completions - 1, and the Job is complete when one Pod succeeds for every index. The index is available through mechanisms including annotation, label, hostname, and the JOB_COMPLETION_INDEX environment variable.
Create:
cat > job-indexed.yaml <<'EOF'
apiVersion: batch/v1
kind: Job
metadata:
name: indexed-job
spec:
completions: 5
parallelism: 2
completionMode: Indexed
template:
spec:
restartPolicy: Never
containers:
- name: indexed-worker
image: busybox:1.36
command:
- /bin/sh
- -c
- |
echo "This is worker index: ${JOB_COMPLETION_INDEX}"
echo "Hostname: $(hostname)"
sleep 5
echo "Completed index: ${JOB_COMPLETION_INDEX}"
EOF
Code language: PHP (php)
Apply:
kubectl apply -f job-indexed.yaml
Code language: CSS (css)
Check Pods:
kubectl get pods -l job-name=indexed-job
Code language: JavaScript (javascript)
Check logs:
kubectl logs -l job-name=indexed-job --prefix=true
Code language: JavaScript (javascript)
Expected style:
[pod/indexed-job-0-xxxxx/indexed-worker] This is worker index: 0
[pod/indexed-job-1-yyyyy/indexed-worker] This is worker index: 1
[pod/indexed-job-2-zzzzz/indexed-worker] This is worker index: 2
Code language: CSS (css)
15. Per-Index Retry with backoffLimitPerIndex
For Indexed Jobs, Kubernetes supports backoffLimitPerIndex. This lets each index have its own retry budget. The Job API says this field can only be used when completionMode: Indexed and the Pod restart policy is Never.
Example:
cat > job-backoff-per-index.yaml <<'EOF'
apiVersion: batch/v1
kind: Job
metadata:
name: backoff-per-index-job
spec:
completions: 4
parallelism: 2
completionMode: Indexed
backoffLimitPerIndex: 1
maxFailedIndexes: 2
template:
spec:
restartPolicy: Never
containers:
- name: worker
image: busybox:1.36
command:
- /bin/sh
- -c
- |
echo "Running index: ${JOB_COMPLETION_INDEX}"
if [ "${JOB_COMPLETION_INDEX}" = "0" ]; then
echo "Index 0 fails intentionally"
exit 1
fi
echo "Index ${JOB_COMPLETION_INDEX} completed"
EOF
Code language: PHP (php)
Apply:
kubectl apply -f job-backoff-per-index.yaml
Code language: CSS (css)
Check:
kubectl get job backoff-per-index-job -o yaml
Code language: JavaScript (javascript)
Useful fields:
kubectl get job backoff-per-index-job \
-o jsonpath='{.status.completedIndexes}{"\n"}{.status.failedIndexes}{"\n"}'
Code language: PHP (php)
16. Suspend and Resume a Job
A Job can be created in suspended mode. When suspended, the Job controller does not create Pods. If a running Job is suspended, Kubernetes deletes active Pods associated with that Job. The API docs also state that suspending a Job resets the Job start time and effectively resets the activeDeadlineSeconds timer.
Create suspended Job:
cat > job-suspend.yaml <<'EOF'
apiVersion: batch/v1
kind: Job
metadata:
name: suspended-job
spec:
suspend: true
template:
spec:
restartPolicy: Never
containers:
- name: task
image: busybox:1.36
command:
- /bin/sh
- -c
- |
echo "This starts only after resume"
date
EOF
Code language: JavaScript (javascript)
Apply:
kubectl apply -f job-suspend.yaml
Code language: CSS (css)
Check:
kubectl get jobs,pods
Code language: JavaScript (javascript)
No Pod should be created yet.
Resume:
kubectl patch job suspended-job -p '{"spec":{"suspend":false}}'
Code language: JavaScript (javascript)
Watch:
kubectl get pods -l job-name=suspended-job -w
Code language: JavaScript (javascript)
17. Create a Job Quickly Using Command Line
You can create a Job without writing YAML.
kubectl create job quick-job \
--image=busybox:1.36 \
-- /bin/sh -c 'echo "Quick Job"; date'
Code language: JavaScript (javascript)
Check:
kubectl get jobs
kubectl logs job/quick-job
Code language: JavaScript (javascript)
For OpenShift:
oc create job quick-job \
--image=busybox:1.36 \
-- /bin/sh -c 'echo "Quick Job"; date'
Code language: JavaScript (javascript)
18. CronJob Schedule Syntax
CronJob uses standard cron-style syntax:
# ┌───────────── minute 0 - 59
# │ ┌───────────── hour 0 - 23
# │ │ ┌───────────── day 1 - 31
# │ │ │ ┌───────────── month 1 - 12
# │ │ │ │ ┌───────────── week 0 - 6, Sunday to Saturday
# │ │ │ │ │
# * * * * *
Code language: PHP (php)
Examples:
| Schedule | Meaning |
|---|---|
* * * * * | Every minute |
*/5 * * * * | Every 5 minutes |
0 * * * * | Every hour |
0 2 * * * | Every day at 2 AM |
0 3 * * 1 | Every Monday at 3 AM |
0 0 1 * * | First day of every month |
@hourly | Once per hour |
@daily | Once per day |
@weekly | Once per week |
@monthly | Once per month |
@yearly | Once per year |
Kubernetes CronJob requires .spec.schedule and supports standard cron syntax, step values such as */2, and macros such as @hourly, @daily, @weekly, and @monthly.
19. Basic CronJob Example
Create a CronJob that runs every minute.
cat > cronjob-hello.yaml <<'EOF'
apiVersion: batch/v1
kind: CronJob
metadata:
name: hello-cronjob
spec:
schedule: "* * * * *"
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: hello
image: busybox:1.36
command:
- /bin/sh
- -c
- |
echo "Hello from Kubernetes CronJob"
echo "Run time:"
date
EOF
Code language: PHP (php)
Apply:
kubectl apply -f cronjob-hello.yaml
Code language: CSS (css)
Check CronJob:
kubectl get cronjobs
Code language: JavaScript (javascript)
Expected:
NAME SCHEDULE SUSPEND ACTIVE LAST SCHEDULE AGE
hello-cronjob * * * * * False 0 <none> 10s
Code language: HTML, XML (xml)
Wait one minute, then check Jobs:
kubectl get jobs
Code language: JavaScript (javascript)
Expected:
NAME STATUS COMPLETIONS DURATION AGE
hello-cronjob-xxxxxxxx Complete 1/1 3s 30s
Check Pods:
kubectl get pods
Code language: JavaScript (javascript)
Check logs:
kubectl logs job/$(kubectl get jobs --sort-by=.metadata.creationTimestamp -o jsonpath='{.items[-1:].metadata.name}')
Code language: PHP (php)
For OpenShift:
oc logs job/$(oc get jobs --sort-by=.metadata.creationTimestamp -o jsonpath='{.items[-1:].metadata.name}')
Code language: PHP (php)
20. Important CronJob Fields
| Field | Meaning |
|---|---|
schedule | Required cron schedule |
jobTemplate | Required Job template |
timeZone | Time zone for schedule |
concurrencyPolicy | Allow, Forbid, or Replace overlapping Jobs |
startingDeadlineSeconds | Deadline for starting missed Jobs |
suspend | Pause future schedules |
successfulJobsHistoryLimit | Number of successful Jobs to keep |
failedJobsHistoryLimit | Number of failed Jobs to keep |
The CronJob API documents concurrencyPolicy, failedJobsHistoryLimit, jobTemplate, schedule, startingDeadlineSeconds, successfulJobsHistoryLimit, suspend, and timeZone. The defaults include concurrencyPolicy: Allow, successfulJobsHistoryLimit: 3, failedJobsHistoryLimit: 1, and suspend: false.
21. CronJob with Time Zone
Kubernetes CronJobs support .spec.timeZone, stable since Kubernetes v1.27. If no time zone is specified, the controller interprets schedules relative to the local time zone of kube-controller-manager. Kubernetes recommends using the timeZone field rather than putting TZ or CRON_TZ inside the schedule.
Example: run at 9 AM Tokyo time.
cat > cronjob-timezone.yaml <<'EOF'
apiVersion: batch/v1
kind: CronJob
metadata:
name: tokyo-cronjob
spec:
schedule: "0 9 * * *"
timeZone: "Asia/Tokyo"
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: tokyo-task
image: busybox:1.36
command:
- /bin/sh
- -c
- |
echo "This runs daily at 9 AM Asia/Tokyo"
date
EOF
Code language: JavaScript (javascript)
Apply:
kubectl apply -f cronjob-timezone.yaml
Code language: CSS (css)
Check:
kubectl get cronjob tokyo-cronjob -o yaml
Code language: JavaScript (javascript)
22. CronJob Concurrency Policy
Sometimes a scheduled task takes longer than the interval between runs.
Example:
CronJob runs every minute.
Each Job takes 5 minutes.
Should Kubernetes start overlapping Jobs?
This is controlled by:
concurrencyPolicy: Allow
Code language: HTTP (http)
Available values:
| Policy | Meaning |
|---|---|
Allow | Default. Allows overlapping Jobs |
Forbid | Skips new run if previous run is still active |
Replace | Cancels currently running Job and starts a new one |
Kubernetes documentation says Allow permits concurrent Jobs, Forbid skips a new run if the previous one is still running, and Replace cancels the current run and replaces it with a new run. This policy applies only to Jobs created by the same CronJob.
22.1 Example: concurrencyPolicy: Forbid
This CronJob runs every minute, but each Job sleeps for 90 seconds. With Forbid, Kubernetes will skip overlapping executions.
cat > cronjob-forbid.yaml <<'EOF'
apiVersion: batch/v1
kind: CronJob
metadata:
name: forbid-cronjob
spec:
schedule: "* * * * *"
concurrencyPolicy: Forbid
jobTemplate:
spec:
template:
spec:
restartPolicy: Never
containers:
- name: slow-task
image: busybox:1.36
command:
- /bin/sh
- -c
- |
echo "Started at:"
date
sleep 90
echo "Finished at:"
date
EOF
Code language: PHP (php)
Apply:
kubectl apply -f cronjob-forbid.yaml
Code language: CSS (css)
Watch:
kubectl get cronjob forbid-cronjob -w
Code language: JavaScript (javascript)
Check Jobs:
kubectl get jobs --sort-by=.metadata.creationTimestamp
Code language: JavaScript (javascript)
You should see fewer Jobs than one per minute because overlapping runs are forbidden.
22.2 Example: concurrencyPolicy: Replace
cat > cronjob-replace.yaml <<'EOF'
apiVersion: batch/v1
kind: CronJob
metadata:
name: replace-cronjob
spec:
schedule: "* * * * *"
concurrencyPolicy: Replace
jobTemplate:
spec:
template:
spec:
restartPolicy: Never
containers:
- name: replace-task
image: busybox:1.36
command:
- /bin/sh
- -c
- |
echo "Started at:"
date
sleep 120
echo "Finished at:"
date
EOF
Code language: PHP (php)
Apply:
kubectl apply -f cronjob-replace.yaml
Code language: CSS (css)
Watch Jobs:
kubectl get jobs,pods -w
Code language: JavaScript (javascript)
Every new scheduled run can replace the currently running one.
23. startingDeadlineSeconds
startingDeadlineSeconds tells Kubernetes how late a missed CronJob run may start. If the delay is greater than the configured deadline, Kubernetes skips that occurrence. Kubernetes treats missed Jobs beyond the deadline as failed Jobs.
Example:
startingDeadlineSeconds: 300
Code language: HTTP (http)
Meaning:
If the Job cannot start within 300 seconds of scheduled time,
skip that occurrence.
Example manifest:
cat > cronjob-deadline.yaml <<'EOF'
apiVersion: batch/v1
kind: CronJob
metadata:
name: deadline-cronjob
spec:
schedule: "*/5 * * * *"
startingDeadlineSeconds: 120
concurrencyPolicy: Forbid
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: deadline-task
image: busybox:1.36
command:
- /bin/sh
- -c
- |
echo "CronJob with startingDeadlineSeconds"
date
EOF
Code language: JavaScript (javascript)
Apply:
kubectl apply -f cronjob-deadline.yaml
Code language: CSS (css)
24. Suspend and Resume CronJob
You can pause future CronJob executions using:
suspend: true
Code language: JavaScript (javascript)
This does not stop already started Jobs. Kubernetes documentation states that suspend affects subsequent executions, not Jobs already started. It also warns that suspended executions count as missed Jobs, and when unsuspended without a starting deadline, missed Jobs may be scheduled immediately.
Suspend:
kubectl patch cronjob hello-cronjob -p '{"spec":{"suspend":true}}'
Code language: JavaScript (javascript)
Check:
kubectl get cronjob hello-cronjob
Code language: JavaScript (javascript)
Resume:
kubectl patch cronjob hello-cronjob -p '{"spec":{"suspend":false}}'
Code language: JavaScript (javascript)
Better production pattern:
startingDeadlineSeconds: 300
Code language: HTTP (http)
Use this with suspend/resume to avoid accidental burst execution after long suspension.
25. CronJob History Limits
CronJobs can keep old successful and failed Jobs.
Defaults:
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 1
Code language: HTTP (http)
Set to lower values to avoid clutter.
Example:
cat > cronjob-history.yaml <<'EOF'
apiVersion: batch/v1
kind: CronJob
metadata:
name: history-cronjob
spec:
schedule: "* * * * *"
successfulJobsHistoryLimit: 2
failedJobsHistoryLimit: 1
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: task
image: busybox:1.36
command:
- /bin/sh
- -c
- |
echo "CronJob history demo"
date
EOF
Code language: JavaScript (javascript)
Apply:
kubectl apply -f cronjob-history.yaml
Code language: CSS (css)
Kubernetes documentation says successfulJobsHistoryLimit controls how many successful Jobs are kept and defaults to 3; failedJobsHistoryLimit controls failed Jobs and defaults to 1. Setting either to 0 keeps none of that type.
26. Manually Trigger a CronJob Immediately
A CronJob runs on schedule, but sometimes you want to run it now.
Use:
kubectl create job manual-run-1 --from=cronjob/hello-cronjob
Code language: JavaScript (javascript)
Check:
kubectl get jobs
kubectl logs job/manual-run-1
Code language: JavaScript (javascript)
For OpenShift:
oc create job manual-run-1 --from=cronjob/hello-cronjob
oc logs job/manual-run-1
Code language: JavaScript (javascript)
This is very useful for testing backup CronJobs without waiting for the real schedule.
27. Real-World Use Case 1: Database Backup CronJob
This example uses busybox for demonstration. In production, use a database-specific image such as PostgreSQL, MySQL, MongoDB, or your internal backup image.
Create Secret:
kubectl create secret generic db-backup-secret \
--from-literal=DB_HOST='mysql.default.svc.cluster.local' \
--from-literal=DB_USER='backup_user' \
--from-literal=DB_PASSWORD='backup_password'
Code language: JavaScript (javascript)
Create CronJob:
cat > cronjob-db-backup.yaml <<'EOF'
apiVersion: batch/v1
kind: CronJob
metadata:
name: db-backup
spec:
schedule: "0 2 * * *"
timeZone: "Etc/UTC"
concurrencyPolicy: Forbid
startingDeadlineSeconds: 1800
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 3
jobTemplate:
spec:
backoffLimit: 2
ttlSecondsAfterFinished: 86400
template:
spec:
restartPolicy: Never
containers:
- name: backup
image: busybox:1.36
env:
- name: DB_HOST
valueFrom:
secretKeyRef:
name: db-backup-secret
key: DB_HOST
- name: DB_USER
valueFrom:
secretKeyRef:
name: db-backup-secret
key: DB_USER
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-backup-secret
key: DB_PASSWORD
command:
- /bin/sh
- -c
- |
echo "Starting DB backup"
echo "DB_HOST=${DB_HOST}"
echo "Backup user=${DB_USER}"
echo "Never print real password in production"
date
echo "Backup completed"
EOF
Code language: PHP (php)
Apply:
kubectl apply -f cronjob-db-backup.yaml
Code language: CSS (css)
Test now:
kubectl create job db-backup-manual --from=cronjob/db-backup
kubectl logs job/db-backup-manual
Code language: JavaScript (javascript)
Production notes:
Do not print secrets in logs.
Store backups in object storage.
Use Workload Identity or IAM roles where possible.
Use encryption for backups.
Use concurrencyPolicy: Forbid for backups.
Use startingDeadlineSeconds to prevent stale backups.
Use resource requests and limits.
Code language: PHP (php)
28. Real-World Use Case 2: Report Generation CronJob
cat > cronjob-report.yaml <<'EOF'
apiVersion: batch/v1
kind: CronJob
metadata:
name: daily-report
spec:
schedule: "30 6 * * *"
timeZone: "Asia/Tokyo"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 5
failedJobsHistoryLimit: 2
jobTemplate:
spec:
backoffLimit: 3
ttlSecondsAfterFinished: 172800
template:
spec:
restartPolicy: Never
containers:
- name: report
image: busybox:1.36
command:
- /bin/sh
- -c
- |
echo "Generating daily report"
date
sleep 10
echo "Report generated successfully"
EOF
Code language: PHP (php)
Apply:
kubectl apply -f cronjob-report.yaml
Code language: CSS (css)
Check:
kubectl get cronjob daily-report
kubectl describe cronjob daily-report
Code language: JavaScript (javascript)
Manual run:
kubectl create job daily-report-test --from=cronjob/daily-report
kubectl logs job/daily-report-test
Code language: JavaScript (javascript)
29. Real-World Use Case 3: Cleanup Job
Use a Job for one-time cleanup.
cat > job-cleanup.yaml <<'EOF'
apiVersion: batch/v1
kind: Job
metadata:
name: one-time-cleanup
spec:
ttlSecondsAfterFinished: 300
backoffLimit: 1
template:
spec:
restartPolicy: Never
containers:
- name: cleanup
image: busybox:1.36
command:
- /bin/sh
- -c
- |
echo "Running one-time cleanup"
date
echo "Cleanup completed"
EOF
Code language: PHP (php)
Apply:
kubectl apply -f job-cleanup.yaml
Code language: CSS (css)
Check:
kubectl get job one-time-cleanup
kubectl logs job/one-time-cleanup
Code language: JavaScript (javascript)
30. Real-World Use Case 4: Parallel File Processing
cat > job-file-processing.yaml <<'EOF'
apiVersion: batch/v1
kind: Job
metadata:
name: file-processing
spec:
completions: 10
parallelism: 3
completionMode: Indexed
backoffLimitPerIndex: 2
maxFailedIndexes: 3
template:
spec:
restartPolicy: Never
containers:
- name: processor
image: busybox:1.36
command:
- /bin/sh
- -c
- |
echo "Processing file partition: ${JOB_COMPLETION_INDEX}"
sleep 5
echo "Completed partition: ${JOB_COMPLETION_INDEX}"
EOF
Code language: PHP (php)
Apply:
kubectl apply -f job-file-processing.yaml
Code language: CSS (css)
Watch:
kubectl get pods -l job-name=file-processing -w
Code language: JavaScript (javascript)
Logs:
kubectl logs -l job-name=file-processing --prefix=true
Code language: JavaScript (javascript)
31. Advanced: Pod Failure Policy
podFailurePolicy gives more precise control over failures than only using backoffLimit. Kubernetes documentation says Pod failure policy became stable in Kubernetes v1.31 and lets the Job handle failures based on container exit codes and Pod conditions.
Example: fail the whole Job immediately if the container exits with code 42.
cat > job-pod-failure-policy.yaml <<'EOF'
apiVersion: batch/v1
kind: Job
metadata:
name: pod-failure-policy-job
spec:
completions: 3
parallelism: 1
backoffLimit: 6
podFailurePolicy:
rules:
- action: FailJob
onExitCodes:
containerName: main
operator: In
values: [42]
template:
spec:
restartPolicy: Never
containers:
- name: main
image: busybox:1.36
command:
- /bin/sh
- -c
- |
echo "Simulating non-retriable application bug"
exit 42
EOF
Code language: PHP (php)
Apply:
kubectl apply -f job-pod-failure-policy.yaml
Code language: CSS (css)
Check:
kubectl describe job pod-failure-policy-job
Expected: the Job fails quickly instead of wasting retries.
Important limitation: the Job API says podFailurePolicy cannot be used together with restartPolicy: OnFailure. Use restartPolicy: Never.
32. Advanced: Success Policy for Indexed Jobs
For some batch workloads, not every Pod must succeed. For example:
Run 10 simulations.
If any 3 important simulations succeed, mark the Job successful.
Kubernetes supports successPolicy for Indexed Jobs. The documentation says success policy lets you declare a Job successful based on succeeded indexes or succeeded count, and after the policy is met, lingering Pods are terminated.
Example:
cat > job-success-policy.yaml <<'EOF'
apiVersion: batch/v1
kind: Job
metadata:
name: success-policy-job
spec:
parallelism: 5
completions: 5
completionMode: Indexed
successPolicy:
rules:
- succeededIndexes: 0-4
succeededCount: 3
template:
spec:
restartPolicy: Never
containers:
- name: worker
image: busybox:1.36
command:
- /bin/sh
- -c
- |
echo "Worker index: ${JOB_COMPLETION_INDEX}"
sleep 5
echo "Success from index ${JOB_COMPLETION_INDEX}"
EOF
Code language: PHP (php)
Apply:
kubectl apply -f job-success-policy.yaml
Code language: CSS (css)
Watch:
kubectl get job success-policy-job -w
Code language: JavaScript (javascript)
33. Debugging Jobs and CronJobs
Check Jobs
kubectl get jobs
kubectl describe job <job-name>
kubectl get job <job-name> -o yaml
Code language: HTML, XML (xml)
Check Pods created by a Job
kubectl get pods -l job-name=<job-name>
Code language: HTML, XML (xml)
View logs
kubectl logs job/<job-name>
Code language: HTML, XML (xml)
If multiple Pods exist:
kubectl logs -l job-name=<job-name> --prefix=true
Code language: HTML, XML (xml)
Check CronJobs
kubectl get cronjobs
kubectl describe cronjob <cronjob-name>
kubectl get cronjob <cronjob-name> -o yaml
Code language: HTML, XML (xml)
Check Jobs created by CronJob
kubectl get jobs --sort-by=.metadata.creationTimestamp
Code language: JavaScript (javascript)
Watch everything
kubectl get cronjobs,jobs,pods -w
Code language: JavaScript (javascript)
Check events
kubectl get events --sort-by=.lastTimestamp
Code language: JavaScript (javascript)
For OpenShift:
oc get cronjobs,jobs,pods
oc describe cronjob <cronjob-name>
oc logs job/<job-name>
oc get events --sort-by=.lastTimestamp
Code language: HTML, XML (xml)
34. Troubleshooting Common Problems
Problem 1: Job keeps failing
Check:
kubectl describe job <job-name>
kubectl get pods -l job-name=<job-name>
kubectl logs -l job-name=<job-name> --prefix=true
Code language: HTML, XML (xml)
Common causes:
Application exits with non-zero code
Wrong command or args
Missing ConfigMap or Secret
ImagePullBackOff
Permission issue
Network dependency unavailable
Resource limit too low
Code language: JavaScript (javascript)
Problem 2: CronJob is not creating Jobs
Check:
kubectl describe cronjob <cronjob-name>
kubectl get events --sort-by=.lastTimestamp
Code language: HTML, XML (xml)
Possible reasons:
CronJob is suspended
Schedule is wrong
Time zone is wrong
startingDeadlineSeconds skipped the run
Controller manager issue
RBAC/admission issue
Check suspend:
kubectl get cronjob <cronjob-name> -o jsonpath='{.spec.suspend}{"\n"}'
Code language: HTML, XML (xml)
Resume:
kubectl patch cronjob <cronjob-name> -p '{"spec":{"suspend":false}}'
Code language: HTML, XML (xml)
Problem 3: Too many old Jobs
Use history limits:
successfulJobsHistoryLimit: 2
failedJobsHistoryLimit: 1
Code language: HTTP (http)
Or Job TTL:
ttlSecondsAfterFinished: 3600
Code language: HTTP (http)
Kubernetes TTL cleanup applies to Jobs and can delete finished Jobs and their dependent objects after the TTL expires.
Problem 4: CronJob creates overlapping Jobs
Use:
concurrencyPolicy: Forbid
Code language: HTTP (http)
For backups, cleanup tasks, and reports, Forbid is usually safer than Allow.
Problem 5: CronJob runs in wrong time zone
Use:
timeZone: "Asia/Tokyo"
Code language: JavaScript (javascript)
Do not use:
schedule: "CRON_TZ=Asia/Tokyo 0 9 * * *"
Code language: JavaScript (javascript)
Kubernetes documentation says CRON_TZ and TZ inside .spec.schedule are not officially supported and should not be used; use .spec.timeZone instead.
35. Production Best Practices
35.1 Make Jobs idempotent
A Job might run more than once due to retries, node failures, or controller behavior. Kubernetes documentation explicitly notes that even with parallelism: 1, completions: 1, and restartPolicy: Never, the same program may sometimes be started twice.
Design your task so repeated execution does not corrupt data.
Good patterns:
Use unique transaction IDs
Use locks carefully
Check whether work is already completed
Write output atomically
Use checkpoints
Use idempotent database operations
Code language: PHP (php)
35.2 Avoid using latest image tag in production
Bad:
image: myapp:latest
Code language: CSS (css)
Better:
image: myapp:1.2.3
Code language: CSS (css)
Best:
image: myapp@sha256:<digest>
Code language: HTML, XML (xml)
35.3 Always set resource requests and limits
Example:
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "512Mi"
Code language: JavaScript (javascript)
35.4 Use concurrencyPolicy: Forbid for critical CronJobs
For backups, financial reports, cleanup tasks, or data processing, overlapping Jobs can create corruption.
Recommended:
concurrencyPolicy: Forbid
Code language: HTTP (http)
35.5 Use startingDeadlineSeconds
This avoids very stale missed runs.
Example:
startingDeadlineSeconds: 1800
Code language: HTTP (http)
Meaning:
Start only if the missed run is less than 30 minutes late.
35.6 Use history limits and TTL
Recommended CronJob:
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 3
Code language: HTTP (http)
Recommended Job template:
ttlSecondsAfterFinished: 86400
Code language: HTTP (http)
35.7 Do not print secrets in logs
Bad:
echo $DB_PASSWORD
Code language: PHP (php)
Good:
echo "Using DB credentials from Secret"
Code language: PHP (php)
35.8 Use ServiceAccount with least privilege
Example:
serviceAccountName: backup-runner
Code language: HTTP (http)
Give the ServiceAccount only the permissions required by the Job.
35.9 Use restartPolicy: Never for clear failure tracking
For most batch jobs:
restartPolicy: Never
Code language: HTTP (http)
This makes retries easier to observe because each retry creates a separate Pod.
35.10 Add labels
Example:
metadata:
labels:
app: reporting
workload-type: cronjob
owner: platform-team
This helps with:
Monitoring
Cost tracking
Log filtering
Troubleshooting
Cleanup
36. Complete Production-Style CronJob Template
cat > cronjob-production-template.yaml <<'EOF'
apiVersion: batch/v1
kind: CronJob
metadata:
name: production-cronjob
labels:
app: production-cronjob
owner: platform-team
spec:
schedule: "0 2 * * *"
timeZone: "Etc/UTC"
concurrencyPolicy: Forbid
startingDeadlineSeconds: 1800
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 3
suspend: false
jobTemplate:
metadata:
labels:
app: production-cronjob
workload-type: batch
spec:
backoffLimit: 2
activeDeadlineSeconds: 3600
ttlSecondsAfterFinished: 86400
template:
metadata:
labels:
app: production-cronjob
spec:
restartPolicy: Never
containers:
- name: task
image: busybox:1.36
imagePullPolicy: IfNotPresent
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "512Mi"
command:
- /bin/sh
- -c
- |
echo "Starting production batch task"
date
echo "Do real work here"
echo "Completed production batch task"
EOF
Code language: PHP (php)
Apply:
kubectl apply -f cronjob-production-template.yaml
Code language: CSS (css)
Test manually:
kubectl create job production-cronjob-test --from=cronjob/production-cronjob
kubectl logs job/production-cronjob-test
Code language: JavaScript (javascript)
37. Complete Production-Style Job Template
cat > job-production-template.yaml <<'EOF'
apiVersion: batch/v1
kind: Job
metadata:
name: production-job
labels:
app: production-job
owner: platform-team
spec:
backoffLimit: 2
activeDeadlineSeconds: 1800
ttlSecondsAfterFinished: 3600
template:
metadata:
labels:
app: production-job
spec:
restartPolicy: Never
containers:
- name: task
image: busybox:1.36
imagePullPolicy: IfNotPresent
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "512Mi"
command:
- /bin/sh
- -c
- |
echo "Starting one-time production job"
date
echo "Do real work here"
echo "Completed one-time production job"
EOF
Code language: PHP (php)
Apply:
kubectl apply -f job-production-template.yaml
Code language: CSS (css)
Check:
kubectl get job production-job
kubectl logs job/production-job
Code language: JavaScript (javascript)
38. Job and CronJob Commands Cheat Sheet
Jobs
kubectl get jobs
kubectl describe job <job-name>
kubectl get job <job-name> -o yaml
kubectl delete job <job-name>
kubectl logs job/<job-name>
kubectl get pods -l job-name=<job-name>
kubectl logs -l job-name=<job-name> --prefix=true
kubectl create job <job-name> --image=<image> -- <command>
Code language: HTML, XML (xml)
CronJobs
kubectl get cronjobs
kubectl describe cronjob <cronjob-name>
kubectl get cronjob <cronjob-name> -o yaml
kubectl delete cronjob <cronjob-name>
kubectl create job <manual-job-name> --from=cronjob/<cronjob-name>
kubectl patch cronjob <cronjob-name> -p '{"spec":{"suspend":true}}'
kubectl patch cronjob <cronjob-name> -p '{"spec":{"suspend":false}}'
Code language: HTML, XML (xml)
Events
kubectl get events --sort-by=.lastTimestamp
Code language: JavaScript (javascript)
Watch
kubectl get cronjobs,jobs,pods -w
Code language: JavaScript (javascript)
39. OpenShift oc Cheat Sheet
Everything works similarly with oc.
oc get jobs
oc describe job <job-name>
oc logs job/<job-name>
oc get cronjobs
oc describe cronjob <cronjob-name>
oc create job manual-run --from=cronjob/<cronjob-name>
oc patch cronjob <cronjob-name> -p '{"spec":{"suspend":true}}'
oc patch cronjob <cronjob-name> -p '{"spec":{"suspend":false}}'
oc get events --sort-by=.lastTimestamp
Code language: HTML, XML (xml)
Create project:
oc new-project jobs-demo
Code language: JavaScript (javascript)
Apply YAML:
oc apply -f job-hello.yaml
oc apply -f cronjob-hello.yaml
Code language: CSS (css)
40. Cleanup All Tutorial Resources
kubectl delete namespace jobs-demo
Code language: JavaScript (javascript)
For OpenShift:
oc delete project jobs-demo
Code language: JavaScript (javascript)
Or delete individual resources:
kubectl delete job --all
kubectl delete cronjob --all
kubectl delete secret db-backup-secret
Code language: JavaScript (javascript)
41. Final Summary
Kubernetes Jobs and CronJobs are essential for running batch workloads.
Use a Job when:
You need to run a task once.
You need a migration.
You need a one-time cleanup.
You need batch processing.
You need parallel workers.
Use a CronJob when:
You need scheduled execution.
You need periodic backups.
You need daily reports.
You need weekly cleanup.
You need recurring sync tasks.
The simplest Job:
apiVersion: batch/v1
kind: Job
metadata:
name: hello-job
spec:
template:
spec:
restartPolicy: Never
containers:
- name: hello
image: busybox:1.36
command: ["/bin/sh", "-c", "echo Hello from Job"]
Code language: JavaScript (javascript)
The simplest CronJob:
apiVersion: batch/v1
kind: CronJob
metadata:
name: hello-cronjob
spec:
schedule: "* * * * *"
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: hello
image: busybox:1.36
command: ["/bin/sh", "-c", "date; echo Hello from CronJob"]
Code language: JavaScript (javascript)
The most important production lesson:
Jobs and CronJobs must be idempotent, observable, resource-limited, retry-controlled, and cleaned up automatically.
The golden rule:
Deployment runs forever.
Job runs once.
CronJob runs Jobs on a schedule.
I’m a DevOps/SRE/DevSecOps/Cloud Expert passionate about sharing knowledge and experiences. I have worked at Cotocus. I share tech blog at DevOps School, travel stories at Holiday Landmark, stock market tips at Stocks Mantra, health and fitness guidance at My Medic Plus, product reviews at TrueReviewNow , and SEO strategies at Wizbrand.
Do you want to learn Quantum Computing?
Please find my social handles as below;
Rajesh Kumar Personal Website
Rajesh Kumar at YOUTUBE
Rajesh Kumar at INSTAGRAM
Rajesh Kumar at X
Rajesh Kumar at FACEBOOK
Rajesh Kumar at LINKEDIN
Rajesh Kumar at WIZBRAND
Find Trusted Cardiac Hospitals
Compare heart hospitals by city and services — all in one place.
Explore Hospitals