Application templates are rendered and deployed by Nelm. Nelm extends the standard Helm behavior with annotations that control deployment order, resource lifecycle, readiness tracking, and log output. This page covers all annotations available in Application templates.
Deployment stages
Nelm processes a deployment in three stages. Different annotation groups affect different stages.
1. Render
Templates are evaluated with the current values. No cluster access is needed (except for lookup). At this stage:
werf.io/deploy-oncontrols whether a resource is included in the render output for the current operation type (install,upgrade, etc.).- Template functions (
werf_secret_file,dump_debug, etc.) are executed. secret-values.yamland files fromsecret/are decrypted.
2. Plan
Nelm connects to the cluster, reads current resource state, and runs a dry-run Server-Side Apply to compute the exact diff. It then builds a DAG of operations — which resources to create, update, or delete, and in what order.
At this stage:
- Order and dependency annotations (
werf.io/weight,werf.io/deploy-dependency-*,*.external-dependency.werf.io/*) shape the DAG. - Lifecycle annotations (
werf.io/ownership,werf.io/delete-policy,werf.io/delete-propagation) determine which operations enter the plan.
Dry-run SSA means the diff is computed by the API server — defaulting, admission plugins, and mutating webhooks are all applied.
3. Apply
The DAG is executed: resources are created, updated, or deleted in dependency order, with parallelism where no dependencies exist. Readiness tracking and log streaming run concurrently.
At this stage:
- Tracking annotations (
werf.io/track-termination-mode,werf.io/fail-mode,werf.io/failures-allowed-per-replica,werf.io/no-activity-timeout) control waiting behavior. - Log annotations control what is printed during deployment.
Common use cases
1. Job that must run before the main application
Classic case: database migration before the application starts.
apiVersion: batch/v1
kind: Job
metadata:
name: {{.Application.Instance.Name}}-db-migrate
annotations:
werf.io/delete-policy: before-creation
spec: ...
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{.Application.Instance.Name}}-app
annotations:
werf.io/deploy-dependency-migrate: state=ready,kind=Job,name=db-migrate
spec: ...
before-creation ensures the Job is recreated on each deploy (Kubernetes otherwise rejects updates to immutable Job fields). The Deployment waits for ready — i.e., successful job completion.
2. Preserve a resource across uninstall or chart removal
Use case: a PVC with database data that must not be deleted even when the Application is uninstalled.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{.Application.Instance.Name}}-postgres-data
annotations:
helm.sh/resource-policy: keep
spec:
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 50Gi
storageClassName: gp3
helm.sh/resource-policy: keep prevents the PVC from being deleted on uninstall or when removed from the chart. It does not protect against d8 k delete pvc — for that, use persistentVolumeReclaimPolicy: Retain on the StorageClass.
3. Resource shared between releases
A TLS Secret used by multiple charts should not disappear when any one of them is uninstalled.
apiVersion: v1
kind: Secret
metadata:
name: {{.Application.Instance.Name}}-shared-tls
annotations:
werf.io/ownership: anyone
type: kubernetes.io/tls
data: ...
anyone tells Nelm it is not the sole owner, so it skips deletion on uninstall.
4. Wait for a resource created by an operator
Deploy only after cert-manager issues a certificate:
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{.Application.Instance.Name}}-app
annotations:
cert.external-dependency.werf.io/resource: certificates.v1.cert-manager.io/myapp-tls
spec: ...
Nelm waits for the Certificate to be present and ready before creating the Deployment.
5. Non-critical component that must not fail the deploy
A metrics DaemonSet whose unavailability should not block the release:
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: {{.Application.Instance.Name}}-metrics-agent
annotations:
werf.io/fail-mode: IgnoreAndContinueDeployProcess
werf.io/track-termination-mode: NonBlocking
spec: ...
Nelm will not wait for readiness and will not fail on timeout.
6. Resource rendered only on first install
An init Job that runs only during install, not upgrade:
apiVersion: batch/v1
kind: Job
metadata:
name: {{.Application.Instance.Name}}-init-data
annotations:
werf.io/deploy-on: install
werf.io/ownership: anyone # CRITICAL
spec: ...
Without werf.io/ownership: anyone, on upgrade the resource renders as absent and the owning release removes it. anyone prevents that.
7. Slow-starting resource with a large image
StatefulSet with a large Docker image (ML models, Elasticsearch with pre-loaded indices):
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: elasticsearch
annotations:
werf.io/no-activity-timeout: 20m
werf.io/failures-allowed-per-replica: "3"
werf.io/show-service-messages: "true"
spec: ...
no-activity-timeout: 20m— 20 minutes without events before Nelm considers it timed out. Use this for slow image pulls or long initialization.failures-allowed-per-replica: "3"— allows up to 3 restarts per replica before declaring failure. Use for flapping init dependencies.show-service-messages: "true"— emit Kubernetes Events in deploy output (useful for diagnosingImagePullBackOff, scheduling failures, OOM events).
Ordering and dependencies
Weights
werf.io/weight groups resources: equal weights deploy in parallel; different weights deploy sequentially in ascending order.
metadata:
annotations:
werf.io/weight: "-10" # Deploys before default-weight (0) resources
Direct dependencies
werf.io/deploy-dependency-<id> waits for a specific resource in the same release to reach a specific state before deploying the annotated resource.
metadata:
annotations:
werf.io/deploy-dependency-db: state=ready,kind=StatefulSet,name=postgres
werf.io/deploy-dependency-migrations: state=present,kind=Job,name=db-migrate
Dependency states:
ready— resource is in a ready state (e.g., Deployment’savailableReplicas == replicas).present— resource exists in the cluster.
Full format:
werf.io/deploy-dependency-<id>: state=ready|present[,name=<name>][,namespace=<namespace>][,kind=<kind>][,group=<group>][,version=<version>]
This annotation has no effect if the dependency resource is in a different deploy stage (pre/main/post). Cross-stage ordering is already enforced by the stage sequence itself.
External dependencies
<id>.external-dependency.werf.io/resource waits for a resource outside the release (created by an operator or another release):
metadata:
annotations:
cert.external-dependency.werf.io/resource: certificates.v1.cert-manager.io/myapp-tls
cert.external-dependency.werf.io/name: myapp-production # Namespace of the external resource
Full format:
<id>.external-dependency.werf.io/resource: <kind>[.<version>.<group>]/<name>
Delete dependencies
werf.io/delete-dependency-<id> — the annotated resource will be deleted only after the referenced resource becomes absent:
metadata:
annotations:
werf.io/delete-dependency-app: state=absent,kind=Deployment,name=app
Lifecycle annotations
helm.sh/resource-policy
keep — do not delete the resource on uninstall or when removed from the chart. The resource continues to be updated on install/upgrade as long as it renders.
werf.io/ownership
release(default for regular resources) — resource is deleted on uninstall and when absent from the chart. Release metadata annotations are applied.anyone(default for hooks and CRDs incrds/) — resource is not deleted on uninstall. Release metadata annotations are not applied.
Use anyone for resources shared between releases or for resources that should survive their release (init Jobs with werf.io/deploy-on: install).
werf.io/deploy-on
Controls on which lifecycle operations the resource is rendered.
werf.io/deploy-on: pre-install,upgrade,post-install
Allowed values: pre-install, install, post-install, pre-upgrade, upgrade, post-upgrade, pre-rollback, rollback, post-rollback, pre-uninstall, uninstall, post-uninstall. Default: install,upgrade,rollback.
If a resource is rendered for install but not upgrade, and werf.io/ownership: release is set, the resource will be deleted on upgrade because it is absent from the upgrade render. Set werf.io/ownership: anyone to prevent this.
werf.io/delete-policy
Controls when a resource is deleted relative to the apply operation.
| Value | When |
|---|---|
before-creation |
Always recreate before apply |
before-creation-if-immutable |
Recreate only on “field is immutable” error (default for Jobs) |
succeeded |
Delete after successful deploy |
failed |
Delete on readiness-check failure |
Values can be combined with a comma: before-creation,succeeded.
werf.io/delete-propagation
Kubernetes deletion propagation strategy.
| Value | Behavior |
|---|---|
Foreground (default) |
Wait for dependent objects to be deleted |
Background |
Delete resource immediately; dependents deleted asynchronously |
Orphan |
Delete resource; leave dependents |
Tracking annotations
| Annotation | Default | Description |
|---|---|---|
werf.io/track-termination-mode |
WaitUntilResourceReady |
WaitUntilResourceReady or NonBlocking |
werf.io/fail-mode |
FailWholeDeployProcessImmediately |
FailWholeDeployProcessImmediately or IgnoreAndContinueDeployProcess |
werf.io/failures-allowed-per-replica |
1 |
Number of restarts per replica before declaring failure |
werf.io/no-activity-timeout |
4m |
Go duration; timeout on absence of events or status changes |
werf.io/show-service-messages |
false |
Show Kubernetes Events in deploy output |
Log annotations
| Annotation | Default | Description |
|---|---|---|
werf.io/skip-logs |
false |
Suppress all pod logs |
werf.io/skip-logs-for-containers |
— | Comma-separated list of containers to suppress |
werf.io/show-logs-only-for-containers |
— | Comma-separated list; only show logs for these containers |
werf.io/show-logs-only-for-number-of-replicas |
1 |
Show logs only for the first N replicas |
werf.io/log-regex |
— | RE2 pattern; show only matching lines |
werf.io/log-regex-skip |
— | RE2 pattern; suppress matching lines |
werf.io/log-regex-for-<container> |
— | Per-container RE2 filter for showing |
werf.io/log-regex-skip-for-<container> |
— | Per-container RE2 filter for suppressing |
Template functions
werf_secret_file
Embed the decrypted content of a file from the secret/ directory:
data:
config.yaml: {{ werf_secret_file "config.yaml" | b64enc }}
Files in secret/ are stored encrypted (AES-128-CBC, key from NELM_SECRET_KEY). They are decrypted in memory during render. Use for certificates, private keys, and large configs that don’t fit in secret-values.yaml.
dump_debug, printf_debug, include_debug, tpl_debug
Debug-level output functions that emit to logs without affecting rendered output. Activated only when debug log level is set by the deploy system.
{{ dump_debug $ }}
{{ printf_debug "replicaCount: %d" .Values.replicaCount }}
{{ include_debug "myapp.labels" . | nindent 4 }}
{{ tpl_debug "{{ .Values.template }}" . }}
Known pitfalls
-
nullvalues and SSA. Server-Side Apply frequently fails on fields with anullvalue. If.Values.fooisnil, the manifest getsfoo: null. Fix with{{ if .Values.foo }}guards ordefault. -
lookuptiming. If cluster resources change between the Plan and Apply stages, the rendered plan may be stale. Avoidlookupfor critical logic; pass data through values instead. -
Non-deterministic functions.
now,randAlphaNum, and map iteration with variable key ordering produce different renders on each call. With plan freezing this can create spurious diffs. -
werf.io/deploy-dependency-*and stages. The annotation has no effect across deploy stages (pre/main/post). Cross-stage ordering is implicit in the stage sequence.