All Articles

What is a Job and CronJob in Kubernetes? Explained Simply (2026)

Deployments run forever. Jobs run once and stop. CronJobs run on a schedule. Here's how both work, when to use them, and simple examples to get started.

DevOpsBoysApr 16, 20264 min read
Share:Tweet

Not every workload runs forever. Database migrations, report generation, data processing — these need to run once (or on a schedule) and stop. That's what Job and CronJob are for.

The Problem They Solve

A Deployment keeps pods running forever — if the pod exits, it restarts.

yaml
# Deployment — restarts on exit (always running)
kind: Deployment
spec:
  template:
    spec:
      containers:
      - name: app
        image: nginx
      restartPolicy: Always   # default

For a database migration, you don't want "always running." You want "run once, succeed, then stop." That's a Job.


What Is a Job?

A Job creates one or more pods, waits for them to complete successfully, then marks itself as Done.

yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: db-migration
spec:
  template:
    spec:
      restartPolicy: Never     # ← required for Jobs (not Always)
      containers:
      - name: migrate
        image: my-app:v2.0
        command: ["python", "manage.py", "migrate"]
bash
kubectl apply -f job.yaml
 
# Watch the job
kubectl get jobs
# NAME           COMPLETIONS   DURATION   AGE
# db-migration   1/1           8s         15s   ← 1/1 = done!
 
# Check the pod it created
kubectl get pods --selector=job-name=db-migration
# NAME                  STATUS
# db-migration-abc12    Completed   ← pod stays for log inspection
 
# View logs
kubectl logs job/db-migration

Job Life Cycle

Job created
     ↓
Pod starts running
     ↓
Pod exits with code 0 (success)?
     ├── Yes → Job = Completed ✅
     └── No  → Retry (up to backoffLimit times)
                    ↓
               All retries failed → Job = Failed ❌

Key Job Fields

yaml
spec:
  completions: 1          # how many pods must succeed (default: 1)
  parallelism: 1          # how many pods run at once (default: 1)
  backoffLimit: 3         # retry count on failure (default: 6)
  activeDeadlineSeconds: 300   # kill job after 5 min regardless
  ttlSecondsAfterFinished: 3600  # auto-delete pods 1hr after completion

Run 10 tasks in parallel

yaml
spec:
  completions: 10   # need 10 successful completions
  parallelism: 3    # run 3 at a time
Round 1: pods 1, 2, 3 run simultaneously
Round 2: pods 4, 5, 6
Round 3: pods 7, 8, 9
Round 4: pod 10
→ Job complete when 10/10 succeed

Common Job Use Cases

Use CaseExample
Database migrationpython manage.py migrate
Sending bulk emailspython send_newsletter.py
Data processingpython process_csv.py
One-time setupkubectl exec alternative
ML training runTrain a model on a dataset
Report generationGenerate and upload PDF report

What Is a CronJob?

A CronJob creates Jobs on a schedule — like Linux cron, but for Kubernetes.

yaml
apiVersion: batch/v1
kind: CronJob
metadata:
  name: nightly-backup
spec:
  schedule: "0 2 * * *"     # 2 AM every day
  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: OnFailure
          containers:
          - name: backup
            image: my-backup:latest
            command: ["sh", "-c", "pg_dump $DATABASE_URL | gzip | aws s3 cp - s3://backups/$(date +%Y%m%d).sql.gz"]
bash
kubectl apply -f cronjob.yaml
 
# Check CronJob
kubectl get cronjobs
# NAME             SCHEDULE    SUSPEND   ACTIVE   LAST SCHEDULE
# nightly-backup   0 2 * * *   False     0        14h
 
# See jobs it created
kubectl get jobs --selector=cronjob=nightly-backup

Cron Schedule Syntax

┌─── minute (0-59)
│ ┌── hour (0-23)
│ │ ┌─ day of month (1-31)
│ │ │ ┌ month (1-12)
│ │ │ │ ┌ day of week (0-7, 0=Sunday)
│ │ │ │ │
* * * * *

Examples:
"*/5 * * * *"    → every 5 minutes
"0 * * * *"      → every hour
"0 9 * * 1-5"    → 9 AM on weekdays
"0 0 1 * *"      → midnight on 1st of every month
"30 3 * * *"     → 3:30 AM daily (UTC)

Set timezone (Kubernetes 1.27+):

yaml
spec:
  schedule: "0 9 * * *"
  timeZone: "Asia/Kolkata"    # 9 AM IST

CronJob Key Fields

yaml
spec:
  schedule: "*/10 * * * *"
  timeZone: "Asia/Kolkata"
  concurrencyPolicy: Forbid       # don't start new job if one is running
  successfulJobsHistoryLimit: 3   # keep last 3 successful jobs
  failedJobsHistoryLimit: 3       # keep last 3 failed jobs
  startingDeadlineSeconds: 60     # skip if more than 60s late
  suspend: false                  # pause CronJob without deleting

concurrencyPolicy options:

  • Allow — run multiple jobs at once (default)
  • Forbid — skip new run if one is still running
  • Replace — kill current job, start new one

Job vs CronJob vs Deployment

DeploymentJobCronJob
RunsForeverOnceOn schedule
Restart on exitAlwaysNever/OnFailureNever/OnFailure
Use forWeb apps, APIsMigrations, one-time tasksBackups, reports, cleanup
CompletesNeverWhen pods succeedEach scheduled run

Useful Commands

bash
# Create a one-off job quickly (no YAML needed)
kubectl create job hello --image=busybox -- echo "Hello World"
 
# Manually trigger a CronJob right now (for testing)
kubectl create job --from=cronjob/nightly-backup manual-test
 
# Pause a CronJob
kubectl patch cronjob nightly-backup -p '{"spec":{"suspend":true}}'
 
# Resume
kubectl patch cronjob nightly-backup -p '{"spec":{"suspend":false}}'
 
# Delete a job and its pods
kubectl delete job db-migration
 
# Delete completed jobs
kubectl delete jobs --field-selector status.successful=1

Complete Example: Weekly Report

yaml
apiVersion: batch/v1
kind: CronJob
metadata:
  name: weekly-report
  namespace: production
spec:
  schedule: "0 8 * * 1"          # 8 AM every Monday
  timeZone: "Asia/Kolkata"
  concurrencyPolicy: Forbid
  successfulJobsHistoryLimit: 4   # keep 1 month of history
  failedJobsHistoryLimit: 4
  jobTemplate:
    spec:
      backoffLimit: 2
      activeDeadlineSeconds: 1800  # fail after 30 min
      ttlSecondsAfterFinished: 604800  # auto-delete after 1 week
      template:
        spec:
          restartPolicy: OnFailure
          containers:
          - name: reporter
            image: my-reporter:latest
            env:
            - name: DATABASE_URL
              valueFrom:
                secretKeyRef:
                  name: app-secrets
                  key: DATABASE_URL
            - name: SLACK_WEBHOOK
              valueFrom:
                secretKeyRef:
                  name: app-secrets
                  key: SLACK_WEBHOOK
            resources:
              requests:
                cpu: "200m"
                memory: "256Mi"

Learn More

Newsletter

Stay ahead of the curve

Get the latest DevOps, Kubernetes, AWS, and AI/ML guides delivered straight to your inbox. No spam — just practical engineering content.

Related Articles

Comments