component-tasks
Renders CronJobs, worker Deployments, and manual jobs for an app. All three share the parent app's image, pull secret, and envFrom secret via the global: block.
When to use it
- Cron: run a command on a schedule (
./craft up,./craft cleanup, …). - Worker: long-running process that consumes a queue (
./craft queue:work). - Manual: one-off command you trigger by hand (migrations, backfills, cache warm). Rendered as a suspended
CronJobso it never auto-fires.
Syntax
component-tasks:
cron:
- name: heartbeat
schedule: "* * * * *"
command: ["./craft", "up"]
- name: nightly-cleanup
schedule: "0 3 * * *"
command: ["./craft", "cleanup"]
resources:
requests:
cpu: 50m
memory: 64Mi
workers:
- name: queue-default
command: ["./craft", "queue:work"]
replicas: 2
- name: queue-emails
command: ["./craft", "queue:work", "emails"]
replicas: 1
manual:
- name: db-migrate
command: ["./craft", "migrate"]
- name: cache-warm
command: ["./craft", "cache:warm"]
- Empty lists (the default) produce no resources.
- Generated names:
<global.nameOverride>-cron-<name>,<global.nameOverride>-worker-<name>, and<global.nameOverride>-manual-<name>.
Cron fields
| Field | Required | Default | Notes |
|---|---|---|---|
name | yes | — | Used in resource name + labels |
schedule | yes | — | Standard cron expression |
command | yes | — | Container command (array form) |
concurrencyPolicy | no | Forbid | Skip new run if previous still running |
successfulJobsHistoryLimit | no | 3 | |
failedJobsHistoryLimit | no | 1 | |
startingDeadlineSeconds | no | — | Drop jobs that miss their slot by this much |
backoffLimit | no | — | Retries before the Job is failed |
activeDeadlineSeconds | no | — | Hard timeout for the Job |
restartPolicy | no | OnFailure | Pod restart policy |
image | no | parent | { repository, tag, pullPolicy } per-task override |
resources | no | — | Standard resource requests/limits |
Worker fields
| Field | Required | Default | Notes |
|---|---|---|---|
name | yes | — | |
command | yes | — | |
replicas | no | 1 | |
terminationGracePeriodSeconds | no | 30 | |
image | no | parent | Per-worker override |
resources | no | — | |
livenessProbe | no | — | Raw probe spec (e.g. exec: { command: [...] }) |
Manual fields
Same shape as cron, minus schedule and startingDeadlineSeconds.
| Field | Required | Default | Notes |
|---|---|---|---|
name | yes | — | Used in resource name + labels |
command | yes | — | Container command (array form) |
concurrencyPolicy | no | Forbid | |
successfulJobsHistoryLimit | no | 3 | |
failedJobsHistoryLimit | no | 1 | |
backoffLimit | no | — | Retries before the Job is failed |
activeDeadlineSeconds | no | — | Hard timeout for the Job |
restartPolicy | no | OnFailure | Pod restart policy |
image | no | parent | Per-task override |
resources | no | — |
Triggering a manual job
The rendered CronJob has suspend: true and a placeholder schedule (0 0 31 2 * — Feb 31, never fires). Run it on demand with:
kubectl create job <app>-manual-<name>-$(date +%s) \
--from=cronjob/<app>-manual-<name> \
-n <namespace>
Example:
kubectl create job myapp-manual-db-migrate-$(date +%s) \
--from=cronjob/myapp-manual-db-migrate \
-n myapp-staging
Generated resources
Each cron entry → one batch/v1 CronJob. Each worker entry → one apps/v1 Deployment with restartPolicy: Always. Each manual entry → one suspended batch/v1 CronJob. All inherit image and pull secret from global.image / global.imagePullSecrets, with envFrom pointing at global.secretName.
Labels and the app Service
Cron pods are labelled app: <global.nameOverride>, task: cron, task-name: <name>; worker pods the same with task: worker; manual job pods the same with task: manual. They share the app label with the main workload but intentionally lack the component: web label, so the app's Service — which selects app and component: web (see App charts → Service and pod labels) — never sends HTTP traffic to a queue worker, cron, or manual pod.