Kubernetes
CronJobs
shell scripting
automation
DevOps

How to run shell script using CronJobs in Kubernetes?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

A Kubernetes CronJob does not run a shell script by magic. It creates a Job on a schedule, and that Job starts a container. So the real question is how the container gets the script. In practice, the script is either baked into the image or mounted from a ConfigMap, then executed with a shell entrypoint.

The Simplest Mental Model

The chain is:

  1. CronJob schedule triggers
  2. Kubernetes creates a Job
  3. the Job creates a Pod
  4. the Pod runs a container
  5. the container executes your script

That means a shell script is not a first-class Kubernetes object. It is just part of what the container runs.

Put the Script in the Image

The most reliable pattern is to build the script into the image. A tiny Dockerfile could look like this:

dockerfile
1FROM alpine:3.20
2
3COPY backup.sh /scripts/backup.sh
4RUN chmod +x /scripts/backup.sh
5
6CMD ["/scripts/backup.sh"]

Then define the CronJob:

yaml
1apiVersion: batch/v1
2kind: CronJob
3metadata:
4  name: backup-job
5spec:
6  schedule: "0 2 * * *"
7  jobTemplate:
8    spec:
9      template:
10        spec:
11          restartPolicy: OnFailure
12          containers:
13            - name: backup
14              image: myrepo/backup:latest

This is the cleanest setup because the script and its runtime environment travel together.

Mount the Script from a ConfigMap

If the script is small and changes often, a ConfigMap can work well.

yaml
1apiVersion: v1
2kind: ConfigMap
3metadata:
4  name: cleanup-script
5data:
6  cleanup.sh: |
7    #!/bin/sh
8    echo "Starting cleanup"
9    date

Then mount and execute it:

yaml
1apiVersion: batch/v1
2kind: CronJob
3metadata:
4  name: cleanup-job
5spec:
6  schedule: "*/15 * * * *"
7  jobTemplate:
8    spec:
9      template:
10        spec:
11          restartPolicy: OnFailure
12          containers:
13            - name: cleanup
14              image: alpine:3.20
15              command: ["/bin/sh", "/scripts/cleanup.sh"]
16              volumeMounts:
17                - name: script-volume
18                  mountPath: /scripts
19          volumes:
20            - name: script-volume
21              configMap:
22                name: cleanup-script
23                defaultMode: 0755

This keeps the script in cluster configuration rather than inside the image.

Use command and args Intentionally

When you need shell features such as environment expansion or several commands in sequence, invoke a shell explicitly:

yaml
1command: ["/bin/sh", "-c"]
2args:
3  - |
4    echo "Starting job"
5    /scripts/cleanup.sh

This is useful, but do not overuse multi-line inline shell when the script is large. At that point, a real script file is easier to maintain.

Cron Settings That Matter

Beyond the script itself, CronJobs have behavior controls worth setting:

  • 'concurrencyPolicy to prevent overlapping runs'
  • 'successfulJobsHistoryLimit and failedJobsHistoryLimit'
  • 'startingDeadlineSeconds for missed schedules'

Those settings often matter more operationally than the shell command syntax.

For example:

yaml
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 2
failedJobsHistoryLimit: 3

If your script is not safe to run twice at once, Forbid is an important guardrail.

Debugging the Script

If the CronJob appears to do nothing, inspect it step by step:

bash
1kubectl get cronjobs
2kubectl get jobs
3kubectl get pods
4kubectl logs job/cleanup-job

Most failures are one of these:

  • wrong image
  • script not executable
  • wrong mount path
  • shell missing from the image
  • cron expression not what you expected

Common Pitfalls

  • Assuming Kubernetes runs the script directly without a container runtime context.
  • Mounting the script from a ConfigMap but forgetting execute permissions.
  • Using /bin/bash in an image that only contains /bin/sh.
  • Writing a CronJob that allows overlapping runs even though the script is not idempotent.
  • Inlining large shell programs in YAML when they should live in a script file or image.

Summary

  • A CronJob runs a container on a schedule; the container is what runs the shell script.
  • The script should usually be baked into the image or mounted from a ConfigMap.
  • Use command and args deliberately when shell features are needed.
  • Configure CronJob behavior such as concurrency, history, and deadlines, not just the schedule.
  • If it fails, debug the chain from CronJob to Job to Pod to container logs.

Course illustration
Course illustration

All Rights Reserved.