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