## DevWorkspace Operator Configuration The DevWorkspace Operator installs the `DevWorkspaceOperatorConfig` Custom Resource Definition (short name: `dwoc`). ### Controller configuration A `DevWorkspaceOperatorConfig` Custom Resource defines the behavior of the DevWorkspace Operator Controller. To see documentation on configuration settings, including default values, use `kubectl explain` or `oc explain` -- e.g. `kubectl explain dwoc.config.routing.clusterHostSuffix`. **The only required configuration setting is `.routing.clusterHostSuffix`, which is required when running on Kubernetes.** Configuration settings in the `DevWorkspaceOperatorConfig` override default values found in [pkg/config](https://github.com/devfile/devworkspace-operator/tree/main/pkg/config). ### Global configuration for the DevWorkspace Operator To configure global behavior of the DevWorkspace Operator, create a `DevWorkspaceOperatorConfig` named `devworkspace-operator-config` in the same namespace where the operator is deployed: ```yaml apiVersion: controller.devfile.io/v1alpha1 kind: DevWorkspaceOperatorConfig metadata: name: devworkspace-operator-config namespace: $OPERATOR_INSTALL_NAMESPACE config: # Configuration fields ``` ### DevWorkspace specific configuration To apply a configuration to a specific `DevWorkspace` instead of globally, an existing `DevWorkspaceOperatorConfig` can be referenced in a `DevWorkspace`'s attributes: ```yaml apiVersion: workspace.devfile.io/v1alpha2 kind: DevWorkspace metadata: name: my-devworkspace spec: template: attributes: controller.devfile.io/devworkspace-config: name: namespace: ``` Configuration specified as above will be merged into the default global configuration, overriding any values present. Note that fields set in the global configuration cannot be cleared (reset to unset) by a workspace-specific configuration, as the merge strategy only overrides fields that are explicitly set. ## Configuring workspace pod scheduling The following: [pod affinity](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity), [pod anti-affinity](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity) and [topology spread constraints](https://kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/) can be configured for workspace pods, PVC cleanup pods and backup pods. Workspace restore is covered implicitly, as it runs as an init container in the workspace pod. These configuration options exist in the DWOC's `config.workspace` field: ```yaml apiVersion: controller.devfile.io/v1alpha1 kind: DevWorkspaceOperatorConfig metadata: name: devworkspace-operator-config namespace: $OPERATOR_INSTALL_NAMESPACE config: workspace: podAffinity: podAntiAffinity: topologySpreadConstraints: <[]topologySpreadConstraint> ``` A typical use case is configuring a scheduling policy that applies to all workspaces at once (e.g. to keep workspaces sharing a persistent volume claim on the same node for I/O performance). For example, to co-locate workspace pods in the same namespace on the same node, which is useful with the per-user storage strategy where workspaces in a namespace share a persistent volume claim: ```yaml apiVersion: controller.devfile.io/v1alpha1 kind: DevWorkspaceOperatorConfig metadata: name: devworkspace-operator-config namespace: $OPERATOR_INSTALL_NAMESPACE config: workspace: podAffinity: # An empty labelSelector ({}) matches all pods in the same namespace as # the workspace pod being scheduled preferredDuringSchedulingIgnoredDuringExecution: - labelSelector: {} topologyKey: kubernetes.io/hostname ``` To spread workspace pods evenly across availability zones, preferring balance but still scheduling pods when an even spread is not possible: ```yaml apiVersion: controller.devfile.io/v1alpha1 kind: DevWorkspaceOperatorConfig metadata: name: devworkspace-operator-config namespace: $OPERATOR_INSTALL_NAMESPACE config: workspace: topologySpreadConstraints: - maxSkew: 1 topologyKey: topology.kubernetes.io/zone # Use DoNotSchedule to make the spread a hard requirement whenUnsatisfiable: ScheduleAnyway labelSelector: matchExpressions: - key: controller.devfile.io/devworkspace_id operator: Exists ``` ## Configuring the Webhook deployment The `devworkspace-webhook-server` deployment can be configured in the global `DevWorkspaceOperatorConfig`. The configuration options include: [replicas](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#replicas), [pod tolerations](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) and [nodeSelector](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#nodeselector). These configuration options exist in the **global** DWOC's `config.webhook` field: ```yaml apiVersion: controller.devfile.io/v1alpha1 kind: DevWorkspaceOperatorConfig metadata: name: devworkspace-operator-config namespace: $OPERATOR_INSTALL_NAMESPACE config: webhook: nodeSelector: tolerations: <[]tolerations> replicas: ``` **Note:** In order for the `devworkspace-webhook-server` configuration options to take effect: - You must place them in the [Global configuration for the DevWorkspace Operator](#global-configuration-for-the-devworkspace-operator), which has the name `devworkspace-operator-config` and exists in the namespace where the DevWorkspaceOperator is installed. If it does not already exist on the cluster, you must create it. - You'll need to terminate the `devworkspace-controller-manager` pod so that the replicaset can recreate it. The new pod will update the `devworkspace-webhook-server` deployment. ## Configuring Cleanup CronJob The DevWorkspace cleanup job helps manage resources by removing DevWorkspaces that have not been started for a configurable period of time. **Note:** By default, the DevWorkspace cleanup job is disabled. You can control the behaviour of the DevWorkspace cleanup job through the `config.workspace.cleanupCronJob` section of the global DWOC:: ```yaml apiVersion: controller.devfile.io/v1alpha1 kind: DevWorkspaceOperatorConfig metadata: name: devworkspace-operator-config namespace: $OPERATOR_INSTALL_NAMESPACE config: workspace: cleanupCronJob: enabled: true dryRun: false retainTime: 2592000 schedule: "0 0 1 * *" ``` Cleanup CronJob configuration fields: - **`enable`**: Set to `true` to enable the cleanup job, `false` to disable it. Default: `false`. - **`schedule`**: A Cron expression defining how often the cleanup job runs. Default: `"0 0 1 * *"` (first day of the month at midnight). - **`retainTime`**: The duration time in seconds since a DevWorkspace was last started before it is considered stale and eligible for cleanup. Default: 2592000 seconds (30 days). - **`dryRun`**: Set to `true` to run the cleanup job in dry-run mode. In this mode, the job logs which DevWorkspaces would be removed but does not actually delete them. Set to `false` to perform the actual deletion. Default: `false`. ## Configuring Backup CronJob The DevWorkspace backup job allows for periodic backups of DevWorkspace data to a specified backup location. Once enabled and configured, the backup job will run at defined intervals to create backups of DevWorkspace data. The backup controller depends on an OCI-compatible registry e.g., [quay.io](https://quay.io/) used as an image artifact storage for backup archives. The backup makes a snapshot of Workspace PVCs and stores them as tar.gz archives in the specified OCI registry. **Note:** By default, the DevWorkspace backup job is disabled. Backup CronJob configuration fields: - **`enable`**: Set to `true` to enable the backup job, `false` to disable it. Default: `false`. - **`schedule`**: A Cron expression defining how often the backup job runs. Default: `"0 1 * * *"`. - **`registry.path`**: A base registry location where the backup archives will be pushed. The value provided for registry.path is only the first segment of the final location. The full registry path is assembled dynamically, incorporating the name of the workspace and the :latest tag, following this pattern: `/:latest` - **`registry.authSecret`**: (Optional) The name of the secret in the **operator namespace** to copy to workspace namespaces. The secret is always copied as `devworkspace-backup-registry-auth` in the workspace namespace. If not provided, backup/restore jobs proceed without authentication. - **`oras.extraArgs`**: (Optional) Additional arguments to pass to the `oras` CLI tool during push and pull operations. There are several configuration options to customize the logic: ### Integrated OpenShift container registry This option is available only on OpenShift clusters with integrated container registry enabled and requires no additional configuration. To enable the backup use following configuration in the global DWOC: ```yaml apiVersion: controller.devfile.io/v1alpha1 kind: DevWorkspaceOperatorConfig metadata: name: devworkspace-operator-config namespace: $OPERATOR_INSTALL_NAMESPACE config: routing: defaultRoutingClass: basic workspace: backupCronJob: enable: true registry: path: default-route-openshift-image-registry.apps.{cluster ID}.openshiftapps.com schedule: '0 */4 * * *' # cron expression with backup frequency imagePullPolicy: Always ``` **Note:** The `path` field must contain the URL to your OpenShift integrated registry given by the cluster. Once the backup job is finished, the backup archives will be available in the DevWorkspace namespace under a repository with a matching Devworkspace name. ### Regular OCI-compatible registry To use a regular OCI-compatible registry for backups, you need to provide registry credentials. Depending on your RBAC policy, the token can be provided via a secret in the operator namespace or in each DevWorkspace namespace. Having the secret in the DevWorkspace namespace allows for using different registry accounts per namespace with more granular access control. ```yaml kind: DevWorkspaceOperatorConfig apiVersion: controller.devfile.io/v1alpha1 metadata: name: devworkspace-operator-config namespace: $OPERATOR_INSTALL_NAMESPACE config: routing: defaultRoutingClass: basic workspace: backupCronJob: enable: true registry: authSecret: my-secret path: quay.io/my-company-org schedule: '0 */4 * * *' imagePullPolicy: Always ``` The `authSecret` must point to a real Kubernetes Secret of type `kubernetes.io/dockerconfigjson` containing credentials to access the registry. To create one you can use following command: ```bash kubectl create secret docker-registry my-secret --from-file=config.json -n devworkspace-controller ``` The secret must contain a label `controller.devfile.io/watch-secret=true` to be recognized by the DevWorkspace Operator. ```bash kubectl label secret my-secret controller.devfile.io/watch-secret=true -n devworkspace-controller ``` ### Restore workspace from backup DevWorkspaces can be restored from a backup by setting the `controller.devfile.io/restore-workspace: 'true'` attribute. When this attribute is set, the workspace deployment includes a restore init container that pulls the backed-up `/projects` content from an OCI registry instead of cloning from Git. By default, the restore source is derived from the admin-configured registry at `//:latest`. Users can optionally specify a custom source image using the `controller.devfile.io/restore-source-image` attribute. ```yaml kind: DevWorkspace spec: template: attributes: controller.devfile.io/restore-workspace: 'true' # Optional: restore from a specific image instead of the default backup registry controller.devfile.io/restore-source-image: 'registry.example.com/my-backup:latest' ``` ## Configuring PVC storage access mode By default, PVCs managed by the DevWorkspace Operator are created with the `ReadWriteOnce` access mode. The access mode can be configured with the `config.workspace.storageAccessMode` section of the global DWOC: ```yaml apiVersion: controller.devfile.io/v1alpha1 kind: DevWorkspaceOperatorConfig metadata: name: devworkspace-operator-config namespace: $OPERATOR_INSTALL_NAMESPACE config: workspace: storageAccessMode: - ReadWriteMany ``` The config above will have newly created PVCs to have its access mode set to `ReadWriteMany`. ## Configuring Workspace NetworkPolicy By default, DevWorkspace pods accept traffic from anywhere in the cluster. Administrators can enable NetworkPolicy provisioning to restrict workspace network access. When enabled, the operator creates one NetworkPolicy per DevWorkspace, named `-networkpolicy` in the workspace's namespace. Each policy applies only to the pods of its own workspace, selected by the `controller.devfile.io/devworkspace_id` label. **Rule semantics:** - Omitting a rule field (e.g., no `ingress` key) means the operator's default rules for that direction apply. - Setting a rule field to an empty list (e.g., `ingress: []`) denies all traffic in that direction. - Setting `ingress` or `egress` to a non-empty list applies exactly those rules and replaces the defaults entirely rather than appending to them. Any default rule that should be kept must be repeated in the configuration. **Lifecycle:** - A workspace's policy is synced on every reconcile of that workspace, whether it is running or stopped, and before any workspace pod is created. - Changing this configuration does not enqueue the workspaces it affects, so a new value reaches a given workspace on its next reconcile rather than immediately. Restarting a workspace is not required. - The policy is owned by its DevWorkspace, so it is garbage collected when the workspace is deleted. - Disabling the feature (setting `enabled: false`) removes the policies of both running and stopped workspaces, restoring connectivity rather than leaving stale policies in place. - While the feature is enabled, the policy of a stopped workspace is left in place, where it governs no pods, and is removed when the workspace is deleted. ```yaml apiVersion: controller.devfile.io/v1alpha1 kind: DevWorkspaceOperatorConfig metadata: name: devworkspace-operator-config namespace: $OPERATOR_INSTALL_NAMESPACE config: workspace: networkPolicy: enabled: true ingress: - {} egress: - {} ``` ## Configuring Custom Init Containers The DevWorkspace Operator allows cluster administrators to inject custom init containers into all workspace pods via the `config.workspace.initContainers` field in the global DWOC. This feature enables use cases such as: - Injecting organization-specific tools or configurations - Customizing the persistent home directory initialization logic - Extracting cluster utilities (e.g., `oc` CLI) to ensure version compatibility **Security Note:** Only trusted administrators should have RBAC permissions to edit the `DevWorkspaceOperatorConfig`, as custom init containers run in every workspace and can execute arbitrary code. ### Basic Example: Injecting Custom Tools ```yaml apiVersion: controller.devfile.io/v1alpha1 kind: DevWorkspaceOperatorConfig metadata: name: devworkspace-operator-config namespace: $OPERATOR_INSTALL_NAMESPACE config: workspace: initContainers: - name: inject-oc-cli image: registry.redhat.io/openshift4/ose-cli:latest command: ["/bin/sh", "-c"] args: - | cp /usr/bin/oc /home/user/bin/oc cp /usr/bin/kubectl /home/user/bin/kubectl volumeMounts: - name: persistent-home mountPath: /home/user/ ``` ### Special Container: `init-persistent-home` A specially-named init container `init-persistent-home` can be used to override the built-in persistent home directory initialization logic when `config.workspace.persistUserHome.enabled: true`. This is useful for enterprises using customized UDI images that require different home directory setup logic. **Prerequisites for `init-persistent-home`:** - **Name:** Must be exactly `init-persistent-home` - **Image:** Optional. If omitted, defaults to the first non-imported workspace container's image. If no suitable image can be inferred, the workspace will fail to start with an error. - **Command:** Optional. If omitted, defaults to `["/bin/sh", "-c"]`. If provided, can be any valid command array. - **Args:** Optional. If omitted and command is also omitted, defaults to a single script string. If provided, can be any valid args array. - **VolumeMounts:** Forbidden. The operator automatically mounts the `persistent-home` volume at `/home/user/`. - **Env:** Optional. Environment variables are allowed. - **Other fields:** Not allowed. Fields such as `ports`, `probes`, `lifecycle`, `securityContext`, `resources`, `volumeDevices`, `stdin`, `tty`, and `workingDir` are rejected to keep behavior predictable. **Note:** If `persistUserHome.enabled` is `false`, any `init-persistent-home` container is ignored. ### Example: Custom Persistent Home Initialization ```yaml apiVersion: controller.devfile.io/v1alpha1 kind: DevWorkspaceOperatorConfig metadata: name: devworkspace-operator-config namespace: $OPERATOR_INSTALL_NAMESPACE config: workspace: persistUserHome: enabled: true initContainers: - name: init-persistent-home # image: optional - defaults to workspace image # command: optional - defaults to ["/bin/sh", "-c"] args: - | echo "Enterprise home init" # Custom logic for enterprise UDI rsync -a --ignore-existing /home/tooling/ /home/user/ || true touch /home/user/.home_initialized env: - name: CUSTOM_VAR value: "custom-value" ``` ### Execution Order Custom init containers are injected after the project-clone init container in the order they are defined in the configuration. The `init-persistent-home` container runs in this sequence along with other custom init containers. ## Always-restricted override fields Regardless of configuration, certain fields are **always** rejected in overrides and cannot be unblocked by any configuration change. **Container overrides:** `name`, `image`, `command`, `args`, `ports`, `env` **Pod overrides:** `containers`, `initContainers` These implicit restrictions exist separately from the configurable restricted fields described below. ## Restricting override fields The DevWorkspace Operator allows cluster administrators to restrict which fields can be set via `pod-overrides` and `container-overrides` attributes. The restrict list supports two formats: - `"fieldName"` -- restricts the field entirely, regardless of value - `"fieldName=value"` -- restricts only a specific value for the field For nested fields such as securityContext or volumes, use dot notation: `securityContext.privileged=true`. On Kubernetes, the operator ships with default restricted fields that align with the Pod Security Standards baseline profile. On OpenShift, no fields are restricted by default since Security Context Constraints (SCC) already enforce security policies at the admission level. **Important:** Configuring `restrictedContainerOverrideFields` or `restrictedPodOverrideFields` **replaces** the platform defaults entirely. Admins who want to extend the default restrict list must re-include the default entries alongside any additional restrictions. **Limitation for plain boolean fields in pod overrides:** Some `PodSpec` fields such as `hostNetwork`, `hostPID`, and `hostIPC` are plain `bool` types in the Kubernetes API (not `*bool` pointers). Because Go zero-initializes unset `bool` fields to `false`, the operator cannot distinguish between a field that was explicitly set to `false` and one that was simply omitted. As a result, using the bare field name format (e.g. `"hostNetwork"`) to restrict these fields entirely will reject **all** pod overrides, including those that never mention the field. To avoid this, use the value-specific format instead (e.g. `"hostNetwork=true"`). The default restricted fields already follow this pattern. This limitation does not affect `*bool` pointer fields (e.g. `automountServiceAccountToken`, `shareProcessNamespace`, `hostUsers`) or non-boolean fields. For example, on Kubernetes, to add `volumeMounts` and `lifecycle` restrictions while keeping the default restricted container fields: ```yaml apiVersion: controller.devfile.io/v1alpha1 kind: DevWorkspaceOperatorConfig metadata: name: devworkspace-operator-config config: workspace: overrides: restrictedContainerOverrideFields: # Default Kubernetes restricted fields (must be re-listed to retain them) - "securityContext.privileged=true" - "securityContext.runAsNonRoot=false" - "securityContext.runAsUser=0" - "securityContext.allowPrivilegeEscalation=true" - "securityContext.procMount=Unmasked" - "securityContext.capabilities.add" # Additional restrictions - "volumeMounts" - "lifecycle" ``` Similarly, to extend the default restricted pod override fields on Kubernetes: ```yaml apiVersion: controller.devfile.io/v1alpha1 kind: DevWorkspaceOperatorConfig metadata: name: devworkspace-operator-config config: workspace: overrides: restrictedPodOverrideFields: # Default Kubernetes restricted fields (must be re-listed to retain them) - "hostNetwork=true" - "hostPID=true" - "hostIPC=true" - "securityContext.runAsNonRoot=false" - "securityContext.runAsUser=0" - "volumes.hostPath" # Additional restrictions - "hostUsers=false" ```