Skip to main content

ZenML: ML Pipeline Orchestration & MLOps Framework

ZenML is the stack-agnostic MLOps framework that makes ML pipelines portable. Write pipeline logic once with @step and @pipeline, then switch between local, Airflow, Kubeflow, Vertex AI, or Sagemaker by changing the active Stack — zero code changes.

✓ Stack-agnostic orchestration ✓ Automatic artifact versioning ✓ Step-level caching ✓ MLflow/W&B experiment tracking integration
Share this tool: X / Twitter LinkedIn
✓ 60,000+ developers use DevPlaybook ★★★★★ 4.8 / 5 on Gumroad ✓ MIT licensed · Instant download ✓ No login required

Related Products

on Gumroad

You Might Also Need

More Free Tools

Enjoying this tool? Get more like it.

Weekly developer tool guides, productivity playbooks & AI tricks. Free. No spam.

DevPlaybook Pro 7 days free

Unlock AI-Powered Dev Tools

  • ⚡ AI Code Review, Doc Generator & SQL Builder
  • ⚡ All premium templates & early access
  • ⚡ Member discounts on Gumroad products
Start Free Trial →

$9/mo after trial · Cancel anytime

Want the full toolkit?

Get DevPlaybook Pro

Every template, guide, boilerplate, and automation script in one bundle. 13 premium products — grab them all at once and save big.

$79
$241 separately Save $162 (67% off)
Get DevPlaybook Pro — $79 →

MIT licensed · Instant download · No subscription

See what's included → Browse all deals →

Defining Steps & Pipelines

Install with pip install zenml and zenml init. Define steps with type annotations — ZenML uses them for artifact serialization:

import pandas as pd
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score
from zenml import step, pipeline
from zenml.integrations.mlflow.experiment_trackers import MLFlowExperimentTracker

# Each @step is an isolated, cacheable unit with typed inputs/outputs
@step
def load_data() -> pd.DataFrame:
    """Load and return raw training data."""
    return pd.read_csv("data/train.csv")

@step
def preprocess(df: pd.DataFrame) -> tuple[pd.DataFrame, pd.Series]:
    """Clean and split features/labels."""
    X = df.drop("target", axis=1).fillna(0)
    y = df["target"]
    return X, y

@step(experiment_tracker="mlflow_tracker")  # attach experiment tracker
def train_model(
    X: pd.DataFrame, y: pd.Series
) -> RandomForestClassifier:
    """Train and return a RandomForest model."""
    from zenml.integrations.mlflow.flavors.mlflow_experiment_tracker_flavor import MLFlowExperimentTrackerSettings
    import mlflow

    clf = RandomForestClassifier(n_estimators=100, max_depth=8)
    clf.fit(X, y)
    mlflow.log_metric("train_accuracy", clf.score(X, y))
    return clf

@step
def evaluate(model: RandomForestClassifier, X: pd.DataFrame, y: pd.Series) -> float:
    """Evaluate model and return accuracy."""
    acc = accuracy_score(y, model.predict(X))
    print(f"Accuracy: {acc:.4f}")
    return acc

# Pipeline wires steps together
@pipeline
def training_pipeline():
    df = load_data()
    X, y = preprocess(df)
    model = train_model(X, y)
    evaluate(model, X, y)

# Run locally
training_pipeline()

Stacks: Switch Infrastructure Without Code Changes

Register Stack components once via CLI, then activate the Stack you need:

# Register individual components
zenml artifact-store register s3_store \
  --flavor=s3 \
  --path=s3://my-ml-bucket/artifacts

zenml orchestrator register kubeflow_orch \
  --flavor=kubeflow \
  --kubeflow_pipelines_ui_port=8080

zenml experiment-tracker register mlflow_tracker \
  --flavor=mlflow \
  --tracking_uri=http://mlflow.internal:5000

zenml container-registry register ecr \
  --flavor=aws \
  --uri=123456789.dkr.ecr.us-east-1.amazonaws.com

# Build a production Stack from components
zenml stack register prod_stack \
  --artifact-store=s3_store \
  --orchestrator=kubeflow_orch \
  --experiment-tracker=mlflow_tracker \
  --container-registry=ecr

# Activate the Stack (same pipeline code, different infrastructure)
zenml stack set prod_stack
python run_pipeline.py    # now runs on Kubeflow, artifacts in S3

# Switch back to local for debugging
zenml stack set default
python run_pipeline.py    # runs locally

Artifact Versioning & Pipeline Lineage

Every step output is versioned — inspect and reuse artifacts programmatically:

from zenml.client import Client

client = Client()

# List all pipeline runs
runs = client.list_pipeline_runs(pipeline_name="training_pipeline")
for run in runs:
    print(f"Run: {run.name} | Status: {run.status} | Created: {run.created}")

# Get a specific artifact from a run
run = client.get_pipeline_run("training_pipeline-2024_04_01")
model_artifact = run.steps["train_model"].output

# Load the artifact (ZenML deserializes it using the appropriate Materializer)
model = model_artifact.load()
print(type(model))  # RandomForestClassifier

# Get artifact metadata and lineage
print(model_artifact.version)
print(model_artifact.producer_step_run_id)

# Compare artifacts across runs (for model promotion decisions)
for run in runs[:5]:
    acc_artifact = run.steps["evaluate"].output
    print(f"{run.name}: accuracy = {acc_artifact.load():.4f}")

ZenML vs Metaflow vs Kubeflow Pipelines vs Prefect

Feature ZenML Metaflow Kubeflow Pipelines Prefect
Stack-agnosticYes (core feature)AWS-focusedK8s onlyYes (workers)
Python decorator API@step, @pipeline@step, @FlowSpec@component, @dsl.pipeline@task, @flow
Artifact versioningAutomatic per-stepS3-backed datastoreML Metadata storeResults API
Step cachingInput fingerprint cache@retry, resumePartial (outputs)Task result caching
MLOps integrationsMLflow/W&B/Feast/EvidentlyLimitedLimitedGeneral purpose
Best forMLOps, multi-cloudAWS ML teamsK8s shopsGeneral data pipelines

Frequently Asked Questions

What is ZenML and what problem does it solve?▼

ZenML is a stack-agnostic MLOps framework that standardizes how ML pipelines are defined and deployed. The problem it solves: ML code written for local development (Jupyter notebooks, local scripts) is not portable to production infrastructure. ZenML separates the pipeline logic (@step, @pipeline decorators) from the infrastructure (called a Stack). The same pipeline code runs locally, on Airflow, Kubeflow, Vertex AI, or Sagemaker — just by switching the Stack.

What is a ZenML Stack?▼

A Stack is a collection of infrastructure components that ZenML uses to run pipelines: an Orchestrator (local/Airflow/Kubeflow/Vertex/Sagemaker), an Artifact Store (local filesystem/S3/GCS/Azure Blob), and optionally a Container Registry, Experiment Tracker (MLflow/W&B), Model Registry, and Step Operator. You register Stack components once and activate a Stack with zenml stack set my-cloud-stack. Pipelines automatically use the active Stack's infrastructure.

How do ZenML Artifacts work?▼

Every step output in ZenML is automatically saved as an immutable Artifact in the Artifact Store with a unique version. ZenML serializes and deserializes artifacts using Materializers — Python classes that define how to save/load a specific type (Pandas DataFrame, Keras model, HuggingFace tokenizer, etc.). Artifacts enable: (1) pipeline caching (unchanged steps re-use existing artifacts), (2) lineage tracking (trace any model back to its training data), (3) artifact sharing between pipelines.

How does ZenML pipeline caching work?▼

ZenML caches step outputs based on input fingerprints. If a step's inputs (data artifacts + code + parameters) are identical to a previous run, ZenML skips re-execution and loads cached outputs. This is critical during development: you can iterate on later steps (e.g., model training) without re-running expensive preprocessing steps. Caching is on by default and can be disabled per-step with @step(enable_cache=False).

What is the difference between ZenML and Metaflow?▼

Both ZenML and Metaflow provide @step/@flow decorators for defining ML pipelines. Key differences: ZenML is stack-agnostic and supports Kubeflow, Vertex, Sagemaker, and Airflow as orchestrators, while Metaflow is tightly integrated with AWS (Batch, Step Functions, Sagemaker). ZenML has a stronger focus on MLOps tooling (model registries, experiment trackers, drift monitoring) as first-class Stack components. Metaflow has a simpler API and better notebook-to-production story for AWS teams.

How do I integrate ZenML with Airflow?▼

Register an Airflow Orchestrator component: zenml orchestrator register airflow_orch --flavor=airflow --local=True (for local Airflow) or configure it to point to a remote Airflow cluster. Add it to a Stack and activate the Stack. When you run the pipeline, ZenML generates an Airflow DAG automatically — each @step becomes an Airflow task with proper dependencies. You don't write any DAG code; ZenML translates your Python pipeline definition.