A CronJob manifest
apiVersion: batch/v1
kind: CronJob
metadata:
name: report
spec:
schedule: "0 9 * * 1-5" # standard 5-field cron
timeZone: "America/New_York" # stable since k8s 1.27; default is UTC
concurrencyPolicy: Forbid # don't overlap runs
startingDeadlineSeconds: 200 # skip if more than 200s late
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 1
jobTemplate:
spec:
template:
spec:
restartPolicy: Never
containers:
- name: report
image: myorg/report:1.4
args: ["--run"]Gotchas
- UTC by default. Without
timeZone,0 9 * * *fires at 09:00 UTC, not your local 9am. SettimeZone(k8s ≥ 1.27). - concurrencyPolicy. Default
Allowlets a slow run overlap the next one. UseForbidorReplacefor jobs that must not double-run. - Missed runs. If the controller is down past
startingDeadlineSeconds, a run is skipped — and if more than 100 schedules are missed, the CronJob stops scheduling entirely. - History limits. Set
successfulJobsHistoryLimitso finished Jobs/Pods don't pile up.
Monitor it free with cron.watch
A CronJob that stops scheduling (missed deadline, bad image, quota) produces no Pod — so there are no logs to alert on. A schedule is only a promise — cron.watch tells you if the job actually ran, alerts you when it silently fails, skips, or runs long, and keeps a history of every run.
# add a check-in as the last step of the container command args: - "sh" - "-c" - "/app/report --run && curl -fsS https://cron.watch/s/your-id"