Kubernetes (Helm)
The official Helm chart in charts/dagu deploys Dagu on Kubernetes 1.19 or newer. Its default configuration is a self-contained standalone installation designed to work with a regular cluster StorageClass.
For the shortest installation path, see Install on Kubernetes.
Deployment modes
| Mode | Components | Storage | Best for |
|---|---|---|---|
standalone (default) | One dagu start-all pod runs the UI, scheduler, and local executor | ReadWriteOnce | Most installations and initial evaluation |
distributed | Separate UI server, scheduler, coordinator, and worker Deployments | ReadWriteMany for server-side state | Horizontal workers and specialized worker pools |
Standalone mode intentionally keeps one UI replica and uses the Recreate update strategy. This prevents two pods from writing the same file-backed state while a ReadWriteOnce volume is attached.
Prerequisites
- Kubernetes 1.19 or newer
- Helm 3
- A default
ReadWriteOnceStorageClass, or the name of one to select - For distributed mode, a StorageClass that supports
ReadWriteMany - For Ingress access, an installed ingress controller and a DNS record for the UI hostname
Install standalone mode
helm repo add dagu https://dagucloud.github.io/dagu
helm repo update
helm upgrade --install dagu dagu/dagu \
--namespace dagu \
--create-namespace \
--waitIf the cluster has no default StorageClass, pass one explicitly:
helm upgrade --install dagu dagu/dagu \
--namespace dagu \
--create-namespace \
--set-string persistence.storageClass=standard \
--waitFrom a Dagu source checkout, replace dagu/dagu with ./charts/dagu.
Rendered resources
For a release named dagu, standalone mode creates these primary resources:
ServiceAccount/daguConfigMap/dagu-configPersistentVolumeClaim/dagu-data, unlesspersistence.existingClaimis setDeployment/dagu-uiService/dagu-uiIngress/dagu-uiwheningress.enabled=true
Distributed mode also creates:
Deployment/dagu-schedulerandService/dagu-schedulerDeployment/dagu-coordinatorandService/dagu-coordinator- one
Deployment/dagu-worker-<pool>for every entry inworkerPools
The chart uses stable selectors so upgrades do not replace workloads merely because chart metadata changes. nameOverride and fullnameOverride can change the resource-name prefix. Run helm get manifest or query resources by the release label when custom names are used:
kubectl --namespace dagu get deployment,service,serviceaccount,configmap,pvc,ingress \
--selector app.kubernetes.io/instance=daguAccess the UI
Port forwarding
The default UI Service is a ClusterIP on port 8080:
kubectl --namespace dagu port-forward service/dagu-ui 8080:8080Open http://localhost:8080. The first visit guides the administrator through builtin-auth setup.
The release notes contain commands with the actual rendered resource names:
helm get notes dagu --namespace daguIngress
Create a values file and replace the ingress class, hostname, and TLS Secret with values for the cluster:
ingress:
enabled: true
className: your-ingress-class
annotations: {}
host: dagu.example.com
tls:
enabled: true
secretName: dagu-tls
config:
publicUrl: https://dagu.example.comThe TLS Secret must exist in the release namespace. Leave ingress.tls.secretName empty only when the ingress controller supplies a default certificate. Provider-specific settings can be added through ingress.annotations.
Apply the values:
helm upgrade --install dagu dagu/dagu \
--namespace dagu \
--create-namespace \
--values dagu-values.yaml \
--waitPoint the hostname at the ingress controller and inspect the assigned address:
kubectl --namespace dagu get ingress dagu-uiOpen https://dagu.example.com. Because the bundled UI and API use the same origin, config.corsAllowedOrigins is not needed for this setup.
When OIDC is enabled, set auth.oidc.clientUrl to the same external URL and register its /oidc-callback path with the identity provider.
Proxy authentication
Proxy-header authentication cannot use the chart-managed Ingress. The authenticating proxy must be the only path to the UI Service. Keep ingress.enabled=false and follow Proxy Authentication.
LoadBalancer or NodePort
Clusters without an ingress controller can expose the Service directly:
ui:
service:
type: LoadBalancer
port: 80
annotations: {}ui.service.port is the Kubernetes Service port. Dagu still listens on ui.containerPort, which defaults to 8080, so exposing Service port 80 does not require the application process to bind a privileged port.
NodePort is also supported. Kubernetes selects the node port because the chart does not set a fixed one.
Persistence
The default persistence settings are:
persistence:
enabled: true
retain: true
existingClaim: ""
accessMode: ReadWriteOnce
size: 10Gi
storageClass: ""
annotations: {}An empty storageClass uses the cluster's default StorageClass. Dagu requires persistence, so persistence.enabled must remain true.
PVC retention
The chart annotates its PVC so Helm retains it when the release is uninstalled. This preserves workflows, run history, credentials, and other Dagu state.
Set persistence.retain: false only when uninstalling the release should also delete the chart-managed PVC. To remove a retained PVC explicitly:
kubectl --namespace dagu delete pvc dagu-dataConfirm the release name and PVC contents before deleting it.
Existing PVC
To mount a PVC managed outside the release:
persistence:
existingClaim: existing-dagu-dataThe claim must already exist in the release namespace. The chart does not create, modify, retain, or delete it.
In distributed mode, also declare persistence.accessMode: ReadWriteMany. The declaration must match the existing PVC; Helm cannot inspect the live claim while rendering.
Distributed mode
Distributed mode is an explicit opt-in:
deploymentMode: distributed
persistence:
accessMode: ReadWriteMany
storageClass: nfs-client
size: 20Gi
worker:
maxActiveRuns: 100
workerPools:
general:
replicas: 2
labels: {}
dataVolume:
sizeLimit: 2GiInstall it with a values file:
helm upgrade --install dagu dagu/dagu \
--namespace dagu \
--create-namespace \
--values distributed-values.yaml \
--waitThe UI server, scheduler, and coordinator share the RWX PVC. Workers use ephemeral pod storage and communicate with the coordinator through its ClusterIP Service; they do not mount the shared PVC.
Each workerPools entry creates an independent worker Deployment. Labels are Dagu worker-selection capabilities, while scheduling fields control Kubernetes placement:
workerPools:
gpu:
replicas: 2
labels:
gpu: "true"
dataVolume:
sizeLimit: 10Gi
resources:
requests:
cpu: "1"
memory: 2Gi
ephemeral-storage: 2Gi
limits:
cpu: "2"
memory: 4Gi
ephemeral-storage: 10Gi
nodeSelector:
accelerator: nvidia
tolerations: []
affinity: {}Non-empty nodeSelector, tolerations, and affinity values on a worker pool override their global counterparts for that pool.
Configuration and environment variables
The chart renders a minimal Dagu configuration into dagu-config and mounts it at /etc/dagu/dagu.yaml. Every Deployment receives a checksum annotation, so configuration changes trigger a rollout.
Common top-level settings include:
config:
publicUrl: https://dagu.example.com
corsAllowedOrigins: []
envPassthrough: []
envPassthroughPrefixes: []
extraEnv: []extraEnv adds variables to every Dagu Deployment. config.envPassthrough and config.envPassthroughPrefixes control which pod environment variables Dagu forwards into workflow processes; they do not create those variables.
Authentication and licensing
Builtin authentication is enabled by default. The chart also supports Secret-backed basic authentication and chart-managed OIDC configuration:
The chart keeps Dagu's runtime-compatible OIDC defaults: first-time identities are created automatically and unmatched validated users receive viewer access to all named workspaces. Set auth.oidc.roleMapping.defaultWorkspaceAccess: none when named workspaces must be isolated, and use allowedDomains, whitelist, or explicit mappings to limit who can sign in.
OIDC and license activation values reference existing Kubernetes Secrets. Secret data is read when the UI pod starts, so restart the UI after changing a referenced Secret:
kubectl --namespace dagu rollout restart deployment/dagu-uiImages and private registries
An empty image.tag uses the chart's appVersion, keeping the default image aligned with the chart release:
image:
repository: ghcr.io/dagucloud/dagu
tag: ""
pullPolicy: IfNotPresentSet an explicit tag only when a different Dagu version is required. Private registry credentials can be applied to every Dagu pod:
imagePullSecrets:
- name: registry-credentialsService account and mounted files
The chart creates a release-scoped ServiceAccount and assigns it to every Dagu pod. Provider-specific annotations can connect it to supported workload identity systems such as EKS IRSA or GKE Workload Identity:
serviceAccount:
create: true
name: ""
annotations:
example.com/workload-identity: daguUse an existing ServiceAccount by disabling creation and setting its name:
serviceAccount:
create: false
name: dagu-runtimeWith create: false and an empty name, Dagu uses the namespace's default ServiceAccount. The chart does not create RoleBindings or grant Kubernetes API access. Bind the permissions required by workflows or the Kubernetes Secret provider to the selected account separately.
Additional volumes and mounts are applied to every Dagu container. This example exposes a custom CA bundle to Dagu and its workflow processes:
extraVolumes:
- name: ca-bundle
secret:
secretName: dagu-ca-bundle
extraVolumeMounts:
- name: ca-bundle
mountPath: /etc/ssl/certs/dagu-ca-bundle.pem
subPath: ca-bundle.pem
readOnly: true
extraEnv:
- name: SSL_CERT_FILE
value: /etc/ssl/certs/dagu-ca-bundle.pem
config:
envPassthrough:
- SSL_CERT_FILEUse unique volume names that do not conflict with the chart-managed data and config volumes. Referenced Secrets, ConfigMaps, PVCs, and CSI resources must already be available in the release namespace where applicable.
Pod settings and resources
These values apply to every Dagu pod:
podAnnotations: {}
nodeSelector: {}
tolerations: []
affinity: {}The default podSecurityContext.fsGroup is 1000 so mounted runtime files remain writable after the image entrypoint switches to the default Dagu user.
The standalone UI container has resource requests but no default limits because local workflow subprocesses run inside it. Set limits only after accounting for the workflows the pod will execute.
Upgrade
Keep release configuration in a values file for reproducible upgrades:
helm repo update
helm upgrade dagu dagu/dagu \
--namespace dagu \
--values dagu-values.yaml \
--waitInspect the proposed manifests before applying them:
helm template dagu dagu/dagu \
--namespace dagu \
--values dagu-values.yamlVerify and troubleshoot
Check the release and run the chart test:
helm status dagu --namespace dagu
kubectl --namespace dagu get pods,pvc,service,ingress
helm test dagu --namespace daguIf a pod remains pending, inspect its events and the PVC:
kubectl --namespace dagu describe pod <pod-name>
kubectl --namespace dagu get pvc
kubectl --namespace dagu describe pvc <pvc-name>
kubectl get storageclassIf Ingress has no address, confirm that ingress.className selects an installed controller. If the address works but the hostname does not, check the DNS record and TLS Secret.
Distributed rendering fails unless persistence.accessMode is ReadWriteMany. A successful render does not guarantee that the selected StorageClass can provision RWX volumes; verify that capability with the storage provider.
Uninstall
helm uninstall dagu --namespace daguThe chart-managed PVC remains by default. See Uninstall before deleting persistent data.
The complete value reference is maintained with the chart in charts/dagu/README.md.

