Scale every Deployment and StatefulSet in selected development namespaces to zero after work. Restore each workload’s original replica count in the morning. This example only schedules actions; it adds no admission restrictions or warnings.

Complete example

apiVersion: addons.projectcapsule.dev/v1alpha1
kind: ChangeWindow
metadata:
  name: after-hours
spec:
  namespaceSelectors:
    - matchLabels:
        environment: development
  schedules:
    - name: every-night
      cron: "0 19 * * *"
      timeZone: Europe/Zurich
      duration: 12h
  serviceAccount:
    namespace: schedule-action-controller-system
    name: after-hours-executor
  targets:
    - name: deployments
      apiVersion: apps/v1
      kind: Deployment
    - name: statefulsets
      apiVersion: apps/v1
      kind: StatefulSet
  actions:
    - name: sleep-and-resume
      targets: [deployments, statefulsets]
      capture:
        replicas:
          subresource: scale
          path: /spec/replicas
      onStart:
        apply:
          subresource: scale
          force: true
          template: |
            spec:
              replicas: 0
      onEnd:
        apply:
          subresource: scale
          template: |-
            spec:
              replicas: {{ .captured.replicas }}

Download after-hours.yaml

The window starts every day at 19:00 in Europe/Zurich and lasts 12 elapsed hours, normally restoring workloads at 07:00. timeZone is set per schedule within the ChangeWindow, so different windows can use different IANA zones without changing controller configuration. Its default is UTC.

On a night when clocks change for daylight saving, twelve elapsed hours can end at 06:00 or 08:00 local time. The API specifies a duration, not a separate local end time. See time zones.

Workloads and namespace scope

Label the affected namespaces environment=development. Omitting target label selectors selects all Deployments and StatefulSets in those namespaces. Use namespaceSelectors: [{}] to include every eligible namespace, and grant executor permissions in each one. Protected infrastructure namespaces remain excluded.

These workloads expose a /scale endpoint. DaemonSets, Jobs, CronJobs, and standalone Pods do not share that scaling behavior and are not selected. ReplicaSets managed by Deployments scale through their owning Deployment.

Each workload’s replica count is captured before downscaling. A workload with three replicas resumes with three; one already at zero stays at zero. Captures survive controller restarts. Newly created workloads join the next window, and deleted workloads or replacements with a different UID are not restored.

Executor permissions

This manifest grants access in the existing development namespace. Repeat the workload Role and RoleBinding for each selected namespace. Adjust the controller namespace and manager ServiceAccount subject for your Helm release, following the executor setup instructions. Set spec.serviceAccount.namespace in the ChangeWindow to the same executor namespace and retain the executor name after-hours-executor.

# Example: an executor in the controller namespace may scale Deployments and StatefulSets only in
# the existing namespace "development". Repeat workload RoleBindings as needed.
apiVersion: v1
kind: ServiceAccount
metadata:
  name: after-hours-executor
  namespace: schedule-action-controller-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: impersonate-after-hours-executor
  namespace: schedule-action-controller-system
rules:
  - apiGroups: [""]
    resources: [serviceaccounts]
    resourceNames: [after-hours-executor]
    verbs: [impersonate]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: impersonate-after-hours-executor
  namespace: schedule-action-controller-system
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: impersonate-after-hours-executor
subjects:
  - kind: ServiceAccount
    name: schedule-action-controller-controller-manager
    namespace: schedule-action-controller-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: after-hours-scale
  namespace: development
rules:
  - apiGroups: [apps]
    resources: [deployments, statefulsets]
    verbs: [get, list]
  - apiGroups: [apps]
    resources: [deployments/scale, statefulsets/scale]
    verbs: [get, patch]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: after-hours-scale
  namespace: development
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: after-hours-scale
subjects:
  - kind: ServiceAccount
    name: after-hours-executor
    namespace: schedule-action-controller-system

Download after-hours-executor-rbac.yaml

The start action explicitly takes ownership of spec.replicas using force. Coordinate with HPAs, GitOps reconciliation, and other replica writers: this action runs once at the start and end, rather than continuously holding replicas at zero. See resource actions.