What's the difference between a Job and a CronJob?

6 minintermediatejobcronjobbatch-workloads

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.

Related Resources