Skip to content
Declarative Temporal schedules

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.

FileTriggerHighlights
00-shared.yamlShared TemporalCluster (app) + TemporalNamespace (scheduling). Apply first.
nightly-report.yamlCron (0 2 * * *, NY time)args, retryPolicy, memo, overlapPolicy: Skip, pauseOnFailure
healthcheck-every-15m.yamlInterval (every: 15m, offset: 2m)jitter
business-hours-sync.yamlStructured calendarweekdays 09:00–17:00 hourly, no cron syntax
maintenance-window.yamlCron (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-report

The 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:

  1. The referenced TemporalCluster is Ready.
  2. The target Temporal namespace exists. The operator does not auto-create namespaces, so 00-shared.yaml registers one (scheduling) via a TemporalNamespace.
  3. A worker is polling the schedule’s taskQueue for the named workflowType. Without a worker the schedules still appear in the Temporal UI and temporal 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 scheduling

Manifests

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: inventory

healthcheck-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: ops

maintenance-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