Cluster Templates let you share workflows across Kubernetes namespaces by abstracting the common parts of a workflow into a reusable template.
Let’s see this in action. Imagine we have a common CI/CD pipeline that we want to deploy to multiple projects, each living in its own namespace. Instead of copying and pasting the same WorkflowTemplate definition into each namespace’s GitOps repository, we can define it once and then create Workflow resources that reference it.
Here’s a WorkflowTemplate that defines a simple build and deploy process:
# workflow-templates/build-and-deploy.yaml
apiVersion: argoproj.io/v1alpha1
kind: WorkflowTemplate
metadata:
name: build-and-deploy
namespace: argo-templates # This is where the template lives
spec:
entrypoint: build-and-deploy
templates:
- name: build-and-deploy
dag:
tasks:
- name: build
template: build-step
- name: deploy
template: deploy-step
dependencies: [build]
- name: build-step
container:
image: docker:latest
command: ["sh", "-c"]
args: ["echo 'Building image...'; sleep 30; echo 'Image built.'"]
- name: deploy-step
container:
image: alpine:latest
command: ["sh", "-c"]
args: ["echo 'Deploying application...'; sleep 15; echo 'Deployment complete.'"]
This WorkflowTemplate is stored in the argo-templates namespace. It defines a DAG with two steps: build and deploy. The build step uses a docker:latest image to simulate building, and the deploy step uses alpine:latest to simulate deployment.
Now, to use this template in a specific project namespace, say project-a, we create a Workflow that references it:
# project-a/workflows/deploy-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
name: my-app-deployment
namespace: project-a # This is where the workflow runs
spec:
workflowTemplateRef:
name: build-and-deploy
namespace: argo-templates # Points to the template's namespace
When Argo Workflows sees this Workflow resource, it doesn’t execute the steps directly from this definition. Instead, it looks up the WorkflowTemplate named build-and-deploy in the argo-templates namespace and effectively uses that definition as the blueprint for this specific Workflow run. The Workflow resource itself becomes a lightweight pointer, reducing duplication and making it easy to manage common patterns.
The problem this solves is the "copy-paste" anti-pattern when managing repetitive workflow structures across different teams or projects. Without cluster templates, updating a common pipeline requires modifying it in many places. With them, you change the WorkflowTemplate once in the argo-templates namespace, and all Workflow resources referencing it automatically pick up the changes. This promotes consistency and reduces the cognitive load on developers.
Internally, when an Argo Workflows controller processes a Workflow that has a workflowTemplateRef, it fetches the referenced WorkflowTemplate. It then merges the WorkflowTemplate’s spec into the Workflow’s spec. If there are any specific parameters or overrides defined in the Workflow resource, they are applied during this merge. This dynamic instantiation means that each Workflow run is a distinct execution, even though it’s based on a shared template.
A common point of confusion is how parameters are handled. You can define parameters in the WorkflowTemplate and then pass values to them when creating the Workflow. For example, if our build-step needed a specific image tag, we could modify the template:
# workflow-templates/build-and-deploy-with-params.yaml
apiVersion: argoproj.io/v1alpha1
kind: WorkflowTemplate
metadata:
name: build-and-deploy-with-params
namespace: argo-templates
spec:
entrypoint: build-and-deploy
templates:
- name: build-and-deploy
dag:
tasks:
- name: build
template: build-step
- name: deploy
template: deploy-step
dependencies: [build]
- name: build-step
inputs:
parameters:
- name: image-tag
default: "latest" # Default value
container:
image: docker:latest
command: ["sh", "-c"]
args: ["echo 'Building image with tag {{inputs.parameters.image-tag}}...'; sleep 30; echo 'Image built.'"]
- name: deploy-step
container:
image: alpine:latest
command: ["sh", "-c"]
args: ["echo 'Deploying application...'; sleep 15; echo 'Deployment complete.'"]
And then in the Workflow resource:
# project-a/workflows/deploy-app-specific.yaml
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
name: my-app-deployment-v1
namespace: project-a
spec:
workflowTemplateRef:
name: build-and-deploy-with-params
namespace: argo-templates
arguments:
parameters:
- name: image-tag
value: "v1.2.3" # Passing a specific tag
This allows for customization of shared templates without duplicating the entire template definition. The key is that the arguments in the Workflow map directly to the inputs.parameters defined in the referenced WorkflowTemplate.
The namespace field within the WorkflowTemplate’s metadata is crucial; it dictates where the template itself is stored and must be explicitly referenced in the Workflow’s workflowTemplateRef.namespace field. If this is omitted, Argo Workflows will look for the template in the same namespace as the Workflow resource, which defeats the purpose of cross-namespace sharing.
The next logical step is to explore how to manage more complex dependencies and conditional logic within these shared templates.