Kubernetes
Run a workflow step as a Kubernetes Job.
The Kubernetes executor has two equivalent type aliases:
type: k8stype: kubernetes
Dagu creates a single-container Kubernetes Job for the step, waits for the Job to finish, streams the Pod logs, and uses the terminated container exit code when available.
Basic Usage
steps:
- id: hello
type: k8s
config:
image: alpine:3.20
command: echo "hello from kubernetes"If you omit command, Dagu leaves the container command unset, so the image default command/entrypoint runs instead:
steps:
- id: run_image_default
type: kubernetes
config:
image: ghcr.io/example/app:latestDAG-Level Defaults
Use the root kubernetes: block for defaults shared by explicit Kubernetes steps:
kubernetes:
kubeconfig: /etc/dagu/kubeconfig
context: production
namespace: batch
service_account: dagu-runner
resources:
requests:
cpu: "100m"
memory: "128Mi"
steps:
- id: report
type: k8s
config:
image: alpine:3.20
command: echo "generate report"
- id: worker
type: kubernetes
config:
image: ghcr.io/example/worker:1.2.3
namespace: jobsImportant behavior:
- The root
kubernetes:block applies only to steps withtype: k8sortype: kubernetes. - It does not change plain command steps into Kubernetes steps.
- Step
configoverrides DAG-levelkubernetes. imageis optional in DAG-level orbase.yamldefaults, but it must be present in the effective step config after inheritance.
Merge behavior is Kubernetes-specific:
- Scalar values replace inherited values.
- Nested objects merge by key, with the step value winning.
- Arrays replace inherited arrays wholesale.
- Empty objects and empty arrays can clear inherited nested values.
The same kubernetes: block can also be set in base.yaml. Precedence is:
base.yamlkubernetes- DAG-level
kubernetes - Step-level
config
See Base Configuration for the base.yaml form.
Cluster Resolution
Dagu resolves Kubernetes client configuration in this order:
- Explicit
config.kubeconfig - Default kubeconfig loading rules (
KUBECONFIG, then the standard local kubeconfig locations) - In-cluster configuration
If you set kubeconfig or context explicitly and they are invalid, Dagu fails fast instead of silently falling back to in-cluster credentials.
Command Behavior
The Kubernetes executor is intentionally narrow:
- Supports a single command only
- Does not support
script: - Does not support step-level
shell: - Does not support multi-command
command: [...]lists where each item is a separate command
These are valid:
steps:
- id: string_command
type: k8s
config:
image: alpine:3.20
command: echo "hello"
- id: argv_command
type: k8s
config:
image: python:3.12-alpine
command: [python3, -c, 'print("hello")']These are not supported:
steps:
- id: bad_script
type: k8s
config:
image: alpine:3.20
script: |
echo hello
- id: bad_multi_command
type: k8s
config:
image: alpine:3.20
command:
- echo one
- echo twoLogging and Cleanup
- Kubernetes exposes a merged container log stream, so stdout and stderr are streamed as a single log stream for this executor.
cleanup_policydefaults todelete, which removes the Job after completion.cleanup_policy: keepkeeps the Job after normal completion or job-reported failure.cleanup_policy: keepalso keeps the Job when pod scheduling fails before the workload starts.- Cancellation, kill, and timeout paths still force cleanup even when
cleanup_policyiskeep.
Configuration Reference
Cluster and Runtime
| Field | Description | Default |
|---|---|---|
image | Container image for the Job | Required in effective step config |
namespace | Kubernetes namespace | default |
kubeconfig | Explicit path to kubeconfig | - |
context | Kubeconfig context name | current context |
working_dir | Working directory inside the container | image default |
service_account | Pod service account name | cluster default |
image_pull_policy | Pull policy: Always, IfNotPresent, Never | unset |
image_pull_secrets | Secret names for private registries | - |
priority_class_name | Pod priority class name | unset |
termination_grace_period_seconds | Pod shutdown grace period in seconds | cluster default |
image_pull_policy is case-insensitive. When omitted, Dagu leaves it unset. termination_grace_period_seconds must be non-negative.
Environment
env sets explicit environment variables:
config:
env:
- name: APP_ENV
value: production
- name: POD_NAME
value_from:
field_ref:
field_path: metadata.nameSupported value_from sources:
secret_key_refconfig_map_key_reffield_ref
env_from imports whole ConfigMaps or Secrets:
config:
env_from:
- config_map_ref:
name: app-config
prefix: APP_
- secret_ref:
name: app-secretsRules enforced by Dagu:
- Every
enventry must includename. valueandvalue_fromare mutually exclusive.value_frommust define exactly one source.secret_key_ref.name,secret_key_ref.key,config_map_key_ref.name,config_map_key_ref.key, andfield_ref.field_pathare required when their source is used.- Every
env_fromentry must define exactly one ofconfig_map_reforsecret_ref. config_map_ref.nameandsecret_ref.nameare required.
Resources, Scheduling, and Metadata
| Field | Description |
|---|---|
resources.requests | Resource requests map, typically cpu / memory |
resources.limits | Resource limits map, typically cpu / memory |
node_selector | Pod node selector labels |
tolerations | Pod tolerations |
affinity | Node affinity, pod affinity, and pod anti-affinity |
labels | Labels applied to both the Job and Pod |
annotations | Annotations applied to both the Job and Pod |
Resource quantities use Kubernetes quantity syntax such as 100m, 128Mi, or 1Gi. Invalid or negative quantities are rejected during config validation.
Security Context
security_context configures container-level Linux security settings:
config:
security_context:
run_as_user: 1000
run_as_group: 1000
run_as_non_root: true
privileged: false
read_only_root_filesystem: true
allow_privilege_escalation: false
capabilities:
drop: [ALL]
seccomp_profile:
type: RuntimeDefaultSupported fields:
run_as_userrun_as_grouprun_as_non_rootprivilegedread_only_root_filesystemallow_privilege_escalationcapabilities.addcapabilities.dropseccomp_profile.typeseccomp_profile.localhost_profile
Rules enforced by Dagu:
run_as_userandrun_as_groupmust be non-negative when set.capabilities.addandcapabilities.dropmust contain only non-empty strings.seccomp_profile.typemust beRuntimeDefault,Unconfined, orLocalhost.seccomp_profile.localhost_profileis required only whentypeisLocalhost.
Pod Security Context
pod_security_context configures Pod-level Linux defaults:
config:
pod_security_context:
run_as_user: 1000
run_as_group: 1000
run_as_non_root: true
fs_group: 2000
fs_group_change_policy: OnRootMismatch
supplemental_groups: [3000, 4000]
sysctls:
- name: net.ipv4.ip_unprivileged_port_start
value: "0"
seccomp_profile:
type: RuntimeDefaultSupported fields:
run_as_userrun_as_grouprun_as_non_rootfs_groupfs_group_change_policysupplemental_groupssysctls[].namesysctls[].valueseccomp_profile.typeseccomp_profile.localhost_profile
Rules enforced by Dagu:
run_as_user,run_as_group,fs_group, and everysupplemental_groupsentry must be non-negative.fs_group_change_policymust beAlwaysorOnRootMismatchwhen set.- Every
sysctlsentry must include non-emptynameandvalue. seccomp_profileuses the same validation rules assecurity_context.
Container-level security_context and Pod-level pod_security_context can both be set. Dagu maps both to Kubernetes and does not try to collapse them into one structure.
Affinity
affinity supports the typed subset implemented by the executor:
config:
affinity:
node_affinity:
required_during_scheduling_ignored_during_execution:
node_selector_terms:
- match_expressions:
- key: kubernetes.io/arch
operator: In
values: [amd64]
preferred_during_scheduling_ignored_during_execution:
- weight: 50
preference:
match_expressions:
- key: topology.kubernetes.io/zone
operator: In
values: [ap-northeast-1a]
pod_anti_affinity:
preferred_during_scheduling_ignored_during_execution:
- weight: 100
pod_affinity_term:
topology_key: kubernetes.io/hostname
label_selector:
match_labels:
app: report-workerSupported fields:
node_affinity.required_during_scheduling_ignored_during_execution.node_selector_terms[].match_expressions[]node_affinity.preferred_during_scheduling_ignored_during_execution[].weightnode_affinity.preferred_during_scheduling_ignored_during_execution[].preference.match_expressions[]pod_affinity.required_during_scheduling_ignored_during_execution[]pod_affinity.preferred_during_scheduling_ignored_during_execution[].weightpod_affinity.preferred_during_scheduling_ignored_during_execution[].pod_affinity_termpod_anti_affinitywith the same shape aspod_affinitylabel_selector.match_labelslabel_selector.match_expressionsnamespace_selector.match_labelsnamespace_selector.match_expressionsnamespacestopology_key
Rules enforced by Dagu:
- Node selector requirement operators must be
In,NotIn,Exists,DoesNotExist,Gt, orLt. InandNotInrequire at least one value.ExistsandDoesNotExistrequirevaluesto be empty.GtandLtrequire exactly one integer value.- Pod and label selector operators are limited to
In,NotIn,Exists, andDoesNotExist. - Every preferred affinity weight must be between
1and100. - Every pod affinity or anti-affinity term must set
topology_key. namespacesentries must be non-empty strings.
At the DAG or base level, affinity: {} clears inherited affinity defaults. You can also clear only the required node-affinity block with:
config:
affinity:
node_affinity:
required_during_scheduling_ignored_during_execution: {}Job Lifecycle
| Field | Description | Default |
|---|---|---|
active_deadline | Kubernetes Job active deadline in seconds | - |
backoff_limit | Kubernetes Job retry count | 0 |
ttl_after_finished | Kubernetes TTL-after-finished in seconds | - |
cleanup_policy | delete or keep | delete |
pod_failure_policy | Kubernetes Job pod failure policy rules | - |
Notes:
active_deadline,backoff_limit, andttl_after_finishedmust be non-negative.ttl_after_finishedmatters only if the Job remains after completion, for example withcleanup_policy: keep.
pod_failure_policy supports this typed subset:
config:
pod_failure_policy:
rules:
- action: FailJob
on_exit_codes:
operator: In
values: [42]
- action: Ignore
on_pod_conditions:
- type: DisruptionTargetSupported fields:
rules[].actionrules[].on_exit_codes.container_namerules[].on_exit_codes.operatorrules[].on_exit_codes.valuesrules[].on_pod_conditions[].typerules[].on_pod_conditions[].status
Rules enforced by Dagu:
rulesmay contain at most20rules.- Each rule must define exactly one of
on_exit_codesoron_pod_conditions. - Supported
actionvalues areFailJob,Ignore, andCount. FailIndexis rejected because this executor creates non-indexed Jobs.on_exit_codes.operatormust beInorNotIn.on_exit_codes.valuesmust contain1to255unique integers.- With
operator: In, exit code0is not allowed. on_pod_conditionsmay contain at most20patterns per rule.on_pod_conditions[].typeis required.on_pod_conditions[].statusmay beTrue,False, orUnknown. If omitted, Kubernetes defaults it toTrue.
At the DAG or base level, pod_failure_policy: {} or pod_failure_policy: { rules: [] } clears inherited rules.
Volumes
Each volume entry must define exactly one source:
empty_dirhost_pathconfig_mapsecretpersistent_volume_claim
Example:
config:
volumes:
- name: scratch
empty_dir:
medium: Memory
size_limit: 256Mi
- name: app-config
config_map:
name: my-config
- name: data
persistent_volume_claim:
claim_name: shared-data
volume_mounts:
- name: scratch
mount_path: /tmp/work
- name: app-config
mount_path: /etc/app
read_only: true
- name: data
mount_path: /dataSupported source shapes:
empty_dir.mediumempty_dir.size_limithost_path.pathhost_path.typeconfig_map.namesecret.secret_namepersistent_volume_claim.claim_namepersistent_volume_claim.read_only
Complete Example
type: graph
kubernetes:
context: production
namespace: batch
service_account: dagu-runner
image_pull_secrets: [regcred]
pod_security_context:
run_as_non_root: true
fs_group: 2000
seccomp_profile:
type: RuntimeDefault
affinity:
pod_anti_affinity:
preferred_during_scheduling_ignored_during_execution:
- weight: 100
pod_affinity_term:
topology_key: kubernetes.io/hostname
label_selector:
match_labels:
app: dagu-job
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "512Mi"
labels:
app: dagu
team: platform
steps:
- id: migrate
type: k8s
config:
image: ghcr.io/example/migrator:2026-03-31
cleanup_policy: keep
ttl_after_finished: 3600
priority_class_name: batch-high
security_context:
run_as_user: 1000
run_as_group: 1000
run_as_non_root: true
read_only_root_filesystem: true
allow_privilege_escalation: false
capabilities:
drop: [ALL]
env:
- name: DATABASE_URL
value_from:
secret_key_ref:
name: app-secrets
key: database_url
command: [app, migrate]
- id: report
type: kubernetes
depends: [migrate]
config:
image: ghcr.io/example/report:2026-03-31
termination_grace_period_seconds: 30
annotations:
batch.example.com/purpose: nightly-report
pod_failure_policy:
rules:
- action: FailJob
on_exit_codes:
operator: In
values: [42]
command: [app, report, --date, "${DATE}"]Unsupported Fields
The current executor does not expose:
- raw Pod spec or Job spec passthrough
- Job
parallelism,completions,completion_mode, orsuccess_policy restart_policyconfiguration- Windows-specific security options
- SELinux, AppArmor, or
proc_mountsettings
If a field is not listed on this page, assume it is not currently supported by the executor.
