Install via the official manifests
kubectl create namespace argo
kubectl apply -n argo -f https://github.com/argoproj/argo-workflows/releases/download/v3.6.0/install.yaml
# Install the CLI
curl -sLO https://github.com/argoproj/argo-workflows/releases/download/v3.6.0/argo-linux-amd64.gz
gunzip argo-linux-amd64.gz
chmod +x argo-linux-amd64
sudo mv argo-linux-amd64 /usr/local/bin/argo
# Optional: web UI
kubectl -n argo port-forward svc/argo-server 2746:2746
# Visit https://localhost:2746
The first workflow
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
generateName: hello-world-
spec:
entrypoint: main
templates:
- name: main
container:
image: alpine:latest
command: [sh, -c]
args: ["echo Hello from Argo on $(hostname); sleep 5"]
argo submit -n argo hello.yaml --watch
# Streams logs as the step runs
# List workflows
argo list -n argo
# Logs of past runs
argo logs -n argo hello-world-abc123
Multi-step DAG
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
generateName: build-test-deploy-
spec:
entrypoint: pipeline
arguments:
parameters:
- name: image_tag
value: latest
templates:
- name: pipeline
dag:
tasks:
- name: lint
template: cmd
arguments:
parameters: [{name: cmd, value: "npm run lint"}]
- name: test
template: cmd
arguments:
parameters: [{name: cmd, value: "npm test"}]
- name: build
template: cmd
dependencies: [lint, test]
arguments:
parameters: [{name: cmd, value: "docker build -t myapp:{{workflow.parameters.image_tag}} ."}]
- name: deploy-staging
template: cmd
dependencies: [build]
arguments:
parameters: [{name: cmd, value: "kubectl apply -f staging.yaml"}]
- name: smoke-test
template: cmd
dependencies: [deploy-staging]
arguments:
parameters: [{name: cmd, value: "./smoke-test.sh staging"}]
- name: deploy-prod
template: cmd
dependencies: [smoke-test]
when: "{{workflow.parameters.environment}} == prod"
arguments:
parameters: [{name: cmd, value: "kubectl apply -f prod.yaml"}]
- name: cmd
inputs:
parameters: [{name: cmd}]
container:
image: alpine/k8s:1.32.0
command: [sh, -c, "{{inputs.parameters.cmd}}"]
The dag: block declares dependencies; Argo runs steps in parallel when their dependencies are met. when: conditions skip steps; failed steps short-circuit downstream.
Artifacts: pass files between steps
templates:
- name: build
container:
image: golang:1.23
command: [sh, -c]
args: ["go build -o app .; cp app /output/"]
outputs:
artifacts:
- name: binary
path: /output/app
- name: test
inputs:
artifacts:
- name: binary
path: /app/app
container:
image: alpine:latest
command: [sh, -c, "/app/app --test"]
Argo stores artifacts in S3 / MinIO / Azure Blob / GCS (configured at install time); each step gets / produces them transparently. Useful for "build once, test in N variants" patterns without rebuilding.
Parameters + loops (with-items / withParam)
# Fan out a parallel step across a list
- name: build-matrix
steps:
- - name: build
template: build-one
arguments:
parameters: [{name: arch, value: "{{item}}"}]
withItems:
- amd64
- arm64
- arm/v7
# Or dynamic (from JSON output of a previous step)
- name: process-files
steps:
- - name: list-files
template: list
- - name: process
template: process-one
arguments:
parameters: [{name: file, value: "{{item}}"}]
withParam: "{{steps.list-files.outputs.result}}"
Exit handlers + on-success / on-failure
spec:
entrypoint: main
onExit: cleanup
templates:
- name: main
dag: { ... }
- name: cleanup
steps:
- - name: notify-slack
template: notify
when: "{{workflow.status}} != Succeeded"
- - name: cleanup-temp
template: cleanup-pvc
The onExit handler always runs; conditional on success/failure via when: expressions referencing {{workflow.status}}.
CronWorkflow: scheduled workflows
apiVersion: argoproj.io/v1alpha1
kind: CronWorkflow
metadata:
name: nightly-etl
spec:
schedule: "0 3 * * *"
timezone: "America/Toronto"
concurrencyPolicy: Forbid # don't start a new one if last is still running
successfulJobsHistoryLimit: 5
failedJobsHistoryLimit: 10
workflowSpec:
entrypoint: etl
templates: [...]
Like a Kubernetes CronJob, but for full multi-step workflows. The argo CLI / UI shows historical runs.
WorkflowTemplate + ClusterWorkflowTemplate
For reuse, factor workflows into templates referenced from other workflows:
apiVersion: argoproj.io/v1alpha1
kind: WorkflowTemplate
metadata:
name: build-container
spec:
templates:
- name: build
inputs:
parameters: [{name: image}, {name: tag}]
container:
image: gcr.io/kaniko-project/executor
args: [
"--destination={{inputs.parameters.image}}:{{inputs.parameters.tag}}",
...
]
# Reference in any workflow
templateRef:
name: build-container
template: build
Argo Events: trigger workflows from external events
Argo Events (sister project) provides event-source → sensor → trigger:
- Event sources: webhooks, S3 events, Kafka messages, GitHub webhooks, scheduled, cron
- Sensors: filter / transform events
- Triggers: launch Argo Workflows (or other K8s resources)
"When a file lands in S3 / a webhook arrives / a Kafka topic gets a message → run this workflow with these parameters."
Web UI
Port-forward the argo-server service; the UI shows a real-time DAG visualization of each workflow, per-step logs streaming live, parameter editing, manual resubmit, retry of failed nodes from the point of failure (saves rerunning successful prior steps).
Argo Workflows vs alternatives
- Dagster (see that tutorial) — asset-first; better for data pipelines where lineage matters. Argo is task-first; better for "I just need to run these jobs in order."
- Airflow — Python DAGs; massive ecosystem; heavier infrastructure. Argo is YAML + containers; lighter; K8s-native.
- Prefect — Python flows; lighter than Airflow; less K8s-native than Argo.
- GitHub Actions (with self-hosted runners) — CI-focused; not a general-purpose workflow engine.
- Kubeflow Pipelines — built on top of Argo Workflows; ML-focused abstraction over the same engine.
- Tekton — Kubernetes-native CI; smaller scope (CI/CD specifically); Argo is the more general engine.
When Argo is the right pick
- You already run Kubernetes and want workflows that scale by adding cluster capacity, not by managing a separate orchestrator's worker pool.
- Each step is naturally a container (Docker image already exists or is easy to build).
- Mix of batch processing, ML, custom one-off jobs — one engine to learn.
When it isn't
- If you don't run Kubernetes, Argo is the wrong starting point — the abstraction overhead overwhelms the value.
- For data-pipeline lineage / data-asset thinking, Dagster fits better.
- For "I just need a cron job," plain Kubernetes CronJob is one resource vs Argo's CronWorkflow CRD overhead.