Declarative Temporal schedules
Four TemporalSchedule resources that exercise the different trigger styles and
options the CRD supports. Each schedule is its own file; they share a cluster and
namespace defined in 00-shared.yaml.
| File | Trigger | Highlights |
|---|---|---|
00-shared.yaml | — | Shared TemporalCluster (app) + TemporalNamespace (scheduling). Apply first. |
nightly-report.yaml | Cron (0 2 * * *, NY time) | args, retryPolicy, memo, overlapPolicy: Skip, pauseOnFailure |
healthcheck-every-15m.yaml | Interval (every: 15m, offset: 2m) | jitter |
business-hours-sync.yaml | Structured calendar | weekdays 09:00–17:00 hourly, no cron syntax |
maintenance-window.yaml | Cron (Sundays 03:00) | starts paused: true |
All four schedules set allowDeletion: true, so deleting the CR also deletes the
schedule in Temporal. Set it to false (the default) to leave the schedule in
place when the CR is removed.
Apply
Apply the shared cluster + namespace first, then any subset of schedules:
kubectl apply -f 00-shared.yaml
kubectl apply -f nightly-report.yaml
# ...or apply everything in the directory at once (00-shared sorts first):
kubectl apply -f .
kubectl get temporalschedules # short name: tsch
kubectl describe tsch nightly-reportThe printed READY column reflects the Ready condition; PAUSED reflects the
live schedule state.
Prerequisites for workflows to actually run
Creating a TemporalSchedule registers the schedule with Temporal, but a fired
action only makes progress when:
- The referenced
TemporalClusterisReady. - The target Temporal namespace exists. The operator does not auto-create
namespaces, so
00-shared.yamlregisters one (scheduling) via aTemporalNamespace. - A worker is polling the schedule’s
taskQueuefor the namedworkflowType. Without a worker the schedules still appear in the Temporal UI andtemporal schedule list; they just won’t execute.
To reuse an existing cluster/namespace, skip 00-shared.yaml and update
clusterRef.name / namespace in each schedule file to match your environment.
Try it
# Pause / resume in place by editing state.paused, then re-apply:
kubectl patch tsch healthcheck-every-15m --type merge \
-p '{"spec":{"state":{"paused":true}}}'
# Inspect from inside the cluster with the Temporal CLI:
temporal schedule list --namespace scheduling --address <frontend>:7233
temporal schedule describe --schedule-id nightly-report --namespace schedulingManifests
00-shared.yaml
# Shared prerequisites for the schedule examples in this directory.
#
# Every schedule here points at this cluster (clusterRef.name: app) and runs in
# this namespace (namespace: scheduling). Apply this file first, then any of the
# per-schedule files.
#
# Reusing an existing cluster/namespace? Skip this file and update clusterRef /
# namespace in the schedule files to match your environment. The operator does
# NOT auto-create Temporal namespaces, so the namespace must be registered by a
# TemporalNamespace before a schedule can use it.
---
apiVersion: temporal.bmor10.com/v1alpha1
kind: TemporalCluster
metadata:
name: app
spec:
version: "1.31.1"
numHistoryShards: 512
persistence:
defaultStore:
sql: { pluginName: postgres12, host: postgres.default.svc, port: 5432, database: temporal, user: temporal, passwordSecretRef: { name: temporal-store, key: password } }
visibilityStore:
sql: { pluginName: postgres12, host: postgres.default.svc, port: 5432, database: temporal_visibility, user: temporal, passwordSecretRef: { name: temporal-store, key: password } }
---
apiVersion: temporal.bmor10.com/v1alpha1
kind: TemporalNamespace
metadata:
name: scheduling
spec:
clusterRef: { name: app }
retentionPeriod: "168h"
description: "Namespace for schedule examples"business-hours-sync.yaml
# Structured calendar: weekdays, every hour from 09:00-17:00 (UTC).
# Field-level control without cron syntax. Ranges are inclusive [start,end].
#
# Requires 00-shared.yaml (cluster "app" + namespace "scheduling").
apiVersion: temporal.bmor10.com/v1alpha1
kind: TemporalSchedule
metadata:
name: business-hours-sync
spec:
clusterRef: { name: app }
namespace: scheduling
allowDeletion: true
schedule:
timezoneName: "UTC"
structuredCalendar:
- comment: "hourly on the hour, weekdays 9-17"
hour:
- { start: 9, end: 17 }
minute:
- { start: 0, end: 0 }
dayOfWeek:
- { start: 1, end: 5 } # Mon(1)..Fri(5); 0 and 7 are Sunday
action:
startWorkflow:
workflowType: SyncInventory
taskQueue: inventoryhealthcheck-every-15m.yaml
# Interval schedule: every 15 minutes (with a 2m phase offset) and jitter.
#
# Requires 00-shared.yaml (cluster "app" + namespace "scheduling").
apiVersion: temporal.bmor10.com/v1alpha1
kind: TemporalSchedule
metadata:
name: healthcheck-every-15m
spec:
clusterRef: { name: app }
namespace: scheduling
allowDeletion: true
schedule:
intervals:
- every: "15m"
offset: "2m"
jitter: "30s" # randomize each fire time by 0..30s
action:
startWorkflow:
workflowType: Healthcheck
taskQueue: opsmaintenance-window.yaml
# Paused schedule: created but not firing. Flip state.paused to false and
# re-apply to resume; the operator reconciles pause/unpause in place.
#
# Requires 00-shared.yaml (cluster "app" + namespace "scheduling").
apiVersion: temporal.bmor10.com/v1alpha1
kind: TemporalSchedule
metadata:
name: maintenance-window
spec:
clusterRef: { name: app }
namespace: scheduling
allowDeletion: true
schedule:
calendars:
- "0 3 * * 0" # Sundays at 03:00
action:
startWorkflow:
workflowType: RunMaintenance
taskQueue: ops
state:
paused: true
notes: "paused until the next maintenance window is approved"nightly-report.yaml
# Calendar (cron) schedule: nightly at 02:00 New York time.
# Exercises args, a retry policy, a memo, an overlap policy, and allowDeletion.
#
# Requires 00-shared.yaml (cluster "app" + namespace "scheduling").
apiVersion: temporal.bmor10.com/v1alpha1
kind: TemporalSchedule
metadata:
name: nightly-report
spec:
clusterRef: { name: app }
namespace: scheduling
allowDeletion: true # let the operator delete the schedule with the CR
schedule:
timezoneName: "America/New_York"
calendars:
- "0 2 * * *" # standard 5-field cron; also supports @daily etc.
action:
startWorkflow:
workflowType: GenerateReport
taskQueue: reports
workflowID: nightly-report # optional; defaults to a generated ID
# Args are raw JSON payloads (one json/plain payload per list item).
args:
- { "scope": "daily", "limit": 1000 }
- "us-east"
workflowExecutionTimeout: "1h"
retryPolicy:
initialInterval: "10s"
backoffCoefficient: "2.0" # decimal string
maximumInterval: "5m"
maximumAttempts: 5
memo:
owner: { "team": "analytics" }
policies:
overlapPolicy: Skip # Skip;BufferOne;BufferAll;CancelOther;TerminateOther;AllowAll
catchupWindow: "1h"
pauseOnFailure: true