What's the difference between a Job and a CronJob?
Quick Answer
A Job runs one or more Pods to completion for a single, one-off task, and tracks successful completions — it's for work that runs once and finishes, unlike a Deployment's Pods, which are expected to run indefinitely and get restarted if they exit. A CronJob is a Job template that gets triggered automatically on a recurring schedule (using standard cron syntax), creating a new Job instance each time it fires — for tasks like nightly backups or scheduled report generation.
Detailed Answer
Job — run-to-completion, not run-forever
apiVersion: batch/v1
kind: Job
metadata:
name: data-migration
spec:
completions: 1
backoffLimit: 3 # retry up to 3 times on failure before giving up
template:
spec:
containers:
- name: migrate
image: myapp-migrator:1.0
restartPolicy: Never
The key behavioral difference from a Deployment: a Deployment expects its Pods to run indefinitely, and treats a container exiting as a failure to be restarted; a Job expects its Pod(s) to eventually exit successfully (exit code 0), and considers that success, not failure — the Job is then marked Complete and no new Pods are created. A Job's Pod template must specify restartPolicy: Never or OnFailure (never Always, which would conflict with the run-to-completion model).
Parallel and repeated Jobs
spec:
completions: 5 # need 5 total successful Pod completions
parallelism: 2 # run at most 2 Pods concurrently
Jobs can run a single Pod once, run multiple Pods in parallel (for a parallelizable batch task, like processing a fixed batch of work items), or use a work-queue pattern where Pods pull tasks from an external queue until the queue is empty. backoffLimit controls how many times a failed Pod is retried before the whole Job is marked as failed.
CronJob — a Job on a recurring schedule
apiVersion: batch/v1
kind: CronJob
metadata:
name: nightly-backup
spec:
schedule: "0 2 * * *" # standard cron syntax: 2am daily
jobTemplate:
spec:
template:
spec:
containers:
- name: backup
image: backup-tool:1.0
restartPolicy: OnFailure
At each scheduled trigger time, the CronJob controller creates a new Job object (using jobTemplate as the spec) — each firing is an entirely independent Job, tracked and retried according to that Job's own backoffLimit, completely separate from any previous or future firing.
Handling overlapping/missed runs
spec:
concurrencyPolicy: Forbid # don't start a new run if the previous one is still going
startingDeadlineSeconds: 200 # if a scheduled run is missed by more than this, skip it
concurrencyPolicy controls what happens if a scheduled run's Job is still active when the next scheduled time arrives: Allow (default — run concurrently), Forbid (skip the new run), or Replace (cancel the still-running one and start the new one). This matters for tasks where overlapping runs would cause real problems (e.g., two concurrent database migration jobs stepping on each other) versus tasks where it's harmless.
Common use cases
- Jobs: one-off data migrations, batch processing of a fixed dataset, running a database schema migration as part of a deployment pipeline.
- CronJobs: nightly backups, scheduled report generation, periodic cleanup tasks (purging old data, rotating logs), health-check/synthetic-monitoring pings on a schedule.
A common gotcha
Completed Job Pods aren't automatically deleted by default (only the newest few, bounded by spec.successfulJobsHistoryLimit/failedJobsHistoryLimit for CronJobs) — over time, an unmonitored, frequently-firing CronJob can accumulate a large number of completed Job and Pod objects, which is a common, easy-to-overlook source of cluster object clutter (and, at large enough scale, real etcd/API server load) if history limits aren't configured sensibly.