Back to Blog
Tutorial8 min read2026-05-01

Kubernetes CronJobs: How to Schedule Tasks in Kubernetes

A hands-on tutorial for Kubernetes CronJobs — covering the CronJob spec, concurrency policies, failure handling, and how PandaStack manages scheduled containers for you.

Kubernetes CronJobs

Kubernetes includes a built-in resource type for scheduled workloads: the CronJob. It creates Job objects on a cron schedule, and each Job spins up one or more Pod instances to run your container. When the Pod completes, the Job records the result and Kubernetes cleans up according to your configured retention policy.

This tutorial covers the Kubernetes CronJob spec, key configuration options, and how PandaStack uses Kubernetes under the hood to manage scheduled container workloads for you.

The CronJob Resource

A Kubernetes CronJob is defined in YAML. Here's a minimal example:

apiVersion: batch/v1
kind: CronJob
metadata:
  name: daily-report
spec:
  schedule: "0 6 * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: report-generator
            image: your-registry/report-generator:latest
            env:
            - name: DATABASE_URL
              valueFrom:
                secretKeyRef:
                  name: app-secrets
                  key: database-url
          restartPolicy: OnFailure

Apply it to your cluster:

kubectl apply -f daily-report-cronjob.yaml

# Verify the CronJob was created
kubectl get cronjobs

# Watch Jobs created by the CronJob
kubectl get jobs --watch

Key Spec Fields

schedule

Standard five-field cron expression. Uses UTC by default, though you can configure a timezone in Kubernetes 1.27+.

# Common cron schedules
"*/5 * * * *"    # Every 5 minutes
"0 2 * * *"      # Every day at 2 AM UTC
"0 9 * * 1"      # Every Monday at 9 AM UTC
"0 0 1 * *"      # First of every month

concurrencyPolicy

Controls what happens when a new scheduled run is triggered and a previous run is still executing.

spec:
  concurrencyPolicy: Forbid   # Skip new run if previous is still running (recommended for most jobs)
  # concurrencyPolicy: Allow  # Allow concurrent runs (default, can cause overlap issues)
  # concurrencyPolicy: Replace # Cancel the running job and start a new one

Use Forbid for jobs that must not overlap — database migrations, data sync jobs that process all pending records, or any idempotency-sensitive workload.

startingDeadlineSeconds

If Kubernetes misses a scheduled run (e.g., the scheduler was down), this field controls how late a run can start before being skipped.

spec:
  startingDeadlineSeconds: 300  # Skip if more than 5 minutes late

successfulJobsHistoryLimit and failedJobsHistoryLimit

Control how many completed and failed Job objects are retained for inspection.

spec:
  successfulJobsHistoryLimit: 5
  failedJobsHistoryLimit: 3

A Complete Production CronJob

apiVersion: batch/v1
kind: CronJob
metadata:
  name: data-sync
  namespace: production
spec:
  schedule: "*/30 * * * *"
  concurrencyPolicy: Forbid
  startingDeadlineSeconds: 120
  successfulJobsHistoryLimit: 5
  failedJobsHistoryLimit: 3
  jobTemplate:
    spec:
      activeDeadlineSeconds: 600   # Kill job if it runs longer than 10 minutes
      backoffLimit: 2              # Retry up to 2 times on failure
      template:
        spec:
          restartPolicy: OnFailure
          containers:
          - name: sync
            image: your-registry/sync:latest
            resources:
              requests:
                memory: "128Mi"
                cpu: "100m"
              limits:
                memory: "256Mi"
                cpu: "500m"
            envFrom:
            - secretRef:
                name: sync-secrets

Debugging Kubernetes CronJobs

# List all CronJobs
kubectl get cronjobs -n production

# List Jobs created by a CronJob
kubectl get jobs -l job-name=data-sync -n production

# Get Pod logs from a specific Job
kubectl logs -l job-name=data-sync --tail=100 -n production

# Manually trigger a CronJob
kubectl create job --from=cronjob/data-sync manual-run-$(date +%s) -n production

# Describe a Job to see events and status
kubectl describe job data-sync-28123456 -n production

Managing Kubernetes CronJobs Without the YAML

Writing and maintaining Kubernetes YAML for every scheduled job adds operational overhead — you still need to manage cluster access, secrets, namespaces, and resource limits.

PandaStack uses Kubernetes internally to run scheduled container workloads, but abstracts away the YAML entirely. You interact through the CLI or dashboard, and PandaStack handles the CronJob spec, concurrency policy, and failure handling automatically.

npm install -g @pandastack/cli

# Create a scheduled container job — no YAML required
panda cronjob create \
  --name data-sync \
  --image your-registry/sync:latest \
  --schedule "*/30 * * * *"

# Trigger a manual run
panda cronjob run data-sync

# View execution history
panda cronjob executions data-sync

# Stream logs from the latest run
panda cronjob logs data-sync --latest

PandaStack tracks execution history for every run and streams logs in real time — the same visibility you'd get from kubectl logs without managing cluster credentials. View all job runs at [dashboard.pandastack.io](https://dashboard.pandastack.io).

Summary

Kubernetes CronJobs are a powerful primitive for scheduled workloads, with fine-grained control over concurrency, failure handling, and retention. For teams that want the power of containerized scheduling without managing Kubernetes directly, PandaStack provides a higher-level platform built on top of it. Visit [docs.pandastack.io](https://docs.pandastack.io) to learn more.

Ready to deploy?

Start free on PandaStack — no credit card required.

Start free on PandaStack

More in Tutorial

Browse all Tutorial articles →

See also