Kubernetes Jobs with Examples

Create a Kubernetes Job with YAML, watch completions and parallelism, control retries with backoffLimit, limit runtime with activeDeadlineSeconds, and clean up finished Jobs with ttlSecondsAfterFinished.

Published

Updated

Read time 11 min read

Reviewed byDeepak Prasad

Kubernetes Job controller running batch Pods to completion with parallelism and retry controls
Tested on Rocky Linux 10.2 (Red Quartz) workstation
Package kubectl 1.36.3
Applies to Any host with kubectl configured; any Kubernetes cluster
Cert prep CKA · CKAD
Lab environment Multi-node kubeadm cluster with containerd — install Kubernetes with kubeadm
Privilege Normal user (no sudo required on the workstation)
Scope Job YAML, completions, parallelism, backoffLimit, activeDeadlineSeconds, ttlSecondsAfterFinished, status inspection, logs, deletion, comparison with bare Pod and CronJob, and common Job problems. Does not cover CronJob schedule syntax, indexed Jobs, Pod failure policies, init or sidecar patterns, or distributed batch frameworks.

A Deployment keeps replicas running until you scale it down. A Job is built for the opposite case: run a command, count successful completions, and stop. That makes Jobs the right controller for migrations, reports, backups, and other work that must finish rather than stay online.

In the job-lab namespace you will build that pattern step by step — start with a minimal busybox Job, read its Pod logs, then change one control at a time to see how completions, parallelism, retries, deadlines, and automatic cleanup behave before you wire the same fields into production batch manifests.


What Is a Kubernetes Job?

A Job creates one or more Pods to perform a finite task. The Job completes after the required number of Pods succeed. Failed Pods can be retried according to Job settings.

Jobs fit one-time batch and maintenance work:

  • Database migrations
  • Data processing
  • One-time backups
  • Report generation
  • Administrative scripts

A Deployment keeps replicas running. A Job tracks successful completions and then stops. For long-running services, see choose a Kubernetes workload resource. For Pod phases and restartPolicy on bare Pods, see Kubernetes Pods and Pod Lifecycle.

Job vs bare Pod

Bare Pod Job
Creates one Pod object directly Manages Pods for a finite task
Does not track required completions Tracks successful completions
No Job-level retry limit Supports backoffLimit
No automatic finished-resource TTL Supports ttlSecondsAfterFinished
Suitable for simple testing Suitable for managed batch work

Job vs CronJob

A Job runs once; a CronJob creates Jobs on a schedule. See Job versus CronJob for the complete comparison.


Create and Inspect a Kubernetes Job

Minimal Job YAML

Save this manifest as report-gen.yaml:

yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: report-gen
  namespace: job-lab
spec:
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: worker
          image: busybox:1.36
          command:
            - sh
            - -c
            - |
              echo "Report generated on $(date)"
              sleep 5

Create the namespace:

bash
kubectl create namespace job-lab

Sample output:

output
namespace/job-lab created

Apply the Job:

bash
kubectl apply -f report-gen.yaml

Sample output:

output
job.batch/report-gen created

Watch until the Job reports Complete:

bash
kubectl get jobs -n job-lab --watch

Sample output:

output
NAME         STATUS     COMPLETIONS   DURATION   AGE
report-gen   Running    0/1           2s         2s
report-gen   Running    0/1           4s         4s
report-gen   Complete   1/1           9s         9s

Press Ctrl+C after report-gen reaches Complete.

Alternatively, wait deterministically:

bash
kubectl wait --for=condition=complete job/report-gen -n job-lab --timeout=60s

Sample output:

output
job.batch/report-gen condition met

COMPLETIONS shows successful Pods versus the required count (1/1). DURATION is how long the Job was active. STATUS becomes Complete when all required Pods succeed.

Job status, Pods, and logs

List the Pod the Job created:

bash
kubectl get pods -n job-lab -l batch.kubernetes.io/job-name=report-gen

Sample output:

output
NAME               READY   STATUS      RESTARTS   AGE
report-gen-nn9kp   0/1     Completed   0          9s

Read the container output through the Job shortcut:

bash
kubectl logs job/report-gen -n job-lab

Sample output:

output
Report generated on Sun Jul 26 09:22:02 UTC 2026

The log line confirms the one-shot command ran and exited successfully.

Task Command
List Jobs kubectl get jobs
Watch completion kubectl get jobs --watch
Inspect Job details kubectl describe job <name>
List Pods created by a Job kubectl get pods -l batch.kubernetes.io/job-name=<name>
View logs kubectl logs job/<name>
View Job YAML kubectl get job <name> -o yaml

When parallelism or completions create multiple Pods, kubectl logs job/<name> may only show one Pod. List Pods by label and read logs per Pod when you need every worker transcript.

Important Job fields

spec.template

spec.template is a Pod template. Kubernetes automatically adds batch.kubernetes.io/job-name and batch.kubernetes.io/controller-uid labels to Job Pods. Set container command, args, and environment here the same way you would on a Pod — see container command, args, and environment when you need to override image defaults.

restartPolicy

Job Pod templates allow only:

  • Never — failed Pods stay failed; the Job controller creates replacement Pods when retries are allowed
  • OnFailure — the kubelet restarts the container inside the same Pod on failure

Always is not valid on a Job Pod template because a Job Pod must exit when the task finishes.

Selectors and reruns

Normally, omit spec.selector. Kubernetes generates a unique selector and matching Pod labels for each Job. Manually reusing selectors can cause Jobs to count or delete unrelated Pods. The Job selector is optional and normally generated by Kubernetes.

A completed Job does not run again when you reapply the same unchanged object. To execute it again, delete and recreate the Job or create another Job with a new name. Use a CronJob when execution must recur on a schedule.

At-least-once execution

Design Job commands to be idempotent. A Job uses the requested number of successful completions as its success criterion, but it does not provide exactly-once execution. Retries, deadlines, or failure limits can prevent the Job from succeeding, and the same program can sometimes start more than once. Even a Job with one completion and parallelism of one can occasionally start the program more than once because of node, kubelet, eviction, or controller recovery behavior.

Job status

kubectl get jobs summarizes controller state:

Column or field Meaning
STATUS Complete, Failed, or active (Running) while work remains
COMPLETIONS Successful Pods versus spec.completions (default 1)
Active Pods Pods still running
Successful Pods Pods that completed without failure
Failed Pods Pods that terminated with failure

When a Job finishes, Events on kubectl describe job record the reason, such as BackoffLimitExceeded or DeadlineExceeded.


Run Multiple Job Completions

Set completions

spec.completions sets how many successful Pod completions the Job needs. Default is 1.

Save this manifest as multi-complete.yaml:

yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: multi-complete
  namespace: job-lab
spec:
  completions: 3
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: worker
          image: busybox:1.36
          command: ["sh", "-c", "echo task $HOSTNAME && sleep 2"]

Delete the first Job, then apply the new one:

bash
kubectl delete job report-gen -n job-lab

Sample output:

output
job.batch "report-gen" deleted

Deleting the Job also deletes the Pods it owns through cascading deletion.

bash
kubectl apply -f multi-complete.yaml

Early in the run:

bash
kubectl get jobs multi-complete -n job-lab

Sample output:

output
NAME             STATUS    COMPLETIONS   DURATION   AGE
multi-complete   Running   0/3           3s         3s

Wait for completion:

bash
kubectl wait --for=condition=complete job/multi-complete -n job-lab --timeout=120s
bash
kubectl get jobs multi-complete -n job-lab

Sample output:

output
NAME             STATUS     COMPLETIONS   DURATION   AGE
multi-complete   Complete   3/3           18s        18s

COMPLETIONS moved from 0/3 to 3/3 as three separate Pods each exited successfully.

Limit parallelism

spec.parallelism limits how many Job Pods run at the same time. Default is 1.

Save this manifest as parallel-batch.yaml:

yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: parallel-batch
  namespace: job-lab
spec:
  completions: 6
  parallelism: 2
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: worker
          image: busybox:1.36
          command: ["sh", "-c", "echo worker $HOSTNAME && sleep 3"]
bash
kubectl delete job multi-complete -n job-lab --ignore-not-found=true
bash
kubectl apply -f parallel-batch.yaml

While the Job is active, count running Pods:

bash
kubectl get pods -n job-lab -l batch.kubernetes.io/job-name=parallel-batch

Sample output:

output
NAME                   READY   STATUS    RESTARTS   AGE
parallel-batch-99mdf   1/1     Running   0          4s
parallel-batch-l4nkt   1/1     Running   0          4s

Two Pods run at once because parallelism is 2. When one finishes, the controller starts another until six Pods succeed.

Field Purpose
completions Number of successful completions required
parallelism Maximum Pods that should run concurrently

Actual parallelism can be lower when remaining completions are fewer than parallelism, nodes lack resources, or scheduling blocks new Pods.

Wait for all six completions:

bash
kubectl wait --for=condition=complete job/parallel-batch -n job-lab --timeout=120s

Sample output:

output
job.batch/parallel-batch condition met
bash
kubectl get jobs parallel-batch -n job-lab

Sample output:

output
NAME             STATUS     COMPLETIONS   DURATION   AGE
parallel-batch   Complete   6/6           23s        23s

NonIndexed work distribution

These examples use the default NonIndexed completion mode. All worker Pods receive the same command. The Job counts successes but does not assign a unique task number to each Pod.

Non-indexed completions are interchangeable; Indexed Jobs provide explicit completion indexes but are correctly outside this article's scope.


Control Job Failure and Runtime

Retry failed work with backoffLimit

backoffLimit caps how many times Kubernetes retries a failed Pod before marking the Job failed. Default is 6. Retries use increasing delay between attempts.

Save this manifest as fail-batch.yaml:

yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: fail-batch
  namespace: job-lab
spec:
  backoffLimit: 3
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: worker
          image: busybox:1.36
          command: ["sh", "-c", "echo failing && exit 1"]
bash
kubectl delete job parallel-batch -n job-lab --ignore-not-found=true
bash
kubectl apply -f fail-batch.yaml

With restartPolicy: Never, backoffLimit: 3 allows the initial attempt followed by up to three replacement attempts. This example can therefore leave four failed Pods before the Job reaches BackoffLimitExceeded. Kubernetes 1.36 evaluates failed Pods against backoffLimit and recreates failed Pods with increasing delay.

Wait until the Job fails:

bash
kubectl wait --for=condition=failed job/fail-batch -n job-lab --timeout=180s
bash
kubectl get jobs fail-batch -n job-lab

Sample output:

output
NAME         STATUS   COMPLETIONS   DURATION   AGE
fail-batch   Failed   0/1           77s        77s

List failed Pods:

bash
kubectl get pods -n job-lab -l batch.kubernetes.io/job-name=fail-batch

Sample output:

output
NAME               READY   STATUS   RESTARTS   AGE
fail-batch-6sq6n   0/1     Error    0          45s
fail-batch-mcfzk   0/1     Error    0          77s
fail-batch-rlpm4   0/1     Error    0          66s
fail-batch-wq467   0/1     Error    0          4s

Inspect Events for the failure reason:

bash
kubectl describe job fail-batch -n job-lab

Sample output (trimmed Events section):

output
Events:
  Type     Reason                Age   From            Message
  ----     ------                ----  ----            -------
  Normal   SuccessfulCreate      78s   job-controller  Created pod: fail-batch-mcfzk
  Normal   SuccessfulCreate      67s   job-controller  Created pod: fail-batch-rlpm4
  Warning  BackoffLimitExceeded  1s    job-controller  Job has reached the specified backoff limit

Read logs from one failed Pod:

bash
FAILED_POD=$(kubectl get pod -n job-lab -l batch.kubernetes.io/job-name=fail-batch -o jsonpath='{.items[0].metadata.name}')
bash
kubectl logs "$FAILED_POD" -n job-lab

Sample output:

output
failing

If you need every attempt, prefix logs from all failed Pods:

bash
kubectl logs -n job-lab -l batch.kubernetes.io/job-name=fail-batch --prefix --max-log-requests=10

Stop long-running work with activeDeadlineSeconds

spec.activeDeadlineSeconds limits total active time for the Job. When the deadline passes, running Pods are terminated and the Job fails with reason DeadlineExceeded. The active deadline takes precedence over backoffLimit.

Set the field on the Job spec, not inside the Pod template.

Save this manifest as slow-batch.yaml:

yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: slow-batch
  namespace: job-lab
spec:
  activeDeadlineSeconds: 10
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: worker
          image: busybox:1.36
          command: ["sh", "-c", "sleep 120"]
bash
kubectl delete job fail-batch -n job-lab --ignore-not-found=true
bash
kubectl apply -f slow-batch.yaml

Wait for failure:

bash
kubectl wait --for=condition=failed job/slow-batch -n job-lab --timeout=120s
bash
kubectl get jobs slow-batch -n job-lab

Sample output:

output
NAME         STATUS   COMPLETIONS   DURATION   AGE
slow-batch   Failed   0/1           41s        41s

Events show the deadline:

bash
kubectl describe job slow-batch -n job-lab

Sample output (Events section):

output
Events:
  Type     Reason            Age   From            Message
  ----     ------            ----  ----            -------
  Normal   SuccessfulCreate  41s   job-controller  Created pod: slow-batch-l87b5
  Normal   SuccessfulDelete  31s   job-controller  Deleted pod: slow-batch-l87b5
  Warning  DeadlineExceeded  0s    job-controller  Job was active longer than specified deadline

The Job controller starts terminating active Pods when the ten-second deadline is reached. Kubernetes 1.31 and later does not add the terminal Failed condition until all Job Pods have terminated. The Pod's termination grace period can therefore make the displayed Job duration longer than ten seconds; a duration near 40 seconds is possible with the default 30-second Pod termination grace period. Kubernetes now delays Complete or Failed until all Job Pods terminate, and terminationGracePeriodSeconds can extend that interval. The deadline still takes precedence over backoffLimit.

Troubleshoot Common Kubernetes Job Problems

Symptom Likely cause Fix
Job stays Running indefinitely Container never exits, wrong completions, or command waits on input Confirm the process exits on success; set activeDeadlineSeconds to cap run time; fix hanging commands
BackoffLimitExceeded in Events Command, image, or dependency failure after retries Read failed Pod logs and exit codes; fix command/args, env vars, and mounted files
Fewer parallel Pods than parallelism Low remaining completions, node resources, scheduling, or ResourceQuota Check kubectl describe pod Events and cluster capacity; reduce parallelism or add nodes
Completed Job still listed No TTL or CronJob history cleanup configured Delete manually, set ttlSecondsAfterFinished, or tune CronJob history limits
Logs missing after completion TTL or manual deletion removed Pods Collect logs before cleanup or raise TTL; use centralized logging for production batch work

What's Next


References


Summary

You created a minimal batch/v1 Job with restartPolicy: Never, watched COMPLETIONS reach 1/1, and read logs with kubectl logs job/<name>. That baseline is the pattern for database migrations, reports, and other tasks that must run once and finish.

completions and parallelism split total work from concurrent workers in the default NonIndexed mode — every Pod runs the same command, and your application must coordinate distinct work externally. backoffLimit and activeDeadlineSeconds answer different failure questions: how many times to retry versus how long the whole Job may stay active. When both apply, the deadline wins. Design commands to be idempotent because Jobs track successful completion counts but do not provide exactly-once execution. Kubernetes explicitly warns that the same program can sometimes start twice even with one completion, one parallel worker, and restartPolicy: Never.

For one-off work, apply a Job directly. For recurring batch work, use a CronJob that creates Jobs on a schedule. Next, compare Job versus CronJob or open the CronJob scheduler guide when you need recurring schedules.


Frequently Asked Questions

1. What is the difference between a Kubernetes Job and a Pod?

A bare Pod creates one Pod object directly. A Pod can contain more than one container. A Job creates and tracks Pods until a required number complete successfully, applies backoffLimit for retries, and can set ttlSecondsAfterFinished for automatic cleanup after the Job finishes.

2. When should I use completions and parallelism on a Job?

Set completions when the Job needs a fixed number of interchangeable successful executions. Set parallelism to limit how many may run concurrently. Kubernetes does not automatically assign different files or work items to non-indexed Pods; the application must coordinate distinct work externally.

3. Why is my Kubernetes Job stuck in Running?

Check whether the container process exits. A Job stays active until required completions succeed or a failure or deadline condition ends it. Hanging commands, wrong completions count, or waiting on external input are common causes.

4. What happens when a Job reaches backoffLimit?

Kubernetes retries failed Pods until the number of failed attempts exceeds backoffLimit. The default is six. After that the Job is marked Failed and the job-controller records BackoffLimitExceeded in Events.

5. Does ttlSecondsAfterFinished delete Pods immediately?

No. The TTL controller starts counting after the Job reaches Complete or Failed. When the timer expires, it deletes the Job object and the Pods the Job created.
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)