Jobs and CronJobs
The idea in one sentence
Section titled “The idea in one sentence”A Job runs one or more Pods until a task finishes successfully rather than keeping them running forever, and a CronJob creates Jobs automatically on a cron-formatted schedule.
Jobs: run to completion, not forever
Section titled “Jobs: run to completion, not forever”A Deployment’s Pods are meant to run indefinitely; a Job’s Pods are meant to finish. A Job tracks successful completions and retries failures up to a limit, instead of restarting a Pod forever the way a long-running controller would.
apiVersion: batch/v1kind: Jobmetadata: name: report-backfillspec: completions: 5 parallelism: 2 backoffLimit: 3 activeDeadlineSeconds: 600 template: spec: restartPolicy: OnFailure containers: - name: backfill image: myregistry/report-backfill:1.0.0 args: ['--month=2026-06']completions: 5 means the Job is not done until 5 Pods have exited successfully; parallelism: 2 caps how many of those Pods run at once; backoffLimit: 3 is how many failed attempts the Job tolerates before it gives up and marks itself Failed; activeDeadlineSeconds: 600 is an overall wall-clock timeout for the whole Job, after which it is terminated regardless of completions. Notice restartPolicy: OnFailure — a Job’s Pod template must use OnFailure or Never, never Always, because a Job’s entire point is to stop once the work is done.
# Watch completions accumulate, then check the Job's final statuskubectl get job report-backfill --watchkubectl describe job report-backfill
# Logs from a finished Job's Podskubectl logs -l job-name=report-backfill --tail=100CronJobs: scheduling Jobs
Section titled “CronJobs: scheduling Jobs”A CronJob does not run Pods directly — it creates a Job on a schedule, and that Job creates the Pods, exactly as if you had submitted the Job by hand at that moment.
apiVersion: batch/v1kind: CronJobmetadata: name: nightly-reportspec: schedule: '0 2 * * *' concurrencyPolicy: Forbid startingDeadlineSeconds: 300 successfulJobsHistoryLimit: 3 failedJobsHistoryLimit: 1 jobTemplate: spec: backoffLimit: 2 template: spec: restartPolicy: OnFailure containers: - name: report image: myregistry/nightly-report:1.0.0schedule: '0 2 * * *' runs at 02:00 every day, in standard cron syntax. concurrencyPolicy decides what happens if a previous run is still going when the next one is due: Allow (the default) runs them side by side, Forbid skips the new run entirely, and Replace cancels the still-running Job and starts the new one in its place. startingDeadlineSeconds bounds how late a missed run is still allowed to start. successfulJobsHistoryLimit and failedJobsHistoryLimit control how many old Job objects the CronJob keeps around for inspection before garbage-collecting them.
flowchart LR cj["CronJob nightly-report (schedule: 0 2 * * *)"] -->|creates| j["Job nightly-report-29102026"] j -->|creates| p1["Pod (runs to completion)"] j -->|creates| p2["Pod (runs to completion)"] p1 --> done["Succeeded, Pods stop"] p2 --> done
Operating Jobs and CronJobs
Section titled “Operating Jobs and CronJobs”# List CronJobs and the Jobs they have createdkubectl get cronjob nightly-reportkubectl get jobs --selector=job-name
# Trigger one ad-hoc run right now, without waiting for the schedulekubectl create job nightly-report-manual --from=cronjob/nightly-reportBecause a CronJob’s schedule can fire many times, successfulJobsHistoryLimit/failedJobsHistoryLimit matter in practice: without them old Job and Pod objects would accumulate forever every night.