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.