### README (README.md)

ZenML Header

One AI Platform From Pipelines to Agents

[![PyPi][pypi-shield]][pypi-url] [![PyPi][pypiversion-shield]][pypi-url] [![PyPi][downloads-shield]][downloads-url] [![Contributors][contributors-shield]][contributors-url] [![License][license-shield]][license-url]
[pypi-shield]: https://img.shields.io/pypi/pyversions/zenml?color=281158 [pypi-url]: https://pypi.org/project/zenml/ [pypiversion-shield]: https://img.shields.io/pypi/v/zenml?color=361776 [downloads-shield]: https://img.shields.io/pepy/dt/zenml?color=431D93 [downloads-url]: https://pypi.org/project/zenml/ [contributors-shield]: https://img.shields.io/github/contributors/zenml-io/zenml?color=7A3EF4 [contributors-url]: https://github.com/zenml-io/zenml/graphs/contributors [license-shield]: https://img.shields.io/github/license/zenml-io/zenml?color=9565F6 [license-url]: https://github.com/zenml-io/zenml/blob/main/LICENSE

ProjectsRoadmapChangelogReport BugSign up for ZenML ProBlogDocs

🎉 For the latest release, see the changelog.

--- ZenML is built for ML or AI Engineers working on traditional ML use-cases, LLM workflows, or agents, in a company setting. At it's core, ZenML allows you to write **workflows (pipelines)** that run on any **infrastructure backend (stacks)**. You can embed any Pythonic logic within these pipelines, like training a model, or running an agentic loop. ZenML then operationalizes your application by: 1. Automatically containerizing and tracking your code. 2. Tracking individual runs with metrics, logs, and metadata. 3. Abstracting away infrastructure complexity. 4. Integrating your existing tools and infrastructure e.g. MLflow, Langgraph, Langfuse, Sagemaker, GCP Vertex, etc. 5. Allowing you to quickly iterate on experiments with an observable layer, in development and in production. ...amongst many other features. ZenML is used by thousands of companies to run their AI workflows. Here are some featured ones:
Airbus     AXA     JetBrains     Rivian     WiseTech Global     Brevo
Leroy Merlin     Koble     Playtika     NIQ     Enel
(please email support@zenml.io if you want to be featured) ## 🚀 Get Started (5 minutes) ```bash # Install ZenML with server capabilities pip install "zenml[server]" # pip install zenml will install a slimmer client # Initialize your ZenML repository zenml init # Start local server or connect to a remote one zenml login ``` You can then explore any of the [examples](examples/) in this repo. We recommend starting with the [quickstart](examples/quickstart/), which demonstrates core ZenML concepts: pipelines, steps, artifacts, snapshots, and deployments. ### 🏗️ Architecture Overview ZenML uses a [**client-server architecture**](https://docs.zenml.io/getting-started/system-architectures) with an integrated web dashboard ([zenml-io/zenml-dashboard](https://github.com/zenml-io/zenml-dashboard)): - **Local Development**: `pip install "zenml[local]"` - runs both client and server locally - **Production**: Deploy server separately, connect with `pip install zenml` + `zenml login ` ## 🎮 Demo Here is a short demo: [](https://zenml.io/demo-video) ## 🖼️ Resources The best way to learn about ZenML is through our comprehensive documentation and tutorials: - **[Documentation](https://docs.zenml.io/)** - Complete product documentation - **[Your First AI Pipeline](https://docs.zenml.io/getting-started/your-first-ai-pipeline)** - Build and evaluate an AI service in minutes - **[Starter Guide](https://docs.zenml.io/user-guides/starter-guide)** - From zero to production in 30 minutes - **[LLMOps Guide](https://docs.zenml.io/user-guides/llmops-guide)** - Specific patterns for LLM applications - **[SDK Reference](https://sdkdocs.zenml.io/)** - Complete SDK reference ## 📚 More examples 1. **[Agent Architecture Comparison](examples/agent_comparison/)** - Compare AI agents with LangGraph workflows, LiteLLM integration, and automatic visualizations via custom materializers 2. **[Deploying ML Models](examples/deploying_ml_model/)** - Deploy classical ML models as production endpoints with monitoring and versioning 3. **[Deploying Agents](examples/deploying_agent/)** - Document analysis service with pipelines, evaluation, and embedded web UI 4. **[E2E Batch Inference](examples/e2e/)** - Complete MLOps pipeline with feature engineering 5. **[LLM RAG Pipeline](https://github.com/zenml-io/zenml-projects/tree/main/llm-complete-guide)** - Production RAG with evaluation loops 6. **[Agentic Workflow (Deep Research)](https://github.com/zenml-io/zenml-projects/tree/main/deep_research)** - Orchestrate your agents with ZenML 7. **[Fine-tuning Pipeline](https://github.com/zenml-io/zenml-projects/tree/main/gamesense)** - Fine-tune and deploy LLMs ## 🗣️ Chat With Your Pipelines: ZenML MCP Server Stop clicking through dashboards to understand your ML workflows. The **[ZenML MCP Server](https://github.com/zenml-io/mcp-zenml)** lets you query your pipelines, analyze runs, and trigger deployments using natural language through Claude Desktop, Cursor, or any MCP-compatible client. ``` 💬 "Which pipeline runs failed this week and why?" 📊 "Show me accuracy metrics for all my customer churn models" 🚀 "Trigger the latest fraud detection pipeline with production data" ``` **Quick Setup:** 1. Download the `.dxt` file from [zenml-io/mcp-zenml](https://github.com/zenml-io/mcp-zenml) 2. Drag it into Claude Desktop settings 3. Add your ZenML server URL and API key 4. Start chatting with your ML infrastructure The MCP (Model Context Protocol) integration transforms your ZenML metadata into conversational insights, making pipeline debugging and analysis as easy as asking a question. Perfect for teams who want to democratize access to ML operations without requiring dashboard expertise. ## 🤖 Kitaru: Durable AI Agents Building AI agents that need to survive crashes, pause for human approval, or run on cloud infrastructure? **[Kitaru](https://kitaru.ai)** is our open-source sister project for making Python agents durable. - **Crash recovery** — checkpoint and replay from failure, not from scratch - **Human-in-the-loop** — built-in approval gates and wait points - **Persistent memory** — versioned, durable state across agent runs with full audit trail - **Framework agnostic** — works with PydanticAI, CrewAI, or raw Python - **Runs anywhere** — local, Kubernetes, Vertex AI, SageMaker, AzureML Built on the same infrastructure that powers ZenML. Two decorators (`@flow` + `@checkpoint`) and you're done. ```bash pip install kitaru ``` 👉 **[kitaru.ai](https://kitaru.ai)** · **[GitHub](https://github.com/zenml-io/kitaru)** · **[Docs](https://docs.zenml.io/kitaru)** ### 🎓 Books & Resources
LLM Engineer's Handbook Cover Machine Learning Engineering with Python Cover DeepSeek in Practice Cover
[ZenML](https://zenml.io) is featured in these comprehensive guides to production AI systems. ## 🤝 Join ML Engineers Building the Future of AI **Contribute:** - 🌟 [Star us on GitHub](https://github.com/zenml-io/zenml/stargazers) - Help others discover ZenML - 🤝 [Contributing Guide](CONTRIBUTING.md) - Start with [`good-first-issue`](https://github.com/issues?q=is%3Aopen+is%3Aissue+archived%3Afalse+user%3Azenml-io+label%3A%22good+first+issue%22) - 💻 [Write Integrations](https://github.com/zenml-io/zenml/blob/main/src/zenml/integrations/README.md) - Add your favorite tools **Stay Updated:** - 🗺 [Public Roadmap](https://zenml.io/roadmap) - See what's coming next - 📰 [Blog](https://zenml.io/blog) - Best practices and case studies - 🎙 [Slack](https://zenml.io/slack) - Talk with AI practitioners ## ❓ FAQs from ML Engineers Like You **Q: "Do I need to rewrite my agents or models to use ZenML?"** A: No. Wrap your existing code in a `@step`. Keep using `scikit-learn`, PyTorch, LangGraph, LlamaIndex, or raw API calls. ZenML orchestrates your tools, it doesn't replace them. **Q: "How is this different from LangSmith/Langfuse?"** A: They provide excellent observability for LLM applications. We orchestrate the **full MLOps lifecycle for your entire AI stack**. With ZenML, you manage both your classical ML models and your AI agents in one unified framework, from development and evaluation all the way to production deployment. **Q: "Can I use my existing MLflow/W&B setup?"** A: Yes! ZenML integrates with both [MLflow](https://docs.zenml.io/stacks/experiment-trackers/mlflow) and [Weights & Biases](https://docs.zenml.io/stacks/experiment-trackers/wandb). Your experiments, our pipelines. **Q: "Is this just MLflow with extra steps?"** A: No. MLflow tracks experiments. We orchestrate the entire development process – from training and evaluation to deployment and monitoring – for both models and agents. **Q: "How do I configure ZenML with Kubernetes?"** A: ZenML integrates with Kubernetes through the native Kubernetes orchestrator, Kubeflow, and other K8s-based orchestrators. See our [Kubernetes orchestrator guide](https://docs.zenml.io/stacks/orchestrators/kubernetes) and [Kubeflow guide](https://docs.zenml.io/stacks/orchestrators/kubeflow), plus [deployment documentation](https://docs.zenml.io/getting-started/deploying-zenml/deploy-with-helm). **Q: "What about cost? I can't afford another platform."** A: ZenML's open-source version is free forever. You likely already have the required infrastructure (like a Kubernetes cluster and object storage). We just help you make better use of it for MLOps. ### 🛠 VS Code / Cursor Extension Manage pipelines directly from your editor:
🖥️ VS Code Extension in Action!
ZenML Extension
Install from [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=ZenML.zenml-vscode). ## 📜 License ZenML is distributed under the terms of the Apache License Version 2.0. See [LICENSE](LICENSE) for details. ---
Linux Foundation Silver Member      CNCF Silver Member
--- ### AGENTS (AGENTS.md) # ZenML Codex Agent Guidelines ZenML is an extensible open-source MLOps framework for creating production-ready ML pipelines. This root guide contains rules that must be loaded for every Codex session in this repository. For detailed workflows, examples, and subsystem recipes, use `.agents/skills/zenml-repo-workflows/SKILL.md`. ## Project Structure - `src/zenml/` - Core source code. - `tests/` - Unit and integration tests. - `docs/book/` - Source documentation. - `examples/` - Example projects. - `scripts/` - Development utilities. ## Always-Loaded Rules - Use US English spelling in code, comments, docstrings, and documentation. - Use Python 3.10+ compatible code. - Type hint function parameters and return values. - Follow Google Python style for docstrings. Include `Args`, `Returns`, `Yields`, and `Raises` sections whenever the function contract requires them; do not use a summary-only docstring to omit applicable sections. - Prefer clear names and small functions over explanatory comments. - Comments should explain intent, trade-offs, constraints, invariants, and tricky edge cases. Avoid comments that restate obvious code. - Do not use multi-line banner comments to group classes or functions. - Prefer typed contracts over `getattr`/`hasattr` capability checks when static typing can express the requirement. - Private methods and functions with a leading underscore should not be called outside their class or module. - Integrations should avoid using ZenML private methods because externalized integrations will not be protected by in-repo type checks. ## Common Commands - Format before committing: `bash scripts/format.sh`. - Check quality: `bash scripts/lint.sh`. - Run targeted tests only: `pytest tests/unit/path/to/test_file.py` or `pytest tests/unit/path/to/test_file.py::test_specific_function`. - Do NOT run the entire local test suite by default; many tests need special environments. - If you make changes after running tests, rerun the relevant tests. ## Branches, Git, and PRs - `develop` is the primary working branch, not `main`. - Always branch off `develop` for new work. - PRs should target `develop`. - `main` is only updated during releases. - Use targeted `git add`; do not stage unrelated files. - Never add anything from `design/` to git history. - PR titles should be concise and human-readable, without prefixes like `feat:`. - Every PR must have exactly one release-notes label: `release-notes` or `no-release-notes`. - When pushing new commits to an open PR, check whether the PR description needs updating. ## Security - NEVER commit secrets, API keys, tokens, passwords, or credentials. - Use environment variables or ZenML secret management for sensitive data. - Validate and sanitize user inputs. - If secrets are accidentally committed, notify the team immediately. ## Architecture and API Safety - Check whether a class or function is exported in `zenml.__init__` before changing a public-looking interface. - Public APIs need backward-compatible evolution or deprecation. - For internal non-underscore methods, search for usages and update all internal callers. - The term "model" can mean Pydantic models, machine learning models, or ZenML model namespaces. Be explicit when writing or reviewing. - When changing cross-cutting features, trace the full path through CLI, client, server, models, schemas, migrations, tests, and docs. ## FastAPI and Runtime Rules - ZenML OSS FastAPI work expects FastAPI, SQLModel, SQLAlchemy 2.0, and Pydantic v2 patterns. - Implement synchronous `def` route handlers for OSS Codex contributions. - Keep shared state inside FastAPI dependency injection or the application factory; never introduce fresh global variables outside initialization. - Start routes, dependencies, and services with guard clauses. - Raise `HTTPException` with precise status codes for expected errors. - Use Pydantic models for route inputs and outputs. - When changing server framework (e.g., `fastapi`) or database library versions, check whether related OpenTelemetry SDK, exporter, and instrumentation dependencies also need updates. Breaking changes in instrumented libraries can require coordinated OTel updates. Keep OTel SDK/exporter versions aligned with the matching OpenTelemetry instrumentation beta line. - Code outside `src/zenml/zen_server/` should NEVER import from `zen_server/`. ## Database and Storage Rules - ZenML uses SQLModel and SQLAlchemy; no raw SQL unless absolutely necessary. - Database schema changes require Alembic migrations. - Never modify existing migrations that are already on `main` or `develop`. - Always consider backward compatibility for rolling deployments. - Test migration upgrade paths with `alembic upgrade head` for meaningful schema changes. - Code outside `zen_stores/` should not import SQL-related code directly from `zen_stores`; use `Client` or, rarely, `client.zen_store`. ## Subsystem Pointers Load `.agents/skills/zenml-repo-workflows/SKILL.md` for detailed guidance on tests, PRs, migrations, docs, integrations, models, orchestrators, FastAPI, server code, and storage. Directory-specific reminders: - `docs/book/AGENTS.md` - documentation source, GitBook links, and docs checks. - `src/zenml/cli/AGENTS.md` - CLI import rules and filter/client coupling. - `src/zenml/integrations/AGENTS.md` - integration flavor import rules. - `src/zenml/models/AGENTS.md` - domain model compatibility and filter fields. - `src/zenml/orchestrators/AGENTS.md` - orchestrator IDs and dynamic pipelines. - `src/zenml/zen_server/AGENTS.md` - server import boundary and endpoint shape. - `src/zenml/zen_stores/migrations/AGENTS.md` - Alembic migration guidance. - `src/zenml/zen_stores/schemas/AGENTS.md` - ORM schema and SQL import rules. ## Reviewer Checklist - Integration PRs: no top-level integration-library imports in flavor files. - Orchestrator PRs: `get_orchestrator_run_id` is unique per run and stable for all steps in that run. - Filter model changes: matching client method signature and body are updated. - Private method changes: all internal usages are updated. - Import checks: no `zen_server` imports outside `zen_server`. - Import checks: no direct SQL imports outside `zen_stores`. - Model changes: adding properties is usually OK; deleting, renaming, or incompatible type changes are risky. - Dependency bumps: dropping old version support is breaking. - Scheduling changes: check both legacy schedule and trigger stacks. - Step operator changes: check `BaseStepOperator`, `StepLauncher`, and at least one concrete integration. ## Documentation Rules - Documentation source files live in `docs/book/`. - Do not edit generated docs directories such as `docs/mkdocs/` or `docs/site/`. - When adding or removing docs pages, update the relevant `toc.md`. - Assets belong in a `.gitbook` folder beside the relevant `toc.md`. For human contributors, see `CONTRIBUTING.md`. --- ### Changelog.Json (changelog.json) [ { "id": 4, "slug": "dynamic-pipelines", "title": "Dynamic pipelines are now available", "description": "Introduced Dynamic Pipelines as an experimental feature, allowing you to generate DAG structures at runtime using native Python control flow (loops, conditionals). Key capabilities include dynamic parallelization, Map/Reduce patterns over collections, and granular runtime configuration (inline vs. isolated). Supported on local, Kubernetes, AWS Sagemaker, and Google Cloud Vertex orchestrators. Dynamic pipelines can be run from snapshots with configurable parameters, and include improvements to Kubernetes orchestrator handling and step mapping operations that return future objects with an `unpack()` method for better control flow.", "published_at": "2025-12-05T06:42:19Z", "published": true, "audience": "oss", "labels": [ "feature" ], "docs_url": "https://docs.zenml.io/concepts/steps_and_pipelines/dynamic_pipelines", "should_highlight": true }, { "id": 3, "slug": "panels-are-now-resizable", "title": "Panels are now resizable", "feature_image_url": "https://public-flavor-logos.s3.eu-central-1.amazonaws.com/whats_new/resizable-panels.gif", "description": "You can now resize the panels in the dashboard to see more or less content at once. This is useful for when you want to see more or less information about a run or a pipeline.", "published": true, "published_at": "2025-10-23T00:00:00Z", "should_highlight": false, "audience": "all", "labels": [ "feature" ] }, { "id": 2, "slug": "introducing-pipeline-deployments", "title": "Introducing Pipeline Deployments", "description": "Pipeline Deployments turn pipelines into persistent HTTP services with warm state, reducing cold start latency by 10-100x while maintaining full traceability. Learn more in our blog post.", "video_url": "https://www.youtube-nocookie.com/embed/whQytRE7kC8", "learn_more_url": "https://www.zenml.io/blog/why-pipelines-are-the-right-abstraction-for-real-time-ai-agents-included", "published": true, "published_at": "2025-10-02T00:00:00Z", "should_highlight": false, "audience": "all", "labels": [ "feature" ] }, { "id": 1, "slug": "new-timeline-view-for-runs", "title": "New Timeline View for Runs", "description": "We've added a new timeline view for runs to help you visualize the execution of your pipelines.", "feature_image_url": "https://public-flavor-logos.s3.eu-central-1.amazonaws.com/whats_new/new_feature_timeline.png", "docs_url": "https://docs.zenml.io/concepts/dashboard-features#timeline-view", "published": true, "published_at": "2025-09-12T00:00:00Z", "should_highlight": false, "audience": "all", "labels": [ "feature" ] } ] --- ### CLA (CLA.md) # Fiduciary License Agreement 2.0 based on the ## Individual Contributor Exclusive License Agreement (including the Traditional Patent License OPTION) Thank you for your interest in contributing to ZenML by ZenML GmbH ("We" or "Us"). The purpose of this contributor agreement ("Agreement") is to clarify and document the rights granted by contributors to Us. To make this document effective, please follow the instructions at https://zenml.io/cla/. ### 0. Preamble Software is deeply embedded in all aspects of our lives and it is important that it empower, rather than restrict us. Free Software gives everybody the rights to use, understand, adapt and share software. These rights help support other fundamental freedoms like freedom of speech, press and privacy. Development of Free Software can follow many patterns. In some cases whole development is handled by a sole programmer or a small group of people. But usually, the creation and maintenance of software is a complex process that requires the contribution of many individuals. This also affects who owns the rights to the software. In the latter case, rights in software are owned jointly by a great number of individuals. To tackle this issue some projects require a full copyright assignment to be signed by all contributors. The problem with such assignments is that they often lack checks and balances that would protect the contributors from potential abuse of power from the new copyright holder. FSFE’s Fiduciary License Agreement (FLA) was created by the Free Software Foundation Europe e.V. with just that in mind – to concentrate all deciding power within one entity and prevent fragmentation of rights on one hand, while on the other preventing that single entity from abusing its power. The main aim is to ensure that the software covered under the FLA will forever remain Free Software. This process only serves for the transfer of economic rights. So-called moral rights (e.g. authors right to be identified as author) remain with the original author(s) and are inalienable. How to use this FLA If You are an employee and have created the Contribution as part of your employment, You need to have Your employer approve this Agreement or sign the Entity version of this document. If You do not own the Copyright in the entire work of authorship, any other author of the Contribution should also sign this – in any event, please contact Us at support@zenml.io ### 1. Definitions "You" means the individual Copyright owner who Submits a Contribution to Us. "Contribution" means any original work of authorship, including any original modifications or additions to an existing work of authorship, Submitted by You to Us, in which You own the Copyright. "Copyright" means all rights protecting works of authorship, including copyright, moral and neighboring rights, as appropriate, for the full term of their existence. "Material" means the software or documentation made available by Us to third parties. When this Agreement covers more than one software project, the Material means the software or documentation to which the Contribution was Submitted. After You Submit the Contribution, it may be included in the Material. "Submit" means any act by which a Contribution is transferred to Us by You by means of tangible or intangible media, including but not limited to electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, Us, but excluding any transfer that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution." "Documentation" means any non-software portion of a Contribution. ### 2. License grant #### 2.1 Copyright license to Us Subject to the terms and conditions of this Agreement, You hereby grant to Us a worldwide, royalty-free, exclusive, perpetual and irrevocable (except as stated in Section 8.2) license, with the right to transfer an unlimited number of non-exclusive licenses or to grant sublicenses to third parties, under the Copyright covering the Contribution to use the Contribution by all means, including, but not limited to: publish the Contribution, modify the Contribution, prepare derivative works based upon or containing the Contribution and/or to combine the Contribution with other Materials, reproduce the Contribution in original or modified form, distribute, to make the Contribution available to the public, display and publicly perform the Contribution in original or modified form. #### 2.2 Moral rights Moral Rights remain unaffected to the extent they are recognized and not waivable by applicable law. Notwithstanding, You may add your name to the attribution mechanism customary used in the Materials you Contribute to, such as the header of the source code files of Your Contribution, and We will respect this attribution when using Your Contribution. #### 2.3 Copyright license back to You Upon such grant of rights to Us, We immediately grant to You a worldwide, royalty-free, non-exclusive, perpetual and irrevocable license, with the right to transfer an unlimited number of non-exclusive licenses or to grant sublicenses to third parties, under the Copyright covering the Contribution to use the Contribution by all means, including, but not limited to: publish the Contribution, modify the Contribution, prepare derivative works based upon or containing the Contribution and/or to combine the Contribution with other Materials, reproduce the Contribution in original or modified form, distribute, to make the Contribution available to the public, display and publicly perform the Contribution in original or modified form. This license back is limited to the Contribution and does not provide any rights to the Material. ### 3. Patents #### 3.1 Patent license Subject to the terms and conditions of this Agreement You hereby grant to Us and to recipients of Materials distributed by Us a worldwide, royalty-free, non-exclusive, perpetual and irrevocable (except as stated in Section 3.2) patent license, with the right to transfer an unlimited number of non-exclusive licenses or to grant sublicenses to third parties, to make, have made, use, sell, offer for sale, import and otherwise transfer the Contribution and the Contribution in combination with any Material (and portions of such combination). This license applies to all patents owned or controlled by You, whether already acquired or hereafter acquired, that would be infringed by making, having made, using, selling, offering for sale, importing or otherwise transferring of Your Contribution(s) alone or by combination of Your Contribution(s) with any Material. #### 3.2 Revocation of patent license You reserve the right to revoke the patent license stated in section 3.1 if We make any infringement claim that is targeted at your Contribution and not asserted for a Defensive Purpose. An assertion of claims of the Patents shall be considered for a "Defensive Purpose" if the claims are asserted against an entity that has filed, maintained, threatened, or voluntarily participated in a patent infringement lawsuit against Us or any of Our licensees. ### 4. License obligations by Us We agree to (sub)license the Contribution or any Materials containing, based on or derived from your Contribution under the terms of any licenses the Free Software Foundation classifies as Free Software License and which are approved by the Open Source Initiative as Open Source licenses. More specifically and in strict accordance with the above paragraph, we agree to (sub)license the Contribution or any Materials containing, based on or derived from the Contribution only under the terms of the following license(s) Apache-2.0 (including any right to adopt any future version of a license if permitted). We agree to license patents owned or controlled by You only to the extent necessary to (sub)license Your Contribution(s) and the combination of Your Contribution(s) with the Material under the terms of any licenses the Free Software Foundation classifies as Free Software licenses and which are approved by the Open Source Initiative as Open Source licenses.. ### 5. Disclaimer THE CONTRIBUTION IS PROVIDED "AS IS". MORE PARTICULARLY, ALL EXPRESS OR IMPLIED WARRANTIES INCLUDING, WITHOUT LIMITATION, ANY IMPLIED WARRANTY OF SATISFACTORY QUALITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE EXPRESSLY DISCLAIMED BY YOU TO US AND BY US TO YOU. TO THE EXTENT THAT ANY SUCH WARRANTIES CANNOT BE DISCLAIMED, SUCH WARRANTY IS LIMITED IN DURATION AND EXTENT TO THE MINIMUM PERIOD AND EXTENT PERMITTED BY LAW. ### 6. Consequential damage waiver TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT WILL YOU OR WE BE LIABLE FOR ANY LOSS OF PROFITS, LOSS OF ANTICIPATED SAVINGS, LOSS OF DATA, INDIRECT, SPECIAL, INCIDENTAL, CONSEQUENTIAL AND EXEMPLARY DAMAGES ARISING OUT OF THIS AGREEMENT REGARDLESS OF THE LEGAL OR EQUITABLE THEORY (CONTRACT, TORT OR OTHERWISE) UPON WHICH THE CLAIM IS BASED. ### 7. Approximation of disclaimer and damage waiver IF THE DISCLAIMER AND DAMAGE WAIVER MENTIONED IN SECTION 5. AND SECTION 6. CANNOT BE GIVEN LEGAL EFFECT UNDER APPLICABLE LOCAL LAW, REVIEWING COURTS SHALL APPLY LOCAL LAW THAT MOST CLOSELY APPROXIMATES AN ABSOLUTE WAIVER OF ALL CIVIL OR CONTRACTUAL LIABILITY IN CONNECTION WITH THE CONTRIBUTION. ### 8. Term #### 8.1 This Agreement shall come into effect upon Your acceptance of the terms and conditions. #### 8.2 This Agreement shall apply for the term of the copyright and patents licensed here. However, You shall have the right to terminate the Agreement if We do not fulfill the obligations as set forth in Section 4. Such termination must be made in writing. #### 8.3 In the event of a termination of this Agreement Sections 5., 6., 7., 8., and 9. shall survive such termination and shall remain in full force thereafter. For the avoidance of doubt, Free and Open Source Software (sub)licenses that have already been granted for Contributions at the date of the termination shall remain in full force after the termination of this Agreement. ### 9. Miscellaneous #### 9.1 This Agreement and all disputes, claims, actions, suits or other proceedings arising out of this agreement or relating in any way to it shall be governed by the laws of Germany excluding its private international law provisions. #### 9.2 This Agreement sets out the entire agreement between You and Us for Your Contributions to Us and overrides all other agreements or understandings. #### 9.3 In case of Your death, this agreement shall continue with Your heirs. In case of more than one heir, all heirs must exercise their rights through a commonly authorized person. #### 9.4 If any provision of this Agreement is found void and unenforceable, such provision will be replaced to the extent possible with a provision that comes closest to the meaning of the original provision and that is enforceable. The terms and conditions set forth in this Agreement shall apply notwithstanding any failure of essential purpose of this Agreement or any limited remedy to the maximum extent possible under law. #### 9.5 You agree to notify Us of any facts or circumstances of which you become aware that would make this Agreement inaccurate in any respect. **You** Date:_______________________________ Name:_______________________________ Title:______________________________ Address:____________________________ **Us** Date:_______________________________ Name:_______________________________ Title:_______________________________ Address:_______________________________ --- ### CLAUDE (CLAUDE.md) # ZenML Claude Code Guidelines This document provides guidance for Claude Code when working with the ZenML codebase. ZenML is an extensible, open-source MLOps framework for creating production-ready ML pipelines. ## Project Structure - `/src/zenml/` - Core source code - `/tests/` - Test suite (unit, integration) - `/docs/` - Documentation - `/examples/` - Example projects - `/scripts/` - Development utilities Use filesystem navigation tools to explore the codebase structure as needed. ## Use ZenML Docs via MCP Claude Code can query ZenML documentation via the built-in GitBook MCP server: https://docs.zenml.io/~gitbook/mcp. This enables real-time, source-of-truth lookups from the docs while you code, reducing hallucinations and speeding up feature discovery. Quick setup (CLI): ```bash claude mcp add zenmldocs --transport http https://docs.zenml.io/~gitbook/mcp ``` Note: The MCP server indexes the latest released docs, not the develop branch. For full setup details and editor alternatives, see docs/book/reference/llms-txt.md. ## Code Style & Quality Standards - **Use US English spelling** in all code, comments, docstrings, and documentation (e.g., "initialize", "stabilize", "color"). The CI enforces this via `typos` (configured in `.typos.toml`). ### Commenting policy — explain why, not what - Use comments to document intent, trade‑offs, constraints, invariants, and tricky edge cases—i.e., why the code is this way—rather than narrating changes. Prefer self‑explanatory code; add comments only where extra context is needed. Write for a reader 6+ months later. - Use for: complex logic/algorithms, non‑obvious design decisions, business rules/constraints, API purpose/contracts, edge cases. - Avoid: change‑tracking comments ("Updated from previous version", "New implementation", "Changed to use X instead of Y", "Refactored this section"). - Avoid simple explanatory comments, where it is already clear from the code itself. - Avoid useless one-line comments interleaved with code that merely narrate the implementation. Favor expressive names and small, focused functions. ```python # Bad x = x + 1 # increment x # Good count += 1 ``` - Do not use multi-line banner comments to group classes/functions. Use a concise module-level docstring or split code into dedicated modules. ```python # Bad """ ===== Dataset Loaders ===== """ class CSVLoader: ... class ParquetLoader: ... # Good (module-level docstring at top) """ Dataset loaders used by data ingestion (CSV, Parquet). """ class CSVLoader: ... class ParquetLoader: ... ``` ### Formatting and Linting - Format code with: `bash scripts/format.sh` (requires Python environment with dev dependencies) - Run this before every commit to ensure proper formatting - Automatically fixes and formats code using ruff and yamlfix - Check code quality with: `bash scripts/lint.sh` - Unlike format.sh, this doesn't auto-fix issues - Runs Ruff, pydoclint (on `src/zenml tests/harness`), yamlfix, zizmor, and mypy - Note: Full mypy check is slow on the entire codebase - For faster checks, run mypy directly on specific files: `mypy src/zenml/path/to/file.py` - The primary code style is enforced by ruff, configured in `pyproject.toml` - YAML formatting uses yamlfix: `yamlfix .github -v` ### Python Standards - Use Python 3.10+ compatible code - Follow Google Python style for docstrings. Include `Args`, `Returns`, `Yields`, and `Raises` sections whenever the function contract requires them; do not use a summary-only docstring to omit applicable sections. - Type hint all function parameters and return values - Use descriptive variable names and documentation - Keep function size manageable (aim for < 50 lines) though there are exceptions #### Prefer typing over dynamic attribute checks - Don't use getattr/hasattr for capability checks when static typing can express the contract - Prefer Protocols/ABCs, Unions with isinstance narrowing, or typed adapters around untyped third-party objects - If getattr/hasattr is unavoidable, isolate it in a small helper and expose a typed interface Example: ```python # Bad if hasattr(handler, "close"): handler.close() # Good from typing import Protocol class Closable(Protocol): def close(self) -> None: ... def shutdown(h: Closable) -> None: h.close() ``` ### Util Function Placement When deciding whether to place a helper function in a utils file or on a class, follow these guidelines: 1. **If a method only makes sense within the context of a class** → Put it on the class 2. **If a static/util method is heavily used by subclasses** → Put it on the parent class **Rationale for placing methods on classes:** - Saves imports for users and subclasses - Subclasses can simply call `self.something()` instead of finding and importing from a util file - Keeps related functionality co-located **Example:** `requires_resources_in_orchestration_environment` in `base_orchestrator.py:495-514` ```python # This is a @staticmethod on BaseOrchestrator, not a standalone util @staticmethod def requires_resources_in_orchestration_environment(step: "Step") -> bool: """Check whether a step needs special orchestration resources. Args: step: The step to check. Returns: Whether the step needs special resources in the orchestration environment. """ if step.config.step_operator: return False return not step.config.resource_settings.empty ``` This method could be a global util, but it's placed on the class because: - All orchestrator subclasses frequently need it - Subclasses can call `self.requires_resources_in_orchestration_environment(step)` without imports - It's conceptually tied to orchestrator behavior **When to use utils files:** - Truly generic functions used across unrelated modules - Functions that don't logically belong to any class - Pure utility functions (string manipulation, date formatting, etc.) **Key utils locations:** - `src/zenml/utils/` — General utilities - `src/zenml/orchestrators/utils.py` — Orchestrator-specific utilities - `src/zenml/orchestrators/step_run_utils.py` — Step execution utilities - `src/zenml/orchestrators/publish_utils.py` — Status/metadata publishing ### Private Methods and API Stability Methods and functions starting with `_` (underscore) are **private** and should NOT be called from outside their class or module. **The rule:** - `_method()` on a class → only call from within that class - `_function()` in a utils module → only call from within that module - This isn't always consistently applied in the codebase, but it's the intended convention **Backwards compatibility — case-by-case judgment:** There are no strict written rules; evaluate each change individually: | Symbol type | Part of public API? | Breaking change if modified? | |-------------|---------------------|------------------------------| | Classes/functions exported in `zenml.__init__` | ✅ Definitely public | ⚠️ Yes — requires deprecation | | Public methods on those classes | ✅ Public | ⚠️ Yes — requires deprecation | | Internal methods deep in the codebase (no underscore) | ❌ Not intended for users | ✅ No — update all internal usages | | `_private_method()` | ❌ No | ✅ No — can change freely | **When changing any non-underscore method:** 1. Check if the class/function is exported in `zenml.__init__` — if so, it's public API 2. Search for usages **within the ZenML codebase** (grep/find references) 3. Update all internal usages 4. For truly internal code not exported at the root, no deprecation needed **Best practice for integrations (future-proofing):** > ⚠️ **Integrations should avoid using ZenML private methods** This is primarily a future concern: when integrations eventually move out of the main ZenML repo (external packages), mypy won't detect if a private method they depend on was changed, leading to silent breakage. Even while integrations live in-repo, using only public APIs is good practice and prepares for this transition. ```python # Bad - integration code using private method from zenml.orchestrators.base_orchestrator import BaseOrchestrator class MyOrchestrator(BaseOrchestrator): def submit_pipeline(self, ...): self._some_private_helper() # ❌ Don't do this # Good - use only public methods or reimplement logic class MyOrchestrator(BaseOrchestrator): def submit_pipeline(self, ...): self.public_method() # ✅ Safe ``` ### FastAPI Conventions Router, service, error-handling, and validation conventions for the server live in `src/zenml/zen_server/AGENTS.md` and load automatically when you work in that directory. ### Testing Requirements - Most new code requires test coverage - Key exceptions are when the code involves integrations with external services. (in those cases we generally test things extensively locally and in the CI. So the developer might have to run things or set things up locally first.) - Tests live in the `/tests/` folder with structure loosely mirroring the main codebase - Unit tests go in `/tests/unit/` - Integration tests go in `/tests/integration/` #### Running Tests - Do NOT try to run the entire test suite locally - many tests require special environments - Run targeted tests for the specific components you've changed: - `pytest tests/unit/path/to/test_file.py` - `pytest tests/unit/path/to/test_file.py::test_specific_function` - For full coverage, use CI (see CI section below) - Some tests use: `bash scripts/test-coverage-xml.sh` (but this won't run all tests) ## Dependencies & Runtime Constraints - Align contributions with the FastAPI + Pydantic v2 + SQLAlchemy 2.0 + SQLModel stack defined for ZenML OSS; confirm any new dependency in `pyproject.toml` before adoption. - When changing server framework (e.g., `fastapi`) or database library versions, check whether related OpenTelemetry SDK, exporter, and instrumentation dependencies also need updates. Breaking changes in instrumented libraries can require coordinated OTel updates. Keep OTel SDK/exporter versions aligned with the matching OpenTelemetry instrumentation beta line. - The OSS runtime forbids async I/O in Claude-authored code even though FastAPI supports it—implement synchronous `def` handlers and delegate background/long-running work to workers or dependency-injected services; this supersedes generic async advice found elsewhere. - Prefer dependency injection over module-level singletons for clients, caches, and repositories so state management stays testable. - Cache static or frequently accessed data (e.g., dependency-scoped in-memory caches) and lazy-load heavyweight resources to control cold-start latency. - Document minimum supported versions when modifying dependency-heavy paths and explain performance trade-offs in PRs when serialization or caching strategies change. ## Development Workflow ### Prerequisites - Set up a Python environment with ZenML dev dependencies - Install ZenML in development mode: `pip install -e ".[dev]"` - Most scripts require these dependencies to be available - ZenML recommends using `uv` for Python package installation in local environments - `uv` is also used in CI workflows - It resolves dependencies more quickly and reliably than pip - It can resolve dependency conflicts that pip sometimes struggles with or takes a long time to resolve ### Environment Variables - Several environment variables are useful during ZenML development: - `ZENML_LOGGING_VERBOSITY=DEBUG`: Controls logging verbosity - `ZENML_ANALYTICS_OPT_IN=false`: Disables analytics during development - `MLSTACKS_ANALYTICS_OPT_OUT=true`: Disables MLStacks analytics - `AUTO_OPEN_DASHBOARD=false`: Prevents automatic dashboard opening - `ZENML_ENABLE_RICH_TRACEBACK=false`: Disables rich traceback formatting - `TOKENIZERS_PARALLELISM=false`: Avoids tokenizers parallelism warnings - Always set the following environment variables: - `ZENML_ANALYTICS_OPT_IN=false`: Disables analytics during development - `ZENML_DEBUG=true`: Uses the development ZenML analytics server to avoid sending analytics to the official ZenML analytics server (IMPORTANT!). This must be set even if `ZENML_ANALYTICS_OPT_IN=true` because in a client-server setup, the server controls the client-side analytics opt-in status. ### Branch Management - **IMPORTANT**: `develop` is our primary working branch, NOT `main` - Always branch off `develop` for all changes - All PRs should target the `develop` branch - The `main` branch is only updated during the release process - If working on a feature branch that's already based on `develop`, you may need to branch off that feature branch for related changes ### Making Changes 1. Run `bash scripts/format.sh` before every commit 2. Run targeted tests to verify changes (see above) 3. Update documentation for user-facing changes (or ensure that nothing was broken) 4. IMPORTANT: **Before opening a PR or making a large commit**, always run `/simplify` to review changed code for reuse opportunities, quality issues, and efficiency improvements. Fix any issues it finds before committing. ### Security Guidelines - **NEVER** commit secrets, API keys, tokens, or passwords - Use environment variables or ZenML's secret management for sensitive data - Review changes for accidental credential exposure before committing - If you accidentally commit secrets, notify the team immediately - Follow the principle of least privilege when implementing access controls - Validate and sanitize all user inputs ### Database and Migration Guidelines - Database schema changes require Alembic migrations - Create migrations with descriptive names: `alembic revision -m "Add X to Y table"` - Test upgrade path: `alembic upgrade head` (downgrade testing is optional—ZenML doesn't support downgrades in most cases) - Never modify existing migrations that are already on main/develop branches - Always consider backward compatibility for rolling deployments - Include both schema changes and data migrations when needed - Run `scripts/check-alembic-branches.sh` to verify migration consistency ### Commit Message Guidelines - Write clear, descriptive commit messages explaining the "why" not just the "what" - First line should be a concise summary (50 chars or less) - Use imperative mood: "Add feature" not "Added feature" - Reference issue numbers when applicable: "Fix user auth bug (#1234)" - For multi-line messages, add a blank line after the summary - Example: ``` Add retry logic to artifact upload Previously, artifact uploads would fail immediately on network errors. This adds exponential backoff retry logic to handle transient failures. Fixes #1234 ``` ### When Implementing Features - Study existing similar implementations first - Follow the established patterns in the codebase - Keep backward compatibility in mind - Add appropriate error handling - Document public APIs thoroughly ### Field Description Standards Templates, the quality bar, and worked examples for Pydantic `Field(description=...)` text in stack component configs live in the `field-descriptions` skill. All descriptions must pass `python scripts/validate_descriptions.py` before merging. ### When Fixing Bugs - Add regression tests that would have caught the bug - Understand root cause before implementing fix - Document the fix in commit messages ### Pull Request Guidelines - Use human-readable names for PRs (no prefixes like "feat:" or "doc:") - Keep PR titles concise but descriptive - Write comprehensive PR descriptions: - Clearly explain what the changes do - Mention why the changes are needed - Detail any important implementation decisions - Note any areas that need special reviewer attention - Detailed PR descriptions help both reviewers and release note creation - Use appropriate PR tags where applicable: - `internal`: For changes relevant only to ZenML team members - `documentation`: For changes related to documentation - `bug`: For bug fixes - `dependencies`: For dependency updates - `enhancement`: For new features or improvements - **REQUIRED: Release Notes Labels** - Every PR must have exactly one of these labels: - `release-notes`: For user-facing features, significant updates, or changes that should appear in the changelog. Use this for new features, important bug fixes affecting users, API changes, or anything users should know about. - `no-release-notes`: For internal changes, CI fixes, refactoring, minor bug fixes, documentation-only changes, or anything that doesn't need to be surfaced to users. - The CI will block merging if neither label is present. When in doubt, use `no-release-notes` for internal/maintenance work. ### Continuous Integration - ZenML uses a two-tier CI approach: - **Fast CI**: Runs automatically on all PRs (basic tests, linting, type checking) - **Full CI**: Includes integration tests, tutorial pipeline regression tests, and more extensive test coverage - The `run-slow-ci` label triggers full CI testing - Full CI is required before merging - maintainers will add the label if needed - Tutorial pipeline testing runs all VSCode tutorial examples against the current branch to catch breaking changes - If your changes touch integrations or core functionality, mention in the PR that full CI should be run - CI failures will show in the PR checks - review logs to understand any issues ## Core Concepts ### Important Terminology - The term "model" has multiple distinct meanings in the codebase: 1. **Pydantic models**: Data structures used throughout the codebase (like `PipelineModel`) 2. **ML models**: Actual machine learning models (PyTorch, sklearn, etc.) 3. **ZenML models**: Namespaces that group artifacts, metadata, and other resources related to an ML model - Be careful with these terms when reading/writing code to avoid confusion ### Pipeline Architecture - Pipelines are collections of steps - Steps produce and consume artifacts - Artifacts are serialized/deserialized by materializers - Pipelines are executed by orchestrators - Stack components provide functionality like storage, orchestration, etc. ### Key Abstractions - `StackComponent` - Base for stack components - `Pipeline` - Pipeline definition - `BaseStep` - Step implementation - `BaseMaterializer` - Artifact serialization - `BaseOrchestrator` - Pipeline execution - `BaseStepOperator` - Remote step execution (submit/status/wait/cancel lifecycle) ## Common Tasks ### Adding New Integrations 1. Create integration package in `/src/zenml/integrations/` 2. Implement required abstractions and register flavors 3. Add tests in `/tests/integrations/` 4. Add documentation in `/docs/book/component-guide/` ### Modifying Core Functionality 1. Understand the impact on existing components 2. Maintain backward compatibility where possible 3. Add comprehensive test coverage 4. Update type hints and documentation ## Task Planning Approach When tackling complex tasks: 1. Break down the task into smaller sub-tasks 2. Research existing implementations in the codebase 3. Plan approach before implementation 4. Test incrementally as you implement 5. Document design decisions in code comments ## Expert Tips - ZenML follows a plugin architecture - study how components are registered - API stability is important - don't break public interfaces - Review similar PRs for implementation patterns - Pipeline execution is complex - test thoroughly when modifying - Centralize FastAPI logging, tracing, and unexpected error handling inside middleware; measure latency/throughput for new endpoints, cache static payloads, lazy-load heavyweight resources, and articulate serialization trade-offs in PR notes. ### Summary Checklist for PR Reviewers Quick reference for common review concerns. Detailed explanations live in the nested AGENTS.md files. - [ ] **Integration PRs:** No library imports in flavor files (`src/zenml/integrations/AGENTS.md`) - [ ] **Orchestrator PRs:** Verify `get_orchestrator_run_id` is unique per run but same for all steps (`src/zenml/orchestrators/AGENTS.md`) - [ ] **Filter model changes:** Check corresponding client method is updated (`src/zenml/models/AGENTS.md`) - [ ] **Private method changes:** Check all internal usages (see "Private Methods" above) - [ ] **Import checking:** No `zen_server` imports from outside `zen_server` (`src/zenml/zen_server/AGENTS.md`) - [ ] **Import checking:** No direct SQL imports from outside `zen_stores` (`src/zenml/zen_stores/schemas/AGENTS.md`) - [ ] **Model changes:** Adding properties OK, deleting/making optional is breaking (`src/zenml/models/AGENTS.md`) - [ ] **Dependency bumps:** If dropping old version support, it's a breaking change (`src/zenml/integrations/AGENTS.md`) - [ ] **Scheduling changes:** Must span both legacy schedule and trigger stacks (CLI + client + server + models + schemas) - [ ] **Step operator changes:** Check `BaseStepOperator`, `StepLauncher`, and at least one concrete integration ## Documentation Guidelines Structure, GitBook conventions, `toc.md` handling, and link checking live in `docs/book/AGENTS.md` and load automatically when you work with files under `docs/book/`. --- *This document is maintained to help Claude Code work effectively with the ZenML codebase. For human contributors, see CONTRIBUTING.md.* --- ### CODE OF CONDUCT (CODE-OF-CONDUCT.md) # Contributor Covenant Code of Conduct ## Our Pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. ## Our Standards Examples of behavior that contributes to a positive environment for our community include: * Demonstrating empathy and kindness toward other people * Being respectful of differing opinions, viewpoints, and experiences * Giving and gracefully accepting constructive feedback * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience * Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: * The use of sexualized language or imagery, and sexual attention or advances of any kind * Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or email address, without their explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. ## Scope This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [support@zenml.io](mailto:support@zenml.io). All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. ## Enforcement Guidelines Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: ### 1. Correction **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. ### 2. Warning **Community Impact**: A violation through a single incident or series of actions. **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. ### 3. Temporary Ban **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within the community. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at [https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0]. Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at [https://www.contributor-covenant.org/translations][translations]. [homepage]: https://www.contributor-covenant.org [v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html [Mozilla CoC]: https://github.com/mozilla/inclusion [FAQ]: https://www.contributor-covenant.org/faq [translations]: https://www.contributor-covenant.org/translations --- ### CONTRIBUTING (CONTRIBUTING.md) # 🧑‍💻 Contributing to ZenML A big welcome and thank you for considering contributing to ZenML! It’s people like you that make it a reality for users in our community. Reading and following these guidelines will help us make the contribution process easy and effective for everyone involved. It also communicates that you agree to respect the developers' time management and develop these open-source projects. In return, we will reciprocate that respect by reading your issue, assessing changes, and helping you finalize your pull requests. ## ⚡️ Quicklinks - [🧑‍💻 Contributing to ZenML](#-contributing-to-zenml) - [⚡️ Quicklinks](#-quicklinks) - [🧑‍⚖️ Code of Conduct](#-code-of-conduct) - [🛫 Getting Started](#-getting-started) - [⁉️ Issues](#-issues) - [🏷 Pull Requests: When to make one](#-pull-requests-when-to-make-one) - [💯 Pull Requests: Workflow to Contribute](#-pull-requests-workflow-to-contribute) - [🧱 Pull Requests: Rebase on develop](#-pull-requests-rebase-your-branch-on-develop) - [🧐 Linting, formatting, and tests](#-linting-formatting-and-tests) - [🚨 Reporting a Vulnerability](#-reporting-a-vulnerability) - [Coding Conventions](#coding-conventions) - [👷 Creating a new Integration](#-creating-a-new-integration) - [🆘 Getting Help](#-getting-help) ## 🧑‍⚖️ Code of Conduct We take our open-source community seriously and hold ourselves and other contributors to high standards of communication. By participating and contributing to this project, you agree to uphold our [Code of Conduct](https://github.com/zenml-io/zenml/blob/master/CODE-OF-CONDUCT.md) . ## 🛫 Getting Started Contributions are made to this repo via Issues and Pull Requests (PRs). A few general guidelines that cover both: - To report security vulnerabilities, please get in touch at [support@zenml.io](mailto:support@zenml.io), monitored by our security team. - Search for existing Issues and PRs before creating your own. - We work hard to make sure issues are handled on time, but it could take a while to investigate the root cause depending on the impact. A friendly ping in the comment thread to the submitter or a contributor can help draw attention if your issue is blocking. ### Good First Issues for New Contributors The best way to start is to check the [`good-first-issue`](https://github.com/issues?q=is%3Aopen+is%3Aissue+archived%3Afalse+user%3Azenml-io+label%3A%22good+first+issue%22) label on the issue board. The core team creates these issues as necessary smaller tasks that you can work on to get deeper into ZenML internals. These should generally require relatively simple changes, probably affecting just one or two files which we think are ideal for people new to ZenML. The next step after that would be to look at the [`good-second-issue`](https://github.com/issues?q=is%3Aopen+is%3Aissue+archived%3Afalse+user%3Azenml-io+label%3A%22good+second+issue%22) label on the issue board. These are a bit more complex, might involve more files, but should still be well-defined and achievable to people relatively new to ZenML. ### ⁉️ Issues Issues should be used to report problems with the library, request a new feature, or to discuss potential changes before a PR is created. When you create a new Issue, a template will be loaded that will guide you through collecting and providing the information we need to investigate. If you find an Issue that addresses your problem, please add your own reproduction information to the existing issue rather than creating a new one. Adding a [reaction](https://github.blog/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/) can also help by indicating to our maintainers that a particular issue is affecting more than just the reporter. ### 🏷 Pull Requests: When to make one Pull Requests (PRs) to ZenML are always welcome and can be a quick way to get your fix or improvement slated for the next release. In general, PRs should: - Only fix/add the functionality in question **OR** address widespread whitespace/style issues, not both. - Add unit or integration tests for fixed or changed functionality (if a test suite already exists). - Address a single concern in the least number of changed lines as possible. - Include documentation in the repo or in your Pull Request. - Be accompanied by a filled-out Pull Request template (loaded automatically when a PR is created). For changes that address core functionality or would require breaking changes (e.g. a major release), it's best to open an Issue to discuss your proposal first. This is not required but can save time creating and reviewing changes. ### 💯 Pull Requests: Workflow to Contribute

Please note that development in ZenML happens off of the develop branch, not main, which is the default branch on GitHub. Therefore, please pay particular attention to step 5 and step 9 below.

In general, we follow the ["fork-and-pull" Git workflow](https://github.com/susam/gitpr) 1. Review and sign the [Contributor License Agreement](https://cla-assistant.io/zenml-io/zenml) ( CLA). 2. Fork the repository to your own Github account. 3. Clone the project to your machine. 4. Checkout the **develop** branch <- `git checkout develop`. 5. Create a branch (again, off of the develop branch) locally with a succinct but descriptive name. 6. Commit changes to the branch 7. Follow the `Linting, formatting, and tests` guide to make sure your code adheres to the ZenML coding style (see below). 8. Push changes to your fork. 9. Open a PR in our repository (to the `develop` branch, **NOT** `main`) and follow the PR template so that we can efficiently review the changes. ### 🧱 Pull Requests: Rebase Your Branch on Develop 1. When making pull requests to ZenML, you should always make your changes on a branch that is based on `develop`. You can create a new branch based on `develop` by running the following command: ``` git checkout -b develop ``` 2. Fetch the latest changes from the remote `develop` branch: ``` git fetch origin develop ``` 3. Switch to your branch: ``` git checkout ``` 4. Rebase your branch on `develop`: ``` git rebase origin/develop ``` This will apply your branch's changes on top of the latest changes in `develop`, one commit at a time. 5. Resolve any conflicts that may arise during the rebase. Git will notify you if there are any conflicts that need to be resolved. Use a text editor to manually resolve the conflicts in the affected files. 6. After resolving the conflicts, stage the changes: ``` git add . ``` 7. Continue the rebase for all of your commits and go to 5) if there are conflicts. ``` git rebase --continue ``` 8. Push the rebased branch to your remote repository: ``` git push origin --force ``` 9. Open a pull request targeting the `develop` branch. The changes from your rebased branch will now be based on the latest `develop` branch. ### 🧐 Linting, formatting, and tests To install ZenML from your local checked out files including all core dev-dependencies, run: ``` pip install -e ".[server,dev]" ``` Optionally, you might want to run the following commands to ensure you have all integrations for `mypy` checks: ``` zenml integration install -y -i feast pip install click~=8.0.3 mypy --install-types ``` Warning: This might take a while for both (~ 15 minutes each, depending on your machine), however if you have time, please run it as it will make the next commands error-free. Note that the `zenml integration install` command might also fail on account of dependency conflicts so you can just install the specific integration you're working on and manually run the mypy command for the files you've been working on. You can now run the following scripts to automatically format your code and to check whether the code formatting, linting, docstrings, and spelling is in order: ``` bash scripts/format.sh bash scripts/run-ci-checks.sh ``` If you're on Windows you might have to run the formatting script as `bash scripts/format.sh --no-yamlfix` and run the yamlfix command separately as `yamlfix .github -v`. Tests can be run as follows: ``` bash scripts/test-coverage-xml.sh ``` Please note that it is good practice to run the above commands before submitting any Pull Request: The CI GitHub Action will run it anyway, so you might as well catch the errors locally! ### 🚨 Reporting a Vulnerability Please refer to [our security / reporting instructions](./SECURITY.md) for details on reporting vulnerabilities. ## Coding Conventions The code within the repository is structured in the following way - the most relevant places for contributors are highlighted with a `<-` arrow: ``` /* Detailed source-code truncated for AI context efficiency. */ ``` ## 👷 Creating a new Integration In case you want to create an entirely new integration that you would like to see supported by ZenML there are a few steps that you should follow: 1. Create the actual integration. Check out the [Integrations README](src/zenml/integrations/README.md) for detailed step-by-step instructions. 2. Create an example of how to use the integration. Check out the [Examples README](examples/README.md) to find out what to do. 3. All integrations deserve to be documented. Make sure to pay a visit to the [Component Guide](https://docs.zenml.io/stack-components/component-guide) in the docs and add your implementations. ## 🆘 Getting Help Join us in the [ZenML Slack Community](https://zenml.io/slack-invite/) to interact directly with the core team and community at large. This is a good place to ideate, discuss concepts or ask for help. --- ### Docker Compose.Yml (docker-compose.yml) x-admin-creds: &admin-creds ZENML_DEFAULT_USER_NAME: admin ZENML_DEFAULT_USER_PASSWORD: password services: # Database db: image: mysql:8.4 container_name: db ports: - 3306:3306 environment: MYSQL_ROOT_PASSWORD: password volumes: - mysql_data:/var/lib/mysql healthcheck: test: [ "CMD", "mysqladmin", "ping", "-h", "localhost" ] interval: 10s timeout: 10s retries: 3 restart: unless-stopped # The ZenML Server zenml_server: # To use the official image, uncomment the following line and # comment out the build section below. # # image: zenmldocker/zenml-server:latest build: context: ./ dockerfile: docker/zenml-server-dev.Dockerfile args: INSTALL_DEBUG_TOOLS: true # NOTE: use the local-runtime target to run the API in dev mode from the # mounted source code (will automatically refresh when code changes are # made); If the target is not set, the container will have all sources # pre-packaged into the image (no need to mount the source code). # target: local-runtime container_name: server platform: linux/amd64 ports: - 80:8080 volumes: - ./src:/zenml/src depends_on: db: condition: service_healthy healthcheck: test: [ "CMD", "python", "-c", "import requests; requests.get('http://localhost:8080/health').raise_for_status()" ] interval: 10s timeout: 10s retries: 3 restart: on-failure environment: ZENML_LOGGING_VERBOSITY: DEBUG ZENML_ANALYTICS_OPT_IN: false ZENML_SERVER_DEPLOYMENT_TYPE: docker ZENML_SERVER_AUTO_ACTIVATE: true ZENML_SERVER_SERVER_URL: http://localhost ZENML_SECRETS_STORE_TYPE: sql ZENML_SECRETS_STORE_ENCRYPTION_KEY: dummy_zenml_secrets_store_encryption_key ZENML_SERVER_JWT_SECRET_KEY: dummy_zenml_server_api_jwt_key ZENML_STORE_TYPE: sql ZENML_STORE_PASSWORD: password ZENML_STORE_URL: mysql://root@db/zenml # <- Docker replaces `db` with IP_Address <<: *admin-creds volumes: mysql_data: --- ### Pull Request Cloudbuild.Yaml (pull_request_cloudbuild.yaml) steps: # login to Dockerhub - name: gcr.io/cloud-builders/docker args: - '-c' - docker login --username=$$USERNAME --password=$$PASSWORD id: docker-login entrypoint: bash secretEnv: - USERNAME - PASSWORD # Build base image - name: gcr.io/cloud-builders/docker args: - '-c' - | docker build . \ --platform linux/amd64 \ -f docker/zenml-dev.Dockerfile \ -t $$USERNAME/zenml-dev:${_ZENML_BRANCH_NAME} id: build-base waitFor: ['-'] entrypoint: bash secretEnv: - USERNAME # Push base image - name: gcr.io/cloud-builders/docker args: - '-c' - docker push $$USERNAME/zenml-dev:${_ZENML_BRANCH_NAME} id: push-base waitFor: - docker-login - build-base entrypoint: bash secretEnv: - USERNAME # Build server image - name: gcr.io/cloud-builders/docker args: - '-c' - | docker build . \ --platform linux/amd64 \ -f docker/zenml-server-dev.Dockerfile \ -t $$USERNAME/zenml-server-dev:${_ZENML_BRANCH_NAME} id: build-server waitFor: ['-'] entrypoint: bash secretEnv: - USERNAME # Push server image - name: gcr.io/cloud-builders/docker args: - '-c' - docker push $$USERNAME/zenml-server-dev:${_ZENML_BRANCH_NAME} id: push-server waitFor: - docker-login - build-server entrypoint: bash secretEnv: - USERNAME timeout: 3600s availableSecrets: secretManager: - versionName: projects/$PROJECT_ID/secrets/docker-password/versions/2 env: PASSWORD - versionName: projects/$PROJECT_ID/secrets/docker-username/versions/1 env: USERNAME --- ### Release Cloudbuild Nightly.Yaml (release-cloudbuild-nightly.yaml) steps: # build client base image - python 3.10 - name: gcr.io/cloud-builders/docker args: - '-c' - | docker build \ --build-arg ZENML_VERSION=$TAG_NAME \ --build-arg PYTHON_VERSION=3.10 \ --build-arg ZENML_NIGHTLY=true \ --target client \ -f docker/base.Dockerfile . \ -t $$USERNAME/zenml:$TAG_NAME-py3.10-nightly # no need to check for official release regex, this is for nightly builds docker tag $$USERNAME/zenml:$TAG_NAME-py3.10-nightly $$USERNAME/zenml:py3.10-nightly id: build-base-3.10-nightly waitFor: ['-'] entrypoint: bash secretEnv: - USERNAME # build server image - python 3.11 only - name: gcr.io/cloud-builders/docker args: - '-c' - | docker build \ --build-arg ZENML_VERSION=$TAG_NAME \ --build-arg PYTHON_VERSION=3.11 \ --build-arg ZENML_NIGHTLY=true \ --target server \ -f docker/base.Dockerfile . \ -t $$USERNAME/zenml-server:$TAG_NAME-nightly id: build-server-nightly waitFor: ['-'] entrypoint: bash secretEnv: - USERNAME # login to Dockerhub - name: gcr.io/cloud-builders/docker args: - '-c' - docker login --username=$$USERNAME --password=$$PASSWORD id: docker-login entrypoint: bash secretEnv: - USERNAME - PASSWORD # push base images - name: gcr.io/cloud-builders/docker args: - '-c' - docker push --all-tags $$USERNAME/zenml id: push-base waitFor: - docker-login - build-base-3.10-nightly entrypoint: bash secretEnv: - USERNAME # push server images - name: gcr.io/cloud-builders/docker args: - '-c' - docker push --all-tags $$USERNAME/zenml-server id: push-server waitFor: - docker-login - build-server-nightly entrypoint: bash secretEnv: - USERNAME timeout: 3600s availableSecrets: secretManager: - versionName: projects/$PROJECT_ID/secrets/docker-password/versions/2 env: PASSWORD - versionName: projects/$PROJECT_ID/secrets/docker-username/versions/1 env: USERNAME --- ### Release Cloudbuild Preparation.Yaml (release-cloudbuild-preparation.yaml) steps: # login to Dockerhub - name: gcr.io/cloud-builders/docker args: - '-c' - docker login --username=$$USERNAME --password=$$PASSWORD id: docker-login entrypoint: bash secretEnv: - USERNAME - PASSWORD # Build server image - name: gcr.io/cloud-builders/docker args: - '-c' - | docker build . \ --platform linux/amd64 \ -f docker/zenml-server-dev.Dockerfile \ -t $$USERNAME/prepare-release:server-${_ZENML_NEW_VERSION} id: build-server waitFor: ['-'] entrypoint: bash secretEnv: - USERNAME # Push server images - name: gcr.io/cloud-builders/docker args: - '-c' - docker push $$USERNAME/prepare-release:server-${_ZENML_NEW_VERSION} id: push-server waitFor: - docker-login - build-server entrypoint: bash secretEnv: - USERNAME timeout: 3600s availableSecrets: secretManager: - versionName: projects/$PROJECT_ID/secrets/docker-password/versions/2 env: PASSWORD - versionName: projects/$PROJECT_ID/secrets/docker-username/versions/1 env: USERNAME --- ### Release Cloudbuild.Yaml (release-cloudbuild.yaml) steps: # build client base image - python 3.10 - name: gcr.io/cloud-builders/docker args: - '-c' - | docker buildx build \ --platform linux/amd64 \ --build-arg ZENML_VERSION=$TAG_NAME \ --build-arg PYTHON_VERSION=3.10 \ --target client \ -f docker/base.Dockerfile . \ -t $$USERNAME/zenml:$TAG_NAME-py3.10 # use latest tags only for official releases if [[ $TAG_NAME =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]; then docker tag $$USERNAME/zenml:$TAG_NAME-py3.10 $$USERNAME/zenml:py3.10 fi id: build-base-3.10 waitFor: ['-'] entrypoint: bash secretEnv: - USERNAME # build client base image - python 3.11 - name: gcr.io/cloud-builders/docker args: - '-c' - | docker buildx build \ --platform linux/amd64 \ --build-arg ZENML_VERSION=$TAG_NAME \ --build-arg PYTHON_VERSION=3.11 \ --target client \ -f docker/base.Dockerfile . \ -t $$USERNAME/zenml:$TAG_NAME-py3.11 \ -t $$USERNAME/zenml:$TAG_NAME # use latest tags only for official releases if [[ $TAG_NAME =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]; then docker tag $$USERNAME/zenml:$TAG_NAME-py3.11 $$USERNAME/zenml:py3.11 docker tag $$USERNAME/zenml:$TAG_NAME-py3.11 $$USERNAME/zenml:latest fi id: build-base-3.11 waitFor: ['-'] entrypoint: bash secretEnv: - USERNAME # build client base image - python 3.12 - name: gcr.io/cloud-builders/docker args: - '-c' - | docker buildx build \ --platform linux/amd64 \ --build-arg ZENML_VERSION=$TAG_NAME \ --build-arg PYTHON_VERSION=3.12 \ --target client \ -f docker/base.Dockerfile . \ -t $$USERNAME/zenml:$TAG_NAME-py3.12 # use latest tags only for official releases if [[ $TAG_NAME =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]; then docker tag $$USERNAME/zenml:$TAG_NAME-py3.12 $$USERNAME/zenml:py3.12 fi id: build-base-3.12 waitFor: ['-'] entrypoint: bash secretEnv: - USERNAME # build client base image - python 3.13 - name: gcr.io/cloud-builders/docker args: - '-c' - | docker buildx build \ --platform linux/amd64 \ --build-arg ZENML_VERSION=$TAG_NAME \ --build-arg PYTHON_VERSION=3.13 \ --target client \ -f docker/base.Dockerfile . \ -t $$USERNAME/zenml:$TAG_NAME-py3.13 # use latest tags only for official releases if [[ $TAG_NAME =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]; then docker tag $$USERNAME/zenml:$TAG_NAME-py3.13 $$USERNAME/zenml:py3.13 fi id: build-base-3.13 waitFor: ['-'] entrypoint: bash secretEnv: - USERNAME # build client base image - python 3.14 - name: gcr.io/cloud-builders/docker args: - '-c' - | docker buildx build \ --platform linux/amd64 \ --build-arg ZENML_VERSION=$TAG_NAME \ --build-arg PYTHON_VERSION=3.14 \ --target client \ -f docker/base.Dockerfile . \ -t $$USERNAME/zenml:$TAG_NAME-py3.14 # use latest tags only for official releases if [[ $TAG_NAME =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]; then docker tag $$USERNAME/zenml:$TAG_NAME-py3.14 $$USERNAME/zenml:py3.14 fi id: build-base-3.14 waitFor: ['-'] entrypoint: bash secretEnv: - USERNAME # build server image - python 3.11 only - name: gcr.io/cloud-builders/docker args: - '-c' - | docker build \ --platform linux/amd64 \ --build-arg ZENML_VERSION=$TAG_NAME \ --build-arg PYTHON_VERSION=3.11 \ --target server \ -f docker/base.Dockerfile . \ -t $$USERNAME/zenml-server:$TAG_NAME # use latest tags only for official releases if [[ $TAG_NAME =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]; then docker tag $$USERNAME/zenml-server:$TAG_NAME $$USERNAME/zenml-server:latest fi id: build-server waitFor: ['-'] entrypoint: bash secretEnv: - USERNAME # login to Dockerhub - name: gcr.io/cloud-builders/docker args: - '-c' - docker login --username=$$USERNAME --password=$$PASSWORD id: docker-login entrypoint: bash secretEnv: - USERNAME - PASSWORD # push base images - name: gcr.io/cloud-builders/docker args: - '-c' - docker push --all-tags $$USERNAME/zenml id: push-base waitFor: - docker-login - build-base-3.10 - build-base-3.11 - build-base-3.12 - build-base-3.13 - build-base-3.14 entrypoint: bash secretEnv: - USERNAME # push server images - name: gcr.io/cloud-builders/docker args: - '-c' - docker push --all-tags $$USERNAME/zenml-server id: push-server waitFor: - build-server - docker-login entrypoint: bash secretEnv: - USERNAME timeout: 3600s availableSecrets: secretManager: - versionName: projects/$PROJECT_ID/secrets/docker-password/versions/2 env: PASSWORD - versionName: projects/$PROJECT_ID/secrets/docker-username/versions/1 env: USERNAME --- ### ROADMAP (ROADMAP.md) # Roadmap The roadmap is an encapsulation of the features that we intend to build for ZenML. However, please note that we limited resources and therefore no means of guaranteeing that this roadmap will be followed precisely as described on this page. Rest assured we are working to follow this diligently - please keep us in check! The roadmap is public and can be found [here](https://zenml.io/roadmap). --- ### SECURITY (SECURITY.md) # 🚨 Reporting a Vulnerability If you think you have found a vulnerability, and even if you are not sure about it, please report it right away by sending an email to: [security@zenml.io](mailto:security@zenml.io?subject=Security%20Vulnerability%20Found). Please try to be as explicit as possible, describing all the steps and example code to reproduce the security issue. We will review it thoroughly and get back to you. Please refrain from publicly discussing a potential security vulnerability as this could potentially put our users at risk! It's better to discuss privately and give us a chance to find a solution first, to limit the potential impact as much as possible. --- ### Trivy Secret.Yaml (trivy-secret.yaml) allow-rules: # Instructions: add rules here to skip false positive secrets detected by # trivy. For more information, see https://aquasecurity.github.io/trivy/latest/docs/scanner/secret/#configuration # # Example: # - id: my-rule # description: skip my secret in my metadata # path: .*/my-package-1\.2\.3\.dist-info/METADATA # Disable false positive secrets detected in the PyJWT package metadata # (see https://github.com/aquasecurity/trivy/discussions/5772). - id: jwt-token description: skip JWT secret in PyJWT package metadata path: .*/PyJWT-2\..\..\.dist-info/METADATA # Disable false positive secrets detected in the aws_profile_manager # package metadata - id: aws-profile-manager-access-key description: skip AWS access key in aws_profile_manager package metadata path: .*/aws_profile_manager-0\.7\.3\.dist-info/METADATA --- ### .Coderabbit.Yaml (.coderabbit.yaml) language: "en" early_access: false reviews: high_level_summary: true poem: true review_status: false collapse_walkthrough: true path_filters: - "!**/.xml" - "!**/.json" path_instructions: - path: "src/zenml/**/*.py" instructions: "Review the Python code for conformity with Python best practices." - path: "docs/**/*.md" instructions: "Review the documentation for readability and clarity." - path: "tests/**/*.py" instructions: | "Assess the unit test code employing the PyTest testing framework. Confirm that: - The tests adhere to PyTest's established best practices. - Test descriptions are sufficiently detailed to clarify the purpose of each test." auto_review: enabled: false ignore_title_keywords: - "WIP" - "DO NOT MERGE" drafts: false base_branches: - "develop" chat: auto_reply: true --- ### .Gitbook.Yaml (.gitbook.yaml) root: ./docs/book/ structure: readme: introduction.md summary: toc.md redirects: how-to/customize-docker-builds/use-code-repositories-to-speed-up-docker-build-times: how-to/code-repositories/code-repositories.md reference/migration-guide: how-to/manage-zenml-server/migration-guide/migration-guide.md reference/migration-guide/migration-zero-twenty: how-to/manage-zenml-server/migration-guide/migration-zero-twenty.md reference/migration-guide/migration-zero-thirty: how-to/manage-zenml-server/migration-guide/migration-zero-thirty.md reference/migration-guide/migration-zero-forty: how-to/manage-zenml-server/migration-guide/migration-zero-forty.md reference/migration-guide/migration-zero-sixty: how-to/manage-zenml-server/migration-guide/migration-zero-sixty.md getting-started/deploying-zenml/manage-the-deployed-services/upgrade-the-version-of-the-zenml-server: how-to/manage-zenml-server/upgrade-zenml-server.md getting-started/deploying-zenml/manage-the-deployed-services/troubleshoot-your-deployed-server: how-to/manage-zenml-server/troubleshoot-your-deployed-server.md how-to/stack-deployment/implement-a-custom-integration: how-to/stack-components/stack_components.md how-to/setting-up-a-project-repository/best-practices: how-to/code-repositories/code-repositories.md getting-started/zenml-pro/system-architectures: getting-started/system-architectures.md how-to/build-pipelines/name-your-pipeline-and-runs: how-to/steps-pipelines/steps_and_pipelines.md how-to/model-management-metrics/track-metrics-metadata/attach-metadata-to-steps: how-to/metadata/metadata.md getting-started/zenml-pro/user-management: getting-started/core-concepts.md getting-started/zenml-pro/zenml-cloud: getting-started/system-architectures.md how-to/setting-up-a-project-repository/: how-to/code-repositories/code-repositories.md how-to/setting-up-a-project-repository/set-up-repository: how-to/code-repositories/code-repositories.md how-to/setting-up-a-project-repository/connect-your-git-repository: how-to/code-repositories/code-repositories.md how-to/setting-up-a-project-repository/using-project-templates: how-to/templates/templates.md how-to/setting-up-a-project-repository/create-your-own-template: how-to/templates/templates.md how-to/setting-up-a-project-repository/shared-components-for-teams: how-to/stack-components/stack_components.md how-to/setting-up-a-project-repository/stacks-pipelines-models: how-to/steps-pipelines/steps_and_pipelines.md how-to/setting-up-a-project-repository/access-management: how-to/tags/tags.md how-to/develop-locally/: how-to/steps-pipelines/steps_and_pipelines.md how-to/develop-locally/local-prod-pipeline-variants: how-to/steps-pipelines/steps_and_pipelines.md how-to/develop-locally/keep-your-dashboard-server-clean: how-to/steps-pipelines/logging.md how-to/connecting-to-zenml/: how-to/manage-zenml-server/connecting-to-zenml/README.md how-to/connecting-to-zenml/connect-in-with-your-user-interactive: how-to/manage-zenml-server/connecting-to-zenml/connect-in-with-your-user-interactive.md how-to/connecting-to-zenml/connect-with-a-service-account: how-to/manage-zenml-server/connecting-to-zenml/connect-with-a-service-account.md how-to/build-pipelines/: how-to/steps-pipelines/steps_and_pipelines.md how-to/build-pipelines/use-pipeline-step-parameters: how-to/steps-pipelines/configuration.md how-to/build-pipelines/configuring-a-pipeline-at-runtime: how-to/steps-pipelines/configuration.md how-to/build-pipelines/reference-environment-variables-in-configurations: how-to/steps-pipelines/configuration.md how-to/build-pipelines/step-output-typing-and-annotation: how-to/steps-pipelines/steps_and_pipelines.md how-to/build-pipelines/control-caching-behavior: how-to/steps-pipelines/advanced_features.md how-to/build-pipelines/schedule-a-pipeline: how-to/steps-pipelines/scheduling.md how-to/build-pipelines/delete-a-pipeline: how-to/steps-pipelines/steps_and_pipelines.md how-to/build-pipelines/compose-pipelines: how-to/steps-pipelines/advanced_features.md how-to/build-pipelines/dynamically-assign-artifact-names: how-to/artifacts/artifacts.md how-to/pipeline-development/build-pipelines/dynamically-assign-artifact-names: how-to/artifacts/artifacts.md how-to/build-pipelines/retry-steps: how-to/steps-pipelines/advanced_features.md how-to/build-pipelines/run-pipelines-asynchronously: how-to/steps-pipelines/steps_and_pipelines.md how-to/build-pipelines/control-execution-order-of-steps: how-to/steps-pipelines/steps_and_pipelines.md how-to/build-pipelines/using-a-custom-step-invocation-id: how-to/steps-pipelines/steps_and_pipelines.md how-to/build-pipelines/tag-your-pipeline-runs: how-to/tags/tags.md how-to/build-pipelines/use-failure-success-hooks: how-to/steps-pipelines/advanced_features.md how-to/build-pipelines/access-secrets-in-a-step: how-to/secrets/secrets.md how-to/build-pipelines/run-an-individual-step: how-to/steps-pipelines/steps_and_pipelines.md how-to/build-pipelines/fetching-pipelines: how-to/steps-pipelines/steps_and_pipelines.md how-to/build-pipelines/get-past-pipeline-step-runs: how-to/steps-pipelines/steps_and_pipelines.md how-to/trigger-pipelines/: how-to/templates/templates.md how-to/trigger-pipelines/use-templates-python: how-to/templates/templates.md how-to/trigger-pipelines/use-templates-dashboard: how-to/templates/templates.md how-to/trigger-pipelines/use-templates-rest-api: how-to/templates/templates.md how-to/use-configuration-files/: how-to/steps-pipelines/yaml_configuration.md how-to/use-configuration-files/how-to-use-config: how-to/steps-pipelines/yaml_configuration.md how-to/use-configuration-files/what-can-be-configured: how-to/steps-pipelines/yaml_configuration.md how-to/use-configuration-files/runtime-configuration: how-to/steps-pipelines/yaml_configuration.md how-to/use-configuration-files/configuration-hierarchy: how-to/steps-pipelines/yaml_configuration.md how-to/use-configuration-files/retrieve-used-configuration-of-a-run: how-to/steps-pipelines/yaml_configuration.md how-to/use-configuration-files/autogenerate-a-template-yaml-file: how-to/steps-pipelines/yaml_configuration.md how-to/handle-data-artifacts/: how-to/artifacts/artifacts.md how-to/handle-data-artifacts/artifact-versioning: how-to/artifacts/artifacts.md how-to/handle-data-artifacts/return-multiple-outputs-from-a-step: how-to/artifacts/artifacts.md how-to/handle-data-artifacts/delete-an-artifact: how-to/artifacts/artifacts.md how-to/handle-data-artifacts/tagging: how-to/tags/tags.md how-to/handle-data-artifacts/get-arbitrary-artifacts-in-a-step: how-to/artifacts/artifacts.md how-to/handle-data-artifacts/handle-custom-data-types: how-to/artifacts/materializers.md how-to/handle-data-artifacts/load-artifacts-into-memory: how-to/artifacts/artifacts.md how-to/handle-data-artifacts/datasets: how-to/artifacts/artifacts.md how-to/handle-data-artifacts/manage-big-data: how-to/artifacts/artifacts.md how-to/handle-data-artifacts/unmaterialized-artifacts: how-to/artifacts/materializers.md how-to/handle-data-artifacts/passing-artifacts-between-pipelines: how-to/artifacts/artifacts.md how-to/handle-data-artifacts/registering-existing-data: how-to/artifacts/artifacts.md how-to/visualize-artifacts/: how-to/artifacts/visualizations.md how-to/visualize-artifacts/types-of-visualizations: how-to/artifacts/visualizations.md how-to/visualize-artifacts/creating-custom-visualizations: how-to/artifacts/visualizations.md how-to/visualize-artifacts/visualizations-in-dashboard: how-to/artifacts/visualizations.md how-to/visualize-artifacts/disabling-visualizations: how-to/artifacts/visualizations.md how-to/use-the-model-control-plane/: how-to/models/models.md how-to/use-the-model-control-plane/register-a-model: how-to/models/models.md how-to/use-the-model-control-plane/delete-a-model: how-to/models/models.md how-to/use-the-model-control-plane/associate-a-pipeline-with-a-model: how-to/models/models.md how-to/use-the-model-control-plane/connecting-artifacts-via-a-model: how-to/models/models.md how-to/use-the-model-control-plane/model-versions: how-to/models/models.md how-to/use-the-model-control-plane/load-a-model-in-code: how-to/models/models.md how-to/use-the-model-control-plane/promote-a-model: how-to/models/models.md how-to/use-the-model-control-plane/linking-model-binaries-data-to-models: how-to/models/models.md how-to/use-the-model-control-plane/load-artifacts-from-model: how-to/models/models.md how-to/track-metrics-metadata/: how-to/metadata/metadata.md how-to/track-metrics-metadata/attach-metadata-to-a-model: how-to/metadata/metadata.md how-to/track-metrics-metadata/attach-metadata-to-an-artifact: how-to/metadata/metadata.md how-to/track-metrics-metadata/attach-metadata-to-steps: how-to/metadata/metadata.md how-to/track-metrics-metadata/grouping-metadata: how-to/metadata/metadata.md how-to/track-metrics-metadata/logging-metadata: how-to/metadata/metadata.md how-to/track-metrics-metadata/fetch-metadata-within-steps: how-to/metadata/metadata.md how-to/track-metrics-metadata/fetch-metadata-within-pipeline: how-to/metadata/metadata.md how-to/customize-docker-builds/: how-to/containerization/containerization.md how-to/customize-docker-builds/docker-settings-on-a-pipeline: how-to/containerization/containerization.md how-to/customize-docker-builds/docker-settings-on-a-step: how-to/containerization/containerization.md how-to/customize-docker-builds/use-a-prebuilt-image: how-to/containerization/containerization.md how-to/customize-docker-builds/specify-pip-dependencies-and-apt-packages: how-to/containerization/containerization.md how-to/customize-docker-builds/how-to-use-a-private-pypi-repository: how-to/containerization/containerization.md how-to/customize-docker-builds/use-your-own-docker-files: how-to/containerization/containerization.md how-to/customize-docker-builds/which-files-are-built-into-the-image: how-to/containerization/containerization.md how-to/customize-docker-builds/how-to-reuse-builds: how-to/containerization/containerization.md how-to/customize-docker-builds/define-where-an-image-is-built: how-to/containerization/containerization.md how-to/configure-python-environments/: how-to/containerization/containerization.md how-to/configure-python-environments/handling-dependencies: how-to/containerization/containerization.md how-to/configure-python-environments/configure-the-server-environment: how-to/containerization/containerization.md how-to/training-with-gpus/training-with-gpus: how-to/containerization/containerization.md how-to/training-with-gpus/accelerate-distributed-training: how-to/containerization/containerization.md how-to/run-remote-steps-and-pipelines-from-notebooks/: how-to/steps-pipelines/steps_and_pipelines.md how-to/run-remote-steps-and-pipelines-from-notebooks/limitations-of-defining-steps-in-notebook-cells: how-to/steps-pipelines/steps_and_pipelines.md how-to/run-remote-steps-and-pipelines-from-notebooks/run-a-single-step-from-a-notebook: how-to/steps-pipelines/steps_and_pipelines.md how-to/manage-the-zenml-server/: how-to/manage-zenml-server/upgrade-zenml-server.md how-to/manage-the-zenml-server/best-practices-upgrading-zenml: how-to/manage-zenml-server/best-practices-upgrading-zenml.md how-to/manage-the-zenml-server/upgrade-zenml-server: how-to/manage-zenml-server/upgrade-zenml-server.md how-to/manage-the-zenml-server/using-zenml-server-in-prod: how-to/manage-zenml-server/using-zenml-server-in-prod.md how-to/manage-the-zenml-server/troubleshoot-your-deployed-server: how-to/manage-zenml-server/troubleshoot-your-deployed-server.md how-to/manage-the-zenml-server/migration-guide/migration-guide: how-to/manage-zenml-server/migration-guide/migration-guide.md how-to/manage-the-zenml-server/migration-guide/migration-zero-twenty: how-to/manage-zenml-server/migration-guide/migration-zero-twenty.md how-to/manage-the-zenml-server/migration-guide/migration-zero-thirty: how-to/manage-zenml-server/migration-guide/migration-zero-thirty.md how-to/manage-the-zenml-server/migration-guide/migration-zero-forty: how-to/manage-zenml-server/migration-guide/migration-zero-forty.md how-to/manage-the-zenml-server/migration-guide/migration-zero-sixty: how-to/manage-zenml-server/migration-guide/migration-zero-sixty.md how-to/control-logging/: how-to/steps-pipelines/logging.md how-to/control-logging/view-logs-on-the-dasbhoard: how-to/steps-pipelines/logging.md how-to/control-logging/enable-or-disable-logs-storing: how-to/steps-pipelines/logging.md how-to/control-logging/set-logging-verbosity: how-to/steps-pipelines/logging.md how-to/control-logging/disable-rich-traceback: how-to/steps-pipelines/logging.md how-to/control-logging/disable-colorful-logging: how-to/steps-pipelines/logging.md docs/getting-started/zenml-pro/core-concepts/tenants: getting-started/core-concepts.md docs/introduction: introduction.md docs/getting-started/installation: getting-started/installation.md docs/getting-started/core-concepts: getting-started/core-concepts.md docs/getting-started/system-architectures: getting-started/system-architectures.md docs/getting-started/deploying-zenml/: getting-started/deploying-zenml/README.md docs/getting-started/zenml-pro/: getting-started/core-concepts.md docs/how-to/interact-with-secrets: how-to/secrets/secrets.md docs/how-to/debug-and-solve-issues: reference/faq.md docs/how-to/contribute-to-zenml/: reference/community-and-content.md docs/component-guide/: how-to/stack-components/stack_components.md docs/reference/python-client: reference/environment-variables.md docs/reference/global-settings: reference/global-settings.md docs/reference/environment-variables: reference/environment-variables.md docs/reference/how-do-i: reference/faq.md docs/reference/community-and-content: reference/community-and-content.md docs/reference/faq: reference/faq.md how-to/advanced-topics/manage-zenml-server/: how-to/manage-zenml-server/upgrade-zenml-server.md how-to/project-setup-and-management/connecting-to-zenml/: how-to/manage-zenml-server/connecting-to-zenml/README.md how-to/project-setup-and-management/connecting-to-zenml/connect-in-with-your-user-interactive: how-to/manage-zenml-server/connecting-to-zenml/connect-in-with-your-user-interactive.md how-to/project-setup-and-management/connecting-to-zenml/connect-with-a-service-account: how-to/manage-zenml-server/connecting-to-zenml/connect-with-a-service-account.md how-to/advanced-topics/manage-zenml-server/upgrade-zenml-server: how-to/manage-zenml-server/upgrade-zenml-server.md how-to/advanced-topics/manage-zenml-server/best-practices-upgrading-zenml: how-to/manage-zenml-server/best-practices-upgrading-zenml.md how-to/advanced-topics/manage-zenml-server/using-zenml-server-in-prod: how-to/manage-zenml-server/using-zenml-server-in-prod.md how-to/advanced-topics/manage-zenml-server/troubleshoot-your-deployed-server: how-to/manage-zenml-server/troubleshoot-your-deployed-server.md how-to/advanced-topics/manage-zenml-server/migration-guide/migration-guide: how-to/manage-zenml-server/migration-guide/migration-guide.md how-to/advanced-topics/manage-zenml-server/migration-guide/migration-zero-twenty: how-to/manage-zenml-server/migration-guide/migration-zero-twenty.md how-to/advanced-topics/manage-zenml-server/migration-guide/migration-zero-thirty: how-to/manage-zenml-server/migration-guide/migration-zero-thirty.md how-to/advanced-topics/manage-zenml-server/migration-guide/migration-zero-forty: how-to/manage-zenml-server/migration-guide/migration-zero-forty.md how-to/advanced-topics/manage-zenml-server/migration-guide/migration-zero-sixty: how-to/manage-zenml-server/migration-guide/migration-zero-sixty.md how-to/project-setup-and-management/setting-up-a-project-repository/access-management: how-to/tags/tags.md how-to/interact-with-secrets: how-to/secrets/secrets.md how-to/project-setup-and-management/develop-locally/: how-to/steps-pipelines/steps_and_pipelines.md how-to/project-setup-and-management/develop-locally/local-prod-pipeline-variants: how-to/steps-pipelines/steps_and_pipelines.md how-to/infrastructure-deployment/configure-python-environments/: how-to/containerization/containerization.md how-to/infrastructure-deployment/configure-python-environments/handling-dependencies: how-to/containerization/containerization.md how-to/infrastructure-deployment/configure-python-environments/configure-the-server-environment: how-to/containerization/containerization.md how-to/infrastructure-deployment/customize-docker-builds/: how-to/containerization/containerization.md how-to/infrastructure-deployment/customize-docker-builds/docker-settings-on-a-pipeline: how-to/containerization/containerization.md how-to/infrastructure-deployment/customize-docker-builds/docker-settings-on-a-step: how-to/containerization/containerization.md how-to/infrastructure-deployment/customize-docker-builds/use-a-prebuilt-image: how-to/containerization/containerization.md how-to/infrastructure-deployment/customize-docker-builds/specify-pip-dependencies-and-apt-packages: how-to/containerization/containerization.md how-to/infrastructure-deployment/customize-docker-builds/how-to-use-a-private-pypi-repository: how-to/containerization/containerization.md how-to/infrastructure-deployment/customize-docker-builds/use-your-own-docker-files: how-to/containerization/containerization.md how-to/infrastructure-deployment/customize-docker-builds/which-files-are-built-into-the-image: how-to/containerization/containerization.md how-to/infrastructure-deployment/customize-docker-builds/how-to-reuse-builds: how-to/containerization/containerization.md how-to/infrastructure-deployment/customize-docker-builds/define-where-an-image-is-built: how-to/containerization/containerization.md how-to/data-artifact-management/handle-data-artifacts/unmaterialized-artifacts: how-to/artifacts/materializers.md how-to/data-artifact-management/handle-data-artifacts/passing-artifacts-between-pipelines: how-to/artifacts/artifacts.md how-to/data-artifact-management/handle-data-artifacts/registering-existing-data: how-to/artifacts/artifacts.md how-to/advanced-topics/control-logging/: how-to/steps-pipelines/logging.md how-to/advanced-topics/control-logging/view-logs-on-the-dasbhoard: how-to/steps-pipelines/logging.md how-to/advanced-topics/control-logging/enable-or-disable-logs-storing: how-to/steps-pipelines/logging.md how-to/advanced-topics/control-logging/set-logging-verbosity: how-to/steps-pipelines/logging.md how-to/advanced-topics/control-logging/disable-rich-traceback: how-to/steps-pipelines/logging.md how-to/advanced-topics/control-logging/disable-colorful-logging: how-to/steps-pipelines/logging.md how-to/pipeline-development/trigger-pipelines/: how-to/templates/templates.md how-to/pipeline-development/trigger-pipelines/use-templates-python: how-to/templates/templates.md how-to/pipeline-development/trigger-pipelines/use-templates-cli: how-to/templates/templates.md how-to/pipeline-development/trigger-pipelines/use-templates-dashboard: how-to/templates/templates.md how-to/pipeline-development/trigger-pipelines/use-templates-rest-api: how-to/templates/templates.md # New redirects getting-started/deploying-zenml: getting-started/deploying-zenml/README.md getting-started/deploying-zenml/custom-secret-stores: getting-started/deploying-zenml/custom-secret-stores.md getting-started/deploying-zenml/deploy-using-huggingface-spaces: getting-started/deploying-zenml/deploy-using-huggingface-spaces.md getting-started/deploying-zenml/deploy-with-custom-image: getting-started/deploying-zenml/deploy-with-custom-image.md getting-started/deploying-zenml/deploy-with-docker: getting-started/deploying-zenml/deploy-with-docker.md getting-started/deploying-zenml/deploy-with-helm: getting-started/deploying-zenml/deploy-with-helm.md getting-started/deploying-zenml/secret-management: getting-started/deploying-zenml/secret-management.md how-to/contribute-to-zenml: reference/community-and-content.md how-to/contribute-to-zenml/implement-a-custom-integration: how-to/stack-components/stack_components.md how-to/control-logging: how-to/steps-pipelines/logging.md how-to/control-logging/disable-step-names-in-logs: how-to/steps-pipelines/logging.md how-to/control-logging/set-logging-format: how-to/steps-pipelines/logging.md how-to/customize-docker-builds: how-to/containerization/containerization.md how-to/data-artifact-management: how-to/artifacts/artifacts.md how-to/data-artifact-management/complex-usecases/passing-artifacts-between-pipelines: how-to/artifacts/artifacts.md how-to/data-artifact-management/complex-usecases/registering-existing-data: how-to/artifacts/artifacts.md how-to/data-artifact-management/complex-usecases/unmaterialized-artifacts: how-to/artifacts/materializers.md how-to/data-artifact-management/handle-data-artifacts: how-to/artifacts/artifacts.md how-to/data-artifact-management/handle-data-artifacts/artifact-versioning: how-to/artifacts/artifacts.md how-to/data-artifact-management/handle-data-artifacts/artifacts-naming: how-to/artifacts/artifacts.md how-to/data-artifact-management/handle-data-artifacts/delete-an-artifact: how-to/artifacts/artifacts.md how-to/data-artifact-management/handle-data-artifacts/get-arbitrary-artifacts-in-a-step: how-to/artifacts/artifacts.md how-to/data-artifact-management/handle-data-artifacts/handle-custom-data-types: how-to/artifacts/materializers.md how-to/data-artifact-management/handle-data-artifacts/load-artifacts-into-memory: how-to/artifacts/artifacts.md how-to/data-artifact-management/handle-data-artifacts/pass-files-through-steps: how-to/artifacts/artifacts.md how-to/data-artifact-management/handle-data-artifacts/return-multiple-outputs-from-a-step: how-to/artifacts/artifacts.md how-to/data-artifact-management/handle-data-artifacts/tagging: how-to/tags/tags.md how-to/data-artifact-management/visualize-artifacts: how-to/artifacts/visualizations.md how-to/data-artifact-management/visualize-artifacts/creating-custom-visualizations: how-to/artifacts/visualizations.md how-to/data-artifact-management/visualize-artifacts/disabling-visualizations: how-to/artifacts/visualizations.md how-to/data-artifact-management/visualize-artifacts/types-of-visualizations: how-to/artifacts/visualizations.md how-to/data-artifact-management/visualize-artifacts/visualizations-in-dashboard: how-to/artifacts/visualizations.md how-to/manage-zenml-server: how-to/manage-zenml-server/upgrade-zenml-server.md how-to/manage-zenml-server/best-practices-upgrading-zenml: how-to/manage-zenml-server/best-practices-upgrading-zenml.md how-to/manage-zenml-server/connecting-to-zenml: how-to/manage-zenml-server/connecting-to-zenml/README.md how-to/manage-zenml-server/connecting-to-zenml/connect-in-with-your-user-interactive: how-to/manage-zenml-server/connecting-to-zenml/connect-in-with-your-user-interactive.md how-to/manage-zenml-server/connecting-to-zenml/connect-with-a-service-account: how-to/manage-zenml-server/connecting-to-zenml/connect-with-a-service-account.md how-to/manage-zenml-server/connecting-to-zenml/connect-with-an-api-token: how-to/manage-zenml-server/connecting-to-zenml/connect-with-a-service-account.md how-to/manage-zenml-server/mcp-chat-with-server: reference/llms-txt.md how-to/manage-zenml-server/migration-guide: how-to/manage-zenml-server/migration-guide/migration-guide.md how-to/manage-zenml-server/migration-guide/migration-zero-forty: how-to/manage-zenml-server/migration-guide/migration-zero-forty.md how-to/manage-zenml-server/migration-guide/migration-zero-sixty: how-to/manage-zenml-server/migration-guide/migration-zero-sixty.md how-to/manage-zenml-server/migration-guide/migration-zero-thirty: how-to/manage-zenml-server/migration-guide/migration-zero-thirty.md how-to/manage-zenml-server/migration-guide/migration-zero-twenty: how-to/manage-zenml-server/migration-guide/migration-zero-twenty.md how-to/manage-zenml-server/troubleshoot-your-deployed-server: how-to/manage-zenml-server/troubleshoot-your-deployed-server.md how-to/manage-zenml-server/upgrade-zenml-server: how-to/manage-zenml-server/upgrade-zenml-server.md how-to/manage-zenml-server/using-zenml-server-in-prod: how-to/manage-zenml-server/using-zenml-server-in-prod.md how-to/manage-zenml-server/vscode-extension: reference/community-and-content.md how-to/model-management-metrics: how-to/models/models.md how-to/model-management-metrics/model-control-plane: how-to/models/models.md how-to/model-management-metrics/model-control-plane/associate-a-pipeline-with-a-model: how-to/models/models.md how-to/model-management-metrics/model-control-plane/connecting-artifacts-via-a-model: how-to/models/models.md how-to/model-management-metrics/model-control-plane/delete-a-model: how-to/models/models.md how-to/model-management-metrics/model-control-plane/linking-model-binaries-data-to-models: how-to/models/models.md how-to/model-management-metrics/model-control-plane/load-a-model-in-code: how-to/models/models.md how-to/model-management-metrics/model-control-plane/load-artifacts-from-model: how-to/models/models.md how-to/model-management-metrics/model-control-plane/model-versions: how-to/models/models.md how-to/model-management-metrics/model-control-plane/promote-a-model: how-to/models/models.md how-to/model-management-metrics/model-control-plane/register-a-model: how-to/models/models.md how-to/model-management-metrics/track-metrics-metadata: how-to/metadata/metadata.md how-to/model-management-metrics/track-metrics-metadata/attach-metadata-to-a-model: how-to/metadata/metadata.md how-to/model-management-metrics/track-metrics-metadata/attach-metadata-to-a-run: how-to/metadata/metadata.md how-to/model-management-metrics/track-metrics-metadata/attach-metadata-to-a-step: how-to/metadata/metadata.md how-to/model-management-metrics/track-metrics-metadata/attach-metadata-to-an-artifact: how-to/metadata/metadata.md how-to/model-management-metrics/track-metrics-metadata/fetch-metadata-within-pipeline: how-to/metadata/metadata.md how-to/model-management-metrics/track-metrics-metadata/fetch-metadata-within-steps: how-to/metadata/metadata.md how-to/model-management-metrics/track-metrics-metadata/grouping-metadata: how-to/metadata/metadata.md how-to/model-management-metrics/track-metrics-metadata/logging-metadata: how-to/metadata/metadata.md how-to/project-setup-and-management: how-to/code-repositories/code-repositories.md how-to/project-setup-and-management/collaborate-with-team: how-to/code-repositories/code-repositories.md how-to/project-setup-and-management/collaborate-with-team/access-management: how-to/tags/tags.md how-to/project-setup-and-management/interact-with-secrets: how-to/secrets/secrets.md how-to/project-setup-and-management/setting-up-a-project-repository: how-to/code-repositories/code-repositories.md how-to/project-setup-and-management/setting-up-a-project-repository/connect-your-git-repository: how-to/code-repositories/code-repositories.md how-to/project-setup-and-management/setting-up-a-project-repository/set-up-repository: how-to/code-repositories/code-repositories.md how-to/trigger-pipelines: how-to/templates/templates.md how-to/trigger-pipelines/use-templates-cli: how-to/templates/templates.md reference/how-do-i: reference/faq.md # Redirects for pipeline-development paths how-to/pipeline-development/use-configuration-files/runtime-configuration: how-to/steps-pipelines/yaml_configuration.md how-to/pipeline-development/build-pipelines/fetching-pipelines: how-to/steps-pipelines/steps_and_pipelines.md # how-to/pipeline-development/training-with-gpus: how-to/containerization/containerization.md how-to/pipeline-development/build-pipelines/schedule-a-pipeline: how-to/steps-pipelines/scheduling.md how-to/pipeline-development/use-configuration-files: how-to/steps-pipelines/yaml_configuration.md how-to/pipeline-development/use-configuration-files/what-can-be-configured: how-to/steps-pipelines/yaml_configuration.md # how-to/pipeline-development/develop-locally/keep-your-dashboard-server-clean: how-to/steps-pipelines/logging.md how-to/pipeline-development/build-pipelines/use-pipeline-step-parameters: how-to/steps-pipelines/configuration.md how-to/pipeline-development/build-pipelines/control-execution-order-of-steps: how-to/steps-pipelines/steps_and_pipelines.md how-to/pipeline-development/build-pipelines/step-output-typing-and-annotation: how-to/steps-pipelines/steps_and_pipelines.md how-to/pipeline-development/use-configuration-files/configuration-hierarchy: how-to/steps-pipelines/yaml_configuration.md # how-to/pipeline-development/configure-python-environments: how-to/containerization/containerization.md how-to/pipeline-development/build-pipelines/using-a-custom-step-invocation-id: how-to/steps-pipelines/steps_and_pipelines.md how-to/pipeline-development/build-pipelines: how-to/steps-pipelines/steps_and_pipelines.md # how-to/pipeline-development/training-with-gpus/accelerate-distributed-training: how-to/containerization/containerization.md how-to/pipeline-development/build-pipelines/name-your-pipeline-runs: how-to/steps-pipelines/steps_and_pipelines.md how-to/artifacts/complex_use_cases: how-to/artifacts/materializers.md # how-to/notebooks: how-to/steps-pipelines/steps_and_pipelines.md --- ### README (docs/README.md) # ZenML Docs ## General documentation We write our [documentation](https://docs.zenml.io/) in Markdown files and use [GitBook](https://www.gitbook.com/) to build it. The documentation source files can be found in this repository at `docs/book` * You can edit the docs by simply editing the markdown files. Once the changes get merged into main, the docs will be automatically refreshed. (Note: you might need to refresh your browser.) * If you're adding a new page, make sure to add a reference to it inside the tables of contents `docs/book/toc.md` * Make sure to be consistent with the existing docs (similar style/messaging, reuse existing images if they fit your use case) ## SDK Docs The ZenML SDK docs are generated from our Python docstrings using [mkdocs](https://www.mkdocs.org/). The SDK docs will be automatically updated each release using a Github workflow and can be found at[https://sdkdocs.zenml.io](https://sdkdocs.zenml.io/). ### Building the SDK Docs locally To build them locally follow these steps: * Clone the repository * Install ZenML and Jinja2 dependencies ```bash pip3 install -e ".[server,dev]" pip3 install "Jinja2==3.0.3" ``` * Modify `docs/mkdocs.yml` as follows: ```yaml watch: - ../src/zenml ``` ```yaml watch: - src/zenml ``` * Run `python3 docs/mkdocstrings_helper.py` * Run: ```bash rm -rf src/zenml/zen_stores/migrations/env.py rm -rf src/zenml/zen_stores/migrations/versions rm -rf src/zenml/zen_stores/migrations/script.py.mako ``` * Run `mkdocs serve -f docs/mkdocs.yml -a localhost:` from the repository root - running it from elsewhere can lead to unexpected errors. This script will compose the docs hierarchy and serve it (http://127.0.0.1:/). ## Link Checker Tool The `link_checker.py` script is a utility tool for managing and validating links in our documentation. It helps maintain the quality of our documentation by: * Finding broken or outdated links * Converting relative links to absolute URLs * Validating link accessibility * Supporting both directory-wide and file-specific scanning ### Usage The script supports several modes of operation: ```bash # Find all links containing 'docs.zenml.io' in a directory python link_checker.py --dir docs/book --substring docs.zenml.io # Check specific files for links containing 'how-to' python link_checker.py --files file1.md file2.md --substring how-to # Preview link replacements without making changes python link_checker.py --files file1.md --replace-links --dry-run # Replace and validate links python link_checker.py --files file1.md --replace-links --validate-links # Transform paths using custom URL mappings python link_checker.py --dir docs/book --replace-links --url-mapping user-guide=user-guides ``` ### Features 1. **Link Detection**: - Scans for various link types (inline, reference-style, HTML, bare URLs) - Supports substring-based filtering - Works with both directories and individual files 2. **Link Transformation**: - Converts relative documentation links to absolute URLs - Handles README.md files and various link formats - Preserves fragments and query parameters - Supports custom URL path mappings to transform specific path segments 3. **Link Validation**: - Validates links via HTTP requests - Parallel validation for better performance - Detailed error reporting - Uses narrow default CI policies for external domains that block automated checks. These policies are status-specific: approved bot-hostile URLs or domains may accept specific statuses such as `403`, `406`, `429`, or `502`, while non-exempt hard failures such as `404` and `410` still fail. ### Requirements For link validation functionality, the `requests` package is required: ```bash pip install requests ``` ## Contributors We welcome and recognize all contributions. You can see a list of current contributors [here](https://github.com/zenml-io/zenml/graphs/contributors). --- ### Mkdocs.Yml (docs/mkdocs.yml) site_name: ZenML SDK Docs site_url: https://sdkdocs.zenml.io/ docs_dir: mkdocs theme: name: material locale: en favicon: _assets/favicon.png custom_dir: mkdocs/overrides repo_url: https://github.com/zenml-io/zenml edit_uri: https://github.com/zenml-io/zenml/docs/ plugins: - search - awesome-pages - mkdocstrings: default_handler: python handlers: python: setup_commands: - import docs.sys_modules_mock options: # Include all public members, not just those in __all__ members: true inherited_members: false # Show all submodules automatically show_submodules: true # Include functions, classes, modules, attributes docstring_style: google # Selection options filters: - "!^_" # Exclude private members (starting with _) - "^__init__$" # But include __init__ methods - "^__new__$" # And __new__ methods # Rendering options show_source: true show_root_heading: true show_root_toc_entry: true show_object_full_path: false show_category_heading: true # Group members by category group_by_category: true # Show type annotations show_signature_annotations: true # Show return type annotations show_return_annotations: true # Show the bases of classes show_bases: true # Merge init method documentation into class docstring merge_init_into_class: true # This setting ensures links don't use directory URLs use_directory_urls: false copyright: > Copyright © 2025 ZenML GmbH – Change cookie settings extra: consent: title: This website uses cookies description: >- We use cookies to recognize your repeated visits and preferences, as well as to measure the effectiveness of our documentation and whether users find what they're searching for. With your consent, you're helping us to make our documentation better. actions: - accept - reject - manage version: provider: mike --- ### Mocked Libs.Json (docs/mocked_libs.json) [ "PIL", "accelerate", "accelerate.commands", "accelerate.commands.launch", "adlfs", "aws_profile_manager", "azure.ai", "azure.ai.ml", "azure.ai.ml.constants", "azure.ai.ml.dsl", "azure.ai.ml.entities", "azure.identity", "azure.keyvault", "azure.keyvault.secrets", "azure.mgmt.containerregistry", "azure.mgmt.containerservice", "azure.mgmt.storage", "azure.storage", "azure.storage.blob", "bentoml", "bentoml._internal", "bentoml._internal.bento", "bentoml.client", "bentoml.exceptions", "boto3", "botocore", "botocore.client", "botocore.exceptions", "botocore.signers", "catboost", "comet_ml", "datasets", "datasets.dataset_dict", "deepchecks", "deepchecks.core", "deepchecks.core.check_result", "deepchecks.core.checks", "deepchecks.core.suite", "deepchecks.tabular", "deepchecks.tabular.checks", "deepchecks.tabular.checks.data_integrity", "deepchecks.tabular.checks.model_evaluation", "deepchecks.tabular.suites", "deepchecks.vision", "deepchecks.vision.checks", "deepchecks.vision.suites", "discord", "evidently", "evidently.metric_preset", "evidently.metric_preset.metric_preset", "evidently.metrics", "evidently.metrics.base_metric", "evidently.pipeline", "evidently.pipeline.column_mapping", "evidently.report", "evidently.test_preset", "evidently.test_preset.test_preset", "evidently.test_suite", "evidently.tests", "evidently.tests.base_test", "evidently.utils", "evidently.utils.generators", "facets_overview", "facets_overview.generic_feature_statistics_generator", "feast", "feast.infra", "feast.infra.registry", "feast.infra.registry.base_registry", "fsspec", "fsspec.asyn", "gcsfs", "github", "github.Repository", "gitlab", "gitlab.v4", "gitlab.v4.objects", "google", "google.api_core", "google.api_core.exceptions", "google.auth", "google.auth.aws", "google.auth._default", "google.auth.exceptions", "google.auth.transport", "google.auth.transport.requests", "google.cloud", "google.cloud.aiplatform", "google.cloud.aiplatform.compat", "google.cloud.aiplatform.compat.types", "google.cloud.aiplatform_v1", "google.cloud.aiplatform_v1.types", "google.cloud.aiplatform_v1.types.job_state", "google.cloud.devtools", "google.cloud.functions_v2", "google.cloud.functions_v2.types", "google.cloud.location", "google.cloud.scheduler_v1", "google.cloud.scheduler_v1.types", "google.cloud.secretmanager", "google.oauth2", "great_expectations", "great_expectations.checkpoint", "great_expectations.checkpoint.types", "great_expectations.checkpoint.types.checkpoint_result", "great_expectations.core", "great_expectations.core.batch", "great_expectations.core.expectation_validation_result", "great_expectations.data_context", "great_expectations.data_context.data_context", "great_expectations.data_context.store", "great_expectations.data_context.store.tuple_store_backend", "great_expectations.data_context.types", "great_expectations.data_context.types.base", "great_expectations.data_context.types.resource_identifiers", "great_expectations.exceptions", "great_expectations.profile", "great_expectations.profile.user_configurable_profiler", "great_expectations.types", "great_expectations.data_context.data_context.abstract_data_context", "great_expectations.data_context.data_context.context_factory", "great_expectations.data_context.data_context.ephemeral_data_context", "great_expectations.util", "hvac", "hvac.exceptions", "huggingface_hub", "huggingface_hub.errors", "huggingface_hub.utils", "keras", "kfp", "kfp.compiler", "kfp.v2", "kfp.v2.compiler", "kfp_server_api", "kfp_server_api.exceptions", "kfp_tekton", "kfp_tekton.compiler", "kfp_tekton.compiler.pipeline_utils", "kubernetes", "kubernetes.client", "kubernetes.client.rest", "label_studio_sdk", "langchain", "langchain.docstore", "langchain.docstore.document", "langchain.embeddings", "langchain.vectorstores", "langchain.vectorstores.base", "langchain_community", "langchain_community.embeddings", "lightgbm", "mlflow", "mlflow.entities", "mlflow.entities.model_registry", "mlflow.exceptions", "mlflow.pyfunc", "mlflow.pyfunc.backend", "mlflow.store", "mlflow.store.db", "mlflow.store.db.db_types", "mlflow.tracking", "mlflow.version", "model_archiver", "model_archiver.model_packaging", "model_archiver.model_packaging_utils", "neptune", "neuralprophet", "numpy", "openai", "pandas", "paramiko", "polars", "pyarrow", "pyarrow.parquet", "pycaret", "pycaret.classification", "pyspark", "pyspark.conf", "pyspark.ml", "pyspark.sql", "python_terraform", "redis", "s3fs", "sagemaker", "sagemaker.network", "sagemaker.processing", "sagemaker.workflow", "sagemaker.workflow.execution_variables", "sagemaker.workflow.pipeline", "sagemaker.workflow.steps", "sagemaker.workflow.triggers", "scipy", "scipy.sparse", "sklearn", "sklearn.base", "sklearn.discriminant_analysis", "sklearn.ensemble", "sklearn.gaussian_process", "sklearn.kernel_ridge", "sklearn.linear_model", "sklearn.naive_bayes", "sklearn.neighbors", "sklearn.neural_network", "sklearn.svm", "sklearn.tree", "sky", "slack_sdk", "slack_sdk.errors", "slack_sdk.rtm", "tensorboard", "tensorboard.manager", "tensorboard.uploader", "tensorflow", "tensorflow.python", "tensorflow.python.keras", "tensorflow.python.keras.utils", "tensorflow.python.keras.utils.layer_utils", "torch", "torch.nn", "torch.utils", "torch.utils.data", "torch.utils.data.dataloader", "transformers", "transformers.tokenization_utils_base", "wandb", "whylogs", "whylogs.api", "whylogs.api.writer", "whylogs.api.writer.whylabs", "whylogs.core", "whylogs.viz", "xgboost", "argilla", "argilla.client", "argilla._exceptions._api", "peewee", "prodigy", "prodigy.components", "prodigy.components.db", "prodigy.errors", "azure", "azure.core", "azure.core.credentials", "azure.core.exceptions", "azure.mgmt", "azure.mgmt.resource", "kfp.client", "databricks", "databricks.sdk", "databricks.sdk.service.compute", "databricks.sdk.service.jobs", "databricks.sdk.service.serving", "IPython", "IPython.core", "IPython.core.display", "IPython.core.display_functions", "ipywidgets", "modal", "modal.cli", "modal.cli.run", "modal_proto", "lightning_sdk", "IPython.display", "github.Auth", "google_cloud_pipeline_components", "google_cloud_pipeline_components.v1", "google_cloud_pipeline_components.v1.custom_job", "google_cloud_pipeline_components.v1.custom_job.utils", "kfp.dsl", "kfp.dsl.base_component" ] --- ### Book/AGENTS (docs/book/AGENTS.md) # Documentation Agent Guidelines This file applies when Codex starts in `docs/book/` or below. For detailed GitBook URL examples, link-checking notes, and docs workflow recipes, use `.agents/skills/zenml-repo-workflows/SKILL.md`. ## Source of Truth - `docs/book/` is the source of truth for documentation content. - Other docs directories such as `docs/mkdocs/` and `docs/site/` contain generated output; do not edit those directly. - Docs changes can affect the API docs buildability check in fast CI. ## Structure - Multiple docs sections have their own `toc.md`; update the relevant `toc.md` when adding, moving, or removing pages. - Assets belong in a `.gitbook` folder at the same level as the relevant `toc.md`. - GitBook URLs follow TOC hierarchy, not filesystem paths. - Use absolute `https://docs.zenml.io/...` links when linking across major docs sections such as OSS docs and Pro docs. ## Style - Include metadata fields at the top of pages when existing nearby pages do so. - Match the tone and structure of nearby documentation. - Prioritize readable prose over dense bullet lists. - Include code examples, cross-references, and usage guidance where they help users complete the task. ## Verification - For changed relative markdown links, run the relevant local link checks. - For absolute docs URLs, check the current URL or inspect the relevant `toc.md`. - Use Lychee when the change affects many links: `lychee --offline --no-progress 'docs/book/**/*.md'`. --- ### Book/Introduction (docs/book/introduction.md) --- description: Build production ML pipelines and production-grade AI agents with ZenML and Kitaru. icon: torii-gate layout: title: visible: true description: visible: true tableOfContents: visible: true outline: visible: false pagination: visible: false --- # Welcome to ZenML ZenML is an open-source framework for developing, evaluating, and deploying your entire AI portfolio: classical ML, LLM pipelines, and AI agents alike. It brings the battle-tested engineering principles of production ML to everything you ship, so you don't maintain one toolchain for models and another for agents. That story has two open-source projects behind it: * **ZenML** is the MLOps framework: portable, production-ready **pipelines** for ML and LLM workloads, with versioned artifacts, caching, and infrastructure abstracted behind [stacks](https://docs.zenml.io/stacks). * **[Kitaru](https://docs.zenml.io/kitaru)** is for **AI agents**: run, replay, improve. It records every model call and tool call as a durable checkpoint, so you can replay a real run with one thing changed, diff the two, and roll the winning change across a cohort of runs. Each works on its own. You can run ZenML and never touch Kitaru, or pick up Kitaru purely to make one agent durable. If you do use both, they compose rather than coexist: a Kitaru flow is a dynamic ZenML pipeline under the hood, so your agents and pipelines run on the same stacks, persist artifacts to the same stores, and show up in the same server and dashboard. ### What are you building? Pick the path that matches your work. Neither path requires the other, and adopting the second one later doesn't mean starting over.
ML Pipelines → ZenMLBuild, version, and deploy classical ML and LLM pipelines. Start with Hello World, then the Starter guide.how-to.pnghello-world.md
AI Agents → KitaruRun agents durably, replay any run with one change, and keep the version that wins on cost, latency, and quality. Start with the Agents guide, or jump to the Kitaru quickstart.llm.pngAgents guide
### How these docs are organized The documentation is split into spaces — the tabs at the top of this page. Knowing what lives where saves you a lot of searching: | Space | What you'll find there | | --- | --- | | **ZenML** (you are here) | The pipelines framework: installation, core concepts, deployment, and how-to guides | | **[Kitaru](https://docs.zenml.io/kitaru)** | The agents project: quickstart, flows and checkpoints, replay and execution management, framework adapters | | **[Learn](https://docs.zenml.io/user-guides)** | Narrative guides for both projects: Starter, Production, LLMOps, and Agents tracks, plus tutorials and best practices | | **[Stacks](https://docs.zenml.io/stacks)** | The infrastructure components — orchestrators, artifact stores, and more — that both pipelines and agents run on | | **[SDK reference](https://docs.zenml.io/sdk-reference)** / **[API reference](https://docs.zenml.io/api-reference)** | Client and REST API references, organized per project | | **[Changelog](https://docs.zenml.io/changelog)** | Release notes, version by version | ### First steps Whichever path you picked, the first steps are the same shape: install, run something real, then learn the concepts. | | ML Pipelines (ZenML) | AI Agents (Kitaru) | | --- | --- | --- | | **Install** | [Installation](getting-started/installation.md) | [Installation](https://docs.zenml.io/kitaru/getting-started/installation) | | **First run** | [Hello World](getting-started/hello-world.md) | [Quickstart](https://docs.zenml.io/kitaru/getting-started/quickstart) | | **Concepts** | [Core Concepts](getting-started/core-concepts.md) | [Core Concepts](https://docs.zenml.io/kitaru/concepts) | If you use AI coding tools, see [LLM tooling](reference/llms-txt.md) for ZenML's MCP server and Agent Skills (including `zenml-scoping` and `zenml-pipeline-authoring`). ### Guides
Starter GuideGet started with ZenML fundamentals and set up your first pipelinestarter.pngStarter guide
Production GuideMove your ML pipelines from development to productionprod.pngProduction guide
Agents GuideRun agents durably, replay a real run with one change, and improve them across a cohort with Kitarullm.pngAgents guide
ZenML Scarf
--- ### Book/Redirect Check.Yaml (docs/book/redirect-check.yaml) # This file defines the mapping between GitBook slugs and their # corresponding .gitbook.yaml files # Format: slug_name: path/to/gitbook.yaml documentation: .gitbook.yaml user-guides: docs/book/user-guide/.gitbook.yaml pro: docs/book/getting-started/zenml-pro/.gitbook.yaml stacks: docs/book/component-guide/.gitbook.yaml api-reference: docs/book/api-docs/.gitbook.yaml sdk-reference: docs/book/sdk-docs/.gitbook.yaml --- ### Book/Toc (docs/book/toc.md) # Table of contents ## Getting Started * [Welcome to ZenML](introduction.md) * [Installation](getting-started/installation.md) * [Hello World](getting-started/hello-world.md) * [Your First AI Pipeline](getting-started/your-first-ai-pipeline.md) * [Core Concepts](getting-started/core-concepts.md) * [System Architecture](getting-started/system-architectures.md) ## Deploying ZenML * [Deploy](getting-started/deploying-zenml/README.md) * [Deploy with Docker](getting-started/deploying-zenml/deploy-with-docker.md) * [Deploy with Helm](getting-started/deploying-zenml/deploy-with-helm.md) * [Migrate to Gateway API](getting-started/deploying-zenml/migrate-to-gateway-api.md) * [Deploy using HuggingFace Spaces](getting-started/deploying-zenml/deploy-using-huggingface-spaces.md) * [Deploy with custom images](getting-started/deploying-zenml/deploy-with-custom-image.md) * [Secret management](getting-started/deploying-zenml/secret-management.md) * [Custom secret stores](getting-started/deploying-zenml/custom-secret-stores.md) * [Live event streaming](getting-started/deploying-zenml/live-event-streaming.md) * [Connect](how-to/manage-zenml-server/connecting-to-zenml/README.md) * [with your User (interactive)](how-to/manage-zenml-server/connecting-to-zenml/connect-in-with-your-user-interactive.md) * [with your User (programmatic)](how-to/manage-zenml-server/connecting-to-zenml/connect-with-a-pat.md) * [with a Service Account](how-to/manage-zenml-server/connecting-to-zenml/connect-with-a-service-account.md) * [Manage](how-to/manage-zenml-server/upgrade-zenml-server.md) * [Best practices for upgrading](how-to/manage-zenml-server/best-practices-upgrading-zenml.md) * [Using ZenML server in production](how-to/manage-zenml-server/using-zenml-server-in-prod.md) * [Troubleshoot your ZenML server](how-to/manage-zenml-server/troubleshoot-your-deployed-server.md) * [Migration guide](how-to/manage-zenml-server/migration-guide/migration-guide.md) * [Migration guide 0.13.2 → 0.20.0](how-to/manage-zenml-server/migration-guide/migration-zero-twenty.md) * [Migration guide 0.23.0 → 0.30.0](how-to/manage-zenml-server/migration-guide/migration-zero-thirty.md) * [Migration guide 0.39.1 → 0.41.0](how-to/manage-zenml-server/migration-guide/migration-zero-forty.md) * [Migration guide 0.58.2 → 0.60.0](how-to/manage-zenml-server/migration-guide/migration-zero-sixty.md) ## Concepts * [Steps & Pipelines](how-to/steps-pipelines/steps_and_pipelines.md) * [Configuration](how-to/steps-pipelines/configuration.md) * [Scheduling](how-to/steps-pipelines/scheduling.md) * [Logging](how-to/steps-pipelines/logging.md) * [YAML Configuration](how-to/steps-pipelines/yaml_configuration.md) * [Source Code and Imports](how-to/steps-pipelines/sources.md) * [Execution](how-to/steps-pipelines/execution.md) * [Wait for External Input](how-to/steps-pipelines/wait_resume.md) * [Advanced Features](how-to/steps-pipelines/advanced_features.md) * [Hooks](how-to/steps-pipelines/hooks.md) * [Dynamic Pipelines](how-to/steps-pipelines/dynamic_pipelines.md) * [Command Steps](how-to/steps-pipelines/command_steps.md) * [Streaming Events](how-to/steps-pipelines/streaming_events.md) * [Artifacts](how-to/artifacts/artifacts.md) * [Materializers](how-to/artifacts/materializers.md) * [Visualizations](how-to/artifacts/visualizations.md) * [Stack & Components](how-to/stack-components/stack_components.md) * [Service Connectors](how-to/stack-components/service_connectors.md) * [Pipeline Snapshots](how-to/snapshots/snapshots.md) * [Pipeline Deployments](how-to/deployment/deployment.md) * [Deployment Settings](how-to/deployment/deployment_settings.md) * [Containerization](how-to/containerization/containerization.md) * [Code Repositories](how-to/code-repositories/code-repositories.md) * [Secrets](how-to/secrets/secrets.md) * [Environment Variables](how-to/environment-variables/environment-variables.md) * [Tags](how-to/tags/tags.md) * [Metadata](how-to/metadata/metadata.md) * [Models](how-to/models/models.md) * [Dashboard](how-to/dashboard/dashboard-features.md) * [Templates](how-to/templates/templates.md) ## Reference * [Community & content](reference/community-and-content.md) * [Environment Variables](reference/environment-variables.md) * [LLM Tooling](reference/llms-txt.md) * [FAQ](reference/faq.md) * [Global settings](reference/global-settings.md) * [Legacy docs](reference/legacy-docs.md) --- ### Book/Api Docs/README (docs/book/api-docs/README.md) --- description: >- The ZenML API provides programmatic access to ZenML services beyond what's available in the Python SDK. --- # Overview Welcome to the ZenML API documentation. This guide provides information about both the open-source (OSS) and Pro API endpoints available in the ZenML platform.
OSS APIOpen-source API endpoints for core ZenML functionalityoss-api.pngoss-api
Pro APIEnhanced API endpoints for our pro featurespro-api.pngpro-api
## Use Cases While the ZenML Python SDK covers most workflow requirements, the Server API offers additional utility for: * Automated scaling of operations in production environments. * Integration with external monitoring and logging systems. * Programmatic management and audit of resources and configurations. * Enterprise team management and access control (Pro). By leveraging the ZenML API, users can enhance the robustness and control of their machine learning workflows, ensuring operations are both efficient and scalable.
ZenML Scarf
--- ### Book/Api Docs/Toc (docs/book/api-docs/toc.md) # Table of contents * [Overview](README.md) ## OSS API * [Getting Started](oss-api/oss-api/getting-started.md) * [OSS API](oss-api/oss-api/README.md) * [Artifacts](oss-api-docs/v1/artifacts.md) * [Artifact versions](oss-api-docs/v1/artifact-versions/README.md) * [Batch](oss-api-docs/v1/artifact-versions/batch.md) * [Visualize](oss-api-docs/v1/artifact-versions/visualize.md) * [Login](oss-api-docs/v1/login.md) * [Logout](oss-api-docs/v1/logout.md) * [Device authorization](oss-api-docs/v1/device-authorization.md) * [Api token](oss-api-docs/v1/api-token.md) * [Code repositories](oss-api-docs/v1/code-repositories.md) * [Logs](oss-api-docs/v1/logs.md) * [Models](oss-api-docs/v1/models/README.md) * [Model versions](oss-api-docs/v1/models/model-versions.md) * [Model versions](oss-api-docs/v1/model-versions/README.md) * [Artifacts](oss-api-docs/v1/model-versions/artifacts.md) * [Runs](oss-api-docs/v1/model-versions/runs.md) * [Pipelines](oss-api-docs/v1/pipelines/README.md) * [Runs](oss-api-docs/v1/pipelines/runs.md) * [Runs](oss-api-docs/v1/runs/README.md) * [Steps](oss-api-docs/v1/runs/steps.md) * [Pipeline configuration](oss-api-docs/v1/runs/pipeline-configuration.md) * [Status](oss-api-docs/v1/runs/status.md) * [Refresh](oss-api-docs/v1/runs/refresh.md) * [Run templates](oss-api-docs/v1/run-templates/README.md) * [Runs](oss-api-docs/v1/run-templates/runs.md) * [Schedules](oss-api-docs/v1/schedules.md) * [Secrets](oss-api-docs/v1/secrets.md) * [Info](oss-api-docs/v1/info.md) * [Service accounts](oss-api-docs/v1/service-accounts/README.md) * [Api keys](oss-api-docs/v1/service-accounts/api-keys/README.md) * [Rotate](oss-api-docs/v1/service-accounts/api-keys/rotate.md) * [Service connectors](oss-api-docs/v1/service-connectors/README.md) * [Verify](oss-api-docs/v1/service-connectors/verify.md) * [Client](oss-api-docs/v1/service-connectors/client.md) * [Full stack resources](oss-api-docs/v1/service-connectors/full-stack-resources.md) * [Services](oss-api-docs/v1/services.md) * [Stacks](oss-api-docs/v1/stacks.md) * [Components](oss-api-docs/v1/components.md) * [Component types](oss-api-docs/v1/component-types.md) * [Steps](oss-api-docs/v1/steps/README.md) * [Step configuration](oss-api-docs/v1/steps/step-configuration.md) * [Status](oss-api-docs/v1/steps/status.md) * [Logs](oss-api-docs/v1/steps/logs.md) * [Tags](oss-api-docs/v1/tags.md) * [Users](oss-api-docs/v1/users/README.md) * [Resource membership](oss-api-docs/v1/users/resource-membership.md) * [Current user](oss-api-docs/v1/current-user.md) * [OSS API Specification](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) ## Pro API * [Getting Started](pro-api/pro-api/getting-started.md) * [Pro API](pro-api/pro-api/README.md) * [Tenants](pro-api-docs/api-reference/tenants/README.md) * [Deploy](pro-api-docs/api-reference/tenants/deploy.md) * [Deactivate](pro-api-docs/api-reference/tenants/deactivate.md) * [Members](pro-api-docs/api-reference/tenants/members.md) * [Tenant status](pro-api-docs/api-reference/tenant-status.md) * [Users](pro-api-docs/api-reference/users/README.md) * [Authorize server](pro-api-docs/api-reference/users/authorize-server.md) * [Me](pro-api-docs/api-reference/users/me.md) * [Invitations](pro-api-docs/api-reference/invitations.md) * [Releases](pro-api-docs/api-reference/releases.md) * [Devices](pro-api-docs/api-reference/devices/README.md) * [Verify](pro-api-docs/api-reference/devices/verify.md) * [Roles](pro-api-docs/api-reference/roles/README.md) * [Assignments](pro-api-docs/api-reference/roles/assignments.md) * [Permissions](pro-api-docs/api-reference/permissions.md) * [Teams](pro-api-docs/api-reference/teams/README.md) * [Members](pro-api-docs/api-reference/teams/members.md) * [Organizations](pro-api-docs/api-reference/organizations/README.md) * [Trial](pro-api-docs/api-reference/organizations/trial.md) * [Invitations](pro-api-docs/api-reference/organizations/invitations.md) * [Members](pro-api-docs/api-reference/organizations/members.md) * [Roles](pro-api-docs/api-reference/organizations/roles.md) * [Teams](pro-api-docs/api-reference/organizations/teams.md) * [Tenants](pro-api-docs/api-reference/organizations/tenants.md) * [Tenant](pro-api-docs/api-reference/organizations/tenant.md) * [Entitlement](pro-api-docs/api-reference/organizations/entitlement.md) * [Validation](pro-api-docs/api-reference/organizations/validation/README.md) * [Name](pro-api-docs/api-reference/organizations/validation/name.md) * [Tenant name](pro-api-docs/api-reference/organizations/validation/tenant-name.md) * [Health](pro-api-docs/api-reference/health.md) * [Usage event](pro-api-docs/api-reference/usage-event.md) * [Usage batch](pro-api-docs/api-reference/usage-batch.md) * [Stigg webhook](pro-api-docs/api-reference/stigg-webhook.md) * [Auth](pro-api-docs/api-reference/auth/README.md) * [Login](pro-api-docs/api-reference/auth/login.md) * [Connections](pro-api-docs/api-reference/auth/connections.md) * [Authorize](pro-api-docs/api-reference/auth/authorize.md) * [Callback](pro-api-docs/api-reference/auth/callback.md) * [Logout](pro-api-docs/api-reference/auth/logout.md) * [Device authorization](pro-api-docs/api-reference/auth/device-authorization.md) * [Api token](pro-api-docs/api-reference/auth/api-token.md) * [Tenant authorization](pro-api-docs/api-reference/auth/tenant-authorization.md) * [Rbac](pro-api-docs/api-reference/rbac/README.md) * [Check permissions](pro-api-docs/api-reference/rbac/check-permissions.md) * [Allowed resource ids](pro-api-docs/api-reference/rbac/allowed-resource-ids.md) * [Resource members](pro-api-docs/api-reference/rbac/resource-members.md) * [Server](pro-api-docs/api-reference/server/README.md) * [Info](pro-api-docs/api-reference/server/info.md) * [Pro API Specification](https://cloudapi.zenml.io/openapi.json) --- ### Book/Api Docs/Oss Api/Oss Api/README (docs/book/api-docs/oss-api/oss-api/README.md) --- icon: github-alt --- # OSS API
ZenML Scarf
--- ### Book/Api Docs/Oss Api/Oss Api/Getting Started (docs/book/api-docs/oss-api/oss-api/getting-started.md) --- icon: person-from-portal --- # Getting Started The ZenML OSS server is a FastAPI application, therefore the OpenAPI-compliant docs are available at `/docs` or `/redoc` of your ZenML server: {% hint style="info" %} In the local case (i.e. using `zenml login --local`, the docs are available on `http://127.0.0.1:8237/docs`) {% endhint %} {% hint style="info" %} **Difference between OpenAPI docs and ReDoc** The OpenAPI docs (`/docs`) provide an interactive interface where you can try out the API endpoints directly from the browser. It is useful for testing and exploring the API functionalities. ReDoc (`/redoc`), on the other hand, offers a more static and visually appealing documentation. It is designed for better readability and is ideal for understanding the API structure and reference. {% endhint %} ## Accessing the ZenML OSS API **For OSS users**: The `server_url` is the root URL of your ZenML server deployment. If you are using the ZenML OSS server API using the methods displayed above, it is enough to be logged in to your ZenML account in the same browser session. However, in order to do this programmatically, you can use one of the methods documented in the following sections. {% hint style="info" %} Choosing a method: - Humans at the CLI: use [interactive login](https://docs.zenml.io/how-to/manage-zenml-server/connecting-to-zenml/connect-in-with-your-user-interactive). - CI/CD and automation: use [service accounts + API keys](https://docs.zenml.io/how-to/manage-zenml-server/connecting-to-zenml/connect-with-a-service-account). {% endhint %} ### Using a service account and an API key You can use a service account's API key to authenticate to the ZenML server's REST API programmatically. This is particularly useful when you need a long-term, secure way to make authenticated HTTP requests to the ZenML API endpoints. Start by [creating a service account and an API key](https://docs.zenml.io/how-to/manage-zenml-server/connecting-to-zenml/connect-with-a-service-account), e.g.: ```shell zenml service-account create myserviceaccount ``` Then, there are two methods to authenticate with the API using the API key - one is simpler but less secure, the other is secure and recommended but more complex: {% tabs %} {% tab title="Direct API key authentication" %} {% hint style="warning" %} This approach, albeit simple, is not recommended because the long-lived API key is exposed with every API request, which makes it easier to be compromised. Use it only in low-risk circumstances. {% endhint %} Use the API key directly to authenticate your API requests by including it in the `Authorization` header. For example, you can use the following command to check your current user: * using curl: ```bash curl -H "Authorization: Bearer YOUR_API_KEY" https://your-zenml-server/api/v1/current-user ``` * using wget: ```bash wget -qO- --header="Authorization: Bearer YOUR_API_KEY" https://your-zenml-server/api/v1/current-user ``` * using python: ```python import requests response = requests.get( "https://your-zenml-server/api/v1/current-user", headers={"Authorization": f"Bearer {YOUR_API_KEY}"} ) print(response.json()) ``` {% endtab %} {% tab title="Token exchange authentication" %} Reduce the risk of API key exposure by periodically exchanging the API key for a short-lived API token. 1. To obtain a short-lived API token using your API key, send a POST request to the `/api/v1/login` endpoint. Here are examples using common HTTP clients: * using curl: ```bash curl -X POST -d "password=" https://your-zenml-server/api/v1/login ``` * using wget: ```bash wget -qO- --post-data="password=" \ --header="Content-Type: application/x-www-form-urlencoded" \ https://your-zenml-server/api/v1/login ``` * using python: ```python import requests import json response = requests.post( "https://your-zenml-server/api/v1/login", data={"password": ""}, headers={"Content-Type": "application/x-www-form-urlencoded"} ) print(response.json()) ``` This will return a response like this (the short-lived API token is the `access_token` field): ```json { "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3MGJjZTg5NC1hN2VjLTRkOTYtYjE1Ny1kOTZkYWY5ZWM2M2IiLCJpc3MiOiJmMGQ5NjI1Ni04YmQyLTQxZDctOWVjZi0xMmYwM2JmYTVlMTYiLCJhdWQiOiJmMGQ5NjI1Ni04YmQyLTQxZDctOWVjZi0xMmYwM2JmYTVlMTYiLCJleHAiOjE3MTk0MDk0NjAsImFwaV9rZXlfaWQiOiIzNDkyM2U0NS0zMGFlLTRkMjctODZiZS0wZGRhNTdkMjA5MDcifQ.ByB1ngCPtBenGE6UugsWC6Blga3qPqkAiPJUSFDR-u4", "token_type": "bearer", "expires_in": 3600, "refresh_token": null, "scope": null } ``` 2. Once you have obtained a short-lived API token, you can use it to authenticate your API requests by including it in the `Authorization` header. When the short-lived API token expires, simply repeat the steps above to obtain a new one. For example, you can use the following command to check your current user: * using curl: ```bash curl -H "Authorization: Bearer YOUR_API_TOKEN" https://your-zenml-server/api/v1/current-user ``` * using wget: ```bash wget -qO- --header="Authorization: Bearer YOUR_API_TOKEN" https://your-zenml-server/api/v1/current-user ``` * using python: ```python import requests response = requests.get( "https://your-zenml-server/api/v1/current-user", headers={"Authorization": f"Bearer {YOUR_API_TOKEN}"} ) print(response.json()) ``` {% endtab %} {% endtabs %} {% hint style="info" %} **Important notes** * Short-lived API tokens are scoped to the service account that created them and inherit their permissions * Tokens are temporary and will expire after a configured duration (typically 1 hour, but it depends on how the server is configured) * You can request a new short-lived API token at any time using the same API key * For security reasons, you should handle short-lived API tokens carefully and never share them * If your API key is compromised, you can rotate it using the ZenML dashboard or by running the `zenml service-account api-key rotate` command {% endhint %}
ZenML Scarf
--- ### Book/Api Docs/Oss Api Docs/V1/Api Token (docs/book/api-docs/oss-api-docs/v1/api-token.md) # Api token {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/api_token" method="get" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Oss Api Docs/V1/Artifacts (docs/book/api-docs/oss-api-docs/v1/artifacts.md) # Artifacts {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/artifacts" method="get" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/artifacts" method="post" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/artifacts/{artifact_id}" method="get" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/artifacts/{artifact_id}" method="put" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/artifacts/{artifact_id}" method="delete" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Oss Api Docs/V1/Code Repositories (docs/book/api-docs/oss-api-docs/v1/code-repositories.md) # Code repositories {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/code_repositories" method="get" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/code_repositories/{code_repository_id}" method="get" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/code_repositories/{code_repository_id}" method="put" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/code_repositories/{code_repository_id}" method="delete" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Oss Api Docs/V1/Component Types (docs/book/api-docs/oss-api-docs/v1/component-types.md) # Component types {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/component-types" method="get" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Oss Api Docs/V1/Components (docs/book/api-docs/oss-api-docs/v1/components.md) # Components {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/components" method="get" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/components/{component_id}" method="get" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/components/{component_id}" method="put" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/components/{component_id}" method="delete" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Oss Api Docs/V1/Current User (docs/book/api-docs/oss-api-docs/v1/current-user.md) # Current user {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/current-user" method="get" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Oss Api Docs/V1/Device Authorization (docs/book/api-docs/oss-api-docs/v1/device-authorization.md) # Device authorization {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/device_authorization" method="post" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Oss Api Docs/V1/Info (docs/book/api-docs/oss-api-docs/v1/info.md) # Info {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/info" method="get" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Oss Api Docs/V1/Login (docs/book/api-docs/oss-api-docs/v1/login.md) # Login {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/login" method="post" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Oss Api Docs/V1/Logout (docs/book/api-docs/oss-api-docs/v1/logout.md) # Logout {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/logout" method="get" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Oss Api Docs/V1/Logs (docs/book/api-docs/oss-api-docs/v1/logs.md) # Logs {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/logs/{logs_id}" method="get" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Oss Api Docs/V1/Schedules (docs/book/api-docs/oss-api-docs/v1/schedules.md) # Schedules {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/schedules" method="get" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/schedules/{schedule_id}" method="get" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/schedules/{schedule_id}" method="put" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/schedules/{schedule_id}" method="delete" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Oss Api Docs/V1/Secrets (docs/book/api-docs/oss-api-docs/v1/secrets.md) # Secrets {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/secrets" method="get" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/secrets/{secret_id}" method="get" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/secrets/{secret_id}" method="put" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/secrets/{secret_id}" method="delete" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Oss Api Docs/V1/Services (docs/book/api-docs/oss-api-docs/v1/services.md) # Services {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/services" method="get" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/services" method="post" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/services/{service_id}" method="get" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/services/{service_id}" method="put" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/services/{service_id}" method="delete" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Oss Api Docs/V1/Stacks (docs/book/api-docs/oss-api-docs/v1/stacks.md) # Stacks {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/stacks" method="get" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/stacks/{stack_id}" method="get" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/stacks/{stack_id}" method="put" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/stacks/{stack_id}" method="delete" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Oss Api Docs/V1/Tags (docs/book/api-docs/oss-api-docs/v1/tags.md) # Tags {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/tags" method="get" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/tags" method="post" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/tags/{tag_name_or_id}" method="get" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/tags/{tag_name_or_id}" method="delete" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/tags/{tag_id}" method="put" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Oss Api Docs/V1/Artifact Versions/README (docs/book/api-docs/oss-api-docs/v1/artifact-versions/README.md) # Artifact versions {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/artifact_versions" method="get" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/artifact_versions" method="post" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/artifact_versions" method="delete" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/artifact_versions/{artifact_version_id}" method="get" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/artifact_versions/{artifact_version_id}" method="put" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/artifact_versions/{artifact_version_id}" method="delete" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Oss Api Docs/V1/Artifact Versions/Batch (docs/book/api-docs/oss-api-docs/v1/artifact-versions/batch.md) # Batch {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/artifact_versions/batch" method="post" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Oss Api Docs/V1/Artifact Versions/Visualize (docs/book/api-docs/oss-api-docs/v1/artifact-versions/visualize.md) # Visualize {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/artifact_versions/{artifact_version_id}/visualize" method="get" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Oss Api Docs/V1/Model Versions/README (docs/book/api-docs/oss-api-docs/v1/model-versions/README.md) # Model versions {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/model_versions" method="get" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/model_versions/{model_version_id}" method="get" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/model_versions/{model_version_id}" method="put" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/model_versions/{model_version_id}" method="delete" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Oss Api Docs/V1/Model Versions/Artifacts (docs/book/api-docs/oss-api-docs/v1/model-versions/artifacts.md) # Artifacts {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/model_versions/{model_version_id}/artifacts/{model_version_artifact_link_name_or_id}" method="delete" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/model_versions/{model_version_id}/artifacts" method="delete" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Oss Api Docs/V1/Model Versions/Runs (docs/book/api-docs/oss-api-docs/v1/model-versions/runs.md) # Runs {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/model_versions/{model_version_id}/runs/{model_version_pipeline_run_link_name_or_id}" method="delete" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Oss Api Docs/V1/Models/README (docs/book/api-docs/oss-api-docs/v1/models/README.md) # Models {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/models" method="get" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/models/{model_name_or_id}" method="get" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/models/{model_name_or_id}" method="delete" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/models/{model_id}" method="put" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Oss Api Docs/V1/Models/Model Versions (docs/book/api-docs/oss-api-docs/v1/models/model-versions.md) # Model versions {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/models/{model_name_or_id}/model_versions" method="get" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Oss Api Docs/V1/Pipelines/README (docs/book/api-docs/oss-api-docs/v1/pipelines/README.md) # Pipelines {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/pipelines" method="get" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/pipelines/{pipeline_id}" method="get" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/pipelines/{pipeline_id}" method="put" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/pipelines/{pipeline_id}" method="delete" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Oss Api Docs/V1/Pipelines/Runs (docs/book/api-docs/oss-api-docs/v1/pipelines/runs.md) # Runs {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/pipelines/{pipeline_id}/runs" method="get" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Oss Api Docs/V1/Run Templates/README (docs/book/api-docs/oss-api-docs/v1/run-templates/README.md) # Run templates {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/run_templates" method="get" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/run_templates/{template_id}" method="get" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/run_templates/{template_id}" method="put" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/run_templates/{template_id}" method="delete" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Oss Api Docs/V1/Run Templates/Runs (docs/book/api-docs/oss-api-docs/v1/run-templates/runs.md) # Runs {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/run_templates/{template_id}/runs" method="post" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Oss Api Docs/V1/Runs/README (docs/book/api-docs/oss-api-docs/v1/runs/README.md) # Runs {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/runs" method="get" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/runs/{run_id}" method="get" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/runs/{run_id}" method="put" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/runs/{run_id}" method="delete" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Oss Api Docs/V1/Runs/Pipeline Configuration (docs/book/api-docs/oss-api-docs/v1/runs/pipeline-configuration.md) # Pipeline configuration {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/runs/{run_id}/pipeline-configuration" method="get" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Oss Api Docs/V1/Runs/Refresh (docs/book/api-docs/oss-api-docs/v1/runs/refresh.md) # Refresh {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/runs/{run_id}/refresh" method="get" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Oss Api Docs/V1/Runs/Status (docs/book/api-docs/oss-api-docs/v1/runs/status.md) # Status {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/runs/{run_id}/status" method="get" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Oss Api Docs/V1/Runs/Steps (docs/book/api-docs/oss-api-docs/v1/runs/steps.md) # Steps {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/runs/{run_id}/steps" method="get" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Oss Api Docs/V1/Service Accounts/README (docs/book/api-docs/oss-api-docs/v1/service-accounts/README.md) # Service accounts {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/service_accounts" method="get" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/service_accounts" method="post" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/service_accounts/{service_account_name_or_id}" method="get" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/service_accounts/{service_account_name_or_id}" method="put" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/service_accounts/{service_account_name_or_id}" method="delete" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Oss Api Docs/V1/Service Accounts/Api Keys/README (docs/book/api-docs/oss-api-docs/v1/service-accounts/api-keys/README.md) # Api keys {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/service_accounts/{service_account_id}/api_keys" method="get" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/service_accounts/{service_account_id}/api_keys" method="post" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/service_accounts/{service_account_id}/api_keys/{api_key_name_or_id}" method="get" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/service_accounts/{service_account_id}/api_keys/{api_key_name_or_id}" method="put" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/service_accounts/{service_account_id}/api_keys/{api_key_name_or_id}" method="delete" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Oss Api Docs/V1/Service Accounts/Api Keys/Rotate (docs/book/api-docs/oss-api-docs/v1/service-accounts/api-keys/rotate.md) # Rotate {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/service_accounts/{service_account_id}/api_keys/{api_key_name_or_id}/rotate" method="put" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Oss Api Docs/V1/Service Connectors/README (docs/book/api-docs/oss-api-docs/v1/service-connectors/README.md) # Service connectors {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/service_connectors" method="get" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/service_connectors/{connector_id}" method="get" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/service_connectors/{connector_id}" method="put" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/service_connectors/{connector_id}" method="delete" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Oss Api Docs/V1/Service Connectors/Client (docs/book/api-docs/oss-api-docs/v1/service-connectors/client.md) # Client {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/service_connectors/{connector_id}/client" method="get" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Oss Api Docs/V1/Service Connectors/Full Stack Resources (docs/book/api-docs/oss-api-docs/v1/service-connectors/full-stack-resources.md) # Full stack resources {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/service_connectors/full_stack_resources" method="post" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Oss Api Docs/V1/Service Connectors/Verify (docs/book/api-docs/oss-api-docs/v1/service-connectors/verify.md) # Verify {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/service_connectors/verify" method="post" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/service_connectors/{connector_id}/verify" method="put" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Oss Api Docs/V1/Steps/README (docs/book/api-docs/oss-api-docs/v1/steps/README.md) # Steps {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/steps" method="get" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/steps" method="post" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/steps/{step_id}" method="get" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/steps/{step_id}" method="put" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Oss Api Docs/V1/Steps/Logs (docs/book/api-docs/oss-api-docs/v1/steps/logs.md) # Logs {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/steps/{step_id}/logs" method="get" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Oss Api Docs/V1/Steps/Status (docs/book/api-docs/oss-api-docs/v1/steps/status.md) # Status {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/steps/{step_id}/status" method="get" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Oss Api Docs/V1/Steps/Step Configuration (docs/book/api-docs/oss-api-docs/v1/steps/step-configuration.md) # Step configuration {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/steps/{step_id}/step-configuration" method="get" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Oss Api Docs/V1/Users/README (docs/book/api-docs/oss-api-docs/v1/users/README.md) # Users {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/users" method="get" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/users/{user_name_or_id}" method="get" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Oss Api Docs/V1/Users/Resource Membership (docs/book/api-docs/oss-api-docs/v1/users/resource-membership.md) # Resource membership {% openapi src="https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json" path="/api/v1/users/{user_name_or_id}/resource_membership" method="post" %} [https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json](https://1cf18d95-zenml.cloudinfra.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Pro Api/Pro Api/README (docs/book/api-docs/pro-api/pro-api/README.md) --- icon: rectangle-pro --- # Pro API
ZenML Scarf
--- ### Book/Api Docs/Pro Api/Pro Api/Getting Started (docs/book/api-docs/pro-api/pro-api/getting-started.md) --- icon: person-from-portal --- # Getting Started The ZenML Pro API extends the open-source API with additional features designed for enterprise users, including: * Enhanced team collaboration features * Advanced role-based access control * Enterprise-grade security features Whether you're using the [SaaS version](https://zenml.io/pro) or a self-hosted ZenML Pro instance, you can leverage the API to manage workspaces, organizations, users, roles, and more. ## Understanding the Two APIs in ZenML Pro ZenML Pro includes two distinct APIs: 1. **Workspace API**: This is similar to the [OSS API](../../oss-api/oss-api/) but includes additional endpoints (for example to run snapshots). Each workspace in ZenML Pro has its own API. 2. **ZenML Pro API**: This is the management API for ZenML Pro and handles organization-level resources like workspaces, users, and roles. ### Server URL For Pro users, the `server_url` for the Workspace API is the URL of your workspace, which can be found in the ZenML Pro dashboard: **Note:** The workspace URL can be found in the ZenML Pro dashboard and typically looks like `https://1bfe8d94-zenml.cloudinfra.zenml.io`. The SaaS version of ZenML Pro API is hosted at [https://cloudapi.zenml.io](https://cloudapi.zenml.io). ## API Overview The ZenML Pro API is a RESTful API that follows OpenAPI 3.1.0 specifications. It provides endpoints for various resources and operations, including: * Workspace management * Organization management * Project management * User management * Role-based access control (RBAC) * Authentication and authorization ## Authentication To use the ZenML Pro API, you need to authenticate your requests. If you are logged in to your ZenML Pro account, you can use the same browser window to authenticate requests to your ZenML Pro API, directly in the OpenAPI docs. For example, for the SaaS variant, you can access the docs here: [https://cloudapi.zenml.io](https://cloudapi.zenml.io). ### Programmatic access with Personal Access Tokens Personal Access Tokens are long-lived credentials scoped to your own user account that can be used to authenticate to the ZenML Pro API and the Workspace API across all the organizations and workspaces that your account has access to. See [Personal Access Tokens](https://docs.zenml.io/pro/access-management/personal-access-tokens) for setup and examples. ### Programmatic access with service accounts and API keys Service accounts and API keys can be used to authenticate to the ZenML Pro API and the Workspace API across all workspaces in your organization. See [Service Accounts](https://docs.zenml.io/pro/access-management/service-accounts) for setup and examples. ## When to Use Which API | Task | Use This API | Authentication Method | |------|-------------|----------------------| | Managing organizations, workspaces, users | **ZenML Pro API** (cloudapi.zenml.io) | Personal Access Tokens | | Accessing the ZenML Pro API programmatically from external systems | **ZenML Pro API** (cloudapi.zenml.io) | Service Accounts + API keys (recommended for automation) | | Running snapshots, pipeline operations | **Workspace API** (your workspace URL) | Service Accounts + API keys (recommended for automation) | | Pipeline development, artifact management | **Workspace API** (your workspace URL) | Personal Access Tokens or Service Accounts + API Keys | See the high‑level overview: [Connect to a server](https://docs.zenml.io/how-to/manage-zenml-server/connecting-to-zenml#choose-how-to-connect). ## Workspace API Authentication The **Workspace API** is different from the ZenML Pro API and supports different authentication methods. This is the API you'll use for running snapshots and other pipeline operations. ### Workspace API Authentication Methods Programmatic access to the ZenML Pro workspace API is achieved mostly the same way as the ZenML OSS server API. This is because the Workspace API in ZenML Pro is an extension of the OSS API with some additional endpoints. For automation, use ZenML Pro service accounts and API keys (see [Service Accounts](https://docs.zenml.io/pro/access-management/service-accounts)). You can use one of these two methods to authenticate with your workspace API: * [Create and use a Personal Access Token](https://docs.zenml.io/pro/access-management/personal-access-tokens) (good for development and short-term tasks) * [Create a ZenML Pro service account and use its API key](https://docs.zenml.io/pro/access-management/service-accounts) (recommended for production automation and run template triggering) When making requests to the Workspace API, make sure to use your workspace URL as the base URL. This is different from the ZenML Pro API URL (cloudapi.zenml.io), which is used for organization-level operations. Please consult the indicated sections for more information. ## Key API Endpoints Here are some important endpoints you can use with ZenML Pro: ### ZenML Pro API Endpoints These endpoints are available at the ZenML Pro API (e.g., https://cloudapi.zenml.io): #### Organization Management * List organizations: `GET /organizations` * Create an organization: `POST /organizations` * Get organization details: `GET /organizations/{organization_id}` * Update an organization: `PATCH /organizations/{organization_id}` #### Workspace Management * List workspaces: `GET /workspaces` * Create a workspace: `POST /workspaces` * Get workspace details: `GET /workspaces/{workspace_id}` * Update a workspace: `PATCH /workspaces/{workspace_id}` #### User Management * List users: `GET /users` * Get current user: `GET /users/me` * Update user: `PATCH /users/{user_id}` #### Role-Based Access Control * Create a role: `POST /roles` * Assign a role: `POST /roles/{role_id}/assignments` * Check permissions: `GET /permissions` Remember to refer to the complete API documentation available at [https://cloudapi.zenml.io](https://cloudapi.zenml.io) for detailed information about all available endpoints, request/response schemas, and additional features. ### Workspace API Endpoints The Workspace API includes all OSS API endpoints plus some additional Pro-specific endpoints. These are available at your workspace URL at the `/docs` path (e.g., https://1bfe8d94-zenml.cloudinfra.zenml.io/docs): #### Running snapshots (Pro-specific) Snapshots allow you to start pipeline runs from external systems using HTTP requests. * List snapshots: `GET /pipeline_snapshots` * Get snapshot details: `GET /pipeline_snapshots/` * Update a snapshot: `PATCH /pipeline_snapshots/` * **Run a snapshot: `POST /pipeline_snapshots//runs`** {% hint style="success" %} **For Snapshot Automation** When running snapshots from external systems or CI/CD pipelines, we strongly recommend using **service accounts with API keys**. Service accounts provide: * Long-term credentials that don't expire hourly * Dedicated authentication for automation (not tied to individual users) * Consistent access even when team members leave See the [service account documentation](../../oss-api/oss-api/#using-a-service-account-and-an-api-key) for setup instructions. {% endhint %} For a complete list of API endpoints available in the Workspace API, refer to the [OSS API documentation](../../oss-api/oss-api/). ## Error Handling The API uses standard HTTP status codes to indicate the success or failure of requests. In case of errors, the response body will contain more details about the error, including a message and sometimes additional information. ## Rate Limiting Be aware that the ZenML Pro API may have rate limiting in place to ensure fair usage. If you exceed the rate limit, you may receive a 429 (Too Many Requests) status code. Implement appropriate backoff and retry logic in your applications to handle this scenario. ## Troubleshooting Authentication ### "Which API and credentials should I use?" Use this decision tree to determine the right approach: | **What do you want to do?** | **Which API?** | **Authentication Method** | **Why?** | |------------------------------|----------------|---------------------------|----------| | Run snapshots from CI/CD or external systems | **Workspace API** (your workspace URL) | Service account + API key | Long-lived, automation-friendly, not tied to users | | Manage organizations, create workspaces, invite users | **ZenML Pro API** (cloudapi.zenml.io) | Personal Access Tokens | Administrative operations need user-level permissions | | Access the ZenML Pro API programmatically from external systems | **ZenML Pro API** (cloudapi.zenml.io) | Service accounts + API keys (recommended for automation) | Long-lived, automation-friendly, not tied to users | | Develop pipelines, explore artifacts | **Workspace API** (your workspace URL) | Personal Access Tokens or Service Accounts + API keys | Flexible for development needs | | One-off pipeline operations | **Workspace API** (your workspace URL) | Personal Access Tokens | Quick and easy for manual tasks | ### Common Issues **"My API calls are getting 401 Unauthorized"** - ✅ Check that you're using the correct API URL (workspace URL vs cloudapi.zenml.io) - ✅ Verify your credentials haven't expired (see next issue) - ✅ Ensure you're using `Bearer ` format in the Authorization header **"My automation keeps failing after an hour"** - ✅ Check what type of authentication you are using (Personal Access Tokens or service account API keys) - ✅ Check the expiration date of the credentials you are using and rotate them if they have expired. Optionally adjust the expiration time during rotation. **"I can't find the endpoint to run a snapshot"** - ✅ Running snapshots is only possible using the **Workspace API**, not the ZenML Pro API - ✅ Use your workspace URL + `/docs` to see the full API documentation
ZenML Scarf
--- ### Book/Api Docs/Pro Api Docs/Api Reference/Health (docs/book/api-docs/pro-api-docs/api-reference/health.md) # Health {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/health" method="get" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Pro Api Docs/Api Reference/Invitations (docs/book/api-docs/pro-api-docs/api-reference/invitations.md) # Invitations {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/invitations/{invitation_id}" method="get" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/invitations/{invitation_id}" method="post" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/invitations" method="get" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Pro Api Docs/Api Reference/Permissions (docs/book/api-docs/pro-api-docs/api-reference/permissions.md) # Permissions {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/permissions" method="get" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Pro Api Docs/Api Reference/Releases (docs/book/api-docs/pro-api-docs/api-reference/releases.md) # Releases {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/releases" method="get" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/releases/{release_service}" method="get" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Pro Api Docs/Api Reference/Stigg Webhook (docs/book/api-docs/pro-api-docs/api-reference/stigg-webhook.md) # Stigg webhook {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/stigg-webhook" method="post" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Pro Api Docs/Api Reference/Tenant Status (docs/book/api-docs/pro-api-docs/api-reference/tenant-status.md) # Tenant status {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/tenant_status" method="patch" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Pro Api Docs/Api Reference/Usage Batch (docs/book/api-docs/pro-api-docs/api-reference/usage-batch.md) # Usage batch {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/usage-batch" method="post" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Pro Api Docs/Api Reference/Usage Event (docs/book/api-docs/pro-api-docs/api-reference/usage-event.md) # Usage event {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/usage-event" method="post" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Pro Api Docs/Api Reference/Auth/README (docs/book/api-docs/pro-api-docs/api-reference/auth/README.md) # Auth
ZenML Scarf
--- ### Book/Api Docs/Pro Api Docs/Api Reference/Auth/Api Token (docs/book/api-docs/pro-api-docs/api-reference/auth/api-token.md) # Api token {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/auth/api_token" method="get" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Pro Api Docs/Api Reference/Auth/Authorize (docs/book/api-docs/pro-api-docs/api-reference/auth/authorize.md) # Authorize {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/auth/authorize" method="get" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Pro Api Docs/Api Reference/Auth/Callback (docs/book/api-docs/pro-api-docs/api-reference/auth/callback.md) # Callback {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/auth/callback" method="get" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Pro Api Docs/Api Reference/Auth/Connections (docs/book/api-docs/pro-api-docs/api-reference/auth/connections.md) # Connections {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/auth/connections" method="get" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Pro Api Docs/Api Reference/Auth/Device Authorization (docs/book/api-docs/pro-api-docs/api-reference/auth/device-authorization.md) # Device authorization {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/auth/device_authorization" method="post" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Pro Api Docs/Api Reference/Auth/Login (docs/book/api-docs/pro-api-docs/api-reference/auth/login.md) # Login {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/auth/login" method="post" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Pro Api Docs/Api Reference/Auth/Logout (docs/book/api-docs/pro-api-docs/api-reference/auth/logout.md) # Logout {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/auth/logout" method="post" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Pro Api Docs/Api Reference/Auth/Tenant Authorization (docs/book/api-docs/pro-api-docs/api-reference/auth/tenant-authorization.md) # Tenant authorization {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/auth/tenant_authorization/{tenant_id}" method="post" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Pro Api Docs/Api Reference/Devices/README (docs/book/api-docs/pro-api-docs/api-reference/devices/README.md) # Devices {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/devices" method="get" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/devices/{device_id_or_user_code}" method="get" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/devices/{device_id}" method="put" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/devices/{device_id}" method="delete" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Pro Api Docs/Api Reference/Devices/Verify (docs/book/api-docs/pro-api-docs/api-reference/devices/verify.md) # Verify {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/devices/verify" method="put" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Pro Api Docs/Api Reference/Organizations/README (docs/book/api-docs/pro-api-docs/api-reference/organizations/README.md) # Organizations {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/organizations" method="get" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/organizations" method="post" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/organizations/{organization_id_or_name}" method="get" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/organizations/{organization_id}" method="delete" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/organizations/{organization_id}" method="patch" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Pro Api Docs/Api Reference/Organizations/Entitlement (docs/book/api-docs/pro-api-docs/api-reference/organizations/entitlement.md) # Entitlement {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/organizations/{organization_id}/entitlement/{feature}" method="get" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Pro Api Docs/Api Reference/Organizations/Invitations (docs/book/api-docs/pro-api-docs/api-reference/organizations/invitations.md) # Invitations {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/organizations/{organization_id}/invitations" method="get" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/organizations/{organization_id}/invitations" method="post" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/organizations/{organization_id}/invitations/{invitation_id}" method="delete" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Pro Api Docs/Api Reference/Organizations/Members (docs/book/api-docs/pro-api-docs/api-reference/organizations/members.md) # Members {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/organizations/{organization_id}/members" method="get" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/organizations/{organization_id}/members" method="delete" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Pro Api Docs/Api Reference/Organizations/Roles (docs/book/api-docs/pro-api-docs/api-reference/organizations/roles.md) # Roles {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/organizations/{organization_id}/roles" method="get" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Pro Api Docs/Api Reference/Organizations/Teams (docs/book/api-docs/pro-api-docs/api-reference/organizations/teams.md) # Teams {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/organizations/{organization_id}/teams" method="get" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Pro Api Docs/Api Reference/Organizations/Tenant (docs/book/api-docs/pro-api-docs/api-reference/organizations/tenant.md) # Tenant {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/organizations/{organization_id}/tenant/{tenant_name}" method="get" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Pro Api Docs/Api Reference/Organizations/Tenants (docs/book/api-docs/pro-api-docs/api-reference/organizations/tenants.md) # Tenants {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/organizations/{organization_id}/tenants" method="get" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Pro Api Docs/Api Reference/Organizations/Trial (docs/book/api-docs/pro-api-docs/api-reference/organizations/trial.md) # Trial {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/organizations/{organization_id}/trial" method="get" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Pro Api Docs/Api Reference/Organizations/Validation/README (docs/book/api-docs/pro-api-docs/api-reference/organizations/validation/README.md) # Validation
ZenML Scarf
--- ### Book/Api Docs/Pro Api Docs/Api Reference/Organizations/Validation/Name (docs/book/api-docs/pro-api-docs/api-reference/organizations/validation/name.md) # Name {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/organizations/validation/name/{organization_name}" method="get" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Pro Api Docs/Api Reference/Organizations/Validation/Tenant Name (docs/book/api-docs/pro-api-docs/api-reference/organizations/validation/tenant-name.md) # Tenant name {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/organizations/{organization_id}/validation/tenant_name/{tenant_name}" method="get" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Pro Api Docs/Api Reference/Rbac/README (docs/book/api-docs/pro-api-docs/api-reference/rbac/README.md) # Rbac
ZenML Scarf
--- ### Book/Api Docs/Pro Api Docs/Api Reference/Rbac/Allowed Resource Ids (docs/book/api-docs/pro-api-docs/api-reference/rbac/allowed-resource-ids.md) # Allowed resource ids {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/rbac/allowed_resource_ids" method="get" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Pro Api Docs/Api Reference/Rbac/Check Permissions (docs/book/api-docs/pro-api-docs/api-reference/rbac/check-permissions.md) # Check permissions {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/rbac/check_permissions" method="get" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Pro Api Docs/Api Reference/Rbac/Resource Members (docs/book/api-docs/pro-api-docs/api-reference/rbac/resource-members.md) # Resource members {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/rbac/resource_members" method="get" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/rbac/resource_members" method="post" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Pro Api Docs/Api Reference/Roles/README (docs/book/api-docs/pro-api-docs/api-reference/roles/README.md) # Roles {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/roles" method="post" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/roles/{role_id}" method="get" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/roles/{role_id}" method="delete" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/roles/{role_id}" method="patch" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Pro Api Docs/Api Reference/Roles/Assignments (docs/book/api-docs/pro-api-docs/api-reference/roles/assignments.md) # Assignments {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/roles/{role_id}/assignments" method="get" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/roles/{role_id}/assignments" method="post" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/roles/{role_id}/assignments" method="delete" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Pro Api Docs/Api Reference/Server/README (docs/book/api-docs/pro-api-docs/api-reference/server/README.md) # Server
ZenML Scarf
--- ### Book/Api Docs/Pro Api Docs/Api Reference/Server/Info (docs/book/api-docs/pro-api-docs/api-reference/server/info.md) # Info {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/server/info" method="get" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Pro Api Docs/Api Reference/Teams/README (docs/book/api-docs/pro-api-docs/api-reference/teams/README.md) # Teams {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/teams" method="post" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/teams/{team_id}" method="get" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/teams/{team_id}" method="delete" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/teams/{team_id}" method="patch" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Pro Api Docs/Api Reference/Teams/Members (docs/book/api-docs/pro-api-docs/api-reference/teams/members.md) # Members {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/teams/{team_id}/members" method="get" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/teams/{team_id}/members" method="post" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/teams/{team_id}/members" method="delete" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Pro Api Docs/Api Reference/Tenants/README (docs/book/api-docs/pro-api-docs/api-reference/tenants/README.md) # Tenants {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/tenants" method="get" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/tenants" method="post" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/tenants" method="delete" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/tenants/{tenant_id_or_name}" method="get" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/tenants/{tenant_id}" method="delete" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/tenants/{tenant_id}" method="patch" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Pro Api Docs/Api Reference/Tenants/Deactivate (docs/book/api-docs/pro-api-docs/api-reference/tenants/deactivate.md) # Deactivate {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/tenants/{tenant_id}/deactivate" method="patch" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Pro Api Docs/Api Reference/Tenants/Deploy (docs/book/api-docs/pro-api-docs/api-reference/tenants/deploy.md) # Deploy {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/tenants/{tenant_id}/deploy" method="patch" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Pro Api Docs/Api Reference/Tenants/Members (docs/book/api-docs/pro-api-docs/api-reference/tenants/members.md) # Members {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/tenants/{tenant_id}/members" method="get" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/tenants/{tenant_id}/members" method="post" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/tenants/{tenant_id}/members" method="delete" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Pro Api Docs/Api Reference/Users/README (docs/book/api-docs/pro-api-docs/api-reference/users/README.md) # Users {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/users" method="get" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/users/{user_id_or_email}" method="get" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/users/{user_id}" method="delete" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/users/{user_id}" method="patch" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Pro Api Docs/Api Reference/Users/Authorize Server (docs/book/api-docs/pro-api-docs/api-reference/users/authorize-server.md) # Authorize server {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/users/authorize_server" method="get" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Api Docs/Pro Api Docs/Api Reference/Users/Me (docs/book/api-docs/pro-api-docs/api-reference/users/me.md) # Me {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/users/me" method="get" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %} {% openapi src="https://cloudapi.zenml.io/openapi.json" path="/users/me" method="patch" %} [https://cloudapi.zenml.io/openapi.json](https://cloudapi.zenml.io/openapi.json) {% endopenapi %}
ZenML Scarf
--- ### Book/Component Guide/Component Guide (docs/book/component-guide/component-guide.md) --- description: Overview of categories of MLOps components and third-party integrations. --- # Overview If you are new to the world of MLOps, it is often daunting to be immediately faced with a sea of tools that seemingly all promise and do the same things. It is useful in this case to try to categorize tools in various groups in order to understand their value in your toolchain in a more precise manner. ## What is a stack? The [stack](https://docs.zenml.io/user-guides/production-guide/understand-stacks) is a fundamental component of the ZenML framework. Put simply, a stack represents the configuration of the infrastructure and tooling that defines where and how a pipeline executes. A stack comprises different stack components, where each component is responsible for a specific task. For example, a stack might have a [container registry](https://docs.zenml.io/stacks/container-registries), a [Kubernetes cluster](https://docs.zenml.io/stacks/orchestrators/kubernetes) as an [orchestrator](https://docs.zenml.io/stacks/orchestrators), an [artifact store](https://docs.zenml.io/stacks/artifact-stores), an [experiment tracker](https://docs.zenml.io/stacks/experiment-trackers) like MLflow and so on. Each pipeline run that you execute with ZenML will require a **stack** and each **stack** will be required to include at least an **orchestrator** and an **artifact store**. Apart from these two, the other components are optional and to be added as your pipeline evolves in MLOps maturity. {% hint style="info" %} Stacks are not just for ML pipelines. [Kitaru](https://docs.zenml.io/kitaru), ZenML's sibling project for running and improving AI agents, runs agent workflows on these same stacks: a [Kitaru stack](https://docs.zenml.io/kitaru/stacks) is a focused view of a ZenML stack (execution placement, artifact persistence, and container registry) built from the components documented in this section. {% endhint %} ## Stacks as a way to organize your execution environment With ZenML, you can run your pipelines on more than one stacks with ease. This pattern helps you test your code across different environments effortlessly. This enables a case like this: a data scientist starts experimentation locally on their system and then once they are satisfied, move to a cloud environment on your staging cloud account to test more advanced features of your pipeline. Finally, when all looks good, they can mark the pipeline ready for production and have it run on a production-grade stack in your production cloud account. Having separate stacks for these environments helps: * avoid wrongfully deploying your staging pipeline to production * curb costs by running less powerful resources in staging and testing locally first * control access to environments by granting permissions for only certain stacks to certain users ## How to manage credentials for your stacks Most stack components require some form of credentials to interact with the underlying infrastructure. For example, a container registry needs to be authenticated to push and pull images, a Kubernetes cluster needs to be authenticated to deploy models as a web service, and so on. The preferred way to handle credentials in ZenML is to use [Service Connectors](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/service-connectors-guide). Service connectors are a powerful feature of ZenML that allow you to abstract away credentials and sensitive information from your team. ### Recommended roles Ideally, you would want that only the people who deal with and have direct access to your cloud resources are the ones that are able to create Service Connectors. This is useful for a few reasons: * **Less chance of credentials leaking**: the more people that have access to your cloud resources, the higher the chance that some of them will be leaked. * **Instant revocation of compromised credentials**: folks who have direct access to your cloud resources can revoke the credentials instantly if they are compromised, making this a much more secure setup. * **Easier auditing**: you can have a much easier time auditing and tracking who did what if you have a clear separation between the people who can create Service Connectors (who have direct access to your cloud resources) and those who can only use them. ### Recommended workflow Here's an approach you can take that is a good balance between convenience and security: * Have a limited set of people that have permissions to create Service Connectors. These are ideally people that have access to your cloud accounts and know what credentials to use. * You can create one connector for your development or staging environment and let your data scientists use that to register their stack components. * When you are ready to go to production, you can create another connector with permissions for your production environment and create stacks that use it. This way you can ensure that your production resources are not accidentally used for development or staging. If you follow this approach, you can keep your data scientists free from the hassle of figuring out the best authentication mechanisms for the different cloud services, having to manage credentials locally, and keep your cloud accounts safe, while still giving them the freedom to run their experiments in the cloud. {% hint style="info" %} Please note that restricting permissions for users through roles is a ZenML Pro feature. You can read more about it [here](https://docs.zenml.io/pro/access-management/roles). Sign up for a free trial here: https://zenml.io/pro. {% endhint %} ## How to deploy and manage stacks Deploying and managing a MLOps stack is tricky. * Each tool comes with a certain set of requirements. For example, a [Kubeflow installation](https://www.kubeflow.org/docs/started/installing-kubeflow/) will require you to have a Kubernetes cluster, and so would a **Seldon Core deployment**. * Figuring out the defaults for infra parameters is not easy. Even if you have identified the backing infra that you need for a stack component, setting up reasonable defaults for parameters like instance size, CPU, memory, etc., needs a lot of experimentation to figure out. * Many times, standard tool installations don't work out of the box. For example, to run a custom pipeline in [Vertex AI](https://cloud.google.com/vertex-ai), it is not enough to just run an imported pipeline. You might also need a custom service account that is configured to perform tasks like reading secrets from your secret store or talking to other GCP services that your pipeline might need. * Some tools need an additional layer of installations to enable a more secure, production-grade setup. For example, a standard **MLflow tracking server** deployment comes without an authentication frontend which might expose all of your tracking data to the world if deployed as-is. * All the components that you deploy must have the right permissions to be able to talk to each other. For example, your workloads running in a Kubernetes cluster might require access to the container registry or the code repository, and so on. * Cleaning up your resources after you're done with your experiments is super important yet very challenging. For example, if your Kubernetes cluster has made use of [Load Balancers](https://kubernetes.io/docs/concepts/services-networking/service/#loadbalancer), you might still have one lying around in your account even after deleting the cluster, costing you money and frustration. All of these points make taking your pipelines to production a more difficult task than it should be. We believe that the expertise in setting up these often-complex stacks shouldn't be a prerequisite to running your ML pipelines. This docs section consists of information that makes it easier to provision, configure, and extend stacks and components in ZenML. ## Stack Components Guide Here is a full list of all stack components currently supported in ZenML, with a description of the role of that component in the MLOps process:
OrchestratorOrchestrating the runs of your pipelineorchestrator.pngorchestrators
DeployerDeploying pipelines as long-running HTTP servicesdeployer.pngdeployers
Artifact StoreStorage for the artifacts created by your pipelinesartifact-store.pngartifact-stores
Container RegistryStore for your containerscontainer-registry.pngcontainer-registries
Data ValidatorData and model validationdata-validator.pngdata-validators
Experiment TrackerTracking your ML experimentsexperiment-tracker.pngexperiment-trackers
Model DeployerServices/platforms responsible for online model servingmodel-deployer.pngmodel-deployers
Step OperatorExecution of individual steps in specialized runtime environmentsstep-operator.pngstep-operators
AlerterSending alerts through specified channelsalerter.pngalerters
Image BuilderBuilds container images.image-builder.pngimage-builders
AnnotatorLabeling and annotating dataannotator.pngannotators
Model RegistryManage and interact with ML Modelsmodel-registry.pngmodel-registries
Feature StoreManagement of your data/featuresfeature-store.pngfeature-stores
## Custom Implementations You can take control of how ZenML behaves by creating your own components. This is done by writing custom component `flavors`.
Component FlavorsHow to write a custom stack component flavorflavors.pngImplement a custom stack component
Custom orchestrator guideLearn how to develop a custom orchestratorcustom-orchestrator.pngcustom.md
ZenML Scarf
--- ### Book/Component Guide/Integrations (docs/book/component-guide/integrations.md) --- icon: box-archive --- # Integrations Categorizing the MLOps stack is a good way to write abstractions for an MLOps pipeline and standardize your processes. But ZenML goes further and also provides concrete implementations of these categories by **integrating** with various tools for each category. Once code is organized into a ZenML pipeline, you can supercharge your ML workflows with the best-in-class solutions from various MLOps areas. For example, you can orchestrate your ML pipeline workflows using [Airflow](orchestrators/airflow.md) or [Kubeflow](orchestrators/kubeflow.md), track experiments using [MLflow Tracking](experiment-trackers/mlflow.md) or [Weights & Biases](experiment-trackers/wandb.md), and transition seamlessly from a local [MLflow deployment](model-deployers/mlflow.md) to a deployed model on Kubernetes using [Seldon Core](model-deployers/seldon.md). There are lots of moving parts for all the MLOps tooling and infrastructure you require for ML in production and ZenML brings them all together and enables you to manage them in one place. This also allows you to delay the decision of which MLOps tool to use in your stack as you have no vendor lock-in with ZenML and can easily switch out tools as soon as your requirements change. ## Available integrations We have a [dedicated webpage](https://zenml.io/integrations) that indexes all supported ZenML integrations and their categories. Another easy way of seeing a list of integrations is to see the list of directories in the [integrations directory](https://github.com/zenml-io/zenml/tree/main/src/zenml/integrations) on our GitHub. ## Installing dependencies for integrations and stacks ZenML provides a way to export the package requirements for both individual integrations and entire stacks, enabling you to install the necessary dependencies manually. This approach gives you full control over the versions and the installation process. ### Exporting integration requirements You can export the requirements for a specific integration using the `zenml integration export-requirements` command. To write the requirements to a file and install them via pip, run: ```bash zenml integration export-requirements --output-file integration_requirements.txt pip install -r integration_requirements.txt ``` If you prefer to see the requirements without writing them to a file, omit the `--output-file` flag: ```bash zenml integration export-requirements ``` This will print the list of dependencies to the console, which you can then pipe to pip: ```bash zenml integration export-requirements | xargs pip install ``` ### Exporting stack requirements To install all dependencies for a specific ZenML stack at once, you can export your stack's requirements: ```bash zenml stack export-requirements --output-file stack_requirements.txt pip install -r stack_requirements.txt ``` Omitting `--output-file` will print the requirements to the console: ```bash zenml stack export-requirements ``` You can also pipe the output directly to pip: ```bash zenml stack export-requirements | xargs pip install ``` {% hint style="info" %} If you use a different package manager such as [`uv`](https://github.com/astral-sh/uv), you can install the exported requirements by replacing `pip install -r …` with your package manager's equivalent command. {% endhint %} ## Help us with integrations! There are countless tools in the ML / MLOps field. We have made an initial prioritization of which tools to support with integrations that are visible on our public [roadmap](https://zenml.io/roadmap). We also welcome community contributions. Check our [Contribution Guide](https://github.com/zenml-io/zenml/blob/main/CONTRIBUTING.md) and [External Integration Guide](https://github.com/zenml-io/zenml/blob/main/src/zenml/integrations/README.md) for more details on how to best contribute to new integrations.
ZenML Scarf
--- ### Book/Component Guide/Toc (docs/book/component-guide/toc.md) # Table of contents * [Overview](component-guide.md) * [Integrations](integrations.md) ## Stack Components * [Orchestrators](orchestrators/README.md) * [Local Orchestrator](orchestrators/local.md) * [Local Docker Orchestrator](orchestrators/local-docker.md) * [Kubeflow Orchestrator](orchestrators/kubeflow.md) * [Kubernetes Orchestrator](orchestrators/kubernetes.md) * [Modal Orchestrator](orchestrators/modal.md) * [Google Cloud VertexAI Orchestrator](orchestrators/vertex.md) * [AWS Sagemaker Orchestrator](orchestrators/sagemaker.md) * [AzureML Orchestrator](orchestrators/azureml.md) * [Databricks Orchestrator](orchestrators/databricks.md) * [Tekton Orchestrator](orchestrators/tekton.md) * [Airflow Orchestrator](orchestrators/airflow.md) * [Skypilot VM Orchestrator](orchestrators/skypilot-vm.md) * [SSH Orchestrator](orchestrators/ssh.md) * [HyperAI Orchestrator](orchestrators/hyperai.md) * [Lightning AI Orchestrator](orchestrators/lightning.md) * [Develop a custom orchestrator](orchestrators/custom.md) * [Deployers](deployers/README.md) * [Local Deployer](deployers/local.md) * [Docker Deployer](deployers/docker.md) * [Kubernetes Deployer](deployers/kubernetes.md) * [AWS App Runner Deployer](deployers/aws-app-runner.md) * [GCP Cloud Run Deployer](deployers/gcp-cloud-run.md) * [Hugging Face Deployer](deployers/huggingface.md) * [Artifact Stores](artifact-stores/README.md) * [Local Artifact Store](artifact-stores/local.md) * [Amazon Simple Cloud Storage (S3)](artifact-stores/s3.md) * [Google Cloud Storage (GCS)](artifact-stores/gcp.md) * [Azure Blob Storage](artifact-stores/azure.md) * [Alibaba Cloud OSS](artifact-stores/alibaba-oss.md) * [MinIO](artifact-stores/minio.md) * [Backblaze B2](artifact-stores/b2.md) * [DigitalOcean Spaces](artifact-stores/digitalocean.md) * [Develop a custom artifact store](artifact-stores/custom.md) * [Container Registries](container-registries/README.md) * [Default Container Registry](container-registries/default.md) * [DockerHub](container-registries/dockerhub.md) * [Amazon Elastic Container Registry (ECR)](container-registries/aws.md) * [Google Cloud Container Registry](container-registries/gcp.md) * [Azure Container Registry](container-registries/azure.md) * [GitHub Container Registry](container-registries/github.md) * [DigitalOcean Container Registry](container-registries/digitalocean.md) * [Develop a custom container registry](container-registries/custom.md) * [Log Stores](log-stores/README.md) * [Artifact Log Store](log-stores/artifact.md) * [OpenTelemetry Log Store](log-stores/otel.md) * [Datadog Log Store](log-stores/datadog.md) * [Develop a Custom Log Store](log-stores/custom.md) * [Sandboxes](sandboxes/README.md) * [Local](sandboxes/local.md) * [Kubernetes](sandboxes/kubernetes.md) * [Modal](sandboxes/modal.md) * [Step Operators](step-operators/README.md) * [Amazon SageMaker](step-operators/sagemaker.md) * [AzureML](step-operators/azureml.md) * [Baseten](step-operators/baseten.md) * [Databricks](step-operators/databricks.md) * [Google Cloud VertexAI](step-operators/vertex.md) * [Kubernetes](step-operators/kubernetes.md) * [Run:AI](step-operators/runai.md) * [Modal](step-operators/modal.md) * [SSH](step-operators/ssh.md) * [Spark](step-operators/spark-kubernetes.md) * [Develop a Custom Step Operator](step-operators/custom.md) * [Experiment Trackers](experiment-trackers/README.md) * [Comet](experiment-trackers/comet.md) * [MLflow](experiment-trackers/mlflow.md) * [Neptune](experiment-trackers/neptune.md) * [Weights & Biases](experiment-trackers/wandb.md) * [Trackio](experiment-trackers/trackio.md) * [Google Cloud VertexAI Experiment Tracker](experiment-trackers/vertexai.md) * [Develop a custom experiment tracker](experiment-trackers/custom.md) * [Image Builders](image-builders/README.md) * [Local Image Builder](image-builders/local.md) * [Kaniko Image Builder](image-builders/kaniko.md) * [AWS Image Builder](image-builders/aws.md) * [Google Cloud Image Builder](image-builders/gcp.md) * [Develop a Custom Image Builder](image-builders/custom.md) * [Alerters](alerters/README.md) * [Discord Alerter](alerters/discord.md) * [Slack Alerter](alerters/slack.md) * [Develop a Custom Alerter](alerters/custom.md) * [Annotators](annotators/README.md) * [Argilla](annotators/argilla.md) * [Label Studio](annotators/label-studio.md) * [Pigeon](annotators/pigeon.md) * [Prodigy](annotators/prodigy.md) * [Develop a Custom Annotator](annotators/custom.md) * [Data Validators](data-validators/README.md) * [Great Expectations](data-validators/great-expectations.md) * [Deepchecks](data-validators/deepchecks.md) * [Evidently](data-validators/evidently.md) * [Whylogs](data-validators/whylogs.md) * [Develop a custom data validator](data-validators/custom.md) * [Feature Stores](feature-stores/README.md) * [Feast](feature-stores/feast.md) * [Develop a Custom Feature Store](feature-stores/custom.md) * [Model Deployers](model-deployers/README.md) * [MLflow](model-deployers/mlflow.md) * [Seldon](model-deployers/seldon.md) * [BentoML](model-deployers/bentoml.md) * [Hugging Face](model-deployers/huggingface.md) * [Databricks](model-deployers/databricks.md) * [vLLM](model-deployers/vllm.md) * [Develop a Custom Model Deployer](model-deployers/custom.md) * [Model Registries](model-registries/README.md) * [MLflow Model Registry](model-registries/mlflow.md) * [Develop a Custom Model Registry](model-registries/custom.md) ## Service Connectors * [Introduction](service-connectors/auth-management.md) * [Complete guide](service-connectors/service-connectors-guide.md) * [Best practices](service-connectors/best-security-practices.md) * [Connector Types](service-connectors/connector-types/README.md) * [Docker Service Connector](service-connectors/connector-types/docker-service-connector.md) * [Kubernetes Service Connector](service-connectors/connector-types/kubernetes-service-connector.md) * [AWS Service Connector](service-connectors/connector-types/aws-service-connector.md) * [GCP Service Connector](service-connectors/connector-types/gcp-service-connector.md) * [Azure Service Connector](service-connectors/connector-types/azure-service-connector.md) * [OAuth2 Service Connector](service-connectors/connector-types/oauth2-service-connector.md) * [HyperAI Service Connector](service-connectors/connector-types/hyperai-service-connector.md) ## Popular Stacks * [AWS](../how-to/popular-integrations/aws-guide.md) * [Azure](../how-to/popular-integrations/azure-guide.md) * [GCP](../how-to/popular-integrations/gcp-guide.md) * [Kubernetes](../how-to/popular-integrations/kubernetes.md) ## Deployment * [1-click Deployment](../how-to/infrastructure-deployment/stack-deployment/deploy-a-cloud-stack.md) * [Terraform Modules](../how-to/infrastructure-deployment/stack-deployment/deploy-a-cloud-stack-with-terraform.md) * [Register a cloud stack](../how-to/infrastructure-deployment/stack-deployment/register-a-cloud-stack.md) * [Infrastructure as code](../how-to/infrastructure-deployment/infrastructure-as-code/README.md) ## Contribute * [Custom Stack Component](contribute/custom-stack-component.md) * [Custom Integration](../how-to/contribute-to-zenml/implement-a-custom-integration.md) --- ### Book/Component Guide/.Gitbook.Yaml (docs/book/component-guide/.gitbook.yaml) root: . structure: readme: component-guide.md summary: toc.md redirects: alerters: alerters/README.md alerters/custom: alerters/custom.md alerters/discord: alerters/discord.md alerters/slack: alerters/slack.md annotators: annotators/README.md annotators/argilla: annotators/argilla.md annotators/custom: annotators/custom.md annotators/label-studio: annotators/label-studio.md annotators/pigeon: annotators/pigeon.md annotators/prodigy: annotators/prodigy.md artifact-stores: artifact-stores/README.md artifact-stores/azure: artifact-stores/azure.md artifact-stores/custom: artifact-stores/custom.md artifact-stores/gcp: artifact-stores/gcp.md artifact-stores/local: artifact-stores/local.md artifact-stores/s3: artifact-stores/s3.md container-registries: container-registries/README.md container-registries/aws: container-registries/aws.md container-registries/azure: container-registries/azure.md container-registries/custom: container-registries/custom.md container-registries/default: container-registries/default.md container-registries/dockerhub: container-registries/dockerhub.md container-registries/gcp: container-registries/gcp.md container-registries/github: container-registries/github.md data-validators: data-validators/README.md data-validators/custom: data-validators/custom.md data-validators/deepchecks: data-validators/deepchecks.md data-validators/evidently: data-validators/evidently.md data-validators/great-expectations: data-validators/great-expectations.md data-validators/whylogs: data-validators/whylogs.md experiment-trackers: experiment-trackers/README.md experiment-trackers/comet: experiment-trackers/comet.md experiment-trackers/custom: experiment-trackers/custom.md experiment-trackers/mlflow: experiment-trackers/mlflow.md experiment-trackers/neptune: experiment-trackers/neptune.md experiment-trackers/vertexai: experiment-trackers/vertexai.md experiment-trackers/wandb: experiment-trackers/wandb.md experiment-trackers/trackio: experiment-trackers/trackio.md feature-stores: feature-stores/README.md feature-stores/custom: feature-stores/custom.md feature-stores/feast: feature-stores/feast.md image-builders: image-builders/README.md image-builders/aws: image-builders/aws.md image-builders/custom: image-builders/custom.md image-builders/gcp: image-builders/gcp.md image-builders/kaniko: image-builders/kaniko.md image-builders/local: image-builders/local.md model-deployers: model-deployers/README.md model-deployers/bentoml: model-deployers/bentoml.md model-deployers/custom: model-deployers/custom.md model-deployers/databricks: model-deployers/databricks.md model-deployers/huggingface: model-deployers/huggingface.md model-deployers/mlflow: model-deployers/mlflow.md model-deployers/seldon: model-deployers/seldon.md model-deployers/vllm: model-deployers/vllm.md model-registries: model-registries/README.md model-registries/custom: model-registries/custom.md model-registries/mlflow: model-registries/mlflow.md orchestrators: orchestrators/README.md orchestrators/airflow: orchestrators/airflow.md orchestrators/azureml: orchestrators/azureml.md orchestrators/custom: orchestrators/custom.md orchestrators/databricks: orchestrators/databricks.md orchestrators/hyperai: orchestrators/hyperai.md orchestrators/kubeflow: orchestrators/kubeflow.md orchestrators/kubernetes: orchestrators/kubernetes.md orchestrators/lightning: orchestrators/lightning.md orchestrators/local: orchestrators/local.md orchestrators/local-docker: orchestrators/local-docker.md orchestrators/modal: orchestrators/modal.md orchestrators/sagemaker: orchestrators/sagemaker.md orchestrators/skypilot-vm: orchestrators/skypilot-vm.md orchestrators/tekton: orchestrators/tekton.md orchestrators/vertex: orchestrators/vertex.md step-operators: step-operators/README.md step-operators/azureml: step-operators/azureml.md step-operators/custom: step-operators/custom.md step-operators/kubernetes: step-operators/kubernetes.md step-operators/modal: step-operators/modal.md step-operators/sagemaker: step-operators/sagemaker.md step-operators/spark-kubernetes: step-operators/spark-kubernetes.md step-operators/vertex: step-operators/vertex.md --- ### Book/Component Guide/Alerters/README (docs/book/component-guide/alerters/README.md) --- description: Sending automated alerts to chat services. icon: message-exclamation --- # Alerters **Alerters** allow you to send messages to chat services (like Slack, Discord, Mattermost, etc.) from within your pipelines. This is useful to immediately get notified when failures happen, for general monitoring/reporting, and also for building human-in-the-loop ML. ## Alerter Flavors Currently, the [SlackAlerter](slack.md) and [DiscordAlerter](discord.md) are the available alerter integrations. However, it is straightforward to extend ZenML and [build an alerter for other chat services](custom.md). | Alerter | Flavor | Integration | Notes | |------------------------------------|-----------|-------------|--------------------------------------------------------------------| | [Slack](slack.md) | `slack` | `slack` | Interacts with a Slack channel | | [Discord](discord.md) | `discord` | `discord` | Interacts with a Discord channel | | [Custom Implementation](custom.md) | _custom_ | | Extend the alerter abstraction and provide your own implementation | {% hint style="info" %} If you would like to see the available flavors of alerters in your terminal, you can use the following command: ```shell zenml alerter flavor list ``` {% endhint %} ## How to use Alerters with ZenML Each alerter integration comes with specific standard steps that you can use out of the box. However, you first need to register an alerter component in your terminal: ```shell zenml alerter register ... ``` Then you can add it to your stack using ```shell zenml stack register ... -al ``` {% hint style="info" %} Stacks can have multiple alerters attached. ZenML treats the first attached alerter as the default one, and singular accessors such as `Client().active_stack.alerter` resolve to that default alerter. {% endhint %} Afterward, you can import the alerter standard steps provided by the respective integration and directly use them in your pipelines. ## Using the Ask Step for Human-in-the-Loop Workflows All alerters provide an `ask()` method and corresponding ask steps that enable human-in-the-loop workflows. These are essential for: - Getting approval before deploying models to production - Confirming critical pipeline decisions - Manual intervention points in automated workflows ### How Ask Steps Work Ask steps (like `discord_alerter_ask_step` and `slack_alerter_ask_step`): 1. **Post a message** to your chat service with your question 2. **Wait for user response** containing specific approval or disapproval keywords 3. **Return a boolean** - `True` if approved, `False` if disapproved or timeout ```python from zenml import step, pipeline from zenml.integrations.slack.steps.slack_alerter_ask_step import slack_alerter_ask_step @step def train_model(): # Training logic here - this is a placeholder function return "trained_model_object" @step def deploy_model(model, approved: bool) -> None: if approved: # Deploy the model to production print("Deploying model to production...") # deployment logic here else: print("Deployment cancelled by user") @pipeline def deployment_pipeline(): trained_model = train_model() # Ask for human approval before deployment approved = slack_alerter_ask_step("Deploy model to production?") deploy_model(trained_model, approved) ``` ### Default Response Keywords By default, alerters recognize these response options: **Approval:** `approve`, `LGTM`, `ok`, `yes` **Disapproval:** `decline`, `disapprove`, `no`, `reject` ### Customizing Response Keywords You can customize the approval and disapproval keywords using alerter parameters: ```python from zenml.integrations.slack.steps.slack_alerter_ask_step import slack_alerter_ask_step from zenml.integrations.slack.alerters.slack_alerter import SlackAlerterParameters # Use custom approval/disapproval keywords params = SlackAlerterParameters( approve_msg_options=["deploy", "ship it", "✅"], disapprove_msg_options=["stop", "cancel", "❌"] ) approved = slack_alerter_ask_step( "Deploy model to production?", params=params ) ``` ### Important Notes - **Return Type**: Ask steps return a boolean value - ensure your pipeline logic handles this correctly - **Keywords**: Response keywords are case-sensitive (except Slack, which converts to lowercase) - **Timeout**: If no valid response is received within the timeout period, the step returns `False` - **Permissions**: Ensure your bot has permissions to read messages in the target channel
ZenML Scarf
--- ### Book/Component Guide/Alerters/Custom (docs/book/component-guide/alerters/custom.md) --- description: Learning how to develop a custom alerter. --- # Develop a Custom Alerter {% hint style="info" %} Before diving into the specifics of this component type, it is beneficial to familiarize yourself with our [general guide to writing custom component flavors in ZenML](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/implement-a-custom-stack-component). This guide provides an essential understanding of ZenML's component flavor concepts. {% endhint %} ### Base Abstraction The base abstraction for alerters is very basic, as it only defines two abstract methods that subclasses should implement: * `post()` takes a string, posts it to the desired chat service, and returns `True` if the operation succeeded, else `False`. * `ask()` does the same as `post()`, but after sending the message, it waits until someone approves or rejects the operation from within the chat service (e.g., by sending "approve" / "reject" to the bot as a response). `ask()` then only returns `True` if the operation succeeded and was approved, else `False`. The `ask()` method is particularly useful for implementing human-in-the-loop workflows. When implementing this method, you should: - Wait for user responses containing approval keywords (like `"approve"`, `"yes"`, `"ok"`, `"LGTM"`) - Wait for user responses containing disapproval keywords (like `"reject"`, `"no"`, `"cancel"`, `"stop"`) - Return `True` only when explicit approval is received - Return `False` for disapproval, timeout, or any errors - Consider implementing configurable approval/disapproval keywords via parameters Then base abstraction looks something like this: ```python from abc import ABC from typing import Optional from zenml.stack import StackComponent from zenml.alerter import BaseAlerterStepParameters class BaseAlerter(StackComponent, ABC): """Base class for all ZenML alerters.""" def post( self, message: str, params: Optional[BaseAlerterStepParameters] ) -> bool: """Post a message to a chat service.""" return True def ask( self, question: str, params: Optional[BaseAlerterStepParameters] ) -> bool: """Post a message to a chat service and wait for approval.""" return True ``` {% hint style="info" %} This is a slimmed-down version of the base implementation. To see the full docstrings and imports, please check [the source code on GitHub](https://github.com/zenml-io/zenml/blob/main/src/zenml/alerter/base\_alerter.py). {% endhint %} ### Building your own custom alerter Creating your own custom alerter can be done in four steps: 1. Create a class that inherits from the `BaseAlerter` and implement the `post()` and `ask()` methods. ``` /* Detailed source-code truncated for AI context efficiency. */ ``` 2. If you need to configure your custom alerter, you can also implement a config object. ```python from zenml.alerter.base_alerter import BaseAlerterConfig class MyAlerterConfig(BaseAlerterConfig): my_param: str ``` 3. Optionally, you can create custom parameter classes to support configurable approval/disapproval keywords: ```python from typing import List, Optional from zenml.alerter.base_alerter import BaseAlerterStepParameters class MyAlerterParameters(BaseAlerterStepParameters): """Custom parameters for MyAlerter.""" # Custom approval/disapproval message options approve_msg_options: Optional[List[str]] = None disapprove_msg_options: Optional[List[str]] = None # Any other custom parameters for your alerter custom_channel: Optional[str] = None ``` 4. Finally, you can bring the implementation and the configuration together in a new flavor object. ```python from typing import Type, TYPE_CHECKING from zenml.alerter import BaseAlerterFlavor if TYPE_CHECKING: from zenml.stack import StackComponent, StackComponentConfig class MyAlerterFlavor(BaseAlerterFlavor): @property def name(self) -> str: return "my_alerter" @property def config_class(self) -> Type[StackComponentConfig]: from my_alerter_config import MyAlerterConfig return MyAlerterConfig @property def implementation_class(self) -> Type[StackComponent]: from my_alerter import MyAlerter return MyAlerter ``` Once you are done with the implementation, you can register your new flavor through the CLI. Please ensure you **point to the flavor class via dot notation**: ```shell zenml alerter flavor register ``` For example, if your flavor class `MyAlerterFlavor` is defined in `flavors/my_flavor.py`, you'd register it by doing: ```shell zenml alerter flavor register flavors.my_flavor.MyAlerterFlavor ``` {% hint style="warning" %} ZenML resolves the flavor class by taking the path where you initialized zenml (via `zenml init`) as the starting point of resolution. Therefore, please ensure you follow [the best practice](https://docs.zenml.io/how-to/project-setup-and-management/setting-up-a-project-repository/set-up-repository) of initializing zenml at the root of your repository. If ZenML does not find an initialized ZenML repository in any parent directory, it will default to the current working directory, but usually, it's better to not have to rely on this mechanism and initialize zenml at the root. {% endhint %} Afterward, you should see the new custom alerter flavor in the list of available alerter flavors: ```shell zenml alerter flavor list ``` {% hint style="warning" %} It is important to draw attention to when and how these abstractions are coming into play in a ZenML workflow. * The **MyAlerterFlavor** class is imported and utilized upon the creation of the custom flavor through the CLI. * The **MyAlerterConfig** class is imported when someone tries to register/update a stack component with the `my_alerter` flavor. Especially, during the registration process of the stack component, the config will be used to validate the values given by the user. As `Config` objects are inherently `pydantic` objects, you can also add your own custom validators here. * The **MyAlerter** only comes into play when the component is ultimately in use. The design behind this interaction lets us separate the configuration of the flavor from its implementation. This way we can register flavors and components even when the major dependencies behind their implementation are not installed in our local setting (assuming the `MyAlerterFlavor` and the `MyAlerterConfig` are implemented in a different module/path than the actual `MyAlerter`). {% endhint %}
ZenML Scarf
--- ### Book/Component Guide/Alerters/Discord (docs/book/component-guide/alerters/discord.md) --- description: Sending automated alerts to a Discord channel. --- # Discord Alerter The `DiscordAlerter` enables you to send messages to a dedicated Discord channel directly from within your ZenML pipelines. The `discord` integration contains the following two standard steps: * [discord\_alerter\_post\_step](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-discord.html#zenml.integrations.discord) takes a string message, posts it to a Discord channel, and returns whether the operation was successful. * [discord\_alerter\_ask\_step](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-discord.html#zenml.integrations.discord) also posts a message to a Discord channel, but waits for user feedback, and only returns `True` if a user explicitly approved the operation from within Discord (e.g., by sending "approve" / "reject" to the bot in response). Interacting with Discord from within your pipelines can be very useful in practice: * The `discord_alerter_post_step` allows you to get notified immediately when failures happen (e.g., model performance degradation, data drift, ...), * The `discord_alerter_ask_step` allows you to integrate a human-in-the-loop into your pipelines before executing critical steps, such as deploying new models. ## How to use it ### Requirements Before you can use the `DiscordAlerter`, you first need to install ZenML's `discord` integration: ```shell zenml integration install discord -y ``` {% hint style="info" %} See the [Integrations](https://docs.zenml.io/component-guide) page for more details on ZenML integrations and how to install and use them. {% endhint %} ### Setting Up a Discord Bot In order to use the `DiscordAlerter`, you first need to have a Discord workspace set up with a channel that you want your pipelines to post to. This is the `` you will need when registering the discord alerter component. Then, you need to [create a Discord App with a bot in your server](https://discordpy.readthedocs.io/en/latest/discord.html) . {% hint style="info" %} Note in the bot token copy step, if you don't find the copy button then click on reset token to reset the bot and you will get a new token which you can use. Also, make sure you give necessary permissions to the bot required for sending and receiving messages. {% endhint %} ### Registering a Discord Alerter in ZenML Next, you need to register a `discord` alerter in ZenML and link it to the bot you just created. You can do this with the following command: ```shell zenml alerter register discord_alerter \ --flavor=discord \ --discord_token= \ --default_discord_channel_id= ``` {% hint style="info" %} **Using Secrets for Token Management**: Instead of passing your Discord token directly, it's recommended to store it as a ZenML secret and reference it in your alerter configuration. This approach keeps sensitive information secure: ```shell # Create a secret for your Discord token zenml secret create discord_secret --discord_token= # Register the alerter referencing the secret zenml alerter register discord_alerter \ --flavor=discord \ --discord_token={{discord_secret.discord_token}} \ --default_discord_channel_id= ``` Learn more about [referencing secrets in stack component attributes and settings](https://docs.zenml.io/concepts/secrets#reference-secrets-in-stack-component-attributes-and-settings). {% endhint %} After you have registered the `discord_alerter`, you can add it to your stack like this: ```shell zenml stack register ... -al discord_alerter ``` Here is where you can find the required parameters: #### DISCORD\_CHANNEL\_ID Open the discord server, then right-click on the text channel and click on the 'Copy Channel ID' option. {% hint style="info" %} If you don't see any 'Copy Channel ID' option for your channel, go to "User Settings" > "Advanced" and make sure "Developer Mode" is active. {% endhint %} #### DISCORD\_TOKEN This is the Discord token of your bot. You can find the instructions on how to set up a bot, invite it to your channel, and find its token [here](https://discordpy.readthedocs.io/en/latest/discord.html). {% hint style="warning" %} When inviting the bot to your channel, make sure it has at least the following permissions: * Read Messages/View Channels * Send Messages * Send Messages in Threads {% endhint %} ### How to Use the Discord Alerter After you have a `DiscordAlerter` configured in your stack, you can directly import the [discord\_alerter\_post\_step](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-discord.html#zenml.integrations.discord) and [discord\_alerter\_ask\_step](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-discord.html#zenml.integrations.discord) steps and use them in your pipelines. Since these steps expect a string message as input (which needs to be the output of another step), you typically also need to define a dedicated formatter step that takes whatever data you want to communicate and generates the string message that the alerter should post. As an example, adding `discord_alerter_ask_step()` to your pipeline could look like this: ```python from zenml.integrations.discord.steps.discord_alerter_ask_step import discord_alerter_ask_step from zenml import step, pipeline @step def my_formatter_step(artifact_to_be_communicated) -> str: return f"Here is my artifact {artifact_to_be_communicated}!" @step def process_approval_response(artifact, approved: bool) -> None: if approved: # Proceed with the operation print(f"User approved! Processing {artifact}") # Your logic here else: print("User disapproved. Skipping operation.") @pipeline def my_pipeline(...): ... artifact_to_be_communicated = ... message = my_formatter_step(artifact_to_be_communicated) approved = discord_alerter_ask_step(message) process_approval_response(artifact_to_be_communicated, approved) if __name__ == "__main__": my_pipeline() ``` ## Using Custom Approval Keywords You can customize which words trigger approval or disapproval by using `DiscordAlerterParameters`: ```python from zenml.integrations.discord.steps.discord_alerter_ask_step import discord_alerter_ask_step from zenml.integrations.discord.alerters.discord_alerter import DiscordAlerterParameters # Custom approval/disapproval keywords params = DiscordAlerterParameters( approve_msg_options=["deploy", "ship it", "✅"], disapprove_msg_options=["stop", "cancel", "❌"] ) approved = discord_alerter_ask_step( "Deploy model to production?", params=params ) ``` ### Default Response Keywords By default, the Discord alerter recognizes these keywords: **Approval:** `approve`, `LGTM`, `ok`, `yes` **Disapproval:** `decline`, `disapprove`, `no`, `reject` **Important Notes:** - The ask step returns a boolean (`True` for approval, `False` for disapproval/timeout) - **Keywords are case-sensitive** - you must respond with exact case (e.g., `LGTM` not `lgtm`) - If no valid response is received, the step returns `False` {% hint style="warning" %} **Discord Case Sensitivity**: The Discord alerter implementation requires exact case matching for approval keywords. Make sure to respond with the exact case specified (e.g., `LGTM`, not `lgtm`). {% endhint %} For more information and a full list of configurable attributes of the Discord alerter, check out the [SDK Docs](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-discord.html#zenml.integrations.discord) .
ZenML Scarf
--- ### Book/Component Guide/Alerters/Slack (docs/book/component-guide/alerters/slack.md) --- description: Sending automated alerts to a Slack channel. --- # Slack Alerter The `SlackAlerter` enables you to send messages or ask questions within a dedicated Slack channel directly from within your ZenML pipelines and steps. ## How to Create ### Set up a Slack app In order to use the `SlackAlerter`, you first need to have a Slack workspace set up with a channel that you want your pipelines to post to. Then, you need to [create a Slack App](https://api.slack.com/apps?new\_app=1) with a bot in your workspace. Make sure to give it the following permissions in the `OAuth & Permissions` tab under `Scopes`: * `chat:write`, * `channels:read` * `channels:history` In order to be able to use the `ask()` functionality, you need to invite the app to your channel. You can either use the `/invite` command directly in the desired channel or add it through the channel settings: {% hint style="warning" %} It might take some time for your app to register within your workspace and show up in the available list of applications. {% endhint %} ### Registering a Slack Alerter in ZenML To create a `SlackAlerter`, you first need to install ZenML's `slack` integration: ```shell zenml integration install slack -y ``` Once the integration is installed, you can use the ZenML CLI to create a secret and register an alerter linked to the app you just created: ```shell zenml secret create slack_token --oauth_token= zenml alerter register slack_alerter \ --flavor=slack \ --slack_token={{slack_token.oauth_token}} \ --slack_channel_id= ``` {% hint style="info" %} **Using Secrets for Token Management**: The example above demonstrates the recommended approach of storing your Slack token as a ZenML secret and referencing it using the `{{secret_name.key}}` syntax. This keeps sensitive information secure and follows security best practices. Learn more about [referencing secrets in stack component attributes and settings](https://docs.zenml.io/concepts/secrets#reference-secrets-in-stack-component-attributes-and-settings). {% endhint %} Here is where you can find the required parameters: * ``: The channel ID can be found in the channel details. It starts with `C....`. * ``: This is the Slack token of your bot. You can find it in the Slack app settings under `OAuth & Permissions`. After you have registered the `slack_alerter`, you can add it to your stack like this: ```shell zenml stack register ... -al slack_alerter --set ``` ## How to Use In ZenML, you can use alerters in various ways. ### Use the `post()` and `ask()` directly You can use the client to fetch the active alerter within your stack and use the `post` and `ask` methods directly: ```python from zenml import pipeline, step from zenml.client import Client @step def post_statement() -> None: Client().active_stack.alerter.post("Step finished!") @step def ask_question() -> bool: return Client().active_stack.alerter.ask("Should I continue?") @pipeline(enable_cache=False) def my_pipeline(): # Step using alerter.post post_statement() # Step using alerter.ask ask_question() if __name__ == "__main__": my_pipeline() ``` {% hint style="warning" %} In case of an error, the output of the `ask()` method default to `False`. {% endhint %} ### Use it with custom settings The Slack alerter comes equipped with a set of options that you can set during runtime: ```python from zenml import pipeline, step from zenml.client import Client # E.g, You can use a different channel ID through the settings. However, if you # want to use the `ask` functionality, make sure that you app is invited to # this channel first. @step(settings={"alerter": {"slack_channel_id": "YOUR_SLACK_CHANNEL_ID"}}) def post_statement() -> None: alerter = Client().active_stack.alerter alerter.post("Posting to another channel!") @pipeline(enable_cache=False) def my_pipeline(): # Using alerter.post post_statement() if __name__ == "__main__": my_pipeline() ``` ## Use it with `SlackAlerterParameters` and `SlackAlerterPayload` You can use these additional classes to further edit your messages: ``` /* Detailed source-code truncated for AI context efficiency. */ ``` ### Use the predefined steps If you want to only use it in a simple manner, you can also use the steps `slack_alerter_post_step` and `slack_alerter_ask_step`, that are built-in to the Slack integration of ZenML: ```python from zenml import pipeline, step from zenml.integrations.slack.steps.slack_alerter_post_step import ( slack_alerter_post_step ) from zenml.integrations.slack.steps.slack_alerter_ask_step import ( slack_alerter_ask_step, ) @step def process_approval_response(approved: bool) -> None: if approved: print("Operation approved!") else: print("Operation declined.") @pipeline(enable_cache=False) def my_pipeline(): slack_alerter_post_step("Posting a statement.") approved = slack_alerter_ask_step("Asking a question. Should I continue?") process_approval_response(approved) if __name__ == "__main__": my_pipeline() ``` ## Default Response Keywords and Ask Step Behavior The `ask()` method and `slack_alerter_ask_step` recognize these keywords by default: **Approval:** `approve`, `LGTM`, `ok`, `yes` **Disapproval:** `decline`, `disapprove`, `no`, `reject` **Important Notes:** - The ask step returns a boolean (`True` for approval, `False` for disapproval/timeout) - **Response keywords are case-insensitive** - keywords are converted to lowercase before matching (e.g., both `LGTM` and `lgtm` work) - If no valid response is received within the timeout period, the step returns `False` - The default timeout is 300 seconds (5 minutes) but can be configured {% hint style="info" %} **Slack Case Handling**: The Slack alerter implementation automatically converts all response keywords to lowercase before matching, making responses case-insensitive. You can respond with `LGTM`, `lgtm`, or `Lgtm` - they'll all work. {% endhint %} For more information and a full list of configurable attributes of the Slack alerter, check out the [SDK Docs](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-slack.html#zenml.integrations.slack) .
ZenML Scarf
--- ### Book/Component Guide/Annotators/README (docs/book/component-guide/annotators/README.md) --- icon: expand description: Annotating the data in your workflow. --- # Annotators Annotators are a stack component that enables the use of data annotation as part of your ZenML stack and pipelines. You can use the associated CLI command to launch annotation, configure your datasets and get stats on how many labeled tasks you have ready for use. Data annotation/labeling is a core part of MLOps that is frequently left out of the conversation. ZenML will incrementally start to build features that support an iterative annotation workflow that sees the people doing labeling (and their workflows/behaviors) as integrated parts of their ML process(es). There are a number of different places in the ML lifecycle where this can happen: * **At the start**: You might be starting out without any data, or with a ton of data but no clear sense of which parts of it are useful to your particular problem. It’s not uncommon to have a lot of data but to be lacking accurate labels for that data. So you can start and get great value from bootstrapping your model: label some data, train your model, and use your model to suggest labels allowing you to speed up your labeling, iterating on and on in this way. Labeling data early on in the process also helps clarify and condense down your specific rules and standards. For example, you might realize that you need to have specific definitions for certain concepts so that your labeling efforts are consistent across your team. * **As new data comes in**: New data will likely continue to come in, and you might want to check in with the labeling process at regular intervals to expose yourself to this new data. (You’ll probably also want to have some kind of automation around detecting data or concept drift, but for certain kinds of unstructured data you probably can never completely abandon the instant feedback of actual contact with the raw data.) * **Samples generated for inference**: Your model will be making predictions on real-world data being passed in. If you store and label this data, you’ll gain a valuable set of data that you can use to compare your labels with what the model was predicting, another possible way to flag drifts of various kinds. This data can then (subject to privacy/user consent) be used in retraining or fine-tuning your model. * **Other ad hoc interventions**: You will probably have some kind of process to identify bad labels, or to find the kinds of examples that your model finds really difficult to make correct predictions. For these, and for areas where you have clear class imbalances, you might want to do ad hoc annotation to supplement the raw materials your model has to learn from. ZenML currently offers standard steps that help you tackle the above use cases, but the stack component and abstraction will continue to be developed to make it easier to use. ### When to use it The annotator is an optional stack component in the ZenML Stack. We designed our abstraction to fit into the larger ML use cases, particularly the training and deployment parts of the lifecycle. The core parts of the annotation workflow include: * using labels or annotations in your training steps in a seamless way * handling the versioning of annotation data * allow for the conversion of annotation data to and from custom formats * handle annotator-specific tasks, for example, the generation of UI config files that Label Studio requires for the web annotation interface ### List of available annotators For production use cases, some more flavors can be found in specific `integrations` modules. In terms of annotators, ZenML features integrations with the following tools. | Annotator | Flavor | Integration | Notes | |-----------------------------------------|----------------|----------------|----------------------------------------------------------------------| | [ArgillaAnnotator](argilla.md) | `argilla` | `argilla` | Connect ZenML with Argilla | | [LabelStudioAnnotator](label-studio.md) | `label_studio` | `label_studio` | Connect ZenML with Label Studio | | [PigeonAnnotator](pigeon.md) | `pigeon` | `pigeon` | Connect ZenML with Pigeon. Notebook only & for image and text classification tasks. | | [ProdigyAnnotator](prodigy.md) | `prodigy` | `prodigy` | Connect ZenML with [Prodigy](https://prodi.gy/) | | [Custom Implementation](custom.md) | _custom_ | | Extend the annotator abstraction and provide your own implementation | If you would like to see the available flavors for annotators, you can use the command: ```shell zenml annotator flavor list ``` ### How to use it The available implementation of the annotator is built on top of the Label Studio integration, which means that using an annotator currently is no different from what's described on the [Label Studio page: How to use it?](label-studio.md#how-do-you-use-it). ([Pigeon](pigeon.md) is also supported, but has a very limited functionality and only works within Jupyter notebooks.) ### A note on names The various annotation tools have mostly standardized around the naming of key concepts as part of how they build their tools. Unfortunately, this hasn't been completely unified so ZenML takes an opinion on which names we use for our stack components and integrations. Key differences to note: * Label Studio refers to the grouping of a set of annotations/tasks as a 'Project', whereas most other tools use the term 'Dataset', so ZenML also calls this grouping a 'Dataset'. * The individual meta-unit for 'an annotation + the source data' is referred to in different ways, but at ZenML (and with Label Studio) we refer to them as 'tasks'. The remaining core concepts ('annotation' and 'prediction', in particular) are broadly used among annotation tools.
ZenML Scarf
--- ### Book/Component Guide/Annotators/Argilla (docs/book/component-guide/annotators/argilla.md) --- description: Annotating data using Argilla. --- # Argilla [Argilla](https://github.com/argilla-io/argilla) is a collaboration tool for AI engineers and domain experts who need to build high-quality datasets for their projects. It enables users to build robust language models through faster data curation using both human and machine feedback, providing support for each step in the MLOps cycle, from data labeling to model monitoring. Argilla distinguishes itself for its focus on specific use cases and human-in-the-loop approaches. While it does offer programmatic features, Argilla's core value lies in actively involving human experts in the tool-building process, setting it apart from other competitors. ### When would you want to use it? If you need to label textual data as part of your ML workflow, that is the point at which you could consider adding the Argilla annotator stack component as part of your ZenML stack. We currently support the use of annotation at the various stages described in[the main annotators docs page](./). The Argilla integration currently is built to support annotation using a local (Docker-backed) instance of Argilla as well as a deployed instance of Argilla. There is an easy way to deploy Argilla as a [Hugging Face Space](https://huggingface.co/docs/hub/spaces-sdks-docker-argilla), for instance, which is documented in the [Argilla documentation](https://argilla.io/). ### How to deploy it? The Argilla Annotator flavor is provided by the Argilla ZenML integration. You need to install it to be able to register it as an Annotator and add it to your stack: ```shell zenml integration install argilla ``` You can either pass the `api_key` directly into the `zenml annotator register` command or you can register it as a secret and pass the secret name into the command. We recommend the latter approach for security reasons. If you want to take the latter approach, be sure to register a secret for whichever artifact store you choose, and then you should make sure to pass the name of that secret into the annotator as the `--authentication_secret`. For example, you'd run: ```shell zenml secret create argilla_secrets --api_key="" ``` (Visit the Argilla documentation and interface to obtain your API key.) Then register your annotator with ZenML: ```shell zenml annotator register argilla --flavor argilla --authentication_secret=argilla_secrets --port=6900 ``` When using a deployed instance of Argilla, the instance URL must be specified without any trailing `/` at the end. If you are using a Hugging Face Spaces instance and its visibility is set to private, you must also set the`headers` parameter which would include a Hugging Face token. For example: ```shell zenml annotator register argilla --flavor argilla --authentication_secret=argilla_secrets --instance_url="https://[your-owner-name]-[your_space_name].hf.space" --headers='{"Authorization": "Bearer {[your_hugging_face_token]}"}' ``` Finally, add all these components to a stack and set it as your active stack. For example: ```shell zenml stack copy default annotation # this must be done separately so that the other required stack components are first registered zenml stack update annotation -an zenml stack set annotation # optionally also zenml stack describe ``` Now if you run a simple CLI command like `zenml annotator dataset list` this should work without any errors. You're ready to use your annotator in your ML workflow! ### How do you use it? ZenML supports access to your data and annotations via the `zenml annotator ...` CLI command. We have also implemented an interface to some of the common Argilla functionality via the ZenML SDK. You can access information about the datasets you're using with the `zenml annotator dataset list`. To work on annotation for a particular dataset, you can run `zenml annotator dataset annotate `. This will open the Argilla web interface for you to start annotating the dataset. #### Argilla Annotator Stack Component Our Argilla annotator component inherits from the `BaseAnnotator` class. There are some methods that are core methods that must be defined, like being able to register or get a dataset. Most annotators handle things like the storage of state and have their own custom features, so there are quite a few extra methods specific to Argilla. The core Argilla functionality that's currently enabled includes a way to register your datasets, export any annotations for use in separate steps as well as start the annotator daemon process. (Argilla requires a server to be running in order to use the web interface, and ZenML handles the connection to this server using the details you passed in when registering the component.) #### Argilla Annotator SDK Visit [the SDK docs](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-argilla.html) to learn more about the methods that ZenML exposes for the Argilla annotator. To access the SDK through Python, you would first get the client object and then call the methods you need. For example: ```python from zenml.client import Client client = Client() annotator = client.active_stack.annotator # list dataset names dataset_names = annotator.get_dataset_names() # get a specific dataset dataset = annotator.get_dataset("dataset_name") # get the annotations for a dataset annotations = annotator.get_labeled_data(dataset_name="dataset_name") ``` For more detailed information on how to use the Argilla annotator and the functionality it provides, visit the [Argilla documentation](https://argilla.io/).
ZenML Scarf
--- ### Book/Component Guide/Annotators/Custom (docs/book/component-guide/annotators/custom.md) --- description: Learning how to develop a custom annotator. --- # Develop a Custom Annotator {% hint style="info" %} Before diving into the specifics of this component type, it is beneficial to familiarize yourself with our [general guide to writing custom component flavors in ZenML](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/implement-a-custom-stack-component). This guide provides an essential understanding of ZenML's component flavor concepts. {% endhint %} Annotators are a stack component that enables the use of data annotation as part of your ZenML stack and pipelines. You can use the associated CLI command to launch annotation, configure your datasets and get stats on how many labeled tasks you have ready for use. {% hint style="warning" %} **Base abstraction in progress!** We are actively working on the base abstraction for the annotators, which will be available soon. As a result, their extension is not possible at the moment. If you would like to use an annotator in your stack, please check the list of already available feature stores down below. {% endhint %}
ZenML Scarf
--- ### Book/Component Guide/Annotators/Label Studio (docs/book/component-guide/annotators/label-studio.md) --- description: Annotating data using Label Studio. --- # Label Studio Label Studio is one of the leading open-source annotation platforms available to data scientists and ML practitioners. It is used to create or edit datasets that you can then use as part of training or validation workflows. It supports a broad range of annotation types, including: * Computer Vision (image classification, object detection, semantic segmentation) * Audio & Speech (classification, speaker diarization, emotion recognition, audio transcription) * Text / NLP (classification, NER, question answering, sentiment analysis) * Time Series (classification, segmentation, event recognition) * Multi-Modal / Domain (dialogue processing, OCR, time series with reference) ### When would you want to use it? If you need to label data as part of your ML workflow, that is the point at which you could consider adding the optional annotator stack component as part of your ZenML stack. We currently support the use of annotation at the various stages described in [the main annotators docs page](./), and also offer custom utility functions to generate Label Studio label config files for image classification and object detection. (More will follow in due course.) The Label Studio integration currently is built to support workflows using the following three cloud artifact stores: AWS S3, GCP/GCS, and Azure Blob Storage. Purely local stacks will currently _not_ work if you want to do add the annotation stack component as part of your stack. ### How to deploy it? The Label Studio Annotator flavor is provided by the Label Studio ZenML integration, you need to install it, to be able to register it as an Annotator and add it to your stack: ```shell zenml integration install label_studio ``` You will then need to obtain your Label Studio API key. This will give you access to the web annotation interface. (The following steps apply to a local instance of Label Studio, but feel free to obtain your API key directly from your deployed instance if that's what you are using.) ```shell git clone https://github.com/HumanSignal/label-studio.git cd label-studio docker-compose up -d # starts label studio at http://localhost:8080 ``` Then visit [http://localhost:8080/](http://localhost:8080/) to log in, and then visit [http://localhost:8080/user/account](http://localhost:8080/user/account) and get your Label Studio API key (from the upper right-hand corner). You will need it for the next step. Keep the Label Studio server running, because the ZenML Label Studio annotator will use it as the backend. At this point you should register the API key under a custom secret name, making sure to replace the two parts in `<>` with whatever you choose: ```shell zenml secret create label_studio_secrets --api_key="" ``` Then register your annotator with ZenML: ```shell zenml annotator register label_studio --flavor label_studio --authentication_secret="label_studio_secrets" --port=8080 # for deployed instances of Label Studio, you can also pass in the URL as follows, for example: # zenml annotator register label_studio --flavor label_studio --authentication_secret="" --instance_url="" --port=80 ``` When using a deployed instance of Label Studio, the instance URL must be specified without any trailing `/` at the end. You should specify the port, for example, port 80 for a standard HTTP connection. For a Hugging Face deployment (the easiest way to get going with Label Studio), please read the [Hugging Face deployment documentation](https://huggingface.co/docs/hub/spaces-sdks-docker-label-studio). Finally, add all these components to a stack and set it as your active stack. For example: ```shell zenml stack copy default annotation zenml stack update annotation -a # this must be done separately so that the other required stack components are first registered zenml stack update annotation -an zenml stack set annotation # optionally also zenml stack describe ``` Now if you run a simple CLI command like `zenml annotator dataset list` this should work without any errors. You're ready to use your annotator in your ML workflow! ### How do you use it? ZenML assumes that users have registered a cloud artifact store and an annotator as described above. ZenML currently only supports this setup, but we will add in the fully local stack option in the future. ZenML supports access to your data and annotations via the `zenml annotator ...` CLI command. You can access information about the datasets you're using with the `zenml annotator dataset list`. To work on annotation for a particular dataset, you can run `zenml annotator dataset annotate `. [Our computer vision end to end example](https://github.com/zenml-io/zenml-projects/tree/main/end-to-end-computer-vision) is the best place to see how all the pieces of making this integration work fit together. What follows is an overview of some key components to the Label Studio integration and how it can be used. #### Label Studio Annotator Stack Component Our Label Studio annotator component inherits from the `BaseAnnotator` class. There are some methods that are core methods that must be defined, like being able to register or get a dataset. Most annotators handle things like the storage of state and have their own custom features, so there are quite a few extra methods specific to Label Studio. The core Label Studio functionality that's currently enabled includes a way to register your datasets, export any annotations for use in separate steps as well as start the annotator daemon process. (Label Studio requires a server to be running in order to use the web interface, and ZenML handles the provisioning of this server locally using the details you passed in when registering the component unless you've specified that you want to use a deployed instance.) #### Standard Steps ZenML offers some standard steps (and their associated config objects) which will get you up and running with the Label Studio integration quickly. These include: * `LabelStudioDatasetRegistrationConfig` - a step config object to be used when registering a dataset with Label studio using the `get_or_create_dataset` step * `LabelStudioDatasetSyncConfig` - a step config object to be used when registering a dataset with Label studio using the `sync_new_data_to_label_studio` step. Note that this requires a ZenML secret to have been pre-registered with your artifact store as being the one that holds authentication secrets specific to your particular cloud provider. (Label Studio provides some documentation on what permissions these secrets require [here](https://labelstud.io/guide/tasks.html).) * `get_or_create_dataset` step - This takes a `LabelStudioDatasetRegistrationConfig` config object which includes the name of the dataset. If it exists, this step will return the name, but if it doesn't exist then ZenML will register the dataset along with the appropriate label config with Label Studio. * `get_labeled_data` step - This step will get all labeled data available for a particular dataset. Note that these are output in a Label Studio annotation format, which will subsequently be converted into a format appropriate for your specific use case. * `sync_new_data_to_label_studio` step - This step is for ensuring that ZenML is handling the annotations and that the files being used are stored and synced with the ZenML cloud artifact store. This is an important step as part of a continuous annotation workflow since you want all the subsequent steps of your workflow to remain in sync with whatever new annotations are being made or have been created. #### Helper Functions Label Studio requires the use of what it calls 'label config' when you are creating/registering your dataset. These are strings containing HTML-like syntax that allow you to define a custom interface for your annotation. ZenML provides three helper functions that will construct these label config strings in the case of object detection, image classification, and OCR. See the[`integrations.label_studio.label_config_generators`](https://github.com/zenml-io/zenml/blob/main/src/zenml/integrations/label_studio/label_config_generators/label_config_generators.py) module for those three functions.
ZenML Scarf
--- ### Book/Component Guide/Annotators/Pigeon (docs/book/component-guide/annotators/pigeon.md) --- description: Annotating data using Pigeon. --- # Pigeon Pigeon is a lightweight, open-source annotation tool designed for quick and easy labeling of data directly within Jupyter notebooks. It provides a simple and intuitive interface for annotating various types of data, including: * Text Classification * Image Classification * Text Captioning ### When would you want to use it? If you need to label a small to medium-sized dataset as part of your ML workflow and prefer the convenience of doing it directly within your Jupyter notebook, Pigeon is a great choice. It is particularly useful for: * Quick labeling tasks that don't require a full-fledged annotation platform * Iterative labeling during the exploratory phase of your ML project * Collaborative labeling within a Jupyter notebook environment ### How to deploy it? To use the Pigeon annotator, you first need to install the ZenML Pigeon integration: ```shell zenml integration install pigeon ``` Next, register the Pigeon annotator with ZenML, specifying the output directory where the annotation files will be stored: ```shell zenml annotator register pigeon --flavor pigeon --output_dir="path/to/dir" ``` Note that the `output_dir` is relative to the repository or notebook root. Finally, add the Pigeon annotator to your stack and set it as the active stack: ```shell zenml stack update --annotator pigeon ``` Now you're ready to use the Pigeon annotator in your ML workflow! ### How do you use it? With the Pigeon annotator registered and added to your active stack, you can easily access it using the ZenML client within your Jupyter notebook. For text classification tasks, you can launch the Pigeon annotator as follows: ````python from zenml.client import Client annotator = Client().active_stack.annotator annotations = annotator.annotate( data=[ 'I love this movie', 'I was really disappointed by the book' ], options=[ 'positive', 'negative' ] ) ```` For image classification tasks, you can provide a custom display function to render the images: ````python from zenml.client import Client from IPython.display import display, Image annotator = Client().active_stack.annotator annotations = annotator.annotate( data=[ '/path/to/image1.png', '/path/to/image2.png' ], options=[ 'cat', 'dog' ], display_fn=lambda filename: display(Image(filename)) ) ```` The `launch` method returns the annotations as a list of tuples, where each tuple contains the data item and its corresponding label. You can also use the `zenml annotator dataset` commands to manage your datasets: * `zenml annotator dataset list` - List all available datasets * `zenml annotator dataset delete ` - Delete a specific dataset * `zenml annotator dataset stats ` - Get statistics for a specific dataset Annotation files are saved as JSON files in the specified output directory. Each annotation file represents a dataset, with the filename serving as the dataset name. ## Acknowledgements Pigeon was created by [Anastasis Germanidis](https://github.com/agermanidis) and released as a [Python package](https://pypi.org/project/pigeon-jupyter/) and [Github repository](https://github.com/agermanidis/pigeon). It is licensed under the Apache License. It has been updated to work with more recent `ipywidgets` versions and some small UI improvements were added. We are grateful to Anastasis for creating this tool and making it available to the community.
ZenML Scarf
--- ### Book/Component Guide/Annotators/Prodigy (docs/book/component-guide/annotators/prodigy.md) --- description: Annotating data using Prodigy. --- # Prodigy [Prodigy](https://prodi.gy/) is a modern annotation tool for creating training and evaluation data for machine learning models. You can also use Prodigy to help you inspect and clean your data, do error analysis and develop rule-based systems to use in combination with your statistical models. {% hint style="info" %} Prodigy is a paid annotation tool. You will need a Prodigy is a paid tool. A license is required to download and use it with ZenML. {% endhint %} The Prodigy Python library includes a range of pre-built workflows and command-line commands for various tasks, and well-documented components for implementing your own workflow scripts. Your scripts can specify how the data is loaded and saved, change which questions are asked in the annotation interface, and can even define custom HTML and JavaScript to change the behavior of the front-end. The web application is optimized for fast, intuitive and efficient annotation. ### When would you want to use it? If you need to label data as part of your ML workflow, that is the point at which you could consider adding the optional annotator stack component as part of your ZenML stack. ### How to deploy it? The Prodigy Annotator flavor is provided by the Prodigy ZenML integration. You need to install it to be able to register it as an Annotator and add it to your stack: ```shell zenml integration export-requirements --output-file prodigy-requirements.txt prodigy ``` Note that you'll need to install Prodigy separately since it requires a license. Please [visit the Prodigy docs](https://prodi.gy/docs/install) for information on how to install it. Currently Prodigy also requires the `urllib3<2` dependency, so make sure to install that. Then register your annotator with ZenML: ```shell zenml annotator register prodigy --flavor prodigy # optionally also pass in --custom_config_path="" ``` See https://prodi.gy/docs/install#config for more on custom Prodigy config files. Passing a `custom_config_path` allows you to override the default Prodigy config. Finally, add all these components to a stack and set it as your active stack. For example: ```shell zenml stack copy default annotation zenml stack update annotation -an prodigy zenml stack set annotation # optionally also zenml stack describe ``` Now if you run a simple CLI command like `zenml annotator dataset list` this should work without any errors. You're ready to use your annotator in your ML workflow! ### How do you use it? With Prodigy, there is no need to specially start the annotator ahead of time like with [Label Studio](label-studio.md). Instead, just use Prodigy as per the [Prodigy docs](https://prodi.gy) and then you can use the ZenML wrapper / API to get your labeled data etc using our Python methods. ZenML supports access to your data and annotations via the `zenml annotator ...` CLI command. You can access information about the datasets you're using with the `zenml annotator dataset list`. To work on annotation for a particular dataset, you can run `zenml annotator dataset annotate `. This is the equivalent of running `prodigy ` in the terminal. For example, you might run: ```shell zenml annotator dataset annotate your_dataset --command="textcat.manual news_topics ./news_headlines.jsonl --label Technology,Politics,Economy,Entertainment" ``` This would launch the Prodigy interface for [the `textcat.manual` recipe](https://prodi.gy/docs/recipes#textcat-manual) with the `news_topics` dataset and the labels `Technology`, `Politics`, `Economy`, and `Entertainment`. The data would be loaded from the `news_headlines.jsonl` file. A common workflow for Prodigy is to annotate data as you would usually do, and then use the connection into ZenML to import those annotations within a step in your pipeline (if running locally). For example, within a ZenML step: ```python from typing import List, Dict, Any from zenml import step from zenml.client import Client @step def import_annotations() -> List[Dict[str, Any]: zenml_client = Client() annotations = zenml_client.active_stack.annotator.get_labeled_data(dataset_name="my_dataset") # Do something with the annotations return annotations ``` If you're running in a cloud environment, you can manually export the annotations, store them somewhere in a cloud environment and then reference or use those within ZenML. The precise way you do this will be very case-dependent, however, so it's difficult to provide a one-size-fits-all solution. #### Prodigy Annotator Stack Component Our Prodigy annotator component inherits from the `BaseAnnotator` class. There are some methods that are core methods that must be defined, like being able to register or get a dataset. Most annotators handle things like the storage of state and have their own custom features, so there are quite a few extra methods specific to Prodigy. The core Prodigy functionality that's currently enabled from within the `annotator` stack component interface includes a way to register your datasets and export any annotations for use in separate steps.
ZenML Scarf
--- ### Book/Component Guide/Artifact Stores/README (docs/book/component-guide/artifact-stores/README.md) --- description: Setting up a persistent storage for your artifacts. icon: folder-closed --- # Artifact Stores The Artifact Store is a central component in any MLOps stack. As the name suggests, it acts as a data persistence layer where artifacts (e.g. datasets, models) ingested or generated by the machine learning pipelines are stored. ZenML automatically serializes and saves the data circulated through your pipelines in the Artifact Store: datasets, models, data profiles, data and model validation reports, and generally any object that is returned by a pipeline step. This is coupled with tracking in ZenML to provide extremely useful features such as caching and provenance/lineage tracking and pipeline reproducibility. {% hint style="info" %} Not all objects returned by pipeline steps are physically stored in the Artifact Store, nor do they have to be. How artifacts are serialized and deserialized and where their contents are stored are determined by the particular implementation of the [Materializer](https://docs.zenml.io/how-to/data-artifact-management/handle-data-artifacts/handle-custom-data-types) associated with the artifact data type. The majority of Materializers shipped with ZenML use the Artifact Store which is part of the active Stack as the location where artifacts are kept. If you need to store _a particular type of pipeline artifact_ in a different medium (e.g. use an external model registry to store model artifacts, or an external data lake or data warehouse to store dataset artifacts), you can write your own [Materializer](https://docs.zenml.io/how-to/data-artifact-management/handle-data-artifacts/handle-custom-data-types) to implement the custom logic required for it. In contrast, if you need to use an entirely different storage backend to store artifacts, one that isn't already covered by one of the ZenML integrations, you can [extend the Artifact Store abstraction](custom.md) to provide your own Artifact Store implementation. {% endhint %} In addition to pipeline artifacts, the Artifact Store may also be used as storage backed by other specialized stack components that need to store their data in the form of persistent object storage. The [Great Expectations Data Validator](https://docs.zenml.io/stacks/data-validators/great-expectations) is such an example. Related concepts: * the Artifact Store is a type of Stack Component that needs to be registered as part of your ZenML [Stack](https://docs.zenml.io/user-guides/production-guide/understand-stacks). * the objects circulated through your pipelines are serialized and stored in the Artifact Store using [Materializer](https://docs.zenml.io/how-to/data-artifact-management/handle-data-artifacts/handle-custom-data-types). Materializers implement the logic required to serialize and deserialize the artifact contents and to store them and retrieve their contents to/from the Artifact Store. ### When to use it The Artifact Store is a mandatory component in the ZenML stack. It is used to store all artifacts produced by pipeline runs, and you are required to configure it in all of your stacks. #### Artifact Store Flavors Out of the box, ZenML comes with a `local` artifact store already part of the default stack that stores artifacts on your local filesystem. Additional Artifact Stores are provided by integrations: | Artifact Store | Flavor | Integration | URI Schema(s) | Notes | | ---------------------------------- | -------- | ----------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------- | | [Local](local.md) | `local` | _built-in_ | None | This is the default Artifact Store. It stores artifacts on your local filesystem. Should be used only for running ZenML locally. | | [Amazon S3](s3.md) | `s3` | `s3` | `s3://` | Uses AWS S3 as an object store backend | | [Backblaze B2](b2.md) | `b2` | `b2` | `s3://` | Uses Backblaze B2 through its S3-compatible API | | [Google Cloud Storage](gcp.md) | `gcp` | `gcp` | `gs://` | Uses Google Cloud Storage as an object store backend | | [Azure](azure.md) | `azure` | `azure` | `abfs://`, `az://` | Uses Azure Blob Storage as an object store backend | | [Alibaba Cloud OSS](alibaba-oss.md) | `s3` | `s3` | `s3://` | Uses S3 integration to connect to Alibaba Cloud OSS | | [MinIO](minio.md) | `s3` | `s3` | `s3://` | Uses S3 integration to connect to self-hosted MinIO | | [Custom Implementation](custom.md) | _custom_ | | _custom_ | Extend the Artifact Store abstraction and provide your own implementation | If you would like to see the available flavors of Artifact Stores, you can use the command: ```shell zenml artifact-store flavor list ``` {% hint style="info" %} Every Artifact Store has a `path` attribute that must be configured when it is registered with ZenML. This is a URI pointing to the root path where all objects are stored in the Artifact Store. It must use a URI schema that is supported by the Artifact Store flavor. For example, the S3 Artifact Store will need a URI that contains the `s3://` schema: ```shell zenml artifact-store register s3_store -f s3 --path s3://my_bucket ``` {% endhint %} ### How to use it The Artifact Store provides low-level object storage services for other ZenML mechanisms. When you develop ZenML pipelines, you normally don't even have to be aware of its existence or interact with it directly. ZenML provides higher-level APIs that can be used as an alternative to store and access artifacts: * return one or more objects from your pipeline steps to have them automatically saved in the active Artifact Store as pipeline artifacts. * [retrieve pipeline artifacts](https://docs.zenml.io/how-to/data-artifact-management/handle-data-artifacts/load-artifacts-into-memory) from the active Artifact Store after a pipeline run is complete. You will probably need to interact with the [low-level Artifact Store API](./#the-artifact-store-api) directly: * if you implement custom [Materializers](https://docs.zenml.io/how-to/data-artifact-management/handle-data-artifacts/handle-custom-data-types) for your artifact data types * if you want to store custom objects in the Artifact Store #### The Artifact Store API All ZenML Artifact Stores implement [the same IO API](custom.md) that resembles a standard file system. This allows you to access and manipulate the objects stored in the Artifact Store in the same manner you would normally handle files on your computer and independently of the particular type of Artifact Store that is configured in your ZenML stack. Accessing the low-level Artifact Store API can be done through the following Python modules: * `zenml.io.fileio` provides low-level utilities for manipulating Artifact Store objects (e.g. `open`, `copy`, `rename` , `remove`, `mkdir`). These functions work seamlessly across Artifact Stores types. They have the same signature as the [Artifact Store abstraction methods](https://sdkdocs.zenml.io/latest/core_code_docs/core-artifact_stores.html#zenml.artifact_stores.base_artifact_store) ( in fact, they are one and the same under the hood). * [zenml.utils.io\_utils](https://sdkdocs.zenml.io/latest/core_code_docs/core-utils.html#zenml.utils.io_utils) includes some higher-level helper utilities that make it easier to find and transfer objects between the Artifact Store and the local filesystem or memory. {% hint style="info" %} When calling the Artifact Store API, you should always use URIs that are relative to the Artifact Store root path, otherwise, you risk using an unsupported protocol or storing objects outside the store. You can use the `Repository` singleton to retrieve the root path of the active Artifact Store and then use it as a base path for artifact URIs, e.g.: ```python import os from zenml.client import Client from zenml.io import fileio root_path = Client().active_stack.artifact_store.path artifact_contents = "example artifact" artifact_path = os.path.join(root_path, "artifacts", "examples") artifact_uri = os.path.join(artifact_path, "test.txt") fileio.makedirs(artifact_path) with fileio.open(artifact_uri, "w") as f: f.write(artifact_contents) ``` When using the Artifact Store API to write custom Materializers, the base artifact URI path is already provided. See the documentation on [Materializers](https://docs.zenml.io/how-to/data-artifact-management/handle-data-artifacts/handle-custom-data-types) for an example. {% endhint %} The following are some code examples showing how to use the Artifact Store API for various operations: * creating folders, writing and reading data directly to/from an artifact store object ```python import os from zenml.utils import io_utils from zenml.io import fileio from zenml.client import Client root_path = Client().active_stack.artifact_store.path artifact_contents = "example artifact" artifact_path = os.path.join(root_path, "artifacts", "examples") artifact_uri = os.path.join(artifact_path, "test.txt") fileio.makedirs(artifact_path) io_utils.write_file_contents_as_string(artifact_uri, artifact_contents) ``` ```python import os from zenml.utils import io_utils from zenml.client import Client root_path = Client().active_stack.artifact_store.path artifact_path = os.path.join(root_path, "artifacts", "examples") artifact_uri = os.path.join(artifact_path, "test.txt") artifact_contents = io_utils.read_file_contents_as_string(artifact_uri) ``` * using a temporary local file/folder to serialize and copy in-memory objects to/from the artifact store (heavily used in Materializers to transfer information between the Artifact Store and external libraries that don't support writing/reading directly to/from the artifact store backend): ```python import os import tempfile import external_lib from zenml.client import Client from zenml.io import fileio root_path = Client().active_stack.artifact_store.path artifact_path = os.path.join(root_path, "artifacts", "examples") artifact_uri = os.path.join(artifact_path, "test.json") fileio.makedirs(artifact_path) with tempfile.NamedTemporaryFile( mode="w", suffix=".json", delete=True ) as f: external_lib.external_object.save_to_file(f.name) # Copy it into artifact store fileio.copy(f.name, artifact_uri) ``` ```python import os import tempfile import external_lib from zenml.client import Client from zenml.io import fileio root_path = Client().active_stack.artifact_store.path artifact_path = os.path.join(root_path, "artifacts", "examples") artifact_uri = os.path.join(artifact_path, "test.json") with tempfile.NamedTemporaryFile( mode="w", suffix=".json", delete=True ) as f: # Copy the serialized object from the artifact store fileio.copy(artifact_uri, f.name) external_lib.external_object.load_from_file(f.name) ```
ZenML Scarf
--- ### Book/Component Guide/Artifact Stores/Alibaba Oss (docs/book/component-guide/artifact-stores/alibaba-oss.md) --- description: Storing artifacts in Alibaba Cloud Object Storage Service (OSS). --- # Alibaba Cloud OSS [Alibaba Cloud Object Storage Service (OSS)](https://www.alibabacloud.com/product/object-storage-service) is an S3-compatible object storage service. Since OSS provides an S3-compatible API, you can use ZenML's S3 Artifact Store integration to connect to [Alibaba Cloud](https://www.alibabacloud.com) OSS. {% hint style="warning" %} **Important:** When using Alibaba Cloud OSS, you must set the following `config_kwargs`: ```json {"signature_version": "s3", "s3": {"addressing_style": "virtual"}} ``` This is required for proper compatibility with Alibaba Cloud OSS's S3 API implementation. {% endhint %} ### When would you want to use it? You should use the Alibaba Cloud OSS Artifact Store when: * Your infrastructure is already deployed on Alibaba Cloud and you want to maintain data locality * You require artifact storage in specific geographic regions served by Alibaba Cloud (China, Asia-Pacific, Europe, Middle East) * You need S3-compatible object storage with Alibaba Cloud's pricing model and service level agreements * Compliance requirements mandate data residency in Alibaba Cloud regions ### How do you deploy it? Since Alibaba Cloud OSS is S3-compatible, you'll use the S3 integration. First, install it: ```shell zenml integration install s3 -y ``` You'll also need to create an OSS bucket and obtain your access credentials from the Alibaba Cloud console. ### How do you configure it? To use Alibaba Cloud OSS with ZenML, you need to configure the S3 Artifact Store with specific settings for OSS compatibility: {% hint style="info" %} Alibaba Cloud OSS does not support ZenML Service Connectors. Use ZenML Secrets to securely store and reference your Alibaba Cloud credentials. {% endhint %} {% tabs %} {% tab title="Using a ZenML Secret (recommended)" %} First, create a ZenML secret with your Alibaba Cloud credentials: ```shell zenml secret create alibaba_secret \ --access_key_id='' \ --secret_access_key='' ``` Then register the artifact store with the required OSS configuration: ```shell zenml artifact-store register alibaba_store -f s3 \ --path='s3://your-bucket-name' \ --authentication_secret=alibaba_secret \ --client_kwargs='{"endpoint_url": "https://oss-.aliyuncs.com"}' \ --config_kwargs='{"signature_version": "s3", "s3": {"addressing_style": "virtual"}}' ``` {% endtab %} {% endtabs %} Replace `` with your OSS region (e.g., `eu-central-1`, `cn-hangzhou`, `ap-southeast-1`). You can find the list of available regions and their endpoints in the [Alibaba Cloud OSS documentation](https://www.alibabacloud.com/help/en/oss/user-guide/regions-and-endpoints). Finally, add the artifact store to your stack: ```shell zenml stack register custom_stack -a alibaba_store ... --set ``` ### How do you use it? Using the Alibaba Cloud OSS Artifact Store is no different from [using any other flavor of Artifact Store](./#how-to-use-it). ZenML handles the S3-compatible API translation automatically. For more details on the S3 Artifact Store configuration options, refer to the [S3 Artifact Store documentation](s3.md).
ZenML Scarf
--- ### Book/Component Guide/Artifact Stores/Azure (docs/book/component-guide/artifact-stores/azure.md) --- description: Storing artifacts using Azure Blob Storage --- # Azure Blob Storage The Azure Artifact Store is an [Artifact Store](./) flavor provided with the Azure ZenML integration that uses [the Azure Blob Storage managed object storage service](https://azure.microsoft.com/en-us/services/storage/blobs/) to store ZenML artifacts in an Azure Blob Storage container. ### When would you want to use it? Running ZenML pipelines with [the local Artifact Store](local.md) is usually sufficient if you just want to evaluate ZenML or get started quickly without incurring the trouble and the cost of employing cloud storage services in your stack. However, the local Artifact Store becomes insufficient or unsuitable if you have more elaborate needs for your project: * if you want to share your pipeline run results with other team members or stakeholders inside or outside your organization * if you have other components in your stack that are running remotely (e.g. a Kubeflow or Kubernetes Orchestrator running in a public cloud). * if you outgrow what your local machine can offer in terms of storage space and need to use some form of private or public storage service that is shared with others * if you are running pipelines at scale and need an Artifact Store that can handle the demands of production-grade MLOps In all these cases, you need an Artifact Store that is backed by a form of public cloud or self-hosted shared object storage service. You should use the Azure Artifact Store when you decide to keep your ZenML artifacts in a shared object storage and if you have access to the Azure Blob Storage managed service. You should consider one of the other [Artifact Store flavors](./#artifact-store-flavors) if you don't have access to the Azure Blob Storage service. ### How do you deploy it? {% hint style="info" %} Would you like to skip ahead and deploy a full ZenML cloud stack already, including an Azure Artifact Store? Check out the[in-browser stack deployment wizard](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/deploy-a-cloud-stack), the [stack registration wizard](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/register-a-cloud-stack), or [the ZenML Azure Terraform module](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/deploy-a-cloud-stack-with-terraform) for a shortcut on how to deploy & register this stack component. {% endhint %} The Azure Artifact Store flavor is provided by the Azure ZenML integration, you need to install it on your local machine to be able to register an Azure Artifact Store and add it to your stack: ```shell zenml integration install azure -y ``` The only configuration parameter mandatory for registering an Azure Artifact Store is the root path URI, which needs to point to an Azure Blog Storage container and take the form `az://container-name` or `abfs://container-name`. Please read [the Azure Blob Storage documentation](https://docs.microsoft.com/en-us/azure/storage/blobs/storage-quickstart-blobs-portal) on how to configure an Azure Blob Storage container. With the URI to your Azure Blob Storage container known, registering an Azure Artifact Store can be done as follows: ```shell # Register the Azure artifact store zenml artifact-store register az_store -f azure --path=az://container-name # Register and set a stack with the new artifact store zenml stack register custom_stack -a az_store ... --set ``` Depending on your use case, however, you may also need to provide additional configuration parameters pertaining to [authentication](azure.md#authentication-methods) to match your deployment scenario. #### Authentication Methods Integrating and using an Azure Artifact Store in your pipelines is not possible without employing some form of authentication. If you're looking for a quick way to get started locally, you can use the _Implicit Authentication_ method. However, the recommended way to authenticate to the Azure cloud platform is through [an Azure Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/azure-service-connector). This is particularly useful if you are configuring ZenML stacks that combine the Azure Artifact Store with other remote stack components also running in Azure. You will need the following information to configure Azure credentials for ZenML, depending on which type of Azure credentials you want to use: * an Azure connection string * an Azure account key * the client ID, client secret and tenant ID of the Azure service principal For more information on how to retrieve information about your Azure Storage Account and Access Key or connection string, please refer to this [Azure guide](https://docs.microsoft.com/en-us/azure/storage/blobs/storage-quickstart-blobs-python?tabs=environment-variable-windows#copy-your-credentials-from-the-azure-portal). For information on how to configure an Azure service principal, please consult the [Azure documentation](https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal). {% tabs %} {% tab title="Implicit Authentication" %} This method uses the implicit Azure authentication available _in the environment where the ZenML code is running_. On your local machine, this is the quickest way to configure an Azure Artifact Store. You don't need to supply credentials explicitly when you register the Azure Artifact Store, instead, you have to set one of the following sets of environment variables: * to use [an Azure storage account key](https://learn.microsoft.com/en-us/azure/storage/common/storage-account-keys-manage) , set `AZURE_STORAGE_ACCOUNT_NAME` to your account name and one of `AZURE_STORAGE_ACCOUNT_KEY` or `AZURE_STORAGE_SAS_TOKEN` to the Azure key value. * to use [an Azure storage account key connection string](https://learn.microsoft.com/en-us/azure/storage/common/storage-account-keys-manage) , set `AZURE_STORAGE_CONNECTION_STRING` to your Azure Storage Key connection string * to use [Azure Service Principal credentials](https://learn.microsoft.com/en-us/azure/active-directory/develop/app-objects-and-service-principals) , [create an Azure Service Principal](https://learn.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal) and then set `AZURE_STORAGE_ACCOUNT_NAME` to your account name and `AZURE_STORAGE_CLIENT_ID` , `AZURE_STORAGE_CLIENT_SECRET` and `AZURE_STORAGE_TENANT_ID` to the client ID, secret and tenant ID of your service principal {% hint style="warning" %} Certain dashboard functionality, such as visualizing or deleting artifacts, is not available when using an implicitly authenticated artifact store together with a deployed ZenML server because the ZenML server will not have permission to access the filesystem. The implicit authentication method also needs to be coordinated with other stack components that are highly dependent on the Artifact Store and need to interact with it directly to the function. If these components are not running on your machine, they do not have access to the local environment variables and will encounter authentication failures while trying to access the Azure Artifact Store: * [Orchestrators](https://docs.zenml.io/stacks/orchestrators/) need to access the Artifact Store to manage pipeline artifacts * [Step Operators](https://docs.zenml.io/stacks/step-operators/) need to access the Artifact Store to manage step-level artifacts * [Model Deployers](https://docs.zenml.io/stacks/model-deployers/) need to access the Artifact Store to load served models To enable these use cases, it is recommended to use [an Azure Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/azure-service-connector) to link your Azure Artifact Store to the remote Azure Blob storage container. {% endhint %} {% endtab %} {% tab title="Azure Service Connector (recommended)" %} To set up the Azure Artifact Store to authenticate to Azure and access an Azure Blob storage container, it is recommended to leverage the many features provided by [the Azure Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/azure-service-connector) such as auto-configuration, best security practices regarding long-lived credentials and reusing the same credentials across multiple stack components. If you don't already have an Azure Service Connector configured in your ZenML deployment, you can register one using the interactive CLI command. You have the option to configure an Azure Service Connector that can be used to access more than one Azure blob storage container or even more than one type of Azure resource: ```sh zenml service-connector register --type azure -i ``` A non-interactive CLI example that uses [Azure Service Principal credentials](https://learn.microsoft.com/en-us/azure/active-directory/develop/app-objects-and-service-principals) to configure an Azure Service Connector targeting a single Azure Blob storage container is: ```sh zenml service-connector register --type azure --auth-method service-principal --tenant_id= --client_id= --client_secret= --resource-type blob-container --resource-id ``` {% code title="Example Command Output" %} ``` $ zenml service-connector register azure-blob-demo --type azure --auth-method service-principal --tenant_id=a79f3633-8f45-4a74-a42e-68871c17b7fb --client_id=8926254a-8c3f-430a-a2fd-bdab234d491e --client_secret=AzureSuperSecret --resource-type blob-container --resource-id az://demo-zenmlartifactstore Successfully registered service connector `azure-blob-demo` with access to the following resources: ┏━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃ RESOURCE TYPE │ RESOURCE NAMES ┃ ┠───────────────────┼──────────────────────────────┨ ┃ 📦 blob-container │ az://demo-zenmlartifactstore ┃ ┗━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ``` {% endcode %} > **Note**: Please remember to grant the Azure service principal permissions to read and write to your Azure Blob storage container as well as to list accessible storage accounts and Blob containers. For a full list of permissions required to use an AWS Service Connector to access one or more S3 buckets, please refer to the [Azure Service Connector Blob storage container resource type documentation](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/azure-service-connector#azure-blob-storage-container) or read the documentation available in the interactive CLI commands and dashboard. The Azure Service Connector supports [many different authentication methods](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/azure-service-connector#authentication-methods) with different levels of security and convenience. You should pick the one that best fits your use-case. If you already have one or more Azure Service Connectors configured in your ZenML deployment, you can check which of them can be used to access the Azure Blob storage container you want to use for your Azure Artifact Store by running e.g.: ```sh zenml service-connector list-resources --resource-type blob-container ``` {% code title="Example Command Output" %} ``` The following 'blob-container' resources can be accessed by service connectors: ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃ CONNECTOR ID │ CONNECTOR NAME │ CONNECTOR TYPE │ RESOURCE TYPE │ RESOURCE NAMES ┃ ┠──────────────────────────────────────┼─────────────────────────┼────────────────┼───────────────────┼──────────────────────────────┨ ┃ 273d2812-2643-4446-82e6-6098b8ccdaa4 │ azure-service-principal │ 🇦 azure │ 📦 blob-container │ az://demo-zenmlartifactstore ┃ ┠──────────────────────────────────────┼─────────────────────────┼────────────────┼───────────────────┼──────────────────────────────┨ ┃ f6b329e1-00f7-4392-94c9-264119e672d0 │ azure-blob-demo │ 🇦 azure │ 📦 blob-container │ az://demo-zenmlartifactstore ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ``` {% endcode %} After having set up or decided on an Azure Service Connector to use to connect to the target Azure Blob storage container, you can register the Azure Artifact Store as follows: ```sh # Register the Azure artifact-store and reference the target blob storage container zenml artifact-store register -f azure \ --path='az://your-container' # Connect the Azure artifact-store to the target container via an Azure Service Connector zenml artifact-store connect -i ``` A non-interactive version that connects the Azure Artifact Store to a target blob storage container through an Azure Service Connector: ```sh zenml artifact-store connect --connector ``` {% code title="Example Command Output" %} ``` $ zenml artifact-store connect azure-blob-demo --connector azure-blob-demo Successfully connected artifact store `azure-blob-demo` to the following resources: ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃ CONNECTOR ID │ CONNECTOR NAME │ CONNECTOR TYPE │ RESOURCE TYPE │ RESOURCE NAMES ┃ ┠──────────────────────────────────────┼─────────────────┼────────────────┼───────────────────┼──────────────────────────────┨ ┃ f6b329e1-00f7-4392-94c9-264119e672d0 │ azure-blob-demo │ 🇦 azure │ 📦 blob-container │ az://demo-zenmlartifactstore ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ``` {% endcode %} As a final step, you can use the Azure Artifact Store in a ZenML Stack: ```sh # Register and set a stack with the new artifact store zenml stack register -a ... --set ``` {% endtab %} {% tab title="ZenML Secret" %} When you register the Azure Artifact Store, you can create a [ZenML Secret](https://docs.zenml.io/getting-started/deploying-zenml/secret-management) to store a variety of Azure credentials and then reference it in the Artifact Store configuration: * to use [an Azure storage account key](https://learn.microsoft.com/en-us/azure/storage/common/storage-account-keys-manage) , set `account_name` to your account name and one of `account_key` or `sas_token` to the Azure key or SAS token value as attributes in the ZenML secret * to use [an Azure storage account key connection string](https://learn.microsoft.com/en-us/azure/storage/common/storage-account-keys-manage) , configure the `connection_string` attribute in the ZenML secret to your Azure Storage Key connection string * to use [Azure Service Principal credentials](https://learn.microsoft.com/en-us/azure/active-directory/develop/app-objects-and-service-principals) , [create an Azure Service Principal](https://learn.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal) and then set `account_name` to your account name and `client_id`, `client_secret` and `tenant_id` to the client ID, secret and tenant ID of your service principal in the ZenML secret This method has some advantages over the implicit authentication method: * you don't need to install and configure the Azure CLI on your host * you don't need to care about enabling your other stack components (orchestrators, step operators and model deployers) to have access to the artifact store through Azure Managed Identities * you can combine the Azure artifact store with other stack components that are not running in Azure Configuring Azure credentials in a ZenML secret and then referencing them in the Artifact Store configuration could look like this: ```shell # Store the Azure storage account key in a ZenML secret zenml secret create az_secret \ --account_name='' \ --account_key='' # or if you want to use a connection string zenml secret create az_secret \ --connection_string='' # or if you want to use Azure ServicePrincipal credentials zenml secret create az_secret \ --account_name='' \ --tenant_id='' \ --client_id='' \ --client_secret='' # Alternatively for providing key-value pairs, you can utilize the '--values' option by specifying a file path containing # key-value pairs in either JSON or YAML format. # File content example: {"account_name":"",...} zenml secret create az_secret \ --values=@path/to/file.txt # Register the Azure artifact store and reference the ZenML secret zenml artifact-store register az_store -f azure \ --path='az://your-container' \ --authentication_secret=az_secret # Register and set a stack with the new artifact store zenml stack register custom_stack -a az_store ... --set ``` {% endtab %} {% endtabs %} For more, up-to-date information on the Azure Artifact Store implementation and its configuration, you can have a look at [the SDK docs](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-azure.html#zenml.integrations.azure) . ### How do you use it? Aside from the fact that the artifacts are stored in Azure Blob Storage, using the Azure Artifact Store is no different from [using any other flavor of Artifact Store](./#how-to-use-it).
ZenML Scarf
--- ### Book/Component Guide/Artifact Stores/B2 (docs/book/component-guide/artifact-stores/b2.md) --- description: Storing artifacts in a Backblaze B2 cloud storage bucket. --- # Backblaze B2 The Backblaze B2 Artifact Store is an [Artifact Store](./) flavor provided with the B2 ZenML integration that uses [Backblaze B2 Cloud Storage](https://www.backblaze.com/cloud-storage) to store ZenML artifacts. Backblaze B2 exposes an [S3-compatible API](https://www.backblaze.com/docs/cloud-storage-s3-compatible-api), so this flavor reuses ZenML's S3 implementation under the hood while adding B2-friendly runtime fallbacks for the endpoint URL and credential environment variables. ### When would you want to use it? Running ZenML pipelines with [the local Artifact Store](local.md) is usually sufficient if you just want to evaluate ZenML or get started quickly without incurring the trouble and the cost of employing cloud storage services in your stack. However, the local Artifact Store becomes insufficient or unsuitable if you have more elaborate needs for your project: * if you want to share your pipeline run results with other team members or stakeholders inside or outside your organization * if you have other components in your stack that are running remotely (e.g. a Kubeflow or Kubernetes Orchestrator running in a public cloud). * if you outgrow what your local machine can offer in terms of storage space and need to use some form of private or public storage service that is shared with others * if you are running pipelines at scale and need an Artifact Store that can handle the demands of production-grade MLOps You should use the Backblaze B2 Artifact Store when you want to keep your ZenML artifacts in shared object storage and you prefer Backblaze B2's pricing, egress terms, or geographic regions over the hyperscalers. Because the integration speaks B2's S3-compatible API, you get the same workflow as the [S3 Artifact Store](s3.md) without having to manage any additional B2-native tooling. You should consider one of the other [Artifact Store flavors](./#artifact-store-flavors) if you don't have a Backblaze B2 account or if you require an S3 service that is co-located with other AWS resources in your stack. ### How do you deploy it? The Backblaze B2 Artifact Store flavor is provided by the B2 ZenML integration, you need to install it on your local machine to be able to register a B2 Artifact Store and add it to your stack: ```shell zenml integration install b2 -y ``` You will also need a Backblaze B2 account and a private bucket to hold your ZenML artifacts. The high-level setup steps in the [Backblaze console](https://secure.backblaze.com/) are: 1. **Create a B2 bucket.** From the Backblaze web console, navigate to **B2 Cloud Storage > Buckets** and click **Create a Bucket**. Pick a globally-unique bucket name (this becomes the `s3://` path used by ZenML), choose **Private**, and select the region you want to store data in. The region segment of the bucket's S3 endpoint (e.g. `us-west-004`, `eu-central-003`) is needed in the next step. 2. **Generate a B2 application key.** Navigate to **Account > Application Keys** and click **Add a New Application Key**. Scope the key to the bucket you just created and grant at minimum **Read and Write** access. Backblaze will display a `keyID` and an `applicationKey` exactly once — copy both values immediately. 3. **Export the credentials as environment variables.** ZenML's B2 artifact store reads Backblaze's standard environment variable names at runtime if no key/secret is configured directly on the artifact store: ```shell export B2_APPLICATION_KEY_ID= export B2_APPLICATION_KEY= ``` This convention matches the [Backblaze CLI and SDKs](https://www.backblaze.com/docs/cloud-storage-application-keys), so the same environment that authenticates `b2-cli` will authenticate ZenML. For full Backblaze documentation on bucket creation and application keys, see the [B2 Cloud Storage docs](https://www.backblaze.com/docs/cloud-storage). ### How do you use it? The only configuration parameter mandatory for registering a B2 Artifact Store is the root path URI, which needs to point to a B2 bucket using the `s3://` scheme — this is intentional, because the B2 flavor talks to Backblaze through its S3-compatible API: ```shell # Register the B2 artifact store (uses the s3:// URI scheme since B2 speaks the S3 API) zenml artifact-store register my_b2 --flavor=b2 --path=s3://my-bucket/zenml-artifacts # Register and set a stack that uses the new artifact store zenml stack register custom_stack -a my_b2 ... --set ``` If `B2_APPLICATION_KEY_ID` and `B2_APPLICATION_KEY` are exported in the shell that runs ZenML, no further authentication configuration is required. The artifact store client will pick them up when it is created without saving them as component configuration. To keep credentials out of the shell environment, store them in a [ZenML Secret](https://docs.zenml.io/getting-started/deploying-zenml/secret-management) and reference them on registration: ```shell # Store the B2 application key in a ZenML secret zenml secret create b2_secret \ --access_key_id='' \ --secret_access_key='' # Register the B2 artifact store and reference the secret zenml artifact-store register my_b2 --flavor=b2 \ --path=s3://my-bucket/zenml-artifacts \ --authentication_secret=b2_secret ``` #### Endpoint defaults and regions Configure the endpoint that matches your B2 bucket region at registration time: ```shell zenml artifact-store register my_b2 --flavor=b2 \ --path=s3://my-bucket/zenml-artifacts \ --client_kwargs='{"endpoint_url": "https://s3.eu-central-003.backblazeb2.com"}' ``` Use the S3 endpoint shown for your bucket in the Backblaze console. It always has the form `https://s3..backblazeb2.com`. If you omit `client_kwargs.endpoint_url`, the runtime client falls back to `https://s3.us-west-004.backblazeb2.com` without saving that fallback on the component configuration. #### Advanced configuration Because the B2 flavor subclasses the S3 flavor, every advanced option supported by the [S3 Artifact Store](s3.md#advanced-configuration) — `client_kwargs`, `config_kwargs`, and `s3_additional_kwargs` — is available here too and is passed transparently to the underlying [s3fs](https://s3fs.readthedocs.io/en/latest/#s3-compatible-storage) / [botocore](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/core/session.html) stack. Use these to configure things like server-side encryption, custom retry behavior, or alternative B2 endpoints. Aside from the fact that the artifacts are stored in Backblaze B2, using the B2 Artifact Store is no different from [using any other flavor of Artifact Store](./#how-to-use-it).
ZenML Scarf
--- ### Book/Component Guide/Artifact Stores/Custom (docs/book/component-guide/artifact-stores/custom.md) --- description: Learning how to develop a custom artifact store. --- # Develop a custom artifact store {% hint style="info" %} Before diving into the specifics of this component type, it is beneficial to familiarize yourself with our [general guide to writing custom component flavors in ZenML](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/implement-a-custom-stack-component). This guide provides an essential understanding of ZenML's component flavor concepts. {% endhint %} ZenML comes equipped with [Artifact Store implementations](./#artifact-store-flavors) that you can use to store artifacts on a local filesystem or in the managed AWS, GCP, or Azure cloud object storage services. However, if you need to use a different type of object storage service as a backend for your ZenML Artifact Store, you can extend ZenML to provide your own custom Artifact Store implementation. ### Base Abstraction The Artifact Store establishes one of the main components in every ZenML stack. Now, let us take a deeper dive into the fundamentals behind its abstraction, namely [the `BaseArtifactStore` class](https://sdkdocs.zenml.io/latest/core_code_docs/core-artifact_stores.html#zenml.artifact_stores.base_artifact_store): 1. As ZenML only supports filesystem-based artifact stores, it features a configuration parameter called `path`, which will indicate the root path of the artifact store. When registering an artifact store, users will have to define this parameter. 2. Moreover, there is another variable in the config class called `SUPPORTED_SCHEMES`. This is a class variable that needs to be defined in every subclass of the base artifact store configuration. It indicates the supported file path schemes for the corresponding implementation. For instance, for the Azure artifact store, this set will be defined as `{"abfs://", "az://"}`. 3. Lastly, the base class features a set of `abstractmethod`s: `open`, `copyfile`,`exists`,`glob`,`isdir`,`listdir` ,`makedirs`,`mkdir`,`remove`, `rename`,`rmtree`,`stat`,`walk`. In the implementation of every `ArtifactStore` flavor, it is required to define these methods with respect to the flavor at hand. Putting all these considerations together, we end up with the following implementation: ``` /* Detailed source-code truncated for AI context efficiency. */ ``` {% hint style="info" %} This is a slimmed-down version of the base implementation which aims to highlight the abstraction layer. In order to see the full implementation and get the complete docstrings, please check the [SDK docs](https://sdkdocs.zenml.io/latest/core_code_docs/core-artifact_stores.html#zenml.artifact_stores.base_artifact_store) . {% endhint %} **The effect on the `zenml.io.fileio`** If you created an instance of an artifact store, added it to your stack, and activated the stack, it will create a filesystem each time you run a ZenML pipeline and make it available to the `zenml.io.fileio` module. This means that when you utilize a method such as `fileio.open(...)` with a file path that starts with one of the `SUPPORTED_SCHEMES` within your steps or materializers, it will be able to use the `open(...)` method that you defined within your artifact store. ### Build your own custom artifact store If you want to implement your own custom Artifact Store, you can follow the following steps: 1. Create a class that inherits from [the `BaseArtifactStore` class](https://sdkdocs.zenml.io/latest/core_code_docs/core-artifact_stores.html#zenml.artifact_stores.base_artifact_store) and implements the abstract methods. 2. Create a class that inherits from [the `BaseArtifactStoreConfig` class](custom.md) and fill in the `SUPPORTED_SCHEMES` based on your file system. 3. Bring both of these classes together by inheriting from [the `BaseArtifactStoreFlavor` class](custom.md). Once you are done with the implementation, you can register it through the CLI. Please ensure you **point to the flavor class via dot notation**: ```shell zenml artifact-store flavor register ``` For example, if your flavor class `MyArtifactStoreFlavor` is defined in `flavors/my_flavor.py`, you'd register it by doing: ```shell zenml artifact-store flavor register flavors.my_flavor.MyArtifactStoreFlavor ``` {% hint style="warning" %} ZenML resolves the flavor class by taking the path where you initialized zenml (via `zenml init`) as the starting point of resolution. Therefore, please ensure you follow [the best practice](https://docs.zenml.io/user-guides/best-practices/iac) of initializing zenml at the root of your repository. If ZenML does not find an initialized ZenML repository in any parent directory, it will default to the current working directory, but usually, it's better to not have to rely on this mechanism and initialize zenml at the root. {% endhint %} Afterward, you should see the new custom artifact store flavor in the list of available artifact store flavors: ```shell zenml artifact-store flavor list ``` {% hint style="warning" %} It is important to draw attention to when and how these base abstractions are coming into play in a ZenML workflow. * The **CustomArtifactStoreFlavor** class is imported and utilized upon the creation of the custom flavor through the CLI. * The **CustomArtifactStoreConfig** class is imported when someone tries to register/update a stack component with this custom flavor. Especially, during the registration process of the stack component, the config will be used to validate the values given by the user. As `Config` objects are inherently `pydantic` objects, you can also add your own custom validators here. * The **CustomArtifactStore** only comes into play when the component is ultimately in use. The design behind this interaction lets us separate the configuration of the flavor from its implementation. This way we can register flavors and components even when the major dependencies behind their implementation are not installed in our local setting (assuming the `CustomArtifactStoreFlavor` and the `CustomArtifactStoreConfig` are implemented in a different module/path than the actual `CustomArtifactStore`). {% endhint %} #### Enabling Artifact Visualizations with Custom Artifact Stores ZenML automatically saves visualizations for many common data types and allows you to view these visualizations in the ZenML dashboard. Under the hood, this works by saving the visualizations together with the artifacts in the artifact store. In order to load and display these visualizations, ZenML needs to be able to load and access the corresponding artifact store. This means that your custom artifact store needs to be configured in a way that allows authenticating to the back-end without relying on the local environment, e.g., by embedding the authentication credentials in the stack component configuration or by referencing a secret. Furthermore, for deployed ZenML instances, you need to install the package dependencies of your artifact store implementation in the environment where you have deployed ZenML. See the [Documentation on deploying ZenML with custom Docker images](https://docs.zenml.io/getting-started/deploying-zenml/deploy-with-custom-image) for more information on how to do that.
ZenML Scarf
--- ### Book/Component Guide/Artifact Stores/Digitalocean (docs/book/component-guide/artifact-stores/digitalocean.md) --- description: Storing artifacts in a DigitalOcean Spaces bucket. --- # DigitalOcean Spaces The DigitalOcean Spaces Artifact Store is an [Artifact Store](./) flavor provided with the DigitalOcean ZenML integration that uses [DigitalOcean Spaces](https://www.digitalocean.com/products/spaces) to store ZenML artifacts. Spaces exposes an [S3-compatible API](https://docs.digitalocean.com/products/spaces/reference/s3-compatibility/), so this flavor reuses ZenML's S3 implementation under the hood and only adds a `region` setting from which the Spaces endpoint URL is derived at runtime. ### When would you want to use it? Running ZenML pipelines with [the local Artifact Store](local.md) is usually sufficient if you just want to evaluate ZenML or get started quickly. However, the local Artifact Store becomes insufficient once you want to share pipeline results with teammates, run other stack components remotely (e.g. an orchestrator on DigitalOcean Kubernetes), or run production-grade pipelines at scale. You should use the DigitalOcean Spaces Artifact Store when your infrastructure runs on DigitalOcean and you want your ZenML artifacts co-located with it — for example next to a DOKS cluster running the [Kubernetes orchestrator](https://docs.zenml.io/stacks/orchestrators/kubernetes). Because the integration speaks the S3-compatible Spaces API, you get the same workflow as the [S3 Artifact Store](s3.md) without managing any additional tooling. Consider one of the other [Artifact Store flavors](./#artifact-store-flavors) if you don't use DigitalOcean. ### How do you deploy it? The DigitalOcean Spaces Artifact Store flavor is provided by the DigitalOcean ZenML integration. Install it on your local machine to be able to register the artifact store and add it to your stack: ```shell zenml integration install digitalocean -y ``` You will also need a Space (bucket) and a set of Spaces access keys: 1. **Create a Space.** In the [DigitalOcean control panel](https://cloud.digitalocean.com/), go to **Spaces Object Storage** and create a bucket. Note the bucket name (this becomes the `s3://` path used by ZenML) and the region slug shown in the bucket's endpoint (e.g. `nyc3`, `ams3`, `fra1`). 2. **Generate Spaces access keys.** Go to **Storage > Spaces Object Storage > Access Keys** and generate a new key pair. Spaces keys are separate credentials from your DigitalOcean API token; copy both the access key and the secret when they are displayed. ### How do you use it? Register the artifact store with the bucket path (using the `s3://` scheme, since Spaces speaks the S3 API) and the region slug: ```shell zenml artifact-store register do_spaces \ --flavor=digitalocean \ --path=s3://my-space \ --region=fra1 # Register and set a stack that uses the new artifact store zenml stack register do_stack -a do_spaces ... --set ``` The Spaces endpoint URL (`https://.digitaloceanspaces.com`) is derived from the `region` at runtime and is not persisted in the component configuration. If you need to address a different endpoint (for example a custom domain), pass it explicitly instead — an explicit endpoint always takes precedence over the region-derived one: ```shell zenml artifact-store register do_spaces \ --flavor=digitalocean \ --path=s3://my-space \ --client_kwargs='{"endpoint_url": "https://fra1.digitaloceanspaces.com"}' ``` To authenticate, store the Spaces access keys in a [ZenML Secret](https://docs.zenml.io/getting-started/deploying-zenml/secret-management) and reference them on registration: ```shell zenml secret create spaces_secret \ --access_key_id='' \ --secret_access_key='' zenml artifact-store register do_spaces \ --flavor=digitalocean \ --path=s3://my-space \ --region=fra1 \ --authentication_secret=spaces_secret ``` All other configuration options of the [S3 Artifact Store](s3.md) (`client_kwargs`, `config_kwargs`, `s3_additional_kwargs`) are available on this flavor as well and are passed through to the underlying S3-compatible filesystem. --- ### Book/Component Guide/Artifact Stores/Gcp (docs/book/component-guide/artifact-stores/gcp.md) --- description: Storing artifacts using GCP Cloud Storage. --- # Google Cloud Storage (GCS) The GCS Artifact Store is an [Artifact Store](./) flavor provided with the GCP ZenML integration that uses [the Google Cloud Storage managed object storage service](https://cloud.google.com/storage/docs/introduction) to store ZenML artifacts in a GCP Cloud Storage bucket. ### When would you want to use it? Running ZenML pipelines with [the local Artifact Store](local.md) is usually sufficient if you just want to evaluate ZenML or get started quickly without incurring the trouble and the cost of employing cloud storage services in your stack. However, the local Artifact Store becomes insufficient or unsuitable if you have more elaborate needs for your project: * if you want to share your pipeline run results with other team members or stakeholders inside or outside your organization * if you have other components in your stack that are running remotely (e.g. a Kubeflow or Kubernetes Orchestrator running in a public cloud). * if you outgrow what your local machine can offer in terms of storage space and need to use some form of private or public storage service that is shared with others * if you are running pipelines at scale and need an Artifact Store that can handle the demands of production-grade MLOps In all these cases, you need an Artifact Store that is backed by a form of public cloud or self-hosted shared object storage service. You should use the GCS Artifact Store when you decide to keep your ZenML artifacts in a shared object storage and if you have access to the Google Cloud Storage managed service. You should consider one of the other [Artifact Store flavors](./#artifact-store-flavors) if you don't have access to the GCP Cloud Storage service. ### How do you deploy it? {% hint style="info" %} Would you like to skip ahead and deploy a full ZenML cloud stack already, including a GCS Artifact Store? Check out the[in-browser stack deployment wizard](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/deploy-a-cloud-stack), the [stack registration wizard](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/register-a-cloud-stack), or [the ZenML GCP Terraform module](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/deploy-a-cloud-stack-with-terraform) for a shortcut on how to deploy & register this stack component. {% endhint %} The GCS Artifact Store flavor is provided by the GCP ZenML integration, you need to install it on your local machine to be able to register a GCS Artifact Store and add it to your stack: ```shell zenml integration install gcp -y ``` The only configuration parameter mandatory for registering a GCS Artifact Store is the root path URI, which needs to point to a GCS bucket and take the form `gs://bucket-name`. Please read [the Google Cloud Storage documentation](https://cloud.google.com/storage/docs/creating-buckets) on how to configure a GCS bucket. With the URI to your GCS bucket known, registering a GCS Artifact Store can be done as follows: ```shell # Register the GCS artifact store zenml artifact-store register gs_store -f gcp --path=gs://bucket-name # Register and set a stack with the new artifact store zenml stack register custom_stack -a gs_store ... --set ``` Depending on your use case, however, you may also need to provide additional configuration parameters pertaining to [authentication](gcp.md#authentication-methods) to match your deployment scenario. #### Authentication Methods Integrating and using a GCS Artifact Store in your pipelines is not possible without employing some form of authentication. If you're looking for a quick way to get started locally, you can use the _Implicit Authentication_ method. However, the recommended way to authenticate to the GCP cloud platform is through [a GCP Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/gcp-service-connector). This is particularly useful if you are configuring ZenML stacks that combine the GCS Artifact Store with other remote stack components also running in GCP. {% tabs %} {% tab title="Implicit Authentication" %} This method uses the implicit GCP authentication available _in the environment where the ZenML code is running_. On your local machine, this is the quickest way to configure a GCS Artifact Store. You don't need to supply credentials explicitly when you register the GCS Artifact Store, as it leverages the local credentials and configuration that the Google Cloud CLI stores on your local machine. However, you will need to install and set up the Google Cloud CLI on your machine as a prerequisite, as covered in [the Google Cloud documentation](https://cloud.google.com/sdk/docs/install-sdk) , before you register the GCS Artifact Store. {% hint style="warning" %} Certain dashboard functionality, such as visualizing or deleting artifacts, is not available when using an implicitly authenticated artifact store together with a deployed ZenML server because the ZenML server will not have permission to access the filesystem. The implicit authentication method also needs to be coordinated with other stack components that are highly dependent on the Artifact Store and need to interact with it directly to the function. If these components are not running on your machine, they do not have access to the local Google Cloud CLI configuration and will encounter authentication failures while trying to access the GCS Artifact Store: * [Orchestrators](https://docs.zenml.io/stacks/orchestrators/) need to access the Artifact Store to manage pipeline artifacts * [Step Operators](https://docs.zenml.io/stacks/step-operators/) need to access the Artifact Store to manage step-level artifacts * [Model Deployers](https://docs.zenml.io/stacks/model-deployers/) need to access the Artifact Store to load served models To enable these use cases, it is recommended to use [a GCP Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/gcp-service-connector) to link your GCS Artifact Store to the remote GCS bucket. {% endhint %} {% endtab %} {% tab title="GCP Service Connector (recommended)" %} To set up the GCS Artifact Store to authenticate to GCP and access a GCS bucket, it is recommended to leverage the many features provided by [the GCP Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/gcp-service-connector) such as auto-configuration, best security practices regarding long-lived credentials and reusing the same credentials across multiple stack components. If you don't already have a GCP Service Connector configured in your ZenML deployment, you can register one using the interactive CLI command. You have the option to configure a GCP Service Connector that can be used to access more than one GCS bucket or even more than one type of GCP resource: ```sh zenml service-connector register --type gcp -i ``` A non-interactive CLI example that leverages [the Google Cloud CLI configuration](https://cloud.google.com/sdk/docs/install-sdk) on your local machine to auto-configure a GCP Service Connector targeting a single GCS bucket is: ```sh zenml service-connector register --type gcp --resource-type gcs-bucket --resource-name --auto-configure ``` {% code title="Example Command Output" %} ``` $ zenml service-connector register gcs-zenml-bucket-sl --type gcp --resource-type gcs-bucket --resource-id gs://zenml-bucket-sl --auto-configure ⠸ Registering service connector 'gcs-zenml-bucket-sl'... Successfully registered service connector `gcs-zenml-bucket-sl` with access to the following resources: ┏━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━┓ ┃ RESOURCE TYPE │ RESOURCE NAMES ┃ ┠───────────────┼──────────────────────┨ ┃ 📦 gcs-bucket │ gs://zenml-bucket-sl ┃ ┗━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━┛ ``` {% endcode %} > **Note**: Please remember to grant the entity associated with your GCP credentials permissions to read and write to your GCS bucket as well as to list accessible GCS buckets. For a full list of permissions required to use a GCP Service Connector to access one or more GCS buckets, please refer to the [GCP Service Connector GCS bucket resource type documentation](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/gcp-service-connector#gcs-bucket) or read the documentation available in the interactive CLI commands and dashboard. The GCP Service Connector supports [many different authentication methods](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/gcp-service-connector#authentication-methods) with different levels of security and convenience. You should pick the one that best fits your use case. If you already have one or more GCP Service Connectors configured in your ZenML deployment, you can check which of them can be used to access the GCS bucket you want to use for your GCS Artifact Store by running e.g.: ```sh zenml service-connector list-resources --resource-type gcs-bucket ``` {% code title="Example Command Output" %} ``` /* Detailed source-code truncated for AI context efficiency. */ ``` {% endcode %} After having set up or decided on a GCP Service Connector to use to connect to the target GCS bucket, you can register the GCS Artifact Store as follows: ```sh # Register the GCS artifact-store and reference the target GCS bucket zenml artifact-store register -f gcp \ --path='gs://your-bucket' # Connect the GCS artifact-store to the target bucket via a GCP Service Connector zenml artifact-store connect -i ``` A non-interactive version that connects the GCS Artifact Store to a target GCP Service Connector: ```sh zenml artifact-store connect --connector ``` {% code title="Example Command Output" %} ``` $ zenml artifact-store connect gcs-zenml-bucket-sl --connector gcs-zenml-bucket-sl Successfully connected artifact store `gcs-zenml-bucket-sl` to the following resources: ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━┓ ┃ CONNECTOR ID │ CONNECTOR NAME │ CONNECTOR TYPE │ RESOURCE TYPE │ RESOURCE NAMES ┃ ┠──────────────────────────────────────┼─────────────────────┼────────────────┼───────────────┼──────────────────────┨ ┃ 2a0bec1b-9787-4bd7-8d4a-9a47b6f61643 │ gcs-zenml-bucket-sl │ 🔵 gcp │ 📦 gcs-bucket │ gs://zenml-bucket-sl ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━┛ ``` {% endcode %} As a final step, you can use the GCS Artifact Store in a ZenML Stack: ```sh # Register and set a stack with the new artifact store zenml stack register -a ... --set ``` {% endtab %} {% tab title="GCP Credentials" %} When you register the GCS Artifact Store, you can [generate a GCP Service Account Key](https://cloud.google.com/docs/authentication/application-default-credentials#attached-sa), store it in a [ZenML Secret](https://docs.zenml.io/getting-started/deploying-zenml/secret-management) and then reference it in the Artifact Store configuration. This method has some advantages over the implicit authentication method: * you don't need to install and configure the GCP CLI on your host * you don't need to care about enabling your other stack components (orchestrators, step operators and model deployers) to have access to the artifact store through GCP Service Accounts and Workload Identity * you can combine the GCS artifact store with other stack components that are not running in GCP For this method, you need to [create a user-managed GCP service account](https://cloud.google.com/iam/docs/service-accounts-create), grant it minimal privileges to read and write to your GCS bucket, and then [create a service account key](https://cloud.google.com/iam/docs/keys-create-delete#creating). {% hint style="info" %} **Security Best Practice:** Instead of using the broad `Storage Object Admin` role, create a custom role with only the specific permissions needed: - `storage.buckets.get` - `storage.buckets.list` - `storage.objects.create` - `storage.objects.delete` - `storage.objects.get` - `storage.objects.list` - `storage.objects.update` Alternatively, you can use the `Storage Object Admin` role scoped to specific buckets rather than project-wide access. {% endhint %} With the service account key downloaded to a local file, you can register a ZenML secret and reference it in the GCS Artifact Store configuration as follows: ```shell # Store the GCP credentials in a ZenML zenml secret create gcp_secret \ --token=@path/to/service_account_key.json # Register the GCS artifact store and reference the ZenML secret zenml artifact-store register gcs_store -f gcp \ --path='gs://your-bucket' \ --authentication_secret=gcp_secret # Register and set a stack with the new artifact store zenml stack register custom_stack -a gs_store ... --set ``` {% endtab %} {% endtabs %} For more, up-to-date information on the GCS Artifact Store implementation and its configuration, you can have a look at [the SDK docs](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-gcp.html#zenml.integrations.gcp) . ### How do you use it? Aside from the fact that the artifacts are stored in GCP Cloud Storage, using the GCS Artifact Store is no different from [using any other flavor of Artifact Store](./#how-to-use-it).
ZenML Scarf
--- ### Book/Component Guide/Artifact Stores/Local (docs/book/component-guide/artifact-stores/local.md) --- description: Storing artifacts on your local filesystem. --- # Local Artifact Store The local Artifact Store is a built-in ZenML [Artifact Store](./) flavor that uses a folder on your local filesystem to store artifacts. ### When would you want to use it? The local Artifact Store is a great way to get started with ZenML, as it doesn't require you to provision additional local resources or to interact with managed object-store services like Amazon S3 and Google Cloud Storage. All you need is the local filesystem. You should use the local Artifact Store if you're just evaluating or getting started with ZenML, or if you are still in the experimental phase and don't need to share your pipeline artifacts (dataset, models, etc.) with others. {% hint style="warning" %} The local Artifact Store is not meant to be utilized in production. The local filesystem cannot be shared across your team and the artifacts stored in it cannot be accessed from other machines. This also means that [artifact visualizations](https://docs.zenml.io/how-to/data-artifact-management/visualize-artifacts/) will not be available when using a local Artifact Store through a [ZenML instance deployed in the cloud](https://docs.zenml.io/getting-started/deploying-zenml/). Furthermore, the local Artifact Store doesn't cover services like high-availability, scalability, backup and restore and other features that are expected from a production grade MLOps system. The fact that it stores artifacts on your local filesystem also means that not all stack components can be used in the same stack as a local Artifact Store: * only [Orchestrators](https://docs.zenml.io/stacks/orchestrators/) running on the local machine, such as the [local Orchestrator](https://docs.zenml.io/stacks/orchestrators/local), a [local Kubeflow Orchestrator](https://docs.zenml.io/stacks/orchestrators/kubeflow), or a [local Kubernetes Orchestrator](https://docs.zenml.io/stacks/orchestrators/kubernetes) can be combined with a local Artifact Store * only [Model Deployers](https://docs.zenml.io/stacks/model-deployers/) that are running locally, such as the [MLflow Model Deployer](https://docs.zenml.io/stacks/model-deployers/mlflow), can be used in combination with a local Artifact Store * [Step Operators](https://docs.zenml.io/stacks/step-operators/): none of the Step Operators can be used in the same stack as a local Artifact Store, given that their very purpose is to run ZenML steps in remote specialized environments As you transition to a team setting or a production setting, you can replace the local Artifact Store in your stack with one of the other flavors that are better suited for these purposes, with no changes required in your code. {% endhint %} ### How do you deploy it? The `default` stack that comes pre-configured with ZenML already contains a local Artifact Store: ``` /* Detailed source-code truncated for AI context efficiency. */ ``` As shown by the `PATH` value in the `zenml artifact-store describe` output, the artifacts are stored inside a folder on your local filesystem. You can create additional instances of local Artifact Stores and use them in your stacks as you see fit, e.g.: ```shell # Register the local artifact store zenml artifact-store register custom_local --flavor local # Register and set a stack with the new artifact store zenml stack register custom_stack -o default -a custom_local --set ``` {% hint style="warning" %} Same as all other Artifact Store flavors, the local Artifact Store does take in a `path` configuration parameter that can be set during registration to point to a custom path on your machine. However, it is highly recommended that you rely on the default `path` value, otherwise, it may lead to unexpected results. Other local stack components depend on the convention used for the default path to be able to access the local Artifact Store. {% endhint %} For more, up-to-date information on the local Artifact Store implementation and its configuration, you can have a look at [the SDK docs](https://sdkdocs.zenml.io/latest/core_code_docs/core-artifact_stores.html#zenml.artifact_stores.local_artifact_store) . ### How do you use it? Aside from the fact that the artifacts are stored locally, using the local Artifact Store is no different from [using any other flavor of Artifact Store](./#how-to-use-it).
ZenML Scarf
--- ### Book/Component Guide/Artifact Stores/Minio (docs/book/component-guide/artifact-stores/minio.md) --- description: Storing artifacts in MinIO object storage. --- # MinIO [MinIO](https://min.io/) is a high-performance, S3-compatible object storage system. Since MinIO provides a fully S3-compatible API, you can use ZenML's S3 Artifact Store integration to connect to MinIO. {% hint style="warning" %} **Maintenance Mode**: The open-source MinIO project is currently in maintenance mode and is not accepting new changes. Only critical security fixes may be evaluated on a case-by-case basis. For development and testing purposes, MinIO remains a viable option, but for production use cases requiring active support, consider [MinIO AIStor](https://min.io/product/aistor) or alternative S3-compatible storage solutions like [Ceph RGW](https://ceph.io/en/discover/technology/#object). {% endhint %} ### When would you want to use it? You should use the MinIO Artifact Store when: * You require self-hosted object storage for data sovereignty or compliance requirements * Your MLOps infrastructure runs on-premises or in a private cloud environment * You need S3-compatible storage co-located with your Kubernetes-based ZenML deployment * You want to eliminate cloud vendor dependencies while maintaining S3 API compatibility * You're developing locally and need a lightweight S3-compatible storage backend for testing ### How do you deploy it? Since MinIO is S3-compatible, you'll use the S3 integration. First, install it: ```shell zenml integration install s3 -y ``` You'll also need a running MinIO instance. MinIO can be deployed in various ways: * **Docker**: `docker run -p 9000:9000 -p 9001:9001 minio/minio server /data --console-address ":9001"` * **Kubernetes**: Follow the instructions [here](https://docs.min.io/enterprise/aistor-object-store/installation/kubernetes/install/deploy-aistor-on-kubernetes/) * **Binary**: Download from [MinIO's website](https://min.io/download) ### How do you configure it? To use MinIO with ZenML, configure the S3 Artifact Store with your MinIO endpoint: {% tabs %} {% tab title="Using a ZenML Secret (recommended)" %} First, create a ZenML secret with your MinIO credentials: ```shell zenml secret create minio_secret \ --access_key_id='' \ --secret_access_key='' ``` Then register the artifact store: ```shell zenml artifact-store register minio_store -f s3 \ --path='s3://your-bucket-name' \ --authentication_secret=minio_secret \ --client_kwargs='{"endpoint_url": "http://minio.example.com:9000"}' ``` {% endtab %} {% endtabs %} Replace `http://minio.example.com:9000` with your actual MinIO endpoint. If you're running MinIO locally for development, this might be `http://localhost:9000`. {% hint style="info" %} If your MinIO instance uses HTTPS with a self-signed certificate, you may need to configure SSL verification. Consult the [S3 Artifact Store documentation](s3.md#advanced-configuration) for advanced configuration options. {% endhint %} Finally, add the artifact store to your stack: ```shell zenml stack register custom_stack -a minio_store ... --set ``` ### How do you use it? Using the MinIO Artifact Store is no different from [using any other flavor of Artifact Store](./#how-to-use-it). ZenML handles the S3-compatible API translation automatically. For more details on the S3 Artifact Store configuration options, refer to the [S3 Artifact Store documentation](s3.md).
ZenML Scarf
--- ### Book/Component Guide/Artifact Stores/S3 (docs/book/component-guide/artifact-stores/s3.md) --- description: Storing artifacts in an AWS S3 bucket. --- # Amazon Simple Cloud Storage (S3) The S3 Artifact Store is an [Artifact Store](./) flavor provided with the S3 ZenML integration that uses [the AWS S3 managed object storage service](https://aws.amazon.com/s3/) or one of the self-hosted S3 alternatives, such as [MinIO](https://min.io/) or [Ceph RGW](https://ceph.io/en/discover/technology/#object), to store artifacts in an S3 compatible object storage backend. ### When would you want to use it? Running ZenML pipelines with [the local Artifact Store](local.md) is usually sufficient if you just want to evaluate ZenML or get started quickly without incurring the trouble and the cost of employing cloud storage services in your stack. However, the local Artifact Store becomes insufficient or unsuitable if you have more elaborate needs for your project: * if you want to share your pipeline run results with other team members or stakeholders inside or outside your organization * if you have other components in your stack that are running remotely (e.g. a Kubeflow or Kubernetes Orchestrator running in a public cloud). * if you outgrow what your local machine can offer in terms of storage space and need to use some form of private or public storage service that is shared with others * if you are running pipelines at scale and need an Artifact Store that can handle the demands of production-grade MLOps In all these cases, you need an Artifact Store that is backed by a form of public cloud or self-hosted shared object storage service. You should use the S3 Artifact Store when you decide to keep your ZenML artifacts in a shared object storage and if you have access to the AWS S3 managed service or one of the S3 compatible alternatives (e.g. Minio, Ceph RGW). You should consider one of the other [Artifact Store flavors](./#artifact-store-flavors) if you don't have access to an S3-compatible service. ### How do you deploy it? {% hint style="info" %} Would you like to skip ahead and deploy a full ZenML cloud stack already, including an S3 Artifact Store? Check out the[in-browser stack deployment wizard](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/deploy-a-cloud-stack), the [stack registration wizard](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/register-a-cloud-stack), or [the ZenML AWS Terraform module](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/deploy-a-cloud-stack-with-terraform) for a shortcut on how to deploy & register this stack component. {% endhint %} The S3 Artifact Store flavor is provided by the S3 ZenML integration, you need to install it on your local machine to be able to register an S3 Artifact Store and add it to your stack: ```shell zenml integration install s3 -y ``` The only configuration parameter mandatory for registering an S3 Artifact Store is the root path URI, which needs to point to an S3 bucket and take the form `s3://bucket-name`. Please read the documentation relevant to the S3 service that you are using on how to create an S3 bucket. For example, the AWS S3 documentation is available [here](https://docs.aws.amazon.com/AmazonS3/latest/userguide/create-bucket-overview.html). With the URI to your S3 bucket known, registering an S3 Artifact Store and using it in a stack can be done as follows: ```shell # Register the S3 artifact-store zenml artifact-store register s3_store -f s3 --path=s3://bucket-name # Register and set a stack with the new artifact store zenml stack register custom_stack -a s3_store ... --set ``` Depending on your use case, however, you may also need to provide additional configuration parameters pertaining to [authentication](s3.md#authentication-methods) or [pass advanced configuration parameters](s3.md#advanced-configuration) to match your S3-compatible service or deployment scenario. #### Authentication Methods Integrating and using an S3-compatible Artifact Store in your pipelines is not possible without employing some form of authentication. If you're looking for a quick way to get started locally, you can use the _Implicit Authentication_ method. However, the recommended way to authenticate to the AWS cloud platform is through [an AWS Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/aws-service-connector). This is particularly useful if you are configuring ZenML stacks that combine the S3 Artifact Store with other remote stack components also running in AWS. {% tabs %} {% tab title="Implicit Authentication" %} This method uses the implicit AWS authentication available _in the environment where the ZenML code is running_. On your local machine, this is the quickest way to configure an S3 Artifact Store. You don't need to supply credentials explicitly when you register the S3 Artifact Store, as it leverages the local credentials and configuration that the AWS CLI stores on your local machine. However, you will need to install and set up the AWS CLI on your machine as a prerequisite, as covered in [the AWS CLI documentation](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html), before you register the S3 Artifact Store. {% hint style="warning" %} Certain dashboard functionality, such as visualizing or deleting artifacts, is not available when using an implicitly authenticated artifact store together with a deployed ZenML server because the ZenML server will not have permission to access the filesystem. The implicit authentication method also needs to be coordinated with other stack components that are highly dependent on the Artifact Store and need to interact with it directly to work. If these components are not running on your machine, they do not have access to the local AWS CLI configuration and will encounter authentication failures while trying to access the S3 Artifact Store: * [Orchestrators](https://docs.zenml.io/stacks/orchestrators/) need to access the Artifact Store to manage pipeline artifacts * [Step Operators](https://docs.zenml.io/stacks/step-operators/) need to access the Artifact Store to manage step-level artifacts * [Model Deployers](https://docs.zenml.io/stacks/model-deployers/) need to access the Artifact Store to load served models To enable these use-cases, it is recommended to use [an AWS Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/aws-service-connector) to link your S3 Artifact Store to the remote S3 bucket. {% endhint %} {% endtab %} {% tab title="AWS Service Connector (recommended)" %} To set up the S3 Artifact Store to authenticate to AWS and access an S3 bucket, it is recommended to leverage the many features provided by [the AWS Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/aws-service-connector) such as auto-configuration, best security practices regarding long-lived credentials and fine-grained access control and reusing the same credentials across multiple stack components. If you don't already have an AWS Service Connector configured in your ZenML deployment, you can register one using the interactive CLI command. You have the option to configure an AWS Service Connector that can be used to access more than one S3 bucket or even more than one type of AWS resource: ```sh zenml service-connector register --type aws -i ``` A non-interactive CLI example that leverages [the AWS CLI configuration](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) on your local machine to auto-configure an AWS Service Connector targeting a single S3 bucket is: ```sh zenml service-connector register --type aws --resource-type s3-bucket --resource-name --auto-configure ``` {% code title="Example Command Output" %} ``` $ zenml service-connector register s3-zenfiles --type aws --resource-type s3-bucket --resource-id s3://zenfiles --auto-configure ⠸ Registering service connector 's3-zenfiles'... Successfully registered service connector `s3-zenfiles` with access to the following resources: ┏━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┓ ┃ RESOURCE TYPE │ RESOURCE NAMES ┃ ┠───────────────┼────────────────┨ ┃ 📦 s3-bucket │ s3://zenfiles ┃ ┗━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┛ ``` {% endcode %} > **Note**: Please remember to grant the entity associated with your AWS credentials permissions to read and write to your S3 bucket as well as to list accessible S3 buckets. For a full list of permissions required to use an AWS Service Connector to access one or more S3 buckets, please refer to the [AWS Service Connector S3 bucket resource type documentation](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/aws-service-connector#s3-bucket) or read the documentation available in the interactive CLI commands and dashboard. The AWS Service Connector supports [many different authentication methods](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/aws-service-connector#authentication-methods) with different levels of security and convenience. You should pick the one that best fits your use case. If you already have one or more AWS Service Connectors configured in your ZenML deployment, you can check which of them can be used to access the S3 bucket you want to use for your S3 Artifact Store by running e.g.: ```sh zenml service-connector list-resources --resource-type s3-bucket ``` {% code title="Example Command Output" %} ``` /* Detailed source-code truncated for AI context efficiency. */ ``` {% endcode %} After having set up or decided on an AWS Service Connector to use to connect to the target S3 bucket, you can register the S3 Artifact Store as follows: ```sh # Register the S3 artifact-store and reference the target S3 bucket zenml artifact-store register -f s3 \ --path='s3://your-bucket' # Connect the S3 artifact-store to the target bucket via an AWS Service Connector zenml artifact-store connect -i ``` A non-interactive version that connects the S3 Artifact Store to a target S3 bucket through an AWS Service Connector: ```sh zenml artifact-store connect --connector ``` {% code title="Example Command Output" %} ``` $ zenml artifact-store connect s3-zenfiles --connector s3-zenfiles Successfully connected artifact store `s3-zenfiles` to the following resources: ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┓ ┃ CONNECTOR ID │ CONNECTOR NAME │ CONNECTOR TYPE │ RESOURCE TYPE │ RESOURCE NAMES ┃ ┠──────────────────────────────────────┼────────────────┼────────────────┼───────────────┼────────────────┨ ┃ c4ee3f0a-bc69-4c79-9a74-297b2dd47d50 │ s3-zenfiles │ 🔶 aws │ 📦 s3-bucket │ s3://zenfiles ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┛ ``` {% endcode %} As a final step, you can use the S3 Artifact Store in a ZenML Stack: ```sh # Register and set a stack with the new artifact store zenml stack register -a ... --set ``` {% endtab %} {% tab title="ZenML Secret" %} When you register the S3 Artifact Store, you can [generate an AWS access key](https://docs.aws.amazon.com/cli/latest/reference/iam/create-access-key.html), store it in a [ZenML Secret](https://docs.zenml.io/getting-started/deploying-zenml/secret-management) and then reference it in the Artifact Store configuration. This method has some advantages over the implicit authentication method: * you don't need to install and configure the AWS CLI on your host * you don't need to care about enabling your other stack components (orchestrators, step operators, and model deployers) to have access to the artifact store through IAM roles and policies * you can combine the S3 artifact store with other stack components that are not running in AWS > **Note**: When you create the IAM user for your AWS access key, please remember to grant the created IAM user permissions to read and write to your S3 bucket (i.e. at a minimum: `s3:PutObject`, `s3:GetObject`, `s3:ListBucket`, `s3:DeleteObject`, `s3:GetBucketVersioning`, `s3:ListBucketVersions`, `s3:DeleteObjectVersion`) After having set up the IAM user and generated the access key, as described in the [AWS documentation](https://docs.aws.amazon.com/cli/latest/reference/iam/create-access-key.html), you can register the S3 Artifact Store as follows: ```shell # Store the AWS access key in a ZenML secret zenml secret create s3_secret \ --access_key_id='' \ --secret_access_key='' # Register the S3 artifact-store and reference the ZenML secret zenml artifact-store register s3_store -f s3 \ --path='s3://your-bucket' \ --authentication_secret=s3_secret # Register and set a stack with the new artifact store zenml stack register custom_stack -a s3_store ... --set ``` {% endtab %} {% endtabs %} #### Advanced Configuration The S3 Artifact Store accepts a range of advanced configuration options that can be used to further customize how ZenML connects to the S3 storage service that you are using. These are accessible via the `client_kwargs`, `config_kwargs` and `s3_additional_kwargs` configuration attributes and are passed transparently to [the underlying S3Fs library](https://s3fs.readthedocs.io/en/latest/#s3-compatible-storage): * `client_kwargs`: arguments that will be transparently passed to [the botocore client](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/core/session.html#boto3.session.Session.client) . You can use it to configure parameters like `endpoint_url` and `region_name` when connecting to an S3-compatible endpoint (e.g. Minio). * `config_kwargs`: advanced parameters passed to [botocore.client.Config](https://botocore.amazonaws.com/v1/documentation/api/latest/reference/config.html). * `s3_additional_kwargs`: advanced parameters that are used when calling S3 API, typically used for things like `ServerSideEncryption` and `ACL`. To include these advanced parameters in your Artifact Store configuration, pass them using JSON format during registration, e.g.: ```shell zenml artifact-store register minio_store -f s3 \ --path='s3://minio_bucket' \ --authentication_secret=s3_secret \ --client_kwargs='{"endpoint_url": "http://minio.cluster.local:9000", "region_name": "us-east-1"}' ``` For more, up-to-date information on the S3 Artifact Store implementation and its configuration, you can have a look at [the SDK docs](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-s3.html#zenml.integrations.s3) . ### How do you use it? Aside from the fact that the artifacts are stored in an S3 compatible backend, using the S3 Artifact Store is no different than [using any other flavor of Artifact Store](./#how-to-use-it).
ZenML Scarf
--- ### Book/Component Guide/Container Registries/README (docs/book/component-guide/container-registries/README.md) --- description: Setting up a storage for Docker images. icon: box --- # Container Registries The container registry is an essential part of most remote MLOps stacks. It is used to store container images that are built to run machine learning pipelines in remote environments. Containerization of the pipeline code creates a portable environment that allows code to run in an isolated manner. ### When to use it The container registry is needed whenever other components of your stack need to push or pull container images. Currently, this is the case for most of ZenML's remote [orchestrators](https://docs.zenml.io/stacks/orchestrators/) , [step operators](https://docs.zenml.io/stacks/step-operators/), and some [model deployers](https://docs.zenml.io/stacks/model-deployers/). These containerize your pipeline code and therefore require a container registry to store the resulting [Docker](https://www.docker.com/) images. Take a look at the documentation page of the component you want to use in your stack to see if it requires a container registry or even a specific container registry flavor. ### Container Registry Flavors ZenML comes with a few container registry flavors that you can use: * Default flavor: Allows any URI without validation. Use this if you want to use a local container registry or when using a remote container registry that is not covered by other flavors. * Specific flavors: Validates your container registry URI and performs additional checks to ensure you're able to push to the registry. {% hint style="warning" %} We highly suggest using the specific container registry flavors in favor of the `default` one to make use of the additional URI validations. {% endhint %} | Container Registry | Flavor | Integration | URI example | | ------------------------------------------ | ----------- | ----------- | ----------------------------------------- | | [DefaultContainerRegistry](default.md) | `default` | _built-in_ | - | | [DockerHubContainerRegistry](dockerhub.md) | `dockerhub` | _built-in_ | docker.io/zenml | | [GCPContainerRegistry](gcp.md) | `gcp` | _built-in_ | gcr.io/zenml | | [AzureContainerRegistry](azure.md) | `azure` | _built-in_ | zenml.azurecr.io | | [GitHubContainerRegistry](github.md) | `github` | _built-in_ | ghcr.io/zenml | | [AWSContainerRegistry](aws.md) | `aws` | `aws` | 123456789.dkr.ecr.us-east-1.amazonaws.com | If you would like to see the available flavors of container registries, you can use the command: ```shell zenml container-registry flavor list ```
ZenML Scarf
--- ### Book/Component Guide/Container Registries/Aws (docs/book/component-guide/container-registries/aws.md) --- description: Storing container images in Amazon ECR. --- # Amazon Elastic Container Registry (ECR) The AWS container registry is a [container registry](./) flavor provided with the ZenML `aws` integration and uses [Amazon ECR](https://aws.amazon.com/ecr/) to store container images. ### When to use it You should use the AWS container registry if: * one or more components of your stack need to pull or push container images. * you have access to AWS ECR. If you're not using AWS, take a look at the other [container registry flavors](./#container-registry-flavors). ### How to deploy it {% hint style="info" %} Would you like to skip ahead and deploy a full ZenML cloud stack already, including an AWS ECR container registry? Check out the[in-browser stack deployment wizard](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/deploy-a-cloud-stack), the [stack registration wizard](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/register-a-cloud-stack), or [the ZenML AWS Terraform module](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/deploy-a-cloud-stack-with-terraform) for a shortcut on how to deploy & register this stack component. {% endhint %} The ECR registry is automatically activated once you create an AWS account. However, you'll need to create a `Repository` in order to push container images to it: * Go to the [ECR website](https://console.aws.amazon.com/ecr). * Make sure the correct region is selected on the top right. * Click on `Create repository`. * Create a private repository. The name of the repository depends on the [orchestrator](https://docs.zenml.io/stacks/orchestrators/) or [step operator](https://docs.zenml.io/stacks/step-operators/) you're using in your stack. ### URI format The AWS container registry URI should have the following format: ```shell .dkr.ecr..amazonaws.com # Examples: 123456789.dkr.ecr.eu-west-2.amazonaws.com 987654321.dkr.ecr.ap-south-1.amazonaws.com 135792468.dkr.ecr.af-south-1.amazonaws.com ``` To figure out the URI for your registry: * Go to the [AWS console](https://console.aws.amazon.com/) and click on your user account in the top right to see the `Account ID`. * Go [here](https://docs.aws.amazon.com/general/latest/gr/rande.html#regional-endpoints) and choose the region in which you would like to store your container images. Make sure to choose a nearby region for faster access. * Once you have both these values, fill in the values in this template `.dkr.ecr..amazonaws.com` to get your container registry URI. ### How to use it To use the AWS container registry, we need: * The ZenML `aws` integration installed. If you haven't done so, run ```shell zenml integration install aws ``` * [Docker](https://www.docker.com) installed and running. * The registry URI. Check out the [previous section](aws.md#how-to-deploy-it) on the URI format and how to get the URI for your registry. We can then register the container registry and use it in our active stack: ```shell zenml container-registry register \ --flavor=aws \ --uri= # Add the container registry to the active stack zenml stack update -c ``` You also need to set up [authentication](aws.md#authentication-methods) required to log in to the container registry. #### Authentication Methods Integrating and using an AWS Container Registry in your pipelines is not possible without employing some form of authentication. If you're looking for a quick way to get started locally, you can use the _Local Authentication_ method. However, the recommended way to authenticate to the AWS cloud platform is through [an AWS Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/aws-service-connector). This is particularly useful if you are configuring ZenML stacks that combine the AWS Container Registry with other remote stack components also running in AWS. {% tabs %} {% tab title="Local Authentication" %} This method uses the Docker client authentication available _in the environment where the ZenML code is running_. On your local machine, this is the quickest way to configure an AWS Container Registry. You don't need to supply credentials explicitly when you register the AWS Container Registry, as it leverages the local credentials and configuration that the AWS CLI and Docker client store on your local machine. However, you will need to install and set up the AWS CLI on your machine as a prerequisite, as covered in [the AWS CLI documentation](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html), before you register the AWS Container Registry. With the AWS CLI installed and set up with credentials, we'll need to log in to the container registry so Docker can pull and push images: ```shell # Fill your REGISTRY_URI and REGION in the placeholders in the following command. # You can find the REGION as part of your REGISTRY_URI: `.dkr.ecr..amazonaws.com` aws ecr get-login-password --region | docker login --username AWS --password-stdin ``` {% hint style="warning" %} Stacks using the AWS Container Registry set up with local authentication are not portable across environments. To make ZenML pipelines fully portable, it is recommended to use [an AWS Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/aws-service-connector) to link your AWS Container Registry to the remote ECR registry. {% endhint %} {% endtab %} {% tab title="AWS Service Connector (recommended)" %} To set up the AWS Container Registry to authenticate to AWS and access an ECR registry, it is recommended to leverage the many features provided by [the AWS Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/aws-service-connector) such as auto-configuration, local login, best security practices regarding long-lived credentials and fine-grained access control and reusing the same credentials across multiple stack components. If you don't already have an AWS Service Connector configured in your ZenML deployment, you can register one using the interactive CLI command. You have the option to configure an AWS Service Connector that can be used to access an ECR registry or even more than one type of AWS resource: ```sh zenml service-connector register --type aws -i ``` A non-interactive CLI example that leverages [the AWS CLI configuration](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) on your local machine to auto-configure an AWS Service Connector targeting an ECR registry is: ```sh zenml service-connector register --type aws --resource-type docker-registry --auto-configure ``` {% code title="Example Command Output" %} ``` $ zenml service-connector register aws-us-east-1 --type aws --resource-type docker-registry --auto-configure ⠸ Registering service connector 'aws-us-east-1'... Successfully registered service connector `aws-us-east-1` with access to the following resources: ┏━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃ RESOURCE TYPE │ RESOURCE NAMES ┃ ┠────────────────────┼──────────────────────────────────────────────┨ ┃ 🐳 docker-registry │ 715803424590.dkr.ecr.us-east-1.amazonaws.com ┃ ┗━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ``` {% endcode %} > **Note**: Please remember to grant the entity associated with your AWS credentials permissions to read and write to one or more ECR repositories as well as to list accessible ECR repositories. For a full list of permissions required to use an AWS Service Connector to access an ECR registry, please refer to the [AWS Service Connector ECR registry resource type documentation](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/aws-service-connector#ecr-container-registry) or read the documentation available in the interactive CLI commands and dashboard. The AWS Service Connector supports [many different authentication methods](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/aws-service-connector#authentication-methods) with different levels of security and convenience. You should pick the one that best fits your use case. If you already have one or more AWS Service Connectors configured in your ZenML deployment, you can check which of them can be used to access the ECR registry you want to use for your AWS Container Registry by running e.g.: ```sh zenml service-connector list-resources --connector-type aws --resource-type docker-registry ``` {% code title="Example Command Output" %} ``` The following 'docker-registry' resources can be accessed by service connectors that you have configured: ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃ CONNECTOR ID │ CONNECTOR NAME │ CONNECTOR TYPE │ RESOURCE TYPE │ RESOURCE NAMES ┃ ┠──────────────────────────────────────┼─────────────────────────┼────────────────┼────────────────────┼──────────────────────────────────────────────┨ ┃ 37c97fa0-fa47-4d55-9970-e2aa6e1b50cf │ aws-secret-key │ 🔶 aws │ 🐳 docker-registry │ 715803424590.dkr.ecr.us-east-1.amazonaws.com ┃ ┠──────────────────────────────────────┼─────────────────────────┼────────────────┼────────────────────┼──────────────────────────────────────────────┨ ┃ d400e0c6-a8e7-4b95-ab34-0359229c5d36 │ aws-us-east-1 │ 🔶 aws │ 🐳 docker-registry │ 715803424590.dkr.ecr.us-east-1.amazonaws.com ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ``` {% endcode %} After having set up or decided on an AWS Service Connector to use to connect to the target ECR registry, you can register the AWS Container Registry as follows: ```sh # Register the AWS container registry and reference the target ECR registry URI zenml container-registry register -f aws \ --uri= # Connect the AWS container registry to the target ECR registry via an AWS Service Connector zenml container-registry connect -i ``` A non-interactive version that connects the AWS Container Registry to a target ECR registry through an AWS Service Connector: ```sh zenml container-registry connect --connector ``` {% code title="Example Command Output" %} ``` $ zenml container-registry connect aws-us-east-1 --connector aws-us-east-1 Successfully connected container registry `aws-us-east-1` to the following resources: ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃ CONNECTOR ID │ CONNECTOR NAME │ CONNECTOR TYPE │ RESOURCE TYPE │ RESOURCE NAMES ┃ ┠──────────────────────────────────────┼────────────────┼────────────────┼────────────────────┼──────────────────────────────────────────────┨ ┃ d400e0c6-a8e7-4b95-ab34-0359229c5d36 │ aws-us-east-1 │ 🔶 aws │ 🐳 docker-registry │ 715803424590.dkr.ecr.us-east-1.amazonaws.com ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ``` {% endcode %} As a final step, you can use the AWS Container Registry in a ZenML Stack: ```sh # Register and set a stack with the new container registry zenml stack register -c ... --set ``` {% hint style="info" %} Linking the AWS Container Registry to a Service Connector means that your local Docker client is no longer authenticated to access the remote registry. If you need to manually interact with the remote registry via the Docker CLI, you can use the [local login Service Connector feature](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/service-connectors-guide#configure-local-clients) to temporarily authenticate your local Docker client to the remote registry: ```sh zenml service-connector login --resource-type docker-registry ``` {% code title="Example Command Output" %} ``` $ zenml service-connector login aws-us-east-1 --resource-type docker-registry ⠼ Attempting to configure local client using service connector 'aws-us-east-1'... WARNING! Your password will be stored unencrypted in /home/stefan/.docker/config.json. Configure a credential helper to remove this warning. See https://docs.docker.com/engine/reference/commandline/login/#credentials-store The 'aws-us-east-1' Docker Service Connector was used to successfully configure the local Docker/OCI container registry client/SDK. ``` {% endcode %} {% endhint %} {% endtab %} {% endtabs %} For more information and a full list of configurable attributes of the AWS container registry, check out the [SDK Docs](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-aws.html#zenml.integrations.aws).
ZenML Scarf
--- ### Book/Component Guide/Container Registries/Azure (docs/book/component-guide/container-registries/azure.md) --- description: Storing container images in Azure. --- # Azure Container Registry The Azure container registry is a [container registry](./) flavor that comes built-in with ZenML and uses the [Azure Container Registry](https://azure.microsoft.com/en-us/services/container-registry/) to store container images. ### When to use it You should use the Azure container registry if: * one or more components of your stack need to pull or push container images. * you have access to Azure. If you're not using Azure, take a look at the other [container registry flavors](./#container-registry-flavors). ### How to deploy it {% hint style="info" %} Would you like to skip ahead and deploy a full ZenML cloud stack already, including an Azure container registry? Check out the[in-browser stack deployment wizard](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/deploy-a-cloud-stack), the [stack registration wizard](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/register-a-cloud-stack), or [the ZenML Azure Terraform module](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/deploy-a-cloud-stack-with-terraform) for a shortcut on how to deploy & register this stack component. {% endhint %} Go [here](https://portal.azure.com/#create/Microsoft.ContainerRegistry) and choose a subscription, resource group, location, and registry name. Then click on `Review + Create` and to create your container registry. ### How to find the registry URI The Azure container registry URI should have the following format: ```shell .azurecr.io # Examples: zenmlregistry.azurecr.io myregistry.azurecr.io ``` To figure out the URI for your registry: * Go to the [Azure portal](https://portal.azure.com/#home). * In the search bar, enter `container registries` and select the container registry you want to use. If you don't have any container registries yet, check out the [deployment section](azure.md#how-to-deploy-it) on how to create one. * Use the name of your registry to fill the template `.azurecr.io` and get your URI. ### How to use it To use the Azure container registry, we need: * [Docker](https://www.docker.com) installed and running. * The registry URI. Check out the [previous section](azure.md#how-to-find-the-registry-uri) on the URI format and how to get the URI for your registry. We can then register the container registry and use it in our active stack: ```shell zenml container-registry register \ --flavor=azure \ --uri= # Add the container registry to the active stack zenml stack update -c ``` You also need to set up [authentication](azure.md#authentication-methods) required to log in to the container registry. #### Authentication Methods Integrating and using an Azure Container Registry in your pipelines is not possible without employing some form of authentication. If you're looking for a quick way to get started locally, you can use the _Local Authentication_ method. However, the recommended way to authenticate to the Azure cloud platform is through [an Azure Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/azure-service-connector). This is particularly useful if you are configuring ZenML stacks that combine the Azure Container Registry with other remote stack components also running in Azure. {% tabs %} {% tab title="Local Authentication" %} This method uses the Docker client authentication available _in the environment where the ZenML code is running_. On your local machine, this is the quickest way to configure an Azure Container Registry. You don't need to supply credentials explicitly when you register the Azure Container Registry, as it leverages the local credentials and configuration that the Azure CLI and Docker client store on your local machine. However, you will need to install and set up the Azure CLI on your machine as a prerequisite, as covered in [the Azure CLI documentation](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli), before you register the Azure Container Registry. With the Azure CLI installed and set up with credentials, you need to login to the container registry so Docker can pull and push images: ```shell # Fill your REGISTRY_NAME in the placeholder in the following command. # You can find the REGISTRY_NAME as part of your registry URI: `.azurecr.io` az acr login --name= ``` {% hint style="warning" %} Stacks using the Azure Container Registry set up with local authentication are not portable across environments. To make ZenML pipelines fully portable, it is recommended to use [an Azure Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/azure-service-connector) to link your Azure Container Registry to the remote ACR registry. {% endhint %} {% endtab %} {% tab title="Azure Service Connector (recommended)" %} To set up the Azure Container Registry to authenticate to Azure and access an ACR registry, it is recommended to leverage the many features provided by [the Azure Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/azure-service-connector) such as auto-configuration, local login, best security practices regarding long-lived credentials and reusing the same credentials across multiple stack components. If you don't already have an Azure Service Connector configured in your ZenML deployment, you can register one using the interactive CLI command. You have the option to configure an Azure Service Connector that can be used to access a ACR registry or even more than one type of Azure resource: ```sh zenml service-connector register --type azure -i ``` A non-interactive CLI example that uses [Azure Service Principal credentials](https://learn.microsoft.com/en-us/azure/active-directory/develop/app-objects-and-service-principals) to configure an Azure Service Connector targeting a single ACR registry is: ```sh zenml service-connector register --type azure --auth-method service-principal --tenant_id= --client_id= --client_secret= --resource-type docker-registry --resource-id ``` {% code title="Example Command Output" %} ``` $ zenml service-connector register azure-demo --type azure --auth-method service-principal --tenant_id=a79f3633-8f45-4a74-a42e-68871c17b7fb --client_id=8926254a-8c3f-430a-a2fd-bdab234d491e --client_secret=AzureSuperSecret --resource-type docker-registry --resource-id demozenmlcontainerregistry.azurecr.io ⠸ Registering service connector 'azure-demo'... Successfully registered service connector `azure-demo` with access to the following resources: ┏━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃ RESOURCE TYPE │ RESOURCE NAMES ┃ ┠────────────────────┼───────────────────────────────────────┨ ┃ 🐳 docker-registry │ demozenmlcontainerregistry.azurecr.io ┃ ┗━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ``` {% endcode %} > **Note**: Please remember to grant the entity associated with your Azure credentials permissions to read and write to your ACR registry as well as to list accessible ACR registries. For a full list of permissions required to use an Azure Service Connector to access a ACR registry, please refer to the [Azure Service Connector ACR registry resource type documentation](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/azure-service-connector#acr-container-registry) or read the documentation available in the interactive CLI commands and dashboard. The Azure Service Connector supports [many different authentication methods](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/azure-service-connector#authentication-methods) with different levels of security and convenience. You should pick the one that best fits your use case. If you already have one or more Azure Service Connectors configured in your ZenML deployment, you can check which of them can be used to access the ACR registry you want to use for your Azure Container Registry by running e.g.: ```sh zenml service-connector list-resources --connector-type azure --resource-type docker-registry ``` {% code title="Example Command Output" %} ``` The following 'docker-registry' resources can be accessed by 'azure' service connectors that you have configured: ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃ CONNECTOR ID │ CONNECTOR NAME │ CONNECTOR TYPE │ RESOURCE TYPE │ RESOURCE NAMES ┃ ┠──────────────────────────────────────┼────────────────┼────────────────┼────────────────────┼───────────────────────────────────────┨ ┃ db5821d0-a658-4504-ae96-04c3302d8f85 │ azure-demo │ 🇦 azure │ 🐳 docker-registry │ demozenmlcontainerregistry.azurecr.io ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ``` {% endcode %} After having set up or decided on an Azure Service Connector to use to connect to the target ACR registry, you can register the Azure Container Registry as follows: ```sh # Register the Azure container registry and reference the target ACR registry URI zenml container-registry register -f azure \ --uri= # Connect the Azure container registry to the target ACR registry via an Azure Service Connector zenml container-registry connect -i ``` A non-interactive version that connects the Azure Container Registry to a target ACR registry through an Azure Service Connector: ```sh zenml container-registry connect --connector ``` {% code title="Example Command Output" %} ``` $ zenml container-registry connect azure-demo --connector azure-demo Successfully connected container registry `azure-demo` to the following resources: ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃ CONNECTOR ID │ CONNECTOR NAME │ CONNECTOR TYPE │ RESOURCE TYPE │ RESOURCE NAMES ┃ ┠──────────────────────────────────────┼────────────────┼────────────────┼────────────────────┼───────────────────────────────────────┨ ┃ db5821d0-a658-4504-ae96-04c3302d8f85 │ azure-demo │ 🇦 azure │ 🐳 docker-registry │ demozenmlcontainerregistry.azurecr.io ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ``` {% endcode %} As a final step, you can use the Azure Container Registry in a ZenML Stack: ```sh # Register and set a stack with the new container registry zenml stack register -c ... --set ``` {% hint style="info" %} Linking the Azure Container Registry to a Service Connector means that your local Docker client is no longer authenticated to access the remote registry. If you need to manually interact with the remote registry via the Docker CLI, you can use the [local login Service Connector feature](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/service-connectors-guide#configure-local-clients) to temporarily authenticate your local Docker client to the remote registry: ```sh zenml service-connector login --resource-type docker-registry --resource-id ``` {% code title="Example Command Output" %} ``` $ zenml service-connector login azure-demo --resource-type docker-registry --resource-id demozenmlcontainerregistry.azurecr.io ⠹ Attempting to configure local client using service connector 'azure-demo'... WARNING! Your password will be stored unencrypted in /home/stefan/.docker/config.json. Configure a credential helper to remove this warning. See https://docs.docker.com/engine/reference/commandline/login/#credentials-store The 'azure-demo' Docker Service Connector was used to successfully configure the local Docker/OCI container registry client/SDK. ``` {% endcode %} {% endhint %} {% endtab %} {% endtabs %} For more information and a full list of configurable attributes of the Azure container registry, check out the [SDK Docs](https://sdkdocs.zenml.io/latest/core_code_docs/core-container_registries.html#zenml.container_registries.azure_container_registry) .
ZenML Scarf
--- ### Book/Component Guide/Container Registries/Custom (docs/book/component-guide/container-registries/custom.md) --- description: Learning how to develop a custom container registry. --- # Develop a custom container registry {% hint style="info" %} Before diving into the specifics of this component type, it is beneficial to familiarize yourself with our [general guide to writing custom component flavors in ZenML](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/implement-a-custom-stack-component). This guide provides an essential understanding of ZenML's component flavor concepts. {% endhint %} ### Base Abstraction In the current version of ZenML, container registries have a rather basic base abstraction. In essence, their base configuration only features a `uri` and their implementation features a non-abstract `prepare_image_push` method for validation. ```python from abc import abstractmethod from typing import Type from zenml.enums import StackComponentType from zenml.stack import Flavor from zenml.stack.authentication_mixin import ( AuthenticationConfigMixin, AuthenticationMixin, ) class BaseContainerRegistryConfig(AuthenticationConfigMixin): """Base config for a container registry.""" uri: str class BaseContainerRegistry(AuthenticationMixin): """Base class for all ZenML container registries.""" def prepare_image_push(self, image_name: str) -> None: """Conduct necessary checks/preparations before an image gets pushed.""" class BaseContainerRegistryFlavor(Flavor): """Base flavor for container registries.""" @property @abstractmethod def name(self) -> str: """Returns the name of the flavor.""" @property def type(self) -> StackComponentType: """Returns the flavor type.""" return StackComponentType.CONTAINER_REGISTRY @property def config_class(self) -> Type[BaseContainerRegistryConfig]: """Config class for this flavor.""" return BaseContainerRegistryConfig @property def implementation_class(self) -> Type[BaseContainerRegistry]: """Implementation class.""" return BaseContainerRegistry ``` {% hint style="info" %} This is a slimmed-down version of the base implementation which aims to highlight the abstraction layer. In order to see the full implementation and get the complete docstrings, please check the [SDK docs](https://sdkdocs.zenml.io/latest/core_code_docs/core-container_registries.html#zenml.container_registries.base_container_registry) . {% endhint %} ### Building your own container registry If you want to create your own custom flavor for a container registry, you can follow the following steps: 1. Create a class that inherits from the `BaseContainerRegistry` class and if you need to execute any checks/validation before the image gets pushed, you can define these operations in the `prepare_image_push` method. As an example, you can check the `AWSContainerRegistry`. 2. If you need further configuration, you can create a class which inherits from the `BaseContainerRegistryConfig` class. 3. Bring both the implementation and the configuration together by inheriting from the `BaseContainerRegistryFlavor` class. Once you are done with the implementation, you can register it through the CLI. Please ensure you **point to the flavor class via dot notation**: ```shell zenml container-registry flavor register ``` For example, your flavor class `MyContainerRegistryFlavor` is defined in `flavors/my_flavor.py`, you'd register it by doing: ```shell zenml container-registry flavor register flavors.my_flavor.MyContainerRegistryFlavor ``` {% hint style="warning" %} ZenML resolves the flavor class by taking the path where you initialized zenml (via `zenml init`) as the starting point of resolution. Therefore, please ensure you follow [the best practice](https://docs.zenml.io/user-guides/best-practices/iac) of initializing zenml at the root of your repository. If ZenML does not find an initialized ZenML repository in any parent directory, it will default to the current working directory, but usually it's better to not have to rely on this mechanism, and initialize zenml at the root. {% endhint %} Afterward, you should see the new flavor in the list of available flavors: ```shell zenml container-registry flavor list ``` {% hint style="warning" %} It is important to draw attention to when and how these base abstractions are coming into play in a ZenML workflow. * The **CustomContainerRegistryFlavor** class is imported and utilized upon the creation of the custom flavor through the CLI. * The **CustomContainerRegistryConfig** class is imported when someone tries to register/update a stack component with this custom flavor. Especially, during the registration process of the stack component, the config will be used to validate the values given by the user. As `Config` object are inherently `pydantic` objects, you can also add your own custom validators here. * The **CustomContainerRegistry** only comes into play when the component is ultimately in use. The design behind this interaction lets us separate the configuration of the flavor from its implementation. This way we can register flavors and components even when the major dependencies behind their implementation are not installed in our local setting (assuming the `CustomContainerRegistryFlavor` and the `CustomContainerRegistryConfig` are implemented in a different module/path than the actual `CustomContainerRegistry`). {% endhint %}
ZenML Scarf
--- ### Book/Component Guide/Container Registries/Default (docs/book/component-guide/container-registries/default.md) --- description: Storing container images locally. --- # Default Container Registry The Default container registry is a [container registry](./) flavor that comes built-in with ZenML and allows container registry URIs of any format. ### When to use it You should use the Default container registry if you want to use a **local** container registry or when using a remote container registry that is not covered by other [container registry flavors](./#container-registry-flavors). ### Local registry URI format To specify a URI for a local container registry, use the following format: ```shell localhost: # Examples: localhost:5000 localhost:8000 localhost:9999 ``` ### How to use it To use the Default container registry, we need: * [Docker](https://www.docker.com) installed and running. * The registry URI. If you're using a local container registry, check out * the [previous section](default.md#local-registry-uri-format) on the URI format. We can then register the container registry and use it in our active stack: ```shell zenml container-registry register \ --flavor=default \ --uri= # Add the container registry to the active stack zenml stack update -c ``` You may also need to set up [authentication](default.md#authentication-methods) required to log in to the container registry. #### Authentication Methods If you are using a private container registry, you will need to configure some form of authentication to login to the registry. If you're looking for a quick way to get started locally, you can use the _Local Authentication_ method. However, the recommended way to authenticate to a remote private container registry is through [a Docker Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/docker-service-connector). If your target private container registry comes from a cloud provider like AWS, GCP or Azure, you should use the [container registry flavor](./#container-registry-flavors) targeted at that cloud provider. For example, if you're using AWS, you should use the [AWS Container Registry](aws.md) flavor. These cloud provider flavors also use specialized cloud provider Service Connectors to authenticate to the container registry. {% tabs %} {% tab title="Local Authentication" %} This method uses the Docker client authentication available _in the environment where the ZenML code is running_. On your local machine, this is the quickest way to configure a Default Container Registry. You don't need to supply credentials explicitly when you register the Default Container Registry, as it leverages the local credentials and configuration that the Docker client stores on your local machine. To log in to the container registry so Docker can pull and push images, you'll need to run the `docker login` command and supply your credentials, e.g.: ```shell docker login --username --password-stdin ``` {% hint style="warning" %} Stacks using the Default Container Registry set up with local authentication are not portable across environments. To make ZenML pipelines fully portable, it is recommended to use [a Docker Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/docker-service-connector) to link your Default Container Registry to the remote private container registry. {% endhint %} {% endtab %} {% tab title="Docker Service Connector (recommended)" %} To set up the Default Container Registry to authenticate to and access a private container registry, it is recommended to leverage the features provided by [the Docker Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/docker-service-connector) such as local login and reusing the same credentials across multiple stack components. If you don't already have a Docker Service Connector configured in your ZenML deployment, you can register one using the interactive CLI command: ```sh zenml service-connector register --type docker -i ``` A non-interactive CLI example is: ```sh zenml service-connector register --type docker --username= --password= ``` {% code title="Example Command Output" %} ``` $ zenml service-connector register dockerhub --type docker --username=username --password=password Successfully registered service connector `dockerhub` with access to the following resources: ┏━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┓ ┃ RESOURCE TYPE │ RESOURCE NAMES ┃ ┠────────────────────┼────────────────┨ ┃ 🐳 docker-registry │ docker.io ┃ ┗━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┛ ``` {% endcode %} If you already have one or more Docker Service Connectors configured in your ZenML deployment, you can check which of them can be used to access the container registry you want to use for your Default Container Registry by running e.g.: ```sh zenml service-connector list-resources --connector-type docker --resource-id ``` {% code title="Example Command Output" %} ``` $ zenml service-connector list-resources --connector-type docker --resource-id docker.io The resource with name 'docker.io' can be accessed by 'docker' service connectors that you have configured: ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┓ ┃ CONNECTOR ID │ CONNECTOR NAME │ CONNECTOR TYPE │ RESOURCE TYPE │ RESOURCE NAMES ┃ ┠──────────────────────────────────────┼────────────────┼────────────────┼────────────────────┼────────────────┨ ┃ cf55339f-dbc8-4ee6-862e-c25aff411292 │ dockerhub │ 🐳 docker │ 🐳 docker-registry │ docker.io ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┛ ``` {% endcode %} After having set up or decided on a Docker Service Connector to use to connect to the target container registry, you can register the Docker Container Registry as follows: ```sh # Register the container registry and reference the target registry URI zenml container-registry register -f default \ --uri= # Connect the container registry to the target registry via a Docker Service Connector zenml container-registry connect -i ``` A non-interactive version that connects the Default Container Registry to a target registry through a Docker Service Connector: ```sh zenml container-registry connect --connector ``` {% code title="Example Command Output" %} ``` $ zenml container-registry connect dockerhub --connector dockerhub Successfully connected container registry `dockerhub` to the following resources: ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┓ ┃ CONNECTOR ID │ CONNECTOR NAME │ CONNECTOR TYPE │ RESOURCE TYPE │ RESOURCE NAMES ┃ ┠──────────────────────────────────────┼────────────────┼────────────────┼────────────────────┼────────────────┨ ┃ cf55339f-dbc8-4ee6-862e-c25aff411292 │ dockerhub │ 🐳 docker │ 🐳 docker-registry │ docker.io ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┛ ``` {% endcode %} As a final step, you can use the Default Container Registry in a ZenML Stack: ```sh # Register and set a stack with the new container registry zenml stack register -c ... --set ``` {% hint style="info" %} Linking the Default Container Registry to a Service Connector means that your local Docker client is no longer authenticated to access the remote registry. If you need to manually interact with the remote registry via the Docker CLI, you can use the [local login Service Connector feature](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/service-connectors-guide#configure-local-clients) to temporarily authenticate your local Docker client to the remote registry: ```sh zenml service-connector login ``` {% code title="Example Command Output" %} ``` $ zenml service-connector login dockerhub ⠹ Attempting to configure local client using service connector 'dockerhub'... WARNING! Your password will be stored unencrypted in /home/stefan/.docker/config.json. Configure a credential helper to remove this warning. See https://docs.docker.com/engine/reference/commandline/login/#credentials-store The 'dockerhub' Docker Service Connector was used to successfully configure the local Docker/OCI container registry client/SDK. ``` {% endcode %} {% endhint %} {% endtab %} {% endtabs %} For more information and a full list of configurable attributes of the Default container registry, check out the [SDK Docs](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-aws.html#zenml.integrations.aws) .
ZenML Scarf
--- ### Book/Component Guide/Container Registries/Digitalocean (docs/book/component-guide/container-registries/digitalocean.md) --- description: Storing container images in the DigitalOcean Container Registry. --- # DigitalOcean Container Registry The DigitalOcean container registry is a [container registry](./) flavor that comes built-in with the DigitalOcean ZenML integration and uses the [DigitalOcean Container Registry (DOCR)](https://www.digitalocean.com/products/container-registry) to store container images. ### When to use it You should use the DigitalOcean container registry if: * one or more components of your stack need to pull or push container images. * your infrastructure runs on DigitalOcean — for example a DOKS cluster running the [Kubernetes orchestrator](https://docs.zenml.io/stacks/orchestrators/kubernetes) that pulls pipeline images from DOCR. ### How to deploy it The flavor is provided by the DigitalOcean ZenML integration: ```shell zenml integration install digitalocean -y ``` You also need a DOCR registry. In the [DigitalOcean control panel](https://cloud.digitalocean.com/), go to **Container Registry** and create one (each account has a single registry with a unique name). The registry URI has the form: ``` registry.digitalocean.com/ ``` ### How to use it To use the DigitalOcean container registry, you need: * [Docker](https://www.docker.com) installed and running. * The registry URI (see above). You can then register the container registry and use it in your active stack: ```shell zenml container-registry register do_registry \ --flavor=digitalocean \ --uri=registry.digitalocean.com/ # Add the container registry to the active stack zenml stack update -c do_registry ``` You also need to authenticate your local Docker client with DOCR. The simplest way is [`doctl`](https://docs.digitalocean.com/reference/doctl/): ```shell doctl registry login ``` {% hint style="info" %} DOCR login credentials issued by `doctl registry login` are short-lived and expire after a while, so you may need to re-run the command. When pipeline images are pulled by a DOKS cluster, you can instead [integrate the registry with the cluster](https://docs.digitalocean.com/products/container-registry/how-to/use-registry-docker-kubernetes/) so nodes authenticate automatically. {% endhint %} For more information and a full list of configurable attributes of the DigitalOcean container registry, check out the [source code on GitHub](https://github.com/zenml-io/zenml/tree/main/src/zenml/integrations/digitalocean). --- ### Book/Component Guide/Container Registries/Dockerhub (docs/book/component-guide/container-registries/dockerhub.md) --- description: Storing container images in DockerHub. --- # DockerHub The DockerHub container registry is a [container registry](./) flavor that comes built-in with ZenML and uses [DockerHub](https://hub.docker.com/) to store container images. ### When to use it You should use the DockerHub container registry if: * one or more components of your stack need to pull or push container images. * you have a DockerHub account. If you're not using DockerHub, take a look at the other [container registry flavors](./#container-registry-flavors). ### How to deploy it To use the DockerHub container registry, all you need to do is create a [DockerHub](https://hub.docker.com/) account. When this container registry is used in a ZenML stack, the Docker images that are built will be published in a \*\* public\*\* repository and everyone will be able to pull your images. If you want to use a **private** repository instead, you'll have to [create a private repository](https://docs.docker.com/docker-hub/repos/#creating-repositories) on the website before running the pipeline. The repository name depends on the remote [orchestrator](https://docs.zenml.io/stacks/orchestrators/) or [step operator](https://docs.zenml.io/stacks/step-operators/) that you're using in your stack. ### How to find the registry URI The DockerHub container registry URI should have one of the two following formats: ```shell # or docker.io/ # Examples: zenml my-username docker.io/zenml docker.io/my-username ``` To figure out the URI for your registry: * Find out the account name of your [DockerHub](https://hub.docker.com/) account. * Use the account name to fill the template `docker.io/` and get your URI. ### How to use it To use the DockerHub container registry, we need: * [Docker](https://www.docker.com) installed and running. * The registry URI. Check out the [previous section](dockerhub.md#how-to-find-the-registry-uri) on the URI format and how to get the URI for your registry. We can then register the container registry and use it in our active stack: ```shell zenml container-registry register \ --flavor=dockerhub \ --uri= # Add the container registry to the active stack zenml stack update -c ``` Additionally, we'll need to log in to the container registry so Docker can pull and push images. This will require your DockerHub account name and either your password or preferably a [personal access token](https://docs.docker.com/docker-hub/access-tokens/). ```shell docker login ```
ZenML Scarf
--- ### Book/Component Guide/Container Registries/Gcp (docs/book/component-guide/container-registries/gcp.md) --- description: Storing container images in GCP. --- # Google Cloud Container Registry The GCP container registry is a [container registry](./) flavor that comes built-in with ZenML and uses the [Google Artifact Registry](https://cloud.google.com/artifact-registry). {% hint style="warning" %} **Important Notice: Google Container Registry** [**is being replaced by Artifact Registry**](https://cloud.google.com/artifact-registry/docs/transition/transition-from-gcr)**. Please start using Artifact Registry for your containers. As per Google's documentation, "after May 15, 2024, Artifact Registry will host images for the gcr.io domain in Google Cloud projects without previous Container Registry usage. After March 18, 2025, Container Registry will be shut down."** The terms `container registry` and `artifact registry` will be used interchangeably throughout this document. {% endhint %} ### When to use it You should use the GCP container registry if: * one or more components of your stack need to pull or push container images. * you have access to GCP. If you're not using GCP, take a look at the other [container registry flavors](./#container-registry-flavors). ### How to deploy it {% hint style="info" %} Would you like to skip ahead and deploy a full ZenML cloud stack already, including a Google Artifact Registry? Check out the[in-browser stack deployment wizard](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/deploy-a-cloud-stack), the [stack registration wizard](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/register-a-cloud-stack), or [the ZenML GCP Terraform module](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/deploy-a-cloud-stack-with-terraform) for a shortcut on how to deploy & register this stack component. {% endhint %} When using the Google Artifact Registry, you need to: * enable it [here](https://console.cloud.google.com/marketplace/product/google/artifactregistry.googleapis.com) * go [here](https://console.cloud.google.com/artifacts) and create a `Docker` repository. ## How to find the registry URI When using the Google Artifact Registry, the GCP container registry URI should have the following format: ```shell -docker.pkg.dev// # Examples: europe-west1-docker.pkg.dev/zenml/my-repo southamerica-east1-docker.pkg.dev/zenml/zenml-test asia-docker.pkg.dev/my-project/another-repo ``` To figure out the URI for your registry: * Go [here](https://console.cloud.google.com/artifacts) and select the repository that you want to use to store Docker images. If you don't have a repository yet, take a look at the [deployment section](gcp.md#how-to-deploy-it). * On the top, click the copy button to copy the full repository URL. ### How to use it To use the GCP container registry, we need: * [Docker](https://www.docker.com) installed and running. * The registry URI. Check out the [previous section](gcp.md#how-to-find-the-registry-uri) on the URI format and how to get the URI for your registry. We can then register the container registry and use it in our active stack: ```shell zenml container-registry register \ --flavor=gcp \ --uri= # Add the container registry to the active stack zenml stack update -c ``` You also need to set up [authentication](gcp.md#authentication-methods) required to log in to the container registry. #### Authentication Methods Integrating and using a GCP Container Registry in your pipelines is not possible without employing some form of authentication. If you're looking for a quick way to get started locally, you can use the _Local Authentication_ method. However, the recommended way to authenticate to the GCP cloud platform is through [a GCP Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/gcp-service-connector). This is particularly useful if you are configuring ZenML stacks that combine the GCP Container Registry with other remote stack components also running in GCP. {% tabs %} {% tab title="Local Authentication" %} This method uses the Docker client authentication available _in the environment where the ZenML code is running_. On your local machine, this is the quickest way to configure a GCP Container Registry. You don't need to supply credentials explicitly when you register the GCP Container Registry, as it leverages the local credentials and configuration that the GCP CLI and Docker client store on your local machine. However, you will need to install and set up the GCP CLI on your machine as a prerequisite, as covered in [the GCP CLI documentation](https://cloud.google.com/sdk/docs/install-sdk), before you register the GCP Container Registry. With the GCP CLI installed and set up with credentials, we'll need to configure Docker, so it can pull and push images: * for a Google Container Registry: ```shell gcloud auth configure-docker ``` * for a Google Artifact Registry: ```shell gcloud auth configure-docker -docker.pkg.dev ``` {% hint style="warning" %} Stacks using the GCP Container Registry set up with local authentication are not portable across environments. To make ZenML pipelines fully portable, it is recommended to use [a GCP Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/gcp-service-connector) to link your GCP Container Registry to the remote GCR registry. {% endhint %} {% endtab %} {% tab title="GCP Service Connector (recommended)" %} To set up the GCP Container Registry to authenticate to GCP and access a Google Artifact Registry, it is recommended to leverage the many features provided by [the GCP Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/gcp-service-connector) such as auto-configuration, local login, best security practices regarding long-lived credentials and reusing the same credentials across multiple stack components. If you don't already have a GCP Service Connector configured in your ZenML deployment, you can register one using the interactive CLI command. You have the option to configure a GCP Service Connector that can be used to access a Google Artifact Registry or even more than one type of GCP resource: ```sh zenml service-connector register --type gcp -i ``` A non-interactive CLI example that leverages [the GCP CLI configuration](https://cloud.google.com/sdk/docs/install-sdk) on your local machine to auto-configure a GCP Service Connector targeting a GCR registry is: ```sh zenml service-connector register --type gcp --resource-type docker-registry --auto-configure ``` {% code title="Example Command Output" %} ``` $ zenml service-connector register gcp-zenml-core --type gcp --resource-type docker-registry --auto-configure ⠸ Registering service connector 'gcp-zenml-core'... Successfully registered service connector `gcp-zenml-core` with access to the following resources: ┏━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃ RESOURCE TYPE │ RESOURCE NAMES ┃ ┠────────────────────┼─────────────────────────────────────────────────┨ ┃ 🐳 docker-registry │ gcr.io/zenml-core ┃ ┃ │ us.gcr.io/zenml-core ┃ ┃ │ eu.gcr.io/zenml-core ┃ ┃ │ asia.gcr.io/zenml-core ┃ ┃ │ asia-docker.pkg.dev/zenml-core/asia.gcr.io ┃ ┃ │ europe-docker.pkg.dev/zenml-core/eu.gcr.io ┃ ┃ │ europe-west1-docker.pkg.dev/zenml-core/test ┃ ┃ │ us-docker.pkg.dev/zenml-core/gcr.io ┃ ┃ │ us-docker.pkg.dev/zenml-core/us.gcr.io ┃ ┗━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ``` {% endcode %} > **Note**: Please remember to grant the entity associated with your GCP credentials permissions to read and write to your GCR registry. For a full list of permissions required to use a GCP Service Connector to access a GCR registry, please refer to the [GCP Service Connector GCR registry resource type documentation](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/gcp-service-connector#gcr-container-registry) or read the documentation available in the interactive CLI commands and dashboard. The GCP Service Connector supports [many different authentication methods](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/gcp-service-connector#authentication-methods) with different levels of security and convenience. You should pick the one that best fits your use-case. If you already have one or more GCP Service Connectors configured in your ZenML deployment, you can check which of them can be used to access the GCR registry you want to use for your GCP Container Registry by running e.g.: ```sh zenml service-connector list-resources --connector-type gcp --resource-type docker-registry ``` {% code title="Example Command Output" %} ``` /* Detailed source-code truncated for AI context efficiency. */ ``` {% endcode %} After having set up or decided on a GCP Service Connector to use to connect to the target GCR registry, you can register the GCP Container Registry as follows: ```sh # Register the GCP container registry and reference the target GCR registry URI zenml container-registry register -f gcp \ --uri= # Connect the GCP container registry to the target GCR registry via a GCP Service Connector zenml container-registry connect -i ``` A non-interactive version that connects the GCP Container Registry to a target GCR registry through a GCP Service Connector: ```sh zenml container-registry connect --connector ``` {% hint style="info" %} Linking the GCP Container Registry to a Service Connector means that your local Docker client is no longer authenticated to access the remote registry. If you need to manually interact with the remote registry via the Docker CLI, you can use the [local login Service Connector feature](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/service-connectors-guide#configure-local-clients) to temporarily authenticate your local Docker client to the remote registry: ```sh zenml service-connector login --resource-type docker-registry ``` {% code title="Example Command Output" %} ``` $ zenml service-connector login gcp-zenml-core --resource-type docker-registry ⠋ Attempting to configure local client using service connector 'gcp-zenml-core'... WARNING! Your password will be stored unencrypted in /home/stefan/.docker/config.json. Configure a credential helper to remove this warning. See https://docs.docker.com/engine/reference/commandline/login/#credentials-store The 'gcp-zenml-core' Docker Service Connector was used to successfully configure the local Docker/OCI container registry client/SDK. ``` {% endcode %} {% endhint %} {% code title="Example Command Output" %} ``` $ zenml container-registry connect gcp-zenml-core --connector gcp-zenml-core Successfully connected container registry `gcp-zenml-core` to the following resources: ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃ CONNECTOR ID │ CONNECTOR NAME │ CONNECTOR TYPE │ RESOURCE TYPE │ RESOURCE NAMES ┃ ┠──────────────────────────────────────┼────────────────┼────────────────┼────────────────────┼─────────────────────────────────────────────┨ ┃ 561b776a-af8b-491c-a4ed-14349b440f30 │ gcp-zenml-core │ 🔵 gcp │ 🐳 docker-registry │ europe-west1-docker.pkg.dev/zenml-core/test ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ``` {% endcode %} As a final step, you can use the GCP Container Registry in a ZenML Stack: ```sh # Register and set a stack with the new container registry zenml stack register -c ... --set ``` {% endtab %} {% endtabs %} For more information and a full list of configurable attributes of the GCP container registry, check out the [SDK Docs](https://sdkdocs.zenml.io/latest/core_code_docs/core-container_registries.html#zenml.container_registries.gcp_container_registry) .
ZenML Scarf
--- ### Book/Component Guide/Container Registries/Github (docs/book/component-guide/container-registries/github.md) --- description: Storing container images in GitHub. --- # GitHub Container Registry The GitHub container registry is a [container registry](./) flavor that comes built-in with ZenML and uses the [GitHub Container Registry](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry) to store container images. ### When to use it You should use the GitHub container registry if: * one or more components of your stack need to pull or push container images. * you're using GitHub for your projects. If you're not using GitHub, take a look at the other [container registry flavors](./#container-registry-flavors). ### How to deploy it The GitHub container registry is enabled by default when you create a GitHub account. ### How to find the registry URI The GitHub container registry URI should have the following format: ```shell ghcr.io/ # Examples: ghcr.io/zenml ghcr.io/my-username ghcr.io/my-organization ``` To figure our the URI for your registry: * Use the GitHub user or organization name to fill the template `ghcr.io/` and get your URI. ### How to use it To use the GitHub container registry, we need: * [Docker](https://www.docker.com) installed and running. * The registry URI. Check out the [previous section](github.md#how-to-find-the-registry-uri) on the URI format and how to get the URI for your registry. * Our Docker client configured, so it can pull and push images. Follow [this guide](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry#authenticating-to-the-container-registry) to create a personal access token and login to the container registry. We can then register the container registry and use it in our active stack: ```shell zenml container-registry register \ --flavor=github \ --uri= # Add the container registry to the active stack zenml stack update -c ``` For more information and a full list of configurable attributes of the GitHub container registry, check out the [SDK Docs](https://sdkdocs.zenml.io/latest/core_code_docs/core-container_registries.html#zenml.container_registries.github_container_registry) .
ZenML Scarf
--- ### Book/Component Guide/Contribute/Custom Stack Component (docs/book/component-guide/contribute/custom-stack-component.md) --- description: How to write a custom stack component flavor icon: cube --- # Custom Stack Component When building a sophisticated MLOps Platform, you will often need to come up with custom-tailored solutions for your infrastructure or tooling. ZenML is built around the values of composability and reusability which is why the stack component flavors in ZenML are designed to be modular and straightforward to extend. This guide will help you understand what a flavor is, and how you can develop and use your own custom flavors in ZenML. ## Understanding component flavors In ZenML, a component type is a broad category that defines the functionality of a stack component. Each type can have multiple flavors, which are specific implementations of the component type. For instance, the type `artifact_store` can have flavors like `local`, `s3`, etc. Each flavor defines a unique implementation of functionality that an artifact store brings to a stack. ## Base Abstractions Before we get into the topic of creating custom stack component flavors, let us briefly discuss the three core abstractions related to stack components: the `StackComponent`, the `StackComponentConfig`, and the `Flavor`. ### Base Abstraction 1: `StackComponent` The `StackComponent` is the abstraction that defines the core functionality. As an example, check out the `BaseArtifactStore` definition below: The `BaseArtifactStore` inherits from `StackComponent` and establishes the public interface of all artifact stores. Any artifact store flavor needs to follow the standards set by this base class. ```python from zenml.stack import StackComponent class BaseArtifactStore(StackComponent): """Base class for all ZenML artifact stores.""" # --- public interface --- @abstractmethod def open(self, path, mode = "r"): """Open a file at the given path.""" @abstractmethod def exists(self, path): """Checks if a path exists.""" ... ``` As each component defines a different interface, make sure to check out the base class definition of the component type that you want to implement and also check out the [documentation on how to extend specific stack components](../contribute/custom-stack-component.md). {% hint style="info" %} If you would like to automatically track some metadata about your custom stack component with each pipeline run, you can do so by defining some additional methods in your stack component implementation class as shown in the [Tracking Custom Stack Component Metadata](https://docs.zenml.io/how-to/model-management-metrics/track-metrics-metadata/fetch-metadata-within-steps) section. {% endhint %} See the full code of the base `StackComponent` class [here](https://github.com/zenml-io/zenml/blob/main/src/zenml/stack/stack\_component.py#L301). ### Base Abstraction 2: `StackComponentConfig` As the name suggests, the `StackComponentConfig` is used to configure a stack component instance. It is separated from the actual implementation on purpose. This way, ZenML can use this class to validate the configuration of a stack component during its registration/update, without having to import heavy (or even non-installed) dependencies. {% hint style="info" %} The `config` and `settings` of a stack component are two separate, yet related entities. The `config` is the static part of your flavor's configuration, defined when you register your flavor. The `settings` are the dynamic part of your flavor's configuration that can be overridden at runtime. You can read more about the differences [here](https://docs.zenml.io/concepts/steps_and_pipelines/configuration). {% endhint %} Let us now continue with the base artifact store example from above and take a look at the `BaseArtifactStoreConfig`: ```python from zenml.stack import StackComponentConfig class BaseArtifactStoreConfig(StackComponentConfig): """Config class for `BaseArtifactStore`.""" path: str SUPPORTED_SCHEMES: ClassVar[Set[str]] ... ``` Through the `BaseArtifactStoreConfig`, each artifact store will require users to define a `path` variable. Additionally, the base config requires all artifact store flavors to define a `SUPPORTED_SCHEMES` class variable that ZenML will use to check if the user-provided `path` is actually supported by the flavor. See the full code of the base `StackComponentConfig` class [here](https://github.com/zenml-io/zenml/blob/main/src/zenml/stack/stack\_component.py#L44). ### Base Abstraction 3: `Flavor` Finally, the `Flavor` abstraction is responsible for bringing the implementation of a `StackComponent` together with the corresponding `StackComponentConfig` definition and also defines the `name` and `type` of the flavor. As an example, check out the definition of the `local` artifact store flavor below: ```python from zenml.enums import StackComponentType from zenml.stack import Flavor class LocalArtifactStore(BaseArtifactStore): ... class LocalArtifactStoreConfig(BaseArtifactStoreConfig): ... class LocalArtifactStoreFlavor(Flavor): @property def name(self) -> str: """Returns the name of the flavor.""" return "local" @property def type(self) -> StackComponentType: """Returns the flavor type.""" return StackComponentType.ARTIFACT_STORE @property def config_class(self) -> Type[LocalArtifactStoreConfig]: """Config class of this flavor.""" return LocalArtifactStoreConfig @property def implementation_class(self) -> Type[LocalArtifactStore]: """Implementation class of this flavor.""" return LocalArtifactStore ``` See the full code of the base `Flavor` class definition [here](https://github.com/zenml-io/zenml/blob/main/src/zenml/stack/flavor.py#L29). ## Implementing a Custom Stack Component Flavor Let's recap what we just learned by reimplementing the `S3ArtifactStore` from the `aws` integration as a custom flavor. We can start with the configuration class: here we need to define the `SUPPORTED_SCHEMES` class variable introduced by the `BaseArtifactStore`. We also define several additional configuration values that users can use to configure how the artifact store will authenticate with AWS: ```python from zenml.artifact_stores import BaseArtifactStoreConfig from zenml.utils.secret_utils import SecretField class MyS3ArtifactStoreConfig(BaseArtifactStoreConfig): """Configuration for the S3 Artifact Store.""" SUPPORTED_SCHEMES: ClassVar[Set[str]] = {"s3://"} key: Optional[str] = SecretField(default=None) secret: Optional[str] = SecretField(default=None) token: Optional[str] = SecretField(default=None) client_kwargs: Optional[Dict[str, Any]] = None config_kwargs: Optional[Dict[str, Any]] = None s3_additional_kwargs: Optional[Dict[str, Any]] = None ``` {% hint style="info" %} You can pass sensitive configuration values as [secrets](https://docs.zenml.io/how-to/project-setup-and-management/interact-with-secrets) by defining them as type `SecretField` in the configuration class. {% endhint %} With the configuration defined, we can move on to the implementation class, which will use the S3 file system to implement the abstract methods of the `BaseArtifactStore`: ```python import s3fs from zenml.artifact_stores import BaseArtifactStore class MyS3ArtifactStore(BaseArtifactStore): """Custom artifact store implementation.""" _filesystem: Optional[s3fs.S3FileSystem] = None @property def filesystem(self) -> s3fs.S3FileSystem: """Get the underlying S3 file system.""" if self._filesystem: return self._filesystem self._filesystem = s3fs.S3FileSystem( key=self.config.key, secret=self.config.secret, token=self.config.token, client_kwargs=self.config.client_kwargs, config_kwargs=self.config.config_kwargs, s3_additional_kwargs=self.config.s3_additional_kwargs, ) return self._filesystem def open(self, path, mode="r"): """Custom logic goes here.""" return self.filesystem.open(path=path, mode=mode) def exists(self, path): """Custom logic goes here.""" return self.filesystem.exists(path=path) ``` {% hint style="info" %} The configuration values defined in the corresponding configuration class are always available in the implementation class under `self.config`. {% endhint %} Finally, let's define a custom flavor that brings these two classes together. Make sure that you give your flavor a globally unique name here. ```python from zenml.artifact_stores import BaseArtifactStoreFlavor class MyS3ArtifactStoreFlavor(BaseArtifactStoreFlavor): """Custom artifact store implementation.""" @property def name(self): """The name of the flavor.""" return 'my_s3_artifact_store' @property def implementation_class(self): """Implementation class for this flavor.""" from ... import MyS3ArtifactStore return MyS3ArtifactStore @property def config_class(self): """Configuration class for this flavor.""" from ... import MyS3ArtifactStoreConfig return MyS3ArtifactStoreConfig ``` {% hint style="info" %} For flavors that require additional dependencies, you should make sure to define your implementation, config, and flavor classes in separate Python files and to only import the implementation class inside the `implementation_class` property of the flavor class. Otherwise, ZenML will not be able to load and validate your flavor configuration without the dependencies installed. {% endhint %} ## Managing a Custom Stack Component Flavor Once you have defined your implementation, config, and flavor classes, you can register your new flavor through the ZenML CLI: ```shell zenml artifact-store flavor register ``` {% hint style="info" %} Make sure to point to the flavor class via dot notation! {% endhint %} For example, if your flavor class `MyS3ArtifactStoreFlavor` is defined in `flavors/my_flavor.py`, you'd register it by doing: ```shell zenml artifact-store flavor register flavors.my_flavor.MyS3ArtifactStoreFlavor ``` Afterwards, you should see the new custom artifact store flavor in the list of available artifact store flavors: ```shell zenml artifact-store flavor list ``` And that's it! You now have a custom stack component flavor that you can use in your stacks just like any other flavor you used before, e.g.: ```shell zenml artifact-store register \ --flavor=my_s3_artifact_store \ --path='some-path' \ ... zenml stack register \ --artifact-store \ ... ``` ## Tips and best practices * ZenML resolves the flavor classes by taking the path where you initialized ZenML (via `zenml init`) as the starting point of resolution. Therefore, you and your team should remember to execute `zenml init` in a consistent manner (usually at the root of the repository where the `.git` folder lives). If the `zenml init` command was not executed, the current working directory is used to find implementation classes, which could lead to unexpected behavior. * You can use the ZenML CLI to find which exact configuration values a specific flavor requires. Check out [this 3-minute video](https://www.youtube.com/watch?v=CQRVSKbBjtQ) for more information. * You can keep changing the `Config` and `Settings` of your flavor after registration. ZenML will pick up these "live" changes when running pipelines. * Note that changing the config in a breaking way requires an update of the component (not a flavor). E.g., adding a mandatory name to flavor X field will break a registered component of that flavor. This may lead to a completely broken state where one should delete the component and re-register it. * Always test your flavor thoroughly before using it in production. Make sure it works as expected and handles errors gracefully. * Keep your flavor code clean and well-documented. This will make it easier for others to use and contribute to your flavor. * Follow best practices for the language and libraries you're using. This will help ensure your flavor is efficient, reliable, and easy to maintain. * We recommend you develop new flavors by using existing flavors as a reference. A good starting point is the flavors defined in the [official ZenML integrations](https://github.com/zenml-io/zenml/tree/main/src/zenml/integrations). ## Extending Specific Stack Components If you would like to learn more about how to build a custom stack component flavor for a specific stack component type, check out the links below: | **Type of Stack Component** | **Description** | | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------- | | [Orchestrator](https://docs.zenml.io/stacks/orchestrators/custom) | Orchestrating the runs of your pipeline | | [Artifact Store](https://docs.zenml.io/stacks/artifact-stores/custom) | Storage for the artifacts created by your pipelines | | [Container Registry](https://docs.zenml.io/stacks/container-registries/custom) | Store for your containers | | [Step Operator](https://docs.zenml.io/stacks/step-operators/custom) | Execution of individual steps in specialized runtime environments | | [Model Deployer](https://docs.zenml.io/stacks/model-deployers/custom) | Services/platforms responsible for online model serving | | [Feature Store](https://docs.zenml.io/stacks/feature-stores/custom) | Management of your data/features | | [Experiment Tracker](https://docs.zenml.io/stacks/experiment-trackers/custom) | Tracking your ML experiments | | [Alerter](https://docs.zenml.io/stacks/alerters/custom) | Sending alerts through specified channels | | [Annotator](https://docs.zenml.io/stacks/annotators/custom) | Annotating and labeling data | | [Data Validator](https://docs.zenml.io/stacks/data-validators/custom) | Validating and monitoring your data |
ZenML Scarf
--- ### Book/Component Guide/Data Validators/README (docs/book/component-guide/data-validators/README.md) --- description: >- How to enhance and maintain the quality of your data and the performance of your models with data profiling and validation icon: chart-column --- # Data Validators Without good data, even the best machine learning models will yield questionable results. A lot of effort goes into ensuring and maintaining data quality not only in the initial stages of model development, but throughout the entire machine learning project lifecycle. Data Validators are a category of ML libraries, tools and frameworks that grant a wide range of features and best practices that should be employed in the ML pipelines to keep data quality in check and to monitor model performance to keep it from degrading over time. Data profiling, data integrity testing, data and model drift detection are all ways of employing data validation techniques at different points in your ML pipelines where data is concerned: data ingestion, model training and evaluation and online or batch inference. Data profiles and model performance evaluation results can be visualized and analyzed to detect problems and take preventive or correcting actions. Related concepts: * the Data Validator is an optional type of Stack Component that needs to be registered as part of your ZenML [Stack](https://docs.zenml.io/user-guides/production-guide/understand-stacks). * Data Validators used in ZenML pipelines usually generate data profiles and data quality check reports that are versioned and stored in the [Artifact Store](https://docs.zenml.io/stacks/artifact-stores/) and can be [retrieved and visualized](https://docs.zenml.io/how-to/data-artifact-management/visualize-artifacts/) later. ### When to use it [Data-centric AI practices](https://blog.zenml.io/data-centric-mlops/) are quickly becoming mainstream and using Data Validators are an easy way to incorporate them into your workflow. These are some common cases where you may consider employing the use of Data Validators in your pipelines: * early on, even if it's just to keep a log of the quality state of your data and the performance of your models at different stages of development. * if you have pipelines that regularly ingest new data, you should use data validation to run regular data integrity checks to signal problems before they are propagated downstream. * in continuous training pipelines, you should use data validation techniques to compare new training data against a data reference and to compare the performance of newly trained models against previous ones. * when you have pipelines that automate batch inference or if you regularly collect data used as input in online inference, you should use data validation to run data drift analyzes and detect training-serving skew, data drift and model drift. #### Data Validator Flavors Data Validator are optional stack components provided by integrations. The following table lists the currently available Data Validators and summarizes their features and the data types and model types that they can be used with in ZenML pipelines: | Data Validator | Validation Features | Data Types | Model Types | Notes | Flavor/Integration | | ------------------------------------------- | --------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | -------------------- | | [Deepchecks](deepchecks.md) |

data quality
data drift
model drift
model performance

|

tabular: pandas.DataFrame
CV: torch.utils.data.dataloader.DataLoader

|

tabular: sklearn.base.ClassifierMixin
CV: torch.nn.Module

| Add Deepchecks data and model validation tests to your pipelines | `deepchecks` | | [Evidently](evidently.md) |

data quality
data drift
model drift
model performance

| tabular: `pandas.DataFrame` | N/A | Use Evidently to generate a variety of data quality and data/model drift reports and visualizations | `evidently` | | [Great Expectations](great-expectations.md) |

data profiling
data quality

| tabular: `pandas.DataFrame` | N/A | Perform data testing, documentation and profiling with Great Expectations | `great_expectations` | | [Whylogs/WhyLabs](whylogs.md) | data drift | tabular: `pandas.DataFrame` | N/A | Generate data profiles with whylogs. Hosted WhyLabs platform is being discontinued after Apple's acquisition—see the integration page for OSS deployment options. | `whylogs` | If you would like to see the available flavors of Data Validator, you can use the command: ```shell zenml data-validator flavor list ``` ### How to use it Every Data Validator has different data profiling and testing capabilities and uses a slightly different way of analyzing your data and your models, but it generally works as follows: * first, you have to configure and add a Data Validator to your ZenML stack * every integration includes one or more builtin data validation steps that you can add to your pipelines. Of course, you can also use the libraries directly in your own custom pipeline steps and simply return the results (e.g. data profiles, test reports) as artifacts that are versioned and stored by ZenML in its Artifact Store. * you can access the data validation artifacts in subsequent pipeline steps, or [fetch them afterwards](https://docs.zenml.io/how-to/data-artifact-management/handle-data-artifacts/load-artifacts-into-memory) to process them or visualize them as needed. Consult the documentation for the particular [Data Validator flavor](./#data-validator-flavors) that you plan on using or are using in your stack for detailed information about how to use it in your ZenML pipelines.
ZenML Scarf
--- ### Book/Component Guide/Data Validators/Custom (docs/book/component-guide/data-validators/custom.md) --- description: How to develop a custom data validator --- # Develop a custom data validator {% hint style="info" %} Before diving into the specifics of this component type, it is beneficial to familiarize yourself with our [general guide to writing custom component flavors in ZenML](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/implement-a-custom-stack-component). This guide provides an essential understanding of ZenML's component flavor concepts. {% endhint %} {% hint style="warning" %} **Base abstraction in progress!** We are actively working on the base abstraction for the Data Validators, which will be available soon. As a result, their extension is not recommended at the moment. When you are selecting a data validator for your stack, you can use one of [the existing flavors](./#data-validator-flavors). If you need to implement your own Data Validator flavor, you can still do so, but keep in mind that you may have to refactor it when the base abstraction is updated. {% endhint %} ZenML comes equipped with [Data Validator implementations](./#data-validator-flavors) that integrate a variety of data logging and validation libraries, frameworks and platforms. However, if you need to use a different library or service as a backend for your ZenML Data Validator, you can extend ZenML to provide your own custom Data Validator implementation. ### Build your own custom data validator If you want to implement your own custom Data Validator, you can follow the following steps: 1. Create a class which inherits from [the `BaseDataValidator` class](https://sdkdocs.zenml.io/latest/core_code_docs/core-data_validators.html#zenml.data_validators.base_data_validator) and override one or more of the abstract methods, depending on the capabilities of the underlying library/service that you want to integrate. 2. If you need any configuration, you can create a class which inherits from the `BaseDataValidatorConfig` class. 3. Bring both of these classes together by inheriting from the `BaseDataValidatorFlavor`. 4. (Optional) You should also provide some standard steps that others can easily insert into their pipelines for instant access to data validation features. Once you are done with the implementation, you can register it through the CLI. Please ensure you **point to the flavor class via dot notation**: ```shell zenml data-validator flavor register ``` For example, if your flavor class `MyDataValidatorFlavor` is defined in `flavors/my_flavor.py`, you'd register it by doing: ```shell zenml data-validator flavor register flavors.my_flavor.MyDataValidatorFlavor ``` {% hint style="warning" %} ZenML resolves the flavor class by taking the path where you initialized zenml (via `zenml init`) as the starting point of resolution. Therefore, please ensure you follow [the best practice](https://docs.zenml.io/user-guides/best-practices/iac) of initializing zenml at the root of your repository. If ZenML does not find an initialized ZenML repository in any parent directory, it will default to the current working directory, but usually it's better to not have to rely on this mechanism, and initialize zenml at the root. {% endhint %} Afterwards, you should see the new flavor in the list of available flavors: ```shell zenml data-validator flavor list ``` {% hint style="warning" %} It is important to draw attention to when and how these base abstractions are coming into play in a ZenML workflow. * The **CustomDataValidatorFlavor** class is imported and utilized upon the creation of the custom flavor through the CLI. * The **CustomDataValidatorConfig** class is imported when someone tries to register/update a stack component with this custom flavor. Especially, during the registration process of the stack component, the config will be used to validate the values given by the user. As `Config` object are inherently `pydantic` objects, you can also add your own custom validators here. * The **CustomDataValidator** only comes into play when the component is ultimately in use. The design behind this interaction lets us separate the configuration of the flavor from its implementation. This way we can register flavors and components even when the major dependencies behind their implementation are not installed in our local setting (assuming the `CustomDataValidatorFlavor` and the `CustomDataValidatorConfig` are implemented in a different module/path than the actual `CustomDataValidator`). {% endhint %}
ZenML Scarf
--- ### Book/Component Guide/Data Validators/Deepchecks (docs/book/component-guide/data-validators/deepchecks.md) --- description: >- How to test the data and models used in your pipelines with Deepchecks test suites --- # Deepchecks The Deepchecks [Data Validator](./) flavor provided with the ZenML integration uses [Deepchecks](https://github.com/deepchecks/deepchecks) to run data integrity, data drift, model drift and model performance tests on the datasets and models circulated in your ZenML pipelines. The test results can be used to implement automated corrective actions in your pipelines or to render interactive representations for further visual interpretation, evaluation and documentation. ### When would you want to use it? Deepchecks is an open-source library that you can use to run a variety of data and model validation tests, from data integrity tests that work with a single dataset to model evaluation tests to data drift analyzes and model performance comparison tests. All this can be done with minimal configuration input from the user, or customized with specialized conditions that the validation tests should perform. Deepchecks works with both tabular data and computer vision data. For tabular, the supported dataset format is `pandas.DataFrame` and the supported model format is `sklearn.base.ClassifierMixin`. For computer vision, the supported dataset format is `torch.utils.data.dataloader.DataLoader` and supported model format is `torch.nn.Module`. You should use the Deepchecks Data Validator when you need the following data and/or model validation features that are possible with Deepchecks: * Data Integrity Checks [for tabular](https://docs.deepchecks.com/stable/tabular/auto_checks/data_integrity/index.html) or [computer vision](https://docs.deepchecks.com/stable/vision/auto_checks/data_integrity/index.html) data: detect data integrity problems within a single dataset (e.g. missing values, conflicting labels, mixed data types etc.). * Data Drift Checks [for tabular](https://docs.deepchecks.com/stable/tabular/auto_checks/train_test_validation/index.html) or [computer vision](https://docs.deepchecks.com/stable/vision/auto_checks/train_test_validation/index.html) data: detect data skew and data drift problems by comparing a target dataset against a reference dataset (e.g. feature drift, label drift, new labels etc.). * Model Performance Checks [for tabular](https://docs.deepchecks.com/stable/tabular/auto_checks/model_evaluation/index.html) or [computer vision](https://docs.deepchecks.com/stable/vision/auto_checks/model_evaluation/index.html) data: evaluate a model and detect problems with its performance (e.g. confusion matrix, boosting overfit, model error analysis) * Multi-Model Performance Reports [for tabular](https://docs.deepchecks.com/stable/tabular/auto_checks/model_evaluation/plot_multi_model_performance_report.html#sphx-glr-tabular-auto-checks-model-evaluation-plot-multi-model-performance-report-py): produce a summary of performance scores for multiple models on test datasets. You should consider one of the other [Data Validator flavors](./#data-validator-flavors) if you need a different set of data validation features. ### How do you deploy it? The Deepchecks Data Validator flavor is included in the Deepchecks ZenML integration, you need to install it on your local machine to be able to register a Deepchecks Data Validator and add it to your stack: ```shell zenml integration install deepchecks -y ``` The Data Validator stack component does not have any configuration parameters. Adding it to a stack is as simple as running e.g.: ```shell # Register the Deepchecks data validator zenml data-validator register deepchecks_data_validator --flavor=deepchecks # Register and set a stack with the new data validator zenml stack register custom_stack -dv deepchecks_data_validator ... --set ``` ### How do you use it? The ZenML integration restructures the way Deepchecks validation checks are organized in four categories, based on the type and number of input parameters that they expect as input. This makes it easier to reason about them when you decide which tests to use in your pipeline steps: * **data integrity checks** expect a single dataset as input. These correspond one-to-one to the set of Deepchecks data integrity checks [for tabular](https://docs.deepchecks.com/stable/tabular/auto_checks/data_integrity/index.html) and [computer vision](https://docs.deepchecks.com/stable/vision/auto_checks/data_integrity/index.html) data * **data drift checks** require two datasets as input: target and reference. These correspond one-to-one to the set of Deepchecks train-test checks [for tabular data](https://docs.deepchecks.com/stable/tabular/auto_checks/train_test_validation/index.html) and [for computer vision](https://docs.deepchecks.com/stable/vision/auto_checks/train_test_validation/index.html). * **model validation checks** require a single dataset and a mandatory model as input. This list includes a subset of the model evaluation checks provided by Deepchecks [for tabular data](https://docs.deepchecks.com/stable/tabular/auto_checks/model_evaluation/index.html) and [for computer vision](https://docs.deepchecks.com/stable/vision/auto_checks/model_evaluation/index.html) that expect a single dataset as input. * **model drift checks** require two datasets and a mandatory model as input. This list includes a subset of the model evaluation checks provided by Deepchecks [for tabular data](https://docs.deepchecks.com/stable/tabular/auto_checks/model_evaluation/index.html) and [for computer vision](https://docs.deepchecks.com/stable/vision/auto_checks/model_evaluation/index.html) that expect two datasets as input: target and reference. This structure is directly reflected in how Deepchecks can be used with ZenML: there are four different Deepchecks standard steps and four different [ZenML enums for Deepchecks checks](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-deepchecks.html) . [The Deepchecks Data Validator API](deepchecks.md#the-deepchecks-data-validator) is also modeled to reflect this same structure. A notable characteristic of Deepchecks is that you don't need to customize the set of Deepchecks tests that are part of a test suite. Both ZenML and Deepchecks provide sane defaults that will run all available Deepchecks tests in a given category with their default conditions if a custom list of tests and conditions are not provided. There are three ways you can use Deepchecks in your ZenML pipelines that allow different levels of flexibility: * instantiate, configure and insert one or more of [the standard Deepchecks steps](deepchecks.md#the-deepchecks-standard-steps) shipped with ZenML into your pipelines. This is the easiest way and the recommended approach, but can only be customized through the supported step configuration parameters. * call the data validation methods provided by [the Deepchecks Data Validator](deepchecks.md#the-deepchecks-data-validator) in your custom step implementation. This method allows for more flexibility concerning what can happen in the pipeline step, but you are still limited to the functionality implemented in the Data Validator. * [use the Deepchecks library directly](deepchecks.md#call-deepchecks-directly) in your custom step implementation. This gives you complete freedom in how you are using Deepchecks' features. You can visualize Deepchecks results in Jupyter notebooks or view them directly in the ZenML dashboard. ### Warning! Usage in remote orchestrators The current ZenML version has a limitation in its base Docker image that requires a workaround for _all_ pipelines using Deepchecks with a remote orchestrator (e.g. [Kubeflow](https://docs.zenml.io/stacks/orchestrators/kubeflow) , [Vertex](https://docs.zenml.io/stacks/orchestrators/vertex)). The limitation being that the base Docker image needs to be extended to include binaries that are required by `opencv2`, which is a package that Deepchecks requires. While these binaries might be available on most operating systems out of the box (and therefore not a problem with the default local orchestrator), we need to tell ZenML to add them to the containerization step when running in remote settings. Here is how: First, create a file called `deepchecks-zenml.Dockerfile` and place it on the same level as your runner script (commonly called `run.py`). The contents of the Dockerfile are as follows: ```shell ARG ZENML_VERSION=0.20.0 FROM zenmldocker/zenml:${ZENML_VERSION} AS base RUN apt-get update RUN apt-get install ffmpeg libsm6 libxext6 -y ``` Then, place the following snippet above your pipeline definition. Note that the path of the `dockerfile` are relative to where the pipeline definition file is. Read [the containerization guide](https://docs.zenml.io/how-to/customize-docker-builds/) for more details: ```python import zenml from zenml import pipeline from zenml.config import DockerSettings from pathlib import Path import sys docker_settings = DockerSettings( dockerfile="deepchecks-zenml.Dockerfile", build_options={ "buildargs": { "ZENML_VERSION": f"{zenml.__version__}" }, }, ) @pipeline(settings={"docker": docker_settings}) def my_pipeline(...): # same code as always ... ``` From here on, you can continue to use the deepchecks integration as is explained below. #### The Deepchecks standard steps ZenML wraps the Deepchecks functionality for tabular data in the form of four standard steps: * [`deepchecks_data_integrity_check_step`](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-deepchecks.html#zenml.integrations.deepchecks): use it in your pipelines to run data integrity tests on a single dataset * [`deepchecks_data_drift_check_step`](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-deepchecks.html#zenml.integrations.deepchecks): use it in your pipelines to run data drift tests on two datasets as input: target and reference. * [`deepchecks_model_validation_check_step`](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-deepchecks.html#zenml.integrations.deepchecks): use it in your pipelines to run model performance tests using a single dataset and a mandatory model artifact as input * [`deepchecks_model_drift_check_step`](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-deepchecks.html#zenml.integrations.deepchecks): use it in your pipelines to run model comparison/drift tests using a mandatory model artifact and two datasets as input: target and reference. The integration doesn't yet include standard steps for computer vision, but you can still write your own custom steps that call [the Deepchecks Data Validator API](deepchecks.md#the-deepchecks-data-validator) or even [call the Deepchecks library directly](deepchecks.md#call-deepchecks-directly). All four standard steps behave similarly regarding the configuration parameters and returned artifacts, with the following differences: * the type and number of input artifacts are different, as mentioned above * each step expects a different enum data type to be used when explicitly listing the checks to be performed via the `check_list` configuration attribute. See the [`zenml.integrations.deepchecks.validation_checks`](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-deepchecks.html) module for more details about these enums (e.g. the data integrity step expects a list of `DeepchecksDataIntegrityCheck` values). This section will only cover how you can use the data integrity step, with a similar usage to be easily inferred for the other three steps. To instantiate a data integrity step that will run all available Deepchecks data integrity tests with their default configuration, e.g.: ```python from zenml.integrations.deepchecks.steps import ( deepchecks_data_integrity_check_step, ) data_validator = deepchecks_data_integrity_check_step.with_options( parameters=dict( dataset_kwargs=dict(label="target", cat_features=[]), ), ) ``` The step can then be inserted into your pipeline where it can take in a dataset, e.g.: ```python docker_settings = DockerSettings(required_integrations=[DEEPCHECKS, SKLEARN]) @pipeline(settings={"docker": docker_settings}) def data_validation_pipeline(): df_train, df_test = data_loader() data_validator(dataset=df_train) data_validation_pipeline() ``` As can be seen from the step definition, the step takes in a dataset and it returns a Deepchecks `SuiteResult` object that contains the test results: ```python @step def deepchecks_data_integrity_check_step( dataset: pd.DataFrame, check_list: Optional[Sequence[DeepchecksDataIntegrityCheck]] = None, dataset_kwargs: Optional[Dict[str, Any]] = None, check_kwargs: Optional[Dict[str, Any]] = None, run_kwargs: Optional[Dict[str, Any]] = None, ) -> SuiteResult: ... ``` If needed, you can specify a custom list of data integrity Deepchecks tests to be executed by supplying a `check_list` argument: ```python from zenml.integrations.deepchecks.validation_checks import DeepchecksDataIntegrityCheck from zenml.integrations.deepchecks.steps import deepchecks_data_integrity_check_step @pipeline def validation_pipeline(): deepchecks_data_integrity_check_step( check_list=[ DeepchecksDataIntegrityCheck.TABULAR_MIXED_DATA_TYPES, DeepchecksDataIntegrityCheck.TABULAR_DATA_DUPLICATES, DeepchecksDataIntegrityCheck.TABULAR_CONFLICTING_LABELS, ], dataset=... ) ``` You should consult [the official Deepchecks documentation](https://docs.deepchecks.com/stable/tabular/auto_checks/data_integrity/index.html) for more information on what each test is useful for. For more customization, the data integrity step also allows for additional keyword arguments to be supplied to be passed transparently to the Deepchecks library: * `dataset_kwargs`: Additional keyword arguments to be passed to the Deepchecks `tabular.Dataset` or `vision.VisionData` constructor. This is used to pass additional information about how the data is structured, e.g.: ```python deepchecks_data_integrity_check_step( dataset_kwargs=dict(label='class', cat_features=['country', 'state']), ... ) ``` * `check_kwargs`: Additional keyword arguments to be passed to the Deepchecks check object constructors. Arguments are grouped for each check and indexed using the full check class name or check enum value as dictionary keys, e.g.: ```python deepchecks_data_integrity_check_step( check_list=[ DeepchecksDataIntegrityCheck.TABULAR_OUTLIER_SAMPLE_DETECTION, DeepchecksDataIntegrityCheck.TABULAR_STRING_LENGTH_OUT_OF_BOUNDS, DeepchecksDataIntegrityCheck.TABULAR_STRING_MISMATCH, ], check_kwargs={ DeepchecksDataIntegrityCheck.TABULAR_OUTLIER_SAMPLE_DETECTION: dict( nearest_neighbors_percent=0.01, extent_parameter=3, ), DeepchecksDataIntegrityCheck.TABULAR_STRING_LENGTH_OUT_OF_BOUNDS: dict( num_percentiles=1000, min_unique_values=3, ), }, ... ) ``` * `run_kwargs`: Additional keyword arguments to be passed to the Deepchecks Suite `run` method. The `check_kwargs` attribute can also be used to customize [the conditions](https://docs.deepchecks.com/stable/general/usage/customizations/auto_examples/plot_configure_check_conditions.html#configure-check-conditions) configured for each Deepchecks test. ZenML attaches a special meaning to all check arguments that start with `condition_` and have a dictionary as value. This is required because there is no declarative way to specify conditions for Deepchecks checks. For example, the following step configuration: ```python deepchecks_data_integrity_check_step( check_list=[ DeepchecksDataIntegrityCheck.TABULAR_OUTLIER_SAMPLE_DETECTION, DeepchecksDataIntegrityCheck.TABULAR_STRING_LENGTH_OUT_OF_BOUNDS, ], dataset_kwargs=dict(label='class', cat_features=['country', 'state']), check_kwargs={ DeepchecksDataIntegrityCheck.TABULAR_OUTLIER_SAMPLE_DETECTION: dict( nearest_neighbors_percent=0.01, extent_parameter=3, condition_outlier_ratio_less_or_equal=dict( max_outliers_ratio=0.007, outlier_score_threshold=0.5, ), condition_no_outliers=dict( outlier_score_threshold=0.6, ) ), DeepchecksDataIntegrityCheck.TABULAR_STRING_LENGTH_OUT_OF_BOUNDS: dict( num_percentiles=1000, min_unique_values=3, condition_number_of_outliers_less_or_equal=dict( max_outliers=3, ) ), }, ... ) ``` is equivalent to running the following Deepchecks tests: ```python import deepchecks.tabular.checks as tabular_checks from deepchecks.tabular import Suite from deepchecks.tabular import Dataset train_dataset = Dataset( reference_dataset, label='class', cat_features=['country', 'state'] ) suite = Suite(name="custom") check = tabular_checks.OutlierSampleDetection( nearest_neighbors_percent=0.01, extent_parameter=3, ) check.add_condition_outlier_ratio_less_or_equal( max_outliers_ratio=0.007, outlier_score_threshold=0.5, ) check.add_condition_no_outliers( outlier_score_threshold=0.6, ) suite.add(check) check = tabular_checks.StringLengthOutOfBounds( num_percentiles=1000, min_unique_values=3, ) check.add_condition_number_of_outliers_less_or_equal( max_outliers=3, ) suite.run(train_dataset=train_dataset) ``` #### The Deepchecks Data Validator The Deepchecks Data Validator implements the same interface as do all Data Validators, so this method forces you to maintain some level of compatibility with the overall Data Validator abstraction, which guarantees an easier migration in case you decide to switch to another Data Validator. All you have to do is call the Deepchecks Data Validator methods when you need to interact with Deepchecks to run tests, e.g.: ```python import pandas as pd from deepchecks.core.suite import SuiteResult from zenml.integrations.deepchecks.data_validators import DeepchecksDataValidator from zenml.integrations.deepchecks.validation_checks import DeepchecksDataIntegrityCheck from zenml import step @step def data_integrity_check( dataset: pd.DataFrame, ) -> SuiteResult: """Custom data integrity check step with Deepchecks Args: dataset: input Pandas DataFrame Returns: Deepchecks test suite execution result """ # validation pre-processing (e.g. dataset preparation) can take place here data_validator = DeepchecksDataValidator.get_active_data_validator() suite = data_validator.data_validation( dataset=dataset, check_list=[ DeepchecksDataIntegrityCheck.TABULAR_OUTLIER_SAMPLE_DETECTION, DeepchecksDataIntegrityCheck.TABULAR_STRING_LENGTH_OUT_OF_BOUNDS, ], ) # validation post-processing (e.g. interpret results, take actions) can happen here return suite ``` The arguments that the Deepchecks Data Validator methods can take in are the same as those used for [the Deepchecks standard steps](deepchecks.md#the-deepchecks-standard-steps). Have a look at [the complete list of methods and parameters available in the `DeepchecksDataValidator` API](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-deepchecks.html#zenml.integrations.deepchecks) in the SDK docs. #### Call Deepchecks directly You can use the Deepchecks library directly in your custom pipeline steps, and only leverage ZenML's capability of serializing, versioning and storing the `SuiteResult` objects in its Artifact Store, e.g.: ```python import pandas as pd import deepchecks.tabular.checks as tabular_checks from deepchecks.core.suite import SuiteResult from deepchecks.tabular import Suite from deepchecks.tabular import Dataset from zenml import step @step def data_integrity_check( dataset: pd.DataFrame, ) -> SuiteResult: """Custom data integrity check step with Deepchecks Args: dataset: a Pandas DataFrame Returns: Deepchecks test suite execution result """ # validation pre-processing (e.g. dataset preparation) can take place here train_dataset = Dataset( dataset, label='class', cat_features=['country', 'state'] ) suite = Suite(name="custom") check = tabular_checks.OutlierSampleDetection( nearest_neighbors_percent=0.01, extent_parameter=3, ) check.add_condition_outlier_ratio_less_or_equal( max_outliers_ratio=0.007, outlier_score_threshold=0.5, ) suite.add(check) check = tabular_checks.StringLengthOutOfBounds( num_percentiles=1000, min_unique_values=3, ) check.add_condition_number_of_outliers_less_or_equal( max_outliers=3, ) results = suite.run(train_dataset=train_dataset) # validation post-processing (e.g. interpret results, take actions) can happen here return results ``` #### Visualizing Deepchecks Suite Results You can view visualizations of the suites and results generated by your pipeline steps directly in the ZenML dashboard by clicking on the respective artifact in the pipeline run DAG. Alternatively, if you are running inside a Jupyter notebook, you can load and render the suites and results using the [artifact.visualize() method](https://docs.zenml.io/how-to/data-artifact-management/visualize-artifacts/), e.g.: ```python from zenml.client import Client def visualize_results(pipeline_name: str, step_name: str) -> None: pipeline = Client().get_pipeline(pipeline=pipeline_name) last_run = pipeline.last_run step = last_run.steps[step_name] step.visualize() if __name__ == "__main__": visualize_results("data_validation_pipeline", "data_integrity_check") ```
ZenML Scarf
--- ### Book/Component Guide/Data Validators/Evidently (docs/book/component-guide/data-validators/evidently.md) --- description: >- How to keep your data quality in check and guard against data and model drift with Evidently profiling --- # Evidently The Evidently [Data Validator](./) flavor provided with the ZenML integration uses [Evidently](https://evidentlyai.com/) to perform data quality, data drift, model drift and model performance analyzes, to generate reports and run checks. The reports and check results can be used to implement automated corrective actions in your pipelines or to render interactive representations for further visual interpretation, evaluation and documentation. ### When would you want to use it? [Evidently](https://evidentlyai.com/) is an open-source library that you can use to monitor and debug machine learning models by analyzing the data that they use through a powerful set of data profiling and visualization features, or to run a variety of data and model validation reports and tests, from data integrity tests that work with a single dataset to model evaluation tests to data drift analysis and model performance comparison tests. All this can be done with minimal configuration input from the user, or customized with specialized conditions that the validation tests should perform. Evidently currently works with tabular data in `pandas.DataFrame` or CSV file formats and can handle both regression and classification tasks. You should use the Evidently Data Validator when you need the following data and/or model validation features that are possible with Evidently: * [Data Quality](https://docs.evidentlyai.com/metrics/preset_data_summary) reports and tests: provides detailed feature statistics and a feature behavior overview for a single dataset. It can also compare any two datasets. E.g. you can use it to compare train and test data, reference and current data, or two subgroups of one dataset. * [Data Drift](https://docs.evidentlyai.com/metrics/preset_data_drift) reports and tests: helps detects and explore feature distribution changes in the input data by comparing two datasets with identical schema. * [Target Drift](https://docs.evidentlyai.com/metrics/preset_data_drift) reports and tests: helps detect and explore changes in the target function and/or model predictions by comparing two datasets where the target and/or prediction columns are available. * [Regression Performance](https://docs.evidentlyai.com/metrics/preset_regression) or [Classification Performance](https://docs.evidentlyai.com/metrics/preset_classification) reports and tests: evaluate the performance of a model by analyzing a single dataset where both the target and prediction columns are available. It can also compare it to the past performance of the same model, or the performance of an alternative model by providing a second dataset. You should consider one of the other [Data Validator flavors](./#data-validator-flavors) if you need a different set of data validation features. ### How do you deploy it? The Evidently Data Validator flavor is included in the Evidently ZenML integration, you need to install it on your local machine to be able to register an Evidently Data Validator and add it to your stack: ```shell zenml integration install evidently -y ``` The Data Validator stack component does not have any configuration parameters. Adding it to a stack is as simple as running e.g.: ```shell # Register the Evidently data validator zenml data-validator register evidently_data_validator --flavor=evidently # Register and set a stack with the new data validator zenml stack register custom_stack -dv evidently_data_validator ... --set ``` ### How do you use it? #### Data Profiling Evidently's profiling functions take in a `pandas.DataFrame` dataset or a pair of datasets and generate results in the form of a `Report` object. One of Evidently's notable characteristics is that it only requires datasets as input. Even when running model performance comparison analyzes, no model needs to be present. However, that does mean that the input data needs to include additional `target` and `prediction` columns for some profiling reports and, you have to include additional information about the dataset columns in the form of [column mappings](https://docs.evidentlyai.com/docs/library/data_definition). Depending on how your data is structured, you may also need to include additional steps in your pipeline before the data validation step to insert the additional `target` and `prediction` columns into your data. This may also require interacting with one or more models. There are three ways you can use Evidently to generate data reports in your ZenML pipelines that allow different levels of flexibility: * instantiate, configure and insert the standard Evidently report step shipped with ZenML into your pipelines. This is the easiest way and the recommended approach. * call the data validation methods provided by [the Evidently Data Validator](evidently.md#the-evidently-data-validator) in your custom step implementation. This method allows for more flexibility concerning what can happen in the pipeline step. * [use the Evidently library directly](evidently.md#call-evidently-directly) in your custom step implementation. This gives you complete freedom in how you are using Evidently's features. You can [visualize Evidently reports](evidently.md#visualizing-evidently-reports) in Jupyter notebooks or view them directly in the ZenML dashboard by clicking on the respective artifact in the pipeline run DAG. **The Evidently Report step** ZenML wraps the Evidently data profiling functionality in the form of a standard Evidently report pipeline step that you can simply instantiate and insert in your pipeline. Here you can see how instantiating and configuring the standard Evidently report step can be done: ```python from zenml.integrations.evidently.metrics import EvidentlyMetricConfig from zenml.integrations.evidently.steps import ( EvidentlyColumnMapping, evidently_report_step, ) text_data_report = evidently_report_step.with_options( parameters=dict( column_mapping=EvidentlyColumnMapping( target="Rating", numerical_features=["Age", "Positive_Feedback_Count"], categorical_features=[ "Division_Name", "Department_Name", "Class_Name", ], text_features=["Review_Text", "Title"], ), metrics=[ EvidentlyMetricConfig.metric("DataQualityPreset"), EvidentlyMetricConfig.metric( "TextOverviewPreset", column_name="Review_Text" ), EvidentlyMetricConfig.metric_generator( "ColumnRegExpMetric", columns=["Review_Text", "Title"], reg_exp=r"[A-Z][A-Za-z0-9 ]*", ), ], # We need to download the NLTK data for the TextOverviewPreset download_nltk_data=True, ), ) ``` The configuration shown in the example is the equivalent of running the following Evidently code inside the step: ```python from evidently.legacy.metrics import ColumnRegExpMetric from evidently.legacy.metric_preset import DataQualityPreset, TextOverviewPreset from evidently.legacy.pipeline.column_mapping import ColumnMapping from evidently.legacy.report import Report from evidently.legacy.metrics.base_metric import generate_column_metrics import nltk nltk.download("words") nltk.download("wordnet") nltk.download("omw-1.4") column_mapping = ColumnMapping( target="Rating", numerical_features=["Age", "Positive_Feedback_Count"], categorical_features=[ "Division_Name", "Department_Name", "Class_Name", ], text_features=["Review_Text", "Title"], ) report = Report( metrics=[ DataQualityPreset(), TextOverviewPreset(column_name="Review_Text"), generate_column_metrics( ColumnRegExpMetric, columns=["Review_Text", "Title"], parameters={"reg_exp": r"[A-Z][A-Za-z0-9 ]*"} ) ] ) # The datasets are those that are passed to the Evidently step # as input artifacts report.run( current_data=current_dataset, reference_data=reference_dataset, column_mapping=column_mapping, ) ``` Let's break this down... We configure the `evidently_report_step` using parameters that you would normally pass to the Evidently `Report` object to [configure and run an Evidently report](https://docs.evidentlyai.com/docs/library/report). It consists of the following fields: * `column_mapping`: This is an `EvidentlyColumnMapping` object that is the exact equivalent of [the `ColumnMapping` object in Evidently](https://docs.evidentlyai.com/docs/library/data_definition). It is used to describe the columns in the dataset and how they should be treated (e.g. as categorical, numerical, or text features). * `metrics`: This is a list of `EvidentlyMetricConfig` objects that are used to configure the metrics that should be used to generate the report in a declarative way. This is the same as configuring the `metrics` that go in the Evidently `Report`. * `download_nltk_data`: This is a boolean that is used to indicate whether the NLTK data should be downloaded. This is only needed if you are using Evidently reports that handle text data, which require the NLTK data to be downloaded ahead of time. There are several ways you can reference the Evidently metrics when configuring `EvidentlyMetricConfig` items: * by class name: this is the easiest way to reference an Evidently metric. You can use the name of a metric or metric preset class as it appears in the Evidently documentation (e.g.`"DataQualityPreset"`, `"DatasetDriftMetric"`). * by full class path: you can also use the full Python class path of the metric or metric preset class ( e.g. `"evidently.legacy.metric_preset.DataQualityPreset"`, `"evidently.legacy.metrics.DatasetDriftMetric"`). This is useful if you want to use metrics or metric presets that are not included in Evidently library. * by passing in the class itself: you can also import and pass in an Evidently metric or metric preset class itself, e.g.: ```python from evidently.legacy.metrics import DatasetDriftMetric ... evidently_report_step.with_options( parameters=dict( metrics=[EvidentlyMetricConfig.metric(DatasetDriftMetric)] ), ) ``` As can be seen in the example, there are two basic ways of adding metrics to your Evidently report step configuration: * to add a single metric or metric preset: call `EvidentlyMetricConfig.metric` with an Evidently metric or metric preset class name (or class path or class). The rest of the parameters are the same ones that you would usually pass to the Evidently metric or metric preset class constructor. * to generate multiple metrics, similar to calling [the Evidently column metric generator](https://docs.evidentlyai.com/docs/library/metric_generator): call `EvidentlyMetricConfig.metric_generator` with an Evidently metric or metric preset class name (or class path or class) and a list of column names. The rest of the parameters are the same ones that you would usually pass to the Evidently metric or metric preset class constructor. The ZenML Evidently report step can then be inserted into your pipeline where it can take in two datasets and outputs the Evidently report generated in both JSON and HTML formats, e.g.: ```python from zenml import pipeline from zenml.config import DockerSettings # Note: docker_settings would be defined elsewhere # Note: data_loader, data_splitter, text_data_report, text_data_test, text_analyzer would be custom step functions @pipeline(enable_cache=False, settings={"docker": docker_settings}) def text_data_report_test_pipeline(): """Links all the steps together in a pipeline.""" data = data_loader() reference_dataset, comparison_dataset = data_splitter(data) report, _ = text_data_report( reference_dataset=reference_dataset, comparison_dataset=comparison_dataset, ) test_report, _ = text_data_test( reference_dataset=reference_dataset, comparison_dataset=comparison_dataset, ) text_analyzer(report) text_data_report_test_pipeline() ``` For a version of the same step that works with a single dataset, simply don't pass any comparison dataset: ```python text_data_report(reference_dataset=reference_dataset) ``` You should consult [the official Evidently documentation](https://docs.evidentlyai.com/metrics/all_metrics) for more information on what each metric is useful for and what data columns it requires as input. The `evidently_report_step` step also allows for additional Report [options](https://docs.evidentlyai.com/metrics/customize_metric) to be passed to the `Report` constructor e.g.: ```python from zenml.integrations.evidently.steps import ( EvidentlyColumnMapping, ) text_data_report = evidently_report_step.with_options( parameters=dict( report_options = [ ( "evidently.legacy.options.ColorOptions", { "primary_color": "#5a86ad", "fill_color": "#fff4f2", "zero_line_color": "#016795", "current_data_color": "#c292a1", "reference_data_color": "#017b92", } ), ], ) ) ``` You can view [the complete list of configuration parameters](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-evidently.html#zenml.integrations.evidently) in the SDK docs. #### Data Validation Aside from data profiling, Evidently can also be used to configure and run automated data validation tests on your data. Similar to using Evidently through ZenML to run data profiling, there are three ways you can use Evidently to run data validation tests in your ZenML pipelines that allow different levels of flexibility: * instantiate, configure and insert [the standard Evidently test step](evidently.md) shipped with ZenML into your pipelines. This is the easiest way and the recommended approach. * call the data validation methods provided by [the Evidently Data Validator](evidently.md#the-evidently-data-validator) in your custom step implementation. This method allows for more flexibility concerning what can happen in the pipeline step. * [use the Evidently library directly](evidently.md#call-evidently-directly) in your custom step implementation. This gives you complete freedom in how you are using Evidently's features. You can visualize Evidently reports in Jupyter notebooks or view them directly in the ZenML dashboard by clicking on the respective artifact in the pipeline run DAG. You can [visualize Evidently reports](evidently.md#visualizing-evidently-reports) in Jupyter notebooks or view them directly in the ZenML dashboard by clicking on the respective artifact in the pipeline run DAG. ZenML wraps the Evidently data validation functionality in the form of a standard Evidently test pipeline step that you can simply instantiate and insert in your pipeline. Here you can see how instantiating and configuring the standard Evidently test step can be done using our included `evidently_test_step` utility function: ```python from zenml.integrations.evidently.steps import ( EvidentlyColumnMapping, evidently_test_step, ) from zenml.integrations.evidently.tests import EvidentlyTestConfig text_data_test = evidently_test_step.with_options( parameters=dict( column_mapping=EvidentlyColumnMapping( target="Rating", numerical_features=["Age", "Positive_Feedback_Count"], categorical_features=[ "Division_Name", "Department_Name", "Class_Name", ], text_features=["Review_Text", "Title"], ), tests=[ EvidentlyTestConfig.test("DataQualityTestPreset"), EvidentlyTestConfig.test_generator( "TestColumnRegExp", columns=["Review_Text", "Title"], reg_exp=r"[A-Z][A-Za-z0-9 ]*", ), ], # We need to download the NLTK data for the TestColumnRegExp test download_nltk_data=True, ), ) ``` The configuration shown in the example is the equivalent of running the following Evidently code inside the step: ```python from evidently.legacy.tests import TestColumnRegExp from evidently.legacy.test_preset import DataQualityTestPreset from evidently.legacy.pipeline.column_mapping import ColumnMapping from evidently.legacy.test_suite import TestSuite from evidently.legacy.tests.base_test import generate_column_tests import nltk nltk.download("words") nltk.download("wordnet") nltk.download("omw-1.4") column_mapping = ColumnMapping( target="Rating", numerical_features=["Age", "Positive_Feedback_Count"], categorical_features=[ "Division_Name", "Department_Name", "Class_Name", ], text_features=["Review_Text", "Title"], ) test_suite = TestSuite( tests=[ DataQualityTestPreset(), generate_column_tests( TestColumnRegExp, columns=["Review_Text", "Title"], parameters={"reg_exp": r"[A-Z][A-Za-z0-9 ]*"} ) ] ) # The datasets are those that are passed to the Evidently step # as input artifacts test_suite.run( current_data=current_dataset, reference_data=reference_dataset, column_mapping=column_mapping, ) ``` Let's break this down... We configure the `evidently_test_step` using parameters that you would normally pass to the Evidently `TestSuite` object to [configure and run an Evidently test suite](https://docs.evidentlyai.com/docs/library/tests) . It consists of the following fields: * `column_mapping`: This is an `EvidentlyColumnMapping` object that is the exact equivalent of [the `ColumnMapping` object in Evidently](https://docs.evidentlyai.com/docs/library/data_definition). It is used to describe the columns in the dataset and how they should be treated (e.g. as categorical, numerical, or text features). * `tests`: This is a list of `EvidentlyTestConfig` objects that are used to configure the tests that will be run as part of your test suite in a declarative way. This is the same as configuring the `tests` that go in the Evidently `TestSuite`. * `download_nltk_data`: This is a boolean that is used to indicate whether the NLTK data should be downloaded. This is only needed if you are using Evidently tests or test presets that handle text data, which require the NLTK data to be downloaded ahead of time. There are several ways you can reference the Evidently tests when configuring `EvidentlyTestConfig` items, similar to how you reference them in an `EvidentlyMetricConfig` object: * by class name: this is the easiest way to reference an Evidently test. You can use the name of a test or test preset class as it appears in the Evidently documentation (e.g.`"DataQualityTestPreset"`, `"TestColumnRegExp"`). * by full class path: you can also use the full Python class path of the test or test preset class ( e.g. `"evidently.legacy.test_preset.DataQualityTestPreset"`, `"evidently.legacy.tests.TestColumnRegExp"`). This is useful if you want to use tests or test presets that are not included in Evidently library. * by passing in the class itself: you can also import and pass in an Evidently test or test preset class itself, e.g.: ```python from evidently.legacy.tests import TestColumnRegExp ... evidently_test_step.with_options( parameters=dict( tests=[EvidentlyTestConfig.test(TestColumnRegExp)] ), ) ``` As can be seen in the example, there are two basic ways of adding tests to your Evidently test step configuration: * to add a single test or test preset: call `EvidentlyTestConfig.test` with an Evidently test or test preset class name (or class path or class). The rest of the parameters are the same ones that you would usually pass to the Evidently test or test preset class constructor. * to generate multiple tests, similar to calling [the Evidently column test generator](https://docs.evidentlyai.com/docs/library/metric_generator): call `EvidentlyTestConfig.test_generator` with an Evidently test or test preset class name (or class path or class) and a list of column names. The rest of the parameters are the same ones that you would usually pass to the Evidently test or test preset class constructor. The ZenML Evidently test step can then be inserted into your pipeline where it can take in two datasets and outputs the Evidently test suite results generated in both JSON and HTML formats, e.g.: ```python @pipeline(enable_cache=False, settings={"docker": docker_settings}) def text_data_test_pipeline(): """Links all the steps together in a pipeline.""" data = data_loader() reference_dataset, comparison_dataset = data_splitter(data) json_report, html_report = text_data_test( reference_dataset=reference_dataset, comparison_dataset=comparison_dataset, ) text_data_test_pipeline() ``` For a version of the same step that works with a single dataset, simply don't pass any comparison dataset: ```python text_data_test(reference_dataset=reference_dataset) ``` You should consult [the official Evidently documentation](https://docs.evidentlyai.com/docs/library/tests) for more information on what each test is useful for and what data columns it requires as input. The `evidently_test_step` step also allows for additional Test [options](https://docs.evidentlyai.com/metrics/customize_metric) to be passed to the `TestSuite` constructor e.g.: ```python from zenml.integrations.evidently.steps import ( EvidentlyColumnMapping, ) text_data_test = evidently_test_step.with_options( parameters=dict( test_options = [ ( "evidently.legacy.options.ColorOptions", { "primary_color": "#5a86ad", "fill_color": "#fff4f2", "zero_line_color": "#016795", "current_data_color": "#c292a1", "reference_data_color": "#017b92", } ), ], ), ) ``` You can view [the complete list of configuration parameters](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-evidently.html#zenml.integrations.evidently) in the SDK docs. #### The Evidently Data Validator The Evidently Data Validator implements the same interface as do all Data Validators, so this method forces you to maintain some level of compatibility with the overall Data Validator abstraction, which guarantees an easier migration in case you decide to switch to another Data Validator. All you have to do is call the Evidently Data Validator methods when you need to interact with Evidently to generate data reports or to run test suites, e.g.: ``` /* Detailed source-code truncated for AI context efficiency. */ ``` Have a look at [the complete list of methods and parameters available in the `EvidentlyDataValidator` API](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-evidently.html#zenml.integrations.evidently) in the SDK docs. #### Call Evidently directly You can use the Evidently library directly in your custom pipeline steps, e.g.: ``` /* Detailed source-code truncated for AI context efficiency. */ ``` ### Visualizing Evidently Reports You can view visualizations of the Evidently reports generated by your pipeline steps directly in the ZenML dashboard by clicking on the respective artifact in the pipeline run DAG. Alternatively, if you are running inside a Jupyter notebook, you can load and render the reports using the [artifact.visualize() method](https://docs.zenml.io/how-to/data-artifact-management/visualize-artifacts/), e.g.: ```python from zenml.client import Client def visualize_results(pipeline_name: str, step_name: str) -> None: pipeline = Client().get_pipeline(pipeline=pipeline_name) evidently_step = pipeline.last_run.steps[step_name] evidently_step.visualize() if __name__ == "__main__": visualize_results("text_data_report_pipeline", "text_report") visualize_results("text_data_test_pipeline", "text_test") ```
ZenML Scarf
--- ### Book/Component Guide/Data Validators/Great Expectations (docs/book/component-guide/data-validators/great-expectations.md) --- description: >- How to use Great Expectations to run data quality checks in your pipelines and document the results --- # Great Expectations The Great Expectations [Data Validator](./) flavor provided with the ZenML integration uses [Great Expectations](https://greatexpectations.io/) to run data profiling and data quality tests on the data circulated through your pipelines. The test results can be used to implement automated corrective actions in your pipelines. They are also automatically rendered into documentation for further visual interpretation and evaluation. ### When would you want to use it? [Great Expectations](https://greatexpectations.io/) is an open-source library that helps keep the quality of your data in check through data testing, documentation, and profiling, and to improve communication and observability. Great Expectations works with tabular data in a variety of formats and data sources, of which ZenML currently supports only `pandas.DataFrame` as part of its pipelines. You should use the Great Expectations Data Validator when you need the following data validation features that are possible with Great Expectations: * [Data Profiling](https://docs.greatexpectations.io/docs/oss/guides/expectations/creating_custom_expectations/how_to_add_support_for_the_auto_initializing_framework_to_a_custom_expectation/#build-a-custom-profiler-for-your-expectation): generates a set of validation rules (Expectations) automatically by inferring them from the properties of an input dataset. * [Data Quality](https://docs.greatexpectations.io/docs/oss/guides/validation/checkpoints/how_to_pass_an_in_memory_dataframe_to_a_checkpoint/): runs a set of predefined or inferred validation rules (Expectations) against an in-memory dataset. * [Data Docs](https://docs.greatexpectations.io/docs/reference/learn/terms/data_docs_store/): generate and maintain human-readable documentation of all your data validation rules, data quality checks and their results. You should consider one of the other [Data Validator flavors](./#data-validator-flavors) if you need a different set of data validation features. ### How do you deploy it? The Great Expectations Data Validator flavor is included in the Great Expectations ZenML integration, you need to install it on your local machine to be able to register a Great Expectations Data Validator and add it to your stack: ```shell zenml integration install great_expectations -y ``` Depending on how you configure the Great Expectations Data Validator, it can reduce or even completely eliminate the complexity associated with setting up the store backends for Great Expectations. If you're only looking for a quick and easy way of adding Great Expectations to your stack and are not concerned with the configuration details, you can simply run: ```shell # Register the Great Expectations data validator zenml data-validator register ge_data_validator --flavor=great_expectations # Register and set a stack with the new data validator zenml stack register custom_stack -dv ge_data_validator ... --set ``` If you already have a Great Expectations deployment, you can configure the Great Expectations Data Validator to reuse or even replace your current configuration. You should consider the pros and cons of every deployment use-case and choose the one that best fits your needs: 1. let ZenML initialize and manage the Great Expectations configuration. The Artifact Store will serve as a storage backend for all the information that Great Expectations needs to persist (e.g. Expectation Suites, Validation Results). However, you will not be able to setup new Data Sources, Metadata Stores or Data Docs sites. Any changes you try and make to the configuration through code will not be persisted and will be lost when your pipeline completes or your local process exits. 2. use ZenML with your existing Great Expectations configuration. You can tell ZenML to replace your existing Metadata Stores with the active ZenML Artifact Store by setting the `configure_zenml_stores` attribute in the Data Validator. The downside is that you will only be able to run pipelines locally with this setup, given that the Great Expectations configuration is a file on your local machine. 3. migrate your existing Great Expectations configuration to ZenML. This is a compromise between 1. and 2. that allows you to continue to use your existing Data Sources, Metadata Stores and Data Docs sites even when running pipelines remotely. {% hint style="warning" %} Some Great Expectations CLI commands will not work well with the deployment methods that puts ZenML in charge of your Great Expectations configuration (i.e. 1. and 3.). You will be required to use Python code to manage your Expectations and you will have to edit the Jupyter notebooks generated by the Great Expectations CLI to connect them to your ZenML managed configuration. . {% endhint %} {% tabs %} {% tab title="Let ZenML Manage The Configuration" %} The default Data Validator setup plugs Great Expectations directly into the [Artifact Store](https://docs.zenml.io/stacks/artifact-stores/) component that is part of the same stack. As a result, the Expectation Suites, Validation Results and Data Docs are stored in the ZenML Artifact Store and you don't have to configure Great Expectations at all, ZenML takes care of that for you: ```shell # Register the Great Expectations data validator zenml data-validator register ge_data_validator --flavor=great_expectations # Register and set a stack with the new data validator zenml stack register custom_stack -dv ge_data_validator ... --set ``` {% endtab %} {% tab title="Use Your Own Configuration" %} If you have an existing Great Expectations configuration that you would like to reuse with your ZenML pipelines, the Data Validator allows you to do so. All you need is to point it to the folder where your local `great_expectations.yaml` configuration file is located: ```shell # Register the Great Expectations data validator zenml data-validator register ge_data_validator --flavor=great_expectations \ --context_root_dir=/path/to/my/great_expectations # Register and set a stack with the new data validator zenml stack register custom_stack -dv ge_data_validator ... --set ``` You can continue to edit your local Great Expectations configuration (e.g. add new Data Sources, update the Metadata Stores etc.) and these changes will be visible in your ZenML pipelines. You can also use the Great Expectations CLI as usual to manage your configuration and your Expectations. {% endtab %} {% tab title="Migrate Your Configuration to ZenML" %} This deployment method migrates your existing Great Expectations configuration to ZenML and allows you to use it with local as well as remote orchestrators. You have to load the Great Expectations configuration contents in one of the Data Validator configuration parameters using the `@` operator, e.g.: ```shell # Register the Great Expectations data validator zenml data-validator register ge_data_validator --flavor=great_expectations \ --context_config=@/path/to/my/great_expectations/great_expectations.yaml # Register and set a stack with the new data validator zenml stack register custom_stack -dv ge_data_validator ... --set ``` When you are migrating your existing Great Expectations configuration to ZenML, keep in mind that the Metadata Stores that you configured there will also need to be accessible from the location where pipelines are running. For example, you cannot use a non-local orchestrator with a Great Expectations Metadata Store that is located on your filesystem. {% endtab %} {% endtabs %} #### Advanced Configuration The Great Expectations Data Validator has a few advanced configuration attributes that might be useful for your particular use-case: * `configure_zenml_stores`: if set, ZenML will automatically update the Great Expectation configuration to include Metadata Stores that use the Artifact Store as a backend. If neither `context_root_dir` nor `context_config` are set, this is the default behavior. You can set this flag to use the ZenML Artifact Store as a backend for Great Expectations with any of the deployment methods described above. Note that ZenML will not copy the information in your existing Great Expectations stores (e.g. Expectation Suites, Validation Results) in the ZenML Artifact Store. This is something that you will have to do yourself. * `configure_local_docs`: set this flag to configure a local Data Docs site where Great Expectations docs are generated and can be visualized locally. Use this in case you don't already have a local Data Docs site in your existing Great Expectations configuration. For more, up-to-date information on the Great Expectations Data Validator configuration, you can have a look at [the SDK docs](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-great_expectations.html#zenml.integrations.great_expectations) . ### How do you use it? The core Great Expectations concepts that you should be aware of when using it within ZenML pipelines are Expectations / Expectation Suites, Validations and Data Docs. ZenML wraps the Great Expectations' functionality in the form of two standard steps: * a Great Expectations data profiler that can be used to automatically generate Expectation Suites from an input `pandas.DataFrame` dataset * a Great Expectations data validator that uses an existing Expectation Suite to validate an input `pandas.DataFrame` dataset You can visualize Great Expectations Suites and Results in Jupyter notebooks or view them directly in the ZenML dashboard. #### The Great Expectation's data profiler step The standard Great Expectation's data profiler step builds an Expectation Suite automatically by running a [`UserConfigurableProfiler`](https://docs.greatexpectations.io/docs/guides/expectations/how_to_create_and_edit_expectations_with_a_profiler) on an input `pandas.DataFrame` dataset. The generated Expectation Suite is saved in the Great Expectations Expectation Store, but also returned as an `ExpectationSuite` artifact that is versioned and saved in the ZenML Artifact Store. The step automatically rebuilds the Data Docs. At a minimum, the step configuration expects a name to be used for the Expectation Suite: ```python from zenml.integrations.great_expectations.steps import ( great_expectations_profiler_step, ) ge_profiler_step = great_expectations_profiler_step.with_options( parameters={ "expectation_suite_name": "steel_plates_suite", "data_asset_name": "steel_plates_train_df", } ) ``` The step can then be inserted into your pipeline where it can take in a pandas dataframe, e.g.: ```python from zenml import pipeline docker_settings = DockerSettings(required_integrations=[SKLEARN, GREAT_EXPECTATIONS]) @pipeline(settings={"docker": docker_settings}) def profiling_pipeline(): """Data profiling pipeline for Great Expectations. The pipeline imports a reference dataset from a source then uses the builtin Great Expectations profiler step to generate an expectation suite (i.e. validation rules) inferred from the schema and statistical properties of the reference dataset. Args: importer: reference data importer step profiler: data profiler step """ dataset, _ = importer() ge_profiler_step(dataset) profiling_pipeline() ``` As can be seen from the step definition, the step takes in a `pandas.DataFrame` dataset, and it returns a Great Expectations `ExpectationSuite` object: ```python @step def great_expectations_profiler_step( dataset: pd.DataFrame, expectation_suite_name: str, data_asset_name: Optional[str] = None, profiler_kwargs: Optional[Dict[str, Any]] = None, overwrite_existing_suite: bool = True, ) -> ExpectationSuite: ... ``` #### The Great Expectations data validator step The standard Great Expectations data validator step validates an input `pandas.DataFrame` dataset by running an existing Expectation Suite on it. The validation results are saved in the Great Expectations Validation Store, but also returned as an `CheckpointResult` artifact that is versioned and saved in the ZenML Artifact Store. The step automatically rebuilds the Data Docs. At a minimum, the step configuration expects the name of the Expectation Suite to be used for the validation: ```python from zenml.integrations.great_expectations.steps import ( great_expectations_validator_step, ) ge_validator_step = great_expectations_validator_step.with_options( parameters={ "expectation_suite_name": "steel_plates_suite", "data_asset_name": "steel_plates_train_df", } ) ``` The step can then be inserted into your pipeline where it can take in a pandas dataframe and a bool flag used solely for order reinforcement purposes, e.g.: ```python docker_settings = DockerSettings(required_integrations=[SKLEARN, GREAT_EXPECTATIONS]) @pipeline(settings={"docker": docker_settings}) def validation_pipeline(): """Data validation pipeline for Great Expectations. The pipeline imports a test data from a source, then uses the builtin Great Expectations data validation step to validate the dataset against the expectation suite generated in the profiling pipeline. Args: importer: test data importer step validator: dataset validation step checker: checks the validation results """ dataset, condition = importer() results = ge_validator_step(dataset, condition) message = checker(results) validation_pipeline() ``` As can be seen from the step definition, the step takes in a `pandas.DataFrame` dataset and a boolean `condition` and it returns a Great Expectations `CheckpointResult` object. The boolean `condition` is only used as a means of ordering steps in a pipeline (e.g. if you must force it to run only after the data profiling step generates an Expectation Suite): ```python @step def great_expectations_validator_step( dataset: pd.DataFrame, expectation_suite_name: str, data_asset_name: Optional[str] = None, action_list: Optional[List[Dict[str, Any]]] = None, exit_on_error: bool = False, ) -> CheckpointResult: ``` #### Call Great Expectations directly You can use the Great Expectations library directly in your custom pipeline steps, while leveraging ZenML's capability of serializing, versioning and storing the `ExpectationSuite` and `CheckpointResult` objects in its Artifact Store. To use the Great Expectations configuration managed by ZenML while interacting with the Great Expectations library directly, you need to use the Data Context managed by ZenML instead of the default one provided by Great Expectations, e.g.: ```python import great_expectations as ge from zenml.integrations.great_expectations.data_validators import ( GreatExpectationsDataValidator ) import pandas as pd from great_expectations.core import ExpectationSuite from zenml import step @step def create_custom_expectation_suite( ) -> ExpectationSuite: """Custom step that creates an Expectation Suite Returns: An Expectation Suite """ context = GreatExpectationsDataValidator.get_data_context() # instead of: # context = ge.get_context() expectation_suite_name = "custom_suite" suite = context.create_expectation_suite( expectation_suite_name=expectation_suite_name ) expectation_configuration = ExpectationConfiguration(...) suite.add_expectation(expectation_configuration=expectation_configuration) ... context.save_expectation_suite( expectation_suite=suite, expectation_suite_name=expectation_suite_name, ) context.build_data_docs() return suite ``` The same approach must be used if you are using a Great Expectations configuration managed by ZenML and are using the Jupyter notebooks generated by the Great Expectations CLI. #### Visualizing Great Expectations Suites and Results You can view visualizations of the suites and results generated by your pipeline steps directly in the ZenML dashboard by clicking on the respective artifact in the pipeline run DAG. Alternatively, if you are running inside a Jupyter notebook, you can load and render the suites and results using the [`artifact.visualize()` method](https://docs.zenml.io/how-to/data-artifact-management/visualize-artifacts/), e.g.: ```python from zenml.client import Client def visualize_results(pipeline_name: str, step_name: str) -> None: pipeline = Client().get_pipeline(pipeline_name) last_run = pipeline.last_run validation_step = last_run.steps[step_name] validation_step.visualize() if __name__ == "__main__": visualize_results("validation_pipeline", "profiler") visualize_results("validation_pipeline", "train_validator") visualize_results("validation_pipeline", "test_validator") ```
ZenML Scarf
--- ### Book/Component Guide/Data Validators/Whylogs (docs/book/component-guide/data-validators/whylogs.md) --- description: >- How to collect and visualize statistics to track changes in your pipelines' data with whylogs/WhyLabs profiling. --- # Whylogs The whylogs/WhyLabs [Data Validator](./) flavor provided with the ZenML integration uses the open-source [whylogs](https://github.com/whylabs/whylogs) library together with the now open-sourced [WhyLabs platform](https://github.com/whylabs/whylabs-oss) to generate and track data profiles, highly accurate descriptive representations of your data. The profiles can be used to implement automated corrective actions in your pipelines, or to render interactive representations for further visual interpretation, evaluation and documentation. > **Warning:** [WhyLabs was acquired by Apple](https://whylabs.ai/) and the hosted WhyLabs platform is being discontinued. While the whylogs library remains open source and the WhyLabs platform source code is publicly available, hosted deployments may no longer be accessible. Make sure to plan your usage of the integration accordingly and consider self-hosting the OSS platform if you still need WhyLabs features. ### When would you want to use it? [Whylogs](https://github.com/whylabs/whylogs) is an open-source library that analyzes your data and creates statistical summaries called whylogs profiles. Whylogs profiles can be processed in your pipelines and visualized locally or uploaded to a WhyLabs deployment for more in depth analysis. The official hosted WhyLabs service is being discontinued, but you can continue to operate a WhyLabs instance yourself by using the open-source release at [https://github.com/whylabs/whylabs-oss](https://github.com/whylabs/whylabs-oss). Even though [whylogs also supports other data types](https://github.com/whylabs/whylogs#data-types), the ZenML whylogs integration currently only works with tabular data in `pandas.DataFrame` format. You should use the whylogs/WhyLabs Data Validator when you need the following data validation features that are possible with whylogs and WhyLabs: * Data Quality: validate data quality in model inputs or in a data pipeline * Data Drift: detect data drift in model input features * Model Drift: Detect training-serving skew, concept drift, and model performance degradation You should consider one of the other [Data Validator flavors](./#data-validator-flavors) if you need a different set of data validation features. ### How do you deploy it? The whylogs Data Validator flavor is included in the whylogs ZenML integration, you need to install it on your local machine to be able to register a whylogs Data Validator and add it to your stack: ```shell zenml integration install whylogs -y ``` If you don't need to connect to a WhyLabs deployment to upload and store the generated whylogs data profiles, the Data Validator stack component does not require any configuration parameters. Adding it to a stack is as simple as running e.g.: ```shell # Register the whylogs data validator zenml data-validator register whylogs_data_validator --flavor=whylogs # Register and set a stack with the new data validator zenml stack register custom_stack -dv whylogs_data_validator ... --set ``` Adding WhyLabs logging capabilities to your whylogs Data Validator is just slightly more complicated, as you also need to create a [ZenML Secret](https://docs.zenml.io/getting-started/deploying-zenml/secret-management) to store the sensitive WhyLabs authentication information in a secure location and then reference the secret in the Data Validator configuration. To generate a WhyLabs access token for a deployment that you host yourself, refer to the guidance in the [WhyLabs OSS repository](https://github.com/whylabs/whylabs-oss). Then, you can register the whylogs Data Validator with WhyLabs logging capabilities as follows: ```shell # Create the secret referenced in the data validator zenml secret create whylabs_secret \ --whylabs_default_org_id= \ --whylabs_api_key= # Register the whylogs data validator zenml data-validator register whylogs_data_validator --flavor=whylogs \ --authentication_secret=whylabs_secret ``` You'll also need to enable whylabs logging for your custom pipeline steps if you want to upload the whylogs data profiles that they return as artifacts to your WhyLabs deployment. This is enabled by default for the standard whylogs step. For custom steps, you can enable WhyLabs logging by setting the `upload_to_whylabs` parameter to `True` in the step configuration, e.g.: ```python from typing import Annotated from typing import Tuple import pandas as pd import whylogs as why from sklearn import datasets from whylogs.core import DatasetProfileView from zenml.integrations.whylogs.flavors.whylogs_data_validator_flavor import ( WhylogsDataValidatorSettings, ) from zenml import step @step( settings={ "data_validator": WhylogsDataValidatorSettings( enable_whylabs=True, dataset_id="model-1" ) } ) def data_loader() -> Tuple[ Annotated[pd.DataFrame, "data"], Annotated[DatasetProfileView, "profile"] ]: """Load the diabetes dataset.""" X, y = datasets.load_diabetes(return_X_y=True, as_frame=True) # merge X and y together df = pd.merge(X, y, left_index=True, right_index=True) profile = why.log(pandas=df).profile().view() return df, profile ``` ### How do you use it? Whylogs's profiling functions take in a `pandas.DataFrame` dataset generate a `DatasetProfileView` object containing all the relevant information extracted from the dataset. There are three ways you can use whylogs in your ZenML pipelines that allow different levels of flexibility: * instantiate, configure and insert [the standard `WhylogsProfilerStep`](whylogs.md#the-whylogs-standard-step) shipped with ZenML into your pipelines. This is the easiest way and the recommended approach, but can only be customized through the supported step configuration parameters. * call the data validation methods provided by [the whylogs Data Validator](whylogs.md#the-whylogs-data-validator) in your custom step implementation. This method allows for more flexibility concerning what can happen in the pipeline step, but you are still limited to the functionality implemented in the Data Validator. * [use the whylogs library directly](whylogs.md#call-whylogs-directly) in your custom step implementation. This gives you complete freedom in how you are using whylogs's features. You can [visualize whylogs profiles](whylogs.md#visualizing-whylogs-profiles) in Jupyter notebooks or view them directly in the ZenML dashboard. #### The whylogs standard step ZenML wraps the whylogs/WhyLabs functionality in the form of a standard `WhylogsProfilerStep` step. The only field in the step config is a `dataset_timestamp` attribute which is only relevant when you upload the profiles to a WhyLabs deployment that uses this field to group and merge together profiles belonging to the same dataset. The helper function `get_whylogs_profiler_step` used to create an instance of this standard step takes in an optional `dataset_id` parameter that is also used only in the context of WhyLabs uploads to identify the model in the context of which the profile is uploaded, e.g.: ```python from zenml.integrations.whylogs.steps import get_whylogs_profiler_step train_data_profiler = get_whylogs_profiler_step(dataset_id="model-2") test_data_profiler = get_whylogs_profiler_step(dataset_id="model-3") ``` The step can then be inserted into your pipeline where it can take in a `pandas.DataFrame` dataset, e.g.: ```python from zenml import pipeline @pipeline def data_profiling_pipeline(): data, _ = data_loader() train, test = data_splitter(data) train_data_profiler(train) test_data_profiler(test) data_profiling_pipeline() ``` As can be seen from the [step definition](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-whylogs.html#zenml.integrations.whylogs) , the step takes in a dataset and returns a whylogs `DatasetProfileView` object: ```python @step def whylogs_profiler_step( dataset: pd.DataFrame, dataset_timestamp: Optional[datetime.datetime] = None, ) -> DatasetProfileView: ... ``` You should consult [the official whylogs documentation](https://whylogs.readthedocs.io/en/latest/index.html) for more information on what you can do with the collected profiles. You can view [the complete list of configuration parameters](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-whylogs.html#zenml.integrations.whylogs) in the SDK docs. #### The whylogs Data Validator The whylogs Data Validator implements the same interface as do all Data Validators, so this method forces you to maintain some level of compatibility with the overall Data Validator abstraction, which guarantees an easier migration in case you decide to switch to another Data Validator. All you have to do is call the whylogs Data Validator methods when you need to interact with whylogs to generate data profiles. You may optionally enable whylabs logging to automatically upload the returned whylogs profile to your WhyLabs deployment, e.g.: ```python import pandas as pd from whylogs.core import DatasetProfileView from zenml.integrations.whylogs.data_validators.whylogs_data_validator import ( WhylogsDataValidator, ) from zenml.integrations.whylogs.flavors.whylogs_data_validator_flavor import ( WhylogsDataValidatorSettings, ) from zenml import step whylogs_settings = WhylogsDataValidatorSettings( enable_whylabs=True, dataset_id="" ) @step( settings={ "data_validator": whylogs_settings } ) def data_profiler( dataset: pd.DataFrame, ) -> DatasetProfileView: """Custom data profiler step with whylogs Args: dataset: a Pandas DataFrame Returns: Whylogs profile generated for the data """ # validation pre-processing (e.g. dataset preparation) can take place here data_validator = WhylogsDataValidator.get_active_data_validator() profile = data_validator.data_profiling( dataset, ) # optionally upload the profile to your WhyLabs deployment, if WhyLabs credentials are configured data_validator.upload_profile_view(profile) # validation post-processing (e.g. interpret results, take actions) can happen here return profile ``` Have a look at [the complete list of methods and parameters available in the `WhylogsDataValidator` API](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-whylogs.html#zenml.integrations.whylogs) in the SDK docs. #### Call whylogs directly You can use the whylogs library directly in your custom pipeline steps, and only leverage ZenML's capability of serializing, versioning and storing the `DatasetProfileView` objects in its Artifact Store. You may optionally enable whylabs logging to automatically upload the returned whylogs profile to your WhyLabs deployment, e.g.: ```python import pandas as pd from whylogs.core import DatasetProfileView import whylogs as why from zenml import step from zenml.integrations.whylogs.flavors.whylogs_data_validator_flavor import ( WhylogsDataValidatorSettings, ) whylogs_settings = WhylogsDataValidatorSettings( enable_whylabs=True, dataset_id="" ) @step( settings={ "data_validator": whylogs_settings } ) def data_profiler( dataset: pd.DataFrame, ) -> DatasetProfileView: """Custom data profiler step with whylogs Args: dataset: a Pandas DataFrame Returns: Whylogs Profile generated for the dataset """ # validation pre-processing (e.g. dataset preparation) can take place here results = why.log(dataset) profile = results.profile() # validation post-processing (e.g. interpret results, take actions) can happen here return profile.view() ``` ### Visualizing whylogs Profiles You can view visualizations of the whylogs profiles generated by your pipeline steps directly in the ZenML dashboard by clicking on the respective artifact in the pipeline run DAG. Alternatively, if you are running inside a Jupyter notebook, you can load and render the whylogs profiles using the [artifact.visualize() method](https://docs.zenml.io/how-to/data-artifact-management/visualize-artifacts/), e.g.: ```python from zenml.client import Client def visualize_statistics( step_name: str, reference_step_name: Optional[str] = None ) -> None: """Helper function to visualize whylogs statistics from step artifacts. Args: step_name: step that generated and returned a whylogs profile reference_step_name: an optional second step that generated a whylogs profile to use for data drift visualization where two whylogs profiles are required. """ pipe = Client().get_pipeline(pipeline="data_profiling_pipeline") whylogs_step = pipe.last_run.steps[step_name] whylogs_step.visualize() if __name__ == "__main__": visualize_statistics("data_loader") visualize_statistics("train_data_profiler", "test_data_profiler") ```
ZenML Scarf
--- ### Book/Component Guide/Deployers/README (docs/book/component-guide/deployers/README.md) --- description: Deploy pipelines as HTTP services for real-time execution icon: rocket-launch --- # Deployers Pipeline deployment is the process of making ZenML pipelines available as long-running HTTP services for real-time execution. Unlike traditional batch execution through orchestrators, deployers create persistent web services that can handle on-demand pipeline invocations through HTTP requests. Deployers are stack components responsible for managing the deployment of pipelines as containerized HTTP services that expose REST APIs for pipeline execution. A deployed pipeline becomes a web service that can be invoked multiple times in parallel, receiving parameters through HTTP requests and returning pipeline outputs as JSON responses. This enables real-time inference, interactive workflows, and integration with web applications. ### When to use it? Deployers are optional components in the ZenML stack. They are useful in the following scenarios: - **Real-time Pipeline Execution**: Execute pipelines on-demand through HTTP requests rather than scheduled batch runs - **Interactive Workflows**: Build applications that need immediate pipeline responses - **API Integration**: Expose ML workflows as REST APIs for web applications or microservices - **Real-time Inference**: Serve ML models through pipeline-based inference workflows - **Agent-based Systems**: Create AI agents that execute pipelines in response to external events Use deployers when you need request-response patterns, and orchestrators for scheduled, batch, or long-running workflows. ### Deployer Flavors Out of the box, ZenML comes with a `local` deployer already part of the default stack that deploys pipelines on your local machine in the form of background processes. Additional Deployers are provided by integrations: | Deployer | Flavor | Integration | Notes | |------------------------------------|-----------|---------------|------------------------------------------------------------------------------| | [Local](local.md) | `local` | _built-in_ | This is the default Deployer. It deploys pipelines on your local machine in the form of background processes. Should be used only for running ZenML locally. | | [Docker](docker.md) | `docker` | Built-in | Deploys pipelines as locally running Docker containers | | [Kubernetes](kubernetes.md) | `kubernetes` | `kubernetes` | Deploys pipelines to any Kubernetes cluster with full control over resources, networking, and scaling | | [GCP Cloud Run](gcp-cloud-run.md) | `gcp` | `gcp` | Deploys pipelines to Google Cloud Run for serverless execution | | [AWS App Runner](aws-app-runner.md) | `aws` | `aws` | Deploys pipelines to AWS App Runner for serverless execution | | [Hugging Face](huggingface.md) | `huggingface` | `huggingface` | Deploys pipelines to Hugging Face Spaces as Docker Spaces | If you would like to see the available flavors of deployers, you can use the command: ```shell zenml deployer flavor list ``` ### How to use it You don't need to directly interact with the ZenML deployer stack component in your code. As long as the deployer that you want to use is part of your active [ZenML stack](../../user-guide/production-guide/understand-stacks.md), you can simply deploy a pipeline or snapshot using the ZenML CLI or the ZenML SDK. The resulting deployment can be managed using the ZenML CLI or the ZenML SDK. Examples: * just use the default stack - it has a default local deployer that will deploy the pipeline on your local machine in the form of a background process: ```bash zenml stack set default ``` * or set up a new stack with a deployer in it: ```bash zenml deployer register docker --flavor=local zenml stack register docker_deployment -a default -o default -D docker --set ``` * deploy a pipeline with the ZenML SDK: ```python from zenml import pipeline @step def my_step(name: str) -> str: return f"Hello, {name}!" @pipeline def my_pipeline(name: str = "John") -> str: return my_step(name=name) if __name__ == "__main__": # Deploy the pipeline `my_pipeline` as a deployment named `my_deployment` deployment = my_pipeline.deploy(deployment_name="my_deployment") print(f"Deployment URL: {deployment.url}") ``` * deploy the same pipeline with the CLI: ```bash zenml pipeline deploy --name my_deployment my_module.my_pipeline ``` * send a request to the deployment with the ZenML CLI: ```bash zenml deployment invoke my_deployment --name="Alice" ``` * or with curl: ```bash curl -X POST http://localhost:8000/invoke \ -H "Content-Type: application/json" \ -d '{"parameters": {"name": "Alice"}}' ``` * alternatively, set up a snapshot and deploy it instead of a pipeline: ```bash zenml pipeline snapshot create --name my_snapshot my_module.my_pipeline zenml pipeline snapshot deploy my_snapshot --deployment my_deployment ``` #### Pipeline Requirements for Deployment Not all pipelines are suitable for deployment as HTTP services. To be deployable, pipelines should follow these guidelines: **Parameter Requirements:** - Pipelines should accept explicit parameters with default values - Parameters must be JSON-serializable types (int, float, str, bool, list, dict, Pydantic models) - Parameter names should match step input names **Output Requirements:** - Pipelines should return meaningful values for HTTP responses - Return values must be JSON-serializable - It's recommended to use type annotations to specify output artifact names Example Deployable Pipeline: ```python from typing import Annotated from zenml import pipeline, step @step def process_weather(city: str, temperature: float) -> Annotated[str, "weather_analysis"]: return f"The weather in {city} is {temperature} degrees Celsius." @pipeline def weather_pipeline(city: str = "Paris", temperature: float = 20.0) -> str: """A deployable pipeline that processes weather data.""" analysis = process_weather(city=city, temperature=temperature) return analysis ``` For more information, see the [Deployable Pipeline Requirements](https://docs.zenml.io/concepts/deployment#deployable-pipeline-requirements) section of the tutorial. #### Deployment Lifecycle Management The Deployment object represents a pipeline that has been deployed to a serving environment. The Deployment object is saved in the ZenML database and contains information about the deployment configuration, status, and connection details. Deployments are standalone entities that can be managed independently of the active stack through the Deployer stack components that were originally used to provision them. Some example of how to manage deployments: * listing deployments with the CLI: ```bash $ zenml deployment list ┏━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃ NAME │ PIPELINE │ URL │ STATUS ┃ ┠──────────────────────┼──────────────────────────────────────┼────────────────────────────────┼──────────────────────────┨ ┃ weather_service │ weather_pipeline │ http://localhost:8001 │ ⚙ RUNNING ┃ ┠──────────────────────┼──────────────────────────────────────┼────────────────────────────────┼──────────────────────────┨ ┃ ml_inference_api │ inference_pipeline │ http://k8s-cluster/ml-api │ ⚙ RUNNING ┃ ┗━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ``` * listing deployments with the SDK: ```python from zenml.client import Client client = Client() deployments = client.list_deployments() for deployment in deployments: print(f"{deployment.name}: {deployment.status}") ``` * showing detailed information about a deployment with the CLI: ``` /* Detailed source-code truncated for AI context efficiency. */ ``` * showing detailed information about a deployment with the SDK: ```python from zenml.client import Client deployment = client.get_deployment("my_deployment") print(deployment) ``` * deprovision and delete a deployment with the CLI: ```bash $ zenml deployment delete my_deployment ``` * deprovisioning and deleting a deployment with the SDK: ```python from zenml.client import Client client = Client() client.delete_deployment("my_deployment") ``` * sending a request to a deployment with the CLI: ```bash $ zenml deployment invoke my_deployment --name="John" Invoked deployment 'my_deployment' with response: { "success": true, "outputs": { "output": "Hello, John!" }, "execution_time": 3.2781872749328613, "metadata": { "deployment_id": "95d60dcf-7c37-4e62-a923-a341601903e5", "deployment_name": "my_deployment", "snapshot_id": "f3122ed4-aa13-4113-9f60-a80545f56244", "snapshot_name": "my_snapshot", "pipeline_name": "my_pipeline", "run_id": "ea448522-d5bf-411e-971e-d4550fdbe713", "run_name": "my_pipeline-2025_09_30-12_52_01_012491", "parameters_used": {} }, "error": null } ``` * sending a request to a deployment with the SDK: ```python from zenml.deployers.utils import invoke_deployment response = invoke_deployment( deployment_name_or_id="my_deployment", name="John", ) print(response) ``` By default, invoking a deployment blocks until the pipeline run finishes and returns its outputs. To submit the run for background execution and get the run ID back immediately instead, pass `--no-wait` on the CLI or `submit=True` with the SDK: ```bash $ zenml deployment invoke my_deployment --name="John" --no-wait ``` ```python response = invoke_deployment( deployment_name_or_id="my_deployment", name="John", submit=True, ) ``` #### Specifying deployment resources If your steps require additional hardware resources, you can specify them on your steps as described [here](https://docs.zenml.io/user-guides/tutorial/distributed-training/).
ZenML Scarf
--- ### Book/Component Guide/Deployers/Aws App Runner (docs/book/component-guide/deployers/aws-app-runner.md) --- description: Deploying your pipelines to AWS App Runner. --- # AWS App Runner Deployer [AWS App Runner](https://aws.amazon.com/apprunner/) is a fully managed serverless platform that allows you to deploy and run your code in a production-ready, repeatable cloud environment without the need to manage any infrastructure. The AWS App Runner deployer is a [deployer](./) flavor included in the ZenML AWS integration that deploys your pipelines to AWS App Runner. {% hint style="warning" %} This component is only meant to be used within the context of a [remote ZenML installation](https://docs.zenml.io/getting-started/deploying-zenml/). Usage with a local ZenML setup may lead to unexpected behavior! {% endhint %} ## When to use it You should use the AWS App Runner deployer if: * you're already using AWS. * you're looking for a proven production-grade deployer. * you're looking for a serverless solution for deploying your pipelines as HTTP micro-services. * you want automatic scaling with pay-per-use pricing. * you need to deploy containerized applications with minimal configuration. ## How to deploy it {% hint style="info" %} Would you like to skip ahead and deploy a full ZenML cloud stack already, including an AWS App Runner deployer? Check out [the ZenML AWS Terraform module](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/deploy-a-cloud-stack-with-terraform) for a shortcut on how to deploy & register this stack component and everything else needed by it. {% endhint %} {% hint style="warning" %} App Runner is available only in [specific AWS regions](https://docs.aws.amazon.com/general/latest/gr/apprunner.html#apprunner_region). {% endhint %} In order to use an AWS App Runner deployer, you need to first deploy [ZenML to the cloud](https://docs.zenml.io/getting-started/deploying-zenml/). It would be recommended to deploy ZenML in the same AWS account and region as where the AWS App Runner infrastructure is deployed, but it is not necessary to do so. You must ensure that you are connected to the remote ZenML server before using this stack component. The AWS App Runner deployer requires that you have [the necessary IAM permissions](aws-app-runner.md#aws-credentials-and-permissions) to create and manage App Runner services, and optionally access to AWS Secrets Manager and CloudWatch Logs for enhanced functionality. ## How to use it To use the AWS App Runner deployer, you need: * The ZenML `aws` integration installed. If you haven't done so, run ```shell zenml integration install aws ``` * [Docker](https://www.docker.com) installed and running. * A [remote artifact store](https://docs.zenml.io/stacks/artifact-stores/) as part of your stack. * A [remote container registry](https://docs.zenml.io/stacks/container-registries/) as part of your stack (**NOTE**: must be Amazon ECR or ECR Public). * [AWS credentials with proper permissions](aws-app-runner.md#aws-credentials-and-permissions) to create and manage the App Runner services themselves. * When using a private ECR container registry, an IAM role with specific ECR permissions should also be created and configured as [the App Runner access role](https://docs.aws.amazon.com/apprunner/latest/dg/security_iam_service-with-iam.html#security_iam_service-with-iam-roles) (see [Required IAM Permissions](aws-app-runner.md#required-iam-permissions) below). If this is not configured, App Runner will attempt to use the default `AWSServiceRoleForAppRunner` service role, which may not have ECR access permissions. * If opting to store sensitive information in the AWS Secrets Manager (enabled by default), an IAM role with specific Secrets Manager permissions should also be created and configured as [the App Runner instance role](https://docs.aws.amazon.com/apprunner/latest/dg/security_iam_service-with-iam.html#security_iam_service-with-iam-roles) (see [Required IAM Permissions](aws-app-runner.md#required-iam-permissions) below). If this is not configured, App Runner will attempt to use the default `AWSServiceRoleForAppRunner` service role, which may not have Secrets Manager access permissions. * The AWS region in which you want to deploy your pipelines. ### AWS credentials and permissions You have two different options to provide credentials to the AWS App Runner deployer: * use the [AWS CLI](https://aws.amazon.com/cli/) to authenticate locally with AWS * (recommended) configure [an AWS Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/aws-service-connector) with AWS credentials and then link the AWS App Runner deployer stack component to the Service Connector. #### AWS Permissions Depending on how you configure the AWS App Runner deployer, there can be at most three different sets of permissions involved: * the client permissions - these are the permissions needed by the Deployer stack component itself to interact with the App Runner service and optionally to manage AWS Secrets Manager secrets. These permissions need to come from either the local AWS SDK or the AWS Service Connector: * the permissions in the `AWSAppRunnerFullAccess` policy. * the following permissions for AWS Secrets Manager are also required if the deployer is configured to use secrets to pass sensitive information to the App Runner services instead of regular environment variables (i.e. if the `use_secrets_manager` setting is set to `True`): * `secretsmanager:CreateSecret` * `secretsmanager:UpdateSecret` * `secretsmanager:DeleteSecret` * `secretsmanager:DescribeSecret` * `secretsmanager:GetSecretValue` * `secretsmanager:PutSecretValue` * `secretsmanager:TagResource` These permissions should additionally be restricted to only allow access to secrets with a name starting with `zenml-` in the target region and account. Note that this prefix is also configurable and can be changed by setting the `secret_name_prefix` setting. * CloudWatch Logs permissions (for log retrieval): * `logs:DescribeLogGroups` * `logs:DescribeLogStreams` * `logs:GetLogEvents` * `iam:PassRole` permission granted for the App Runner access role and instance role, if they are also configured (see below). * [the App Runner access role](https://docs.aws.amazon.com/apprunner/latest/dg/security_iam_service-with-iam.html#security_iam_service-with-iam-roles) - this is a role that App Runner uses for accessing images in Amazon ECR in your account. It's only required to access an image in Amazon ECR, and isn't required with Amazon ECR Public. This role should include the `AWSAppRunnerServicePolicyForECRAccess` policy or something similar restricted to the target ECR repository. * [the App Runner instance role](https://docs.aws.amazon.com/apprunner/latest/dg/security_iam_service-with-iam.html#security_iam_service-with-iam-roles) - this is a role that the App Runner instances themselves use for accessing the AWS Secrets Manager secrets. It's only required if you use the AWS Secrets Manager to store sensitive information (i.e. if you keep the `use_secrets_manager` option set to `True` in the [deployer settings](aws-app-runner.md#additional-configuration)). This role should include the `secretsmanager:GetSecretValue` permission optionally restricted to only allow access to secrets with a name starting with `zenml-` in the target region and account. Note that this prefix is also configurable and can be changed by setting the `secret_name_prefix` setting. #### Configuration use-case: local AWS CLI with user account This configuration use-case assumes you have configured the [AWS CLI](https://aws.amazon.com/cli/) to authenticate locally with your AWS account (i.e. by running `aws configure`). It also assumes that your AWS account has [the client permissions required to use the AWS App Runner deployer](aws-app-runner.md#aws-permissions). This is the easiest way to configure the AWS App Runner deployer, but it has the following drawbacks: * the setup is not portable on other machines and reproducible by other users (i.e. other users won't be able to use the Deployer to deploy pipelines or manage your Deployments, although they would still be able to access their exposed endpoints and send HTTP requests). * it uses your personal AWS credentials, which may have broader permissions than necessary for the deployer. The deployer can be registered as follows: ```shell zenml deployer register \ --flavor=aws \ --region= \ --instance_role_arn= \ --access_role_arn= ``` #### Configuration use-case: AWS Service Connector This use-case assumes you have already configured an AWS IAM user or role with the [client permissions required to use the AWS App Runner deployer](aws-app-runner.md#aws-permissions). It also assumes you have already created access keys for this IAM user and have them available (access key ID and secret access key), although there are [ways to authenticate with AWS through an AWS Service Connector that don't require long-term access keys](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/aws-service-connector#aws-iam-role). With the IAM credentials ready, you can register [the AWS Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/aws-service-connector) and AWS App Runner deployer as follows: ```shell zenml service-connector register --type aws --auth-method=secret-key --aws_access_key_id= --aws_secret_access_key= --region= --resource-type aws-generic zenml deployer register \ --flavor=aws \ --instance_role_arn= \ --access_role_arn= \ --connector ``` ### Configuring the stack With the deployer registered, it can be used in the active stack: ```shell # Register and activate a stack with the new deployer zenml stack register -D ... --set ``` {% hint style="info" %} ZenML will build a Docker image called `/zenml:` and use it to deploy your pipeline as an App Runner service. The container registry must be Amazon ECR (private) or ECR Public. Check out [this page](https://docs.zenml.io/how-to/customize-docker-builds/) if you want to learn more about how ZenML builds these images and how you can customize them. {% endhint %} You can now [deploy any ZenML pipeline](https://docs.zenml.io/concepts/deployment) using the AWS App Runner deployer: ```shell zenml pipeline deploy --name my_deployment my_module.my_pipeline ``` ### Additional configuration For additional configuration of the AWS App Runner deployer, you can pass the following `AWSDeployerSettings` attributes defined in the `zenml.integrations.aws.flavors.aws_deployer_flavor` module when configuring the deployer or defining or deploying your pipeline: * Basic settings common to all Deployers: * `auth_key`: A user-defined authentication key to use to authenticate with deployment API calls. * `generate_auth_key`: Whether to generate and use a random authentication key instead of the user-defined one. * `lcm_timeout`: The maximum time in seconds to wait for the deployment lifecycle management to complete. * AWS App Runner-specific settings: * `region` (default: `None`): AWS region where the App Runner service will be deployed. If not specified, the region will be determined from the authenticated session. App Runner is available in specific regions: https://docs.aws.amazon.com/apprunner/latest/dg/regions.html. Setting this has no effect if the deployer is configured with an AWS Service Connector. * `service_name_prefix` (default: `"zenml-"`): Prefix for service names in App Runner to avoid naming conflicts. * `port` (default: `8080`): Port on which the container listens for requests. * `health_check_grace_period_seconds` (default: `20`): Grace period for health checks in seconds. Range: 0-20. * `health_check_interval_seconds` (default: `10`): Interval between health checks in seconds. Range: 1-20. * `health_check_path` (default: `"/health"`): Health check path for the App Runner service. * `health_check_protocol` (default: `"TCP"`): Health check protocol. Options: 'TCP', 'HTTP'. * `health_check_timeout_seconds` (default: `2`): Timeout for health checks in seconds. Range: 1-20. * `health_check_healthy_threshold` (default: `1`): Number of consecutive successful health checks required. * `health_check_unhealthy_threshold` (default: `5`): Number of consecutive failed health checks before unhealthy. * `is_publicly_accessible` (default: `True`): Whether the App Runner service is publicly accessible. * `ingress_vpc_configuration` (default: `None`): VPC configuration for private App Runner services. JSON string with VpcId, VpcEndpointId, and VpcIngressConnectionName. * `environment_variables` (default: `{}`): Dictionary of environment variables to set in the App Runner service. * `tags` (default: `{}`): Dictionary of tags to apply to the App Runner service. * `use_secrets_manager` (default: `True`): Whether to store sensitive environment variables in AWS Secrets Manager instead of directly in the App Runner service configuration. When this is set to `True`, the deployer will also require additional permissions to access the AWS Secrets Manager secrets and an [App Runner instance role](https://docs.aws.amazon.com/apprunner/latest/dg/security_iam_service-with-iam.html#security_iam_service-with-iam-roles) to be configured as [the App Runner instance role](aws-app-runner.md#aws-permissions). * `secret_name_prefix` (default: `"zenml-"`): Prefix for secret names in Secrets Manager to avoid naming conflicts. * `observability_configuration_arn` (default: `None`): ARN of the observability configuration to associate with the App Runner service. * `encryption_kms_key` (default: `None`): KMS key ARN for encrypting App Runner service data. * `instance_role_arn` (default: `None`): ARN of the IAM role to assign to the App Runner service instances. Required if the `use_secrets_manager` setting is set to `True`. * `access_role_arn` (default: `None`): ARN of the IAM role that App Runner uses to access the image repository (ECR). Required for private ECR repositories. * `strict_resource_matching` (default: `False`): Whether to enforce strict matching of resource requirements to AWS App Runner supported CPU and memory combinations. When True, raises an error if no exact match is found. When False, automatically selects the closest matching supported combination. Check out [this docs page](https://docs.zenml.io/concepts/steps_and_pipelines/configuration) for more information on how to specify settings. For example, if you wanted to disable the use of AWS Secrets Manager for the deployment, you would configure settings as follows: ```python from zenml import step, pipeline from zenml.integrations.aws.flavors.aws_deployer_flavor import AWSDeployerSettings @step def greet(name: str) -> str: return f"Hello {name}!" settings = { "deployer": AWSDeployerSettings( use_secrets_manager=False ) } @pipeline(settings=settings) def greet_pipeline(name: str = "John"): greet(name=name) ``` ### Resource and scaling settings You can specify the resource and scaling requirements for the pipeline deployment using the `ResourceSettings` class at the pipeline level, as described in our documentation on [resource settings](https://docs.zenml.io/concepts/steps_and_pipelines/configuration#resource-settings): ```python from zenml import step, pipeline from zenml.config import ResourceSettings resource_settings = ResourceSettings( cpu_count=1.0, memory="2GB", min_replicas=4, max_replicas=25, max_concurrency=100 ) ... @pipeline(settings={"resources": resource_settings}) def greet_pipeline(name: str = "John"): greet(name=name) ``` {% hint style="warning" %} AWS App Runner defines specific rules concerning allowed combinations of CPU (vCPU) and memory (GB) values. For more information, see the [AWS App Runner documentation](https://docs.aws.amazon.com/apprunner/latest/dg/architecture.html#architecture.vcpu-memory). Supported combinations (as of October 2025) include: - 0.25 vCPU: 0.5 GB, 1 GB - 0.5 vCPU: 1 GB - 1 vCPU: 2 GB, 3 GB, 4 GB - 2 vCPU: 4 GB, 6 GB - 4 vCPU: 8 GB, 10 GB, 12 GB By default, specifying `cpu_count` and `memory` values that are not valid according to these rules will **not** result in an error when deploying the pipeline. Instead, the values will be automatically adjusted to the nearest matching valid combination using an algorithm that prioritizes CPU requirements over memory requirements and aims to minimize waste. You can enable `strict_resource_matching=True` in the deployer settings to enforce exact matches and raise an error if no valid combination is found. You can also override and configure your own allowed resource combinations in the deployer's configuration via the `resource_combinations` option. {% endhint %}
ZenML Scarf
--- ### Book/Component Guide/Deployers/Docker (docs/book/component-guide/deployers/docker.md) --- description: Deploying your pipelines locally with Docker. --- # Docker Deployer The Docker deployer is a [deployer](./) flavor that comes built-in with ZenML and deploys your pipelines locally using Docker. ## When to use it You should use the Docker deployer if: * you need a quick and easy way to deploy your pipelines locally. * you want to debug issues that happen when deploying your pipeline in Docker containers without waiting and paying for remote infrastructure. * you need an easy way to test out how pipeline deployments work ## How to deploy it To use the Docker deployer, you only need to have [Docker](https://www.docker.com/) installed and running. ## How to use it To use the Docker deployer, you can register it and use it in your active stack: ```shell zenml deployer register docker --flavor=docker # Register and activate a stack with the new deployer zenml stack register docker-deployer -D docker -o default -a default --set ``` {% hint style="info" %} ZenML will build a local Docker image called `zenml:` and use it to deploy your pipeline as a Docker container. Check out [this page](https://docs.zenml.io/how-to/customize-docker-builds/) if you want to learn more about how ZenML builds these images and how you can customize them. {% endhint %} You can now [deploy any ZenML pipeline](https://docs.zenml.io/concepts/deployment) using the Docker deployer: ```shell zenml pipeline deploy my_module.my_pipeline ``` ### Additional configuration For additional configuration of the Docker deployer, you can pass the following `DockerDeployerSettings` attributes defined in the `zenml.deployers.docker.docker_deployer` module when configuring the deployer or defining or deploying your pipeline: * Basic settings common to all Deployers: * `auth_key`: A user-defined authentication key to use to authenticate with deployment API calls. * `generate_auth_key`: Whether to generate and use a random authentication key instead of the user-defined one. * `lcm_timeout`: The maximum time in seconds to wait for the deployment lifecycle management to complete. * Docker-specific settings: * `port`: The port to expose the deployment on. * `allocate_port_if_busy`: If True, allocate a free port if the configured port is busy. * `port_range`: The range of ports to search for a free port. * `run_args`: Arguments to pass to the `docker run` call. A full list of what can be passed in via the `run_args` can be found [in the Docker Python SDK documentation](https://docker-py.readthedocs.io/en/stable/containers.html). Check out [this docs page](https://docs.zenml.io/concepts/steps_and_pipelines/configuration) for more information on how to specify settings. For example, if you wanted to specify the port to use for the deployment, you would configure settings as follows: ```python from zenml import step, pipeline from zenml.deployers.docker.docker_deployer import DockerDeployerSettings @step def greet(name: str) -> str: return f"Hello {name}!" settings = { "deployer": DockerDeployerSettings( port=8000 ) } @pipeline(settings=settings) def greet_pipeline(name: str = "John"): greet(name=name) ```
ZenML Scarf
--- ### Book/Component Guide/Deployers/Gcp Cloud Run (docs/book/component-guide/deployers/gcp-cloud-run.md) --- description: Deploying your pipelines to GCP Cloud Run. --- # GCP Cloud Run Deployer [GCP Cloud Run](https://cloud.google.com/run) is a fully managed serverless platform that allows you to deploy and run your code in a production-ready, repeatable cloud environment without the need to manage any infrastructure. The GCP Cloud Run deployer is a [deployer](./) flavor included in the ZenML GCP integration that deploys your pipelines to GCP Cloud Run. {% hint style="warning" %} This component is only meant to be used within the context of a [remote ZenML installation](https://docs.zenml.io/getting-started/deploying-zenml/). Usage with a local ZenML setup may lead to unexpected behavior! {% endhint %} ## When to use it You should use the GCP Cloud Run deployer if: * you're already using GCP. * you're looking for a proven production-grade deployer. * you're looking for a serverless solution for deploying your pipelines as HTTP micro-services. * you want automatic scaling with pay-per-use pricing. * you need to deploy containerized applications with minimal configuration. ## How to deploy it {% hint style="info" %} Would you like to skip ahead and deploy a full ZenML cloud stack already, including a GCP Cloud Run deployer? Check out [the ZenML GCP Terraform module](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/deploy-a-cloud-stack-with-terraform) for a shortcut on how to deploy & register this stack component and everything else needed by it. {% endhint %} In order to use a GCP Cloud Run deployer, you need to first deploy [ZenML to the cloud](https://docs.zenml.io/getting-started/deploying-zenml/). It would be recommended to deploy ZenML in the same Google Cloud project as where the GCP Cloud Run infrastructure is deployed, but it is not necessary to do so. You must ensure that you are connected to the remote ZenML server before using this stack component. The only other thing necessary to use the ZenML GCP Cloud Run deployer is enabling GCP Cloud Run-relevant APIs on the Google Cloud project. ## How to use it To use the GCP Cloud Run deployer, you need: * The ZenML `gcp` integration installed. If you haven't done so, run ```shell zenml integration install gcp ``` * [Docker](https://www.docker.com) installed and running. * A [remote artifact store](https://docs.zenml.io/stacks/artifact-stores/) as part of your stack. * A [remote container registry](https://docs.zenml.io/stacks/container-registries/) as part of your stack. * [GCP credentials with proper permissions](gcp-cloud-run.md#gcp-credentials-and-permissions) * The GCP project ID and location in which you want to deploy your pipelines. ### GCP credentials and permissions You have two different options to provide credentials to the GCP Cloud Run deployer: * use the [`gcloud` CLI](https://cloud.google.com/sdk/gcloud) to authenticate locally with GCP * (recommended) configure [a GCP Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/gcp-service-connector) with GCP credentials and then link the GCP Cloud Run deployer stack component to the Service Connector. #### GCP Permissions Regardless of the authentication method used, the credentials used with the GCP Cloud Run deployer need the following permissions in the target GCP project: * the `roles/run.admin` role - for managing Cloud Run services * the following permissions to manage GCP secrets are required only if the Deployer is configured to use secrets to pass sensitive information to the Cloud Run services instead of regular environment variables (i.e. if the `use_secret_manager` setting is set to `True`): * the unconditional `secretmanager.secrets.create` permission is required to create new secrets in the target GCP project. * the `roles/secretmanager.admin` role restricted to only manage secrets with a name prefix of `zenml-`. Note that this prefix is also configurable and can be changed by setting the `secret_name_prefix` setting. As a simpler alternative, the `roles/secretmanager.admin` role can be granted at the project level with no condition applied. #### Configuration use-case: local `gcloud` CLI with user account This configuration use-case assumes you have configured the [`gcloud` CLI](https://cloud.google.com/sdk/gcloud) to authenticate locally with your GCP account (i.e. by running `gcloud auth login`). It also assumes that your GCP account has [the permissions required to use the GCP Cloud Run deployer](gcp-cloud-run.md#gcp-permissions). This is the easiest way to configure the GCP Cloud Run deployer, but it has the following drawbacks: * the setup is not portable on other machines and reproducible by other users (i.e. other users won't be able to use the Deployer to deploy pipelines or manage your Deployments, although they would still be able to access their exposed endpoints and send HTTP requests). * it uses the Compute Engine default service account, which is not recommended, given that it has a lot of permissions by default and is used by many other GCP services. The deployer can be registered as follows: ```shell zenml deployer register \ --flavor=gcp \ --project= \ --location= \ ``` #### Configuration use-case: GCP Service Connector This use-case assumes you have already configured a GCP service account with the [permissions required to use the GCP Cloud Run deployer](gcp-cloud-run.md#gcp-permissions). It also assumes you have already created a service account key for this service account and downloaded it to your local machine (e.g. in a `zenml-cloud-run-deployer.json` file), although there are [ways to authenticate with GCP through a GCP Service Connector that don't require a service account key](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/gcp-service-connector#external-account-gcp-workload-identity). With the service account and the key ready, you can register [the GCP Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/gcp-service-connector) and GCP Cloud Run deployer as follows: ```shell zenml service-connector register --type gcp --auth-method=service-account --project_id= --service_account_json=@zenml-cloud-run-deployer.json --resource-type gcp-generic zenml deployer register \ --flavor=gcp \ --location= \ --connector ``` ### Configuring the stack With the deployer registered, it can be used in the active stack: ```shell # Register and activate a stack with the new deployer zenml stack register -D ... --set ``` {% hint style="info" %} ZenML will build a Docker image called `/zenml:` and use it to deploy your pipeline as a Cloud Run service. Check out [this page](https://docs.zenml.io/how-to/customize-docker-builds/) if you want to learn more about how ZenML builds these images and how you can customize them. {% endhint %} You can now [deploy any ZenML pipeline](https://docs.zenml.io/concepts/deployment) using the GCP Cloud Run deployer: ```shell zenml pipeline deploy --name my_deployment my_module.my_pipeline ``` ### Additional configuration For additional configuration of the GCP Cloud Run deployer, you can pass the following `GCPDeployerSettings` attributes defined in the `zenml.integrations.gcp.flavors.gcp_deployer_flavor` module when configuring the deployer or defining or deploying your pipeline: * Basic settings common to all Deployers: * `auth_key`: A user-defined authentication key to use to authenticate with deployment API calls. * `generate_auth_key`: Whether to generate and use a random authentication key instead of the user-defined one. * `lcm_timeout`: The maximum time in seconds to wait for the deployment lifecycle management to complete. * GCP Cloud Run-specific settings: * `location` (default: `"europe-west3"`): Name of GCP region where the pipeline will be deployed. Cloud Run is available in specific regions: https://cloud.google.com/run/docs/locations * `service_name_prefix` (default: `"zenml-"`): Prefix for service names in Cloud Run to avoid naming conflicts. * `timeout_seconds` (default: `300`): Request timeout in seconds. Must be between 1 and 3600 seconds (1 hour maximum). * `ingress` (default: `"all"`): Ingress settings for the service. Available options: `'all'`, `'internal'`, `'internal-and-cloud-load-balancing'`. * `vpc_connector` (default: `None`): VPC connector for private networking. Format: `projects/PROJECT_ID/locations/LOCATION/connectors/CONNECTOR_NAME` * `service_account` (default: `None`): Service account email to run the Cloud Run service. If not specified, uses the default Compute Engine service account. * `environment_variables` (default: `{}`): Dictionary of environment variables to set in the Cloud Run service. * `labels` (default: `{}`): Dictionary of labels to apply to the Cloud Run service for organization and billing purposes. * `annotations` (default: `{}`): Dictionary of annotations to apply to the Cloud Run service for additional metadata. * `execution_environment` (default: `"gen2"`): Execution environment generation. Available options: `'gen1'`, `'gen2'`. * `traffic_allocation` (default: `{"LATEST": 100}`): Traffic allocation between revisions. Keys are revision names or `'LATEST'`, values are percentages that must sum to 100. * `allow_unauthenticated` (default: `True`): Whether to allow unauthenticated requests to the service. Set to `False` for private services requiring GCP specific authentication. * `use_secret_manager` (default: `True`): Whether to store sensitive environment variables in GCP Secret Manager instead of directly in the Cloud Run service configuration for enhanced security. * `secret_name_prefix` (default: `"zenml-"`): Prefix for secret names in Secret Manager to avoid naming conflicts when using Secret Manager for sensitive data. Check out [this docs page](https://docs.zenml.io/concepts/steps_and_pipelines/configuration) for more information on how to specify settings. For example, if you wanted to disable the use of GCP Secret Manager for the deployment, you would configure settings as follows: ```python from zenml import step, pipeline from zenml.integrations.gcp.flavors.gcp_deployer_flavor import GCPDeployerSettings @step def greet(name: str) -> str: return f"Hello {name}!" settings = { "deployer": GCPDeployerSettings( use_secret_manager=False ) } @pipeline(settings=settings) def greet_pipeline(name: str = "John"): greet(name=name) ``` ### Resource and scaling settings You can specify the resource and scaling requirements for the pipeline deployment using the `ResourceSettings` class at the pipeline level, as described in our documentation on [resource settings](https://docs.zenml.io/concepts/steps_and_pipelines/configuration#resource-settings): ```python from zenml import step, pipeline from zenml.config import ResourceSettings resource_settings = ResourceSettings( cpu_count=2, memory="32GB", min_replicas=0, max_replicas=10, max_concurrency=50 ) ... @pipeline(settings={"resources": resource_settings}) def greet_pipeline(name: str = "John"): greet(name=name) ``` If resource settings are not set, the default values are as follows: * `cpu_count` is `1` * `memory` is `2GiB` * `min_replicas` is `1` * `max_replicas` is `100` * `max_concurrency` is `80` {% hint style="warning" %} GCP Cloud Run defines specific rules concerning allowed combinations of CPU and memory values. The following rules apply (as of October 2025): * CPU constraints: * fractional CPUs: 0.08 to < 1.0 (in increments of 0.01) * integer CPUs: 1, 2, 4, 6, or 8 (no fractional values allowed >= 1.0) * minimum memory requirements per CPU configuration: * <=1 CPU: 128 MiB minimum * 2 CPU: 128 MiB minimum * 4 CPU: 2 GiB minimum * 6 CPU: 4 GiB minimum * 8 CPU: 4 GiB minimum For more information, see the [GCP Cloud Run documentation](https://cloud.google.com/run/docs/configuring/services/cpu). Specifying `cpu_count` and `memory` values that are not valid according to these rules will **not** result in an error when deploying the pipeline. Instead, the values will be automatically adjusted to the nearest matching valid values that satisfy the rules. Some examples: * `cpu_count=0.25` and `memory="100MiB"` will be adjusted to `cpu_count=0.25` and `memory="128MiB"` * `cpu_count=1.5` and `memory` not specified will be adjusted to `cpu_count=2` and `memory="128MiB"` * `cpu_count=6` and `memory="1GB"` will be adjusted to `cpu_count=6` and `memory="4GiB"` {% endhint %}
ZenML Scarf
--- ### Book/Component Guide/Deployers/Huggingface (docs/book/component-guide/deployers/huggingface.md) --- description: Deploying your pipelines to Hugging Face Spaces. --- # Hugging Face Deployer [Hugging Face Spaces](https://huggingface.co/spaces) is a platform for hosting and sharing machine learning applications. The Hugging Face deployer is a [deployer](./) flavor included in the ZenML Hugging Face integration that deploys your pipelines to Hugging Face Spaces as Docker-based applications. {% hint style="warning" %} This component is only meant to be used within the context of a [remote ZenML installation](https://docs.zenml.io/getting-started/deploying-zenml). Usage with a local ZenML setup may lead to unexpected behavior! {% endhint %} ## When to use it You should use the Hugging Face deployer if: * you're already using Hugging Face for model hosting or datasets. * you want to share your AI pipelines as publicly accessible or private Spaces. * you're looking for a simple, managed platform for deploying Docker-based applications. * you want to leverage Hugging Face's infrastructure for hosting your pipeline deployments. * you need an easy way to showcase ML workflows to the community. ## How to deploy it {% hint style="info" %} The Hugging Face deployer requires a remote ZenML installation. You must ensure that you are connected to the remote ZenML server before using this stack component. {% endhint %} In order to use a Hugging Face deployer, you need to first deploy [ZenML to the cloud](https://docs.zenml.io/getting-started/deploying-zenml/). The only other requirement is having a Hugging Face account and generating an access token with write permissions. ## How to use it To use the Hugging Face deployer, you need: * The ZenML `huggingface` integration installed. If you haven't done so, run ```shell zenml integration install huggingface ``` * [Docker](https://www.docker.com) installed and running. * A [remote artifact store](https://docs.zenml.io/stacks/artifact-stores/) as part of your stack. * A [remote container registry](https://docs.zenml.io/stacks/container-registries/) as part of your stack. * A [Hugging Face access token with write permissions](https://huggingface.co/settings/tokens) ### Hugging Face credentials You need a Hugging Face access token with write permissions to deploy pipelines. You can create one at [https://huggingface.co/settings/tokens](https://huggingface.co/settings/tokens). You have two options to provide credentials to the Hugging Face deployer: * Pass the token directly when registering the deployer using the `--token` parameter * (recommended) Store the token in a ZenML secret and reference it using [secret reference syntax](https://docs.zenml.io/how-to/project-setup-and-management/interact-with-secrets) ### Registering the deployer The deployer can be registered as follows: ```shell # Option 1: Direct token (not recommended for production) zenml deployer register \ --flavor=huggingface \ --token= # Option 2: Using a secret (recommended) zenml secret create hf_token --token= zenml deployer register \ --flavor=huggingface \ --token='{{hf_token.token}}' ``` ### Configuring the stack With the deployer registered, it can be used in the active stack: ```shell # Register and activate a stack with the new deployer zenml stack register -D ... --set ``` {% hint style="info" %} ZenML will build a Docker image called `/zenml:` which will be referenced in a Dockerfile deployed to your Hugging Face Space. Check out [this page](https://docs.zenml.io/how-to/customize-docker-builds/) if you want to learn more about how ZenML builds these images and how you can customize them. {% endhint %} You can now [deploy any ZenML pipeline](https://docs.zenml.io/concepts/deployment) using the Hugging Face deployer: ```shell zenml pipeline deploy --name my_deployment my_module.my_pipeline ``` ### Additional configuration For additional configuration of the Hugging Face deployer, you can pass the following `HuggingFaceDeployerSettings` attributes defined in the `zenml.integrations.huggingface.flavors.huggingface_deployer_flavor` module when configuring the deployer or defining or deploying your pipeline: * Basic settings common to all Deployers: * `auth_key`: A user-defined authentication key to use to authenticate with deployment API calls. * `generate_auth_key`: Whether to generate and use a random authentication key instead of the user-defined one. * `lcm_timeout`: The maximum time in seconds to wait for the deployment lifecycle management to complete. * Hugging Face Spaces-specific settings: * `space_hardware` (default: `None`): Hardware tier for the Space (e.g., `'cpu-basic'`, `'cpu-upgrade'`, `'t4-small'`, `'t4-medium'`, `'a10g-small'`, `'a10g-large'`). If not specified, uses free CPU tier. See [Hugging Face Spaces GPU documentation](https://huggingface.co/docs/hub/spaces-gpus) for available options and pricing. * `space_storage` (default: `None`): Persistent storage tier for the Space (e.g., `'small'`, `'medium'`, `'large'`). If not specified, no persistent storage is allocated. * `private` (default: `True`): Whether to create the Space as private. Set to `False` to make the Space publicly visible to everyone. * `app_port` (default: `8000`): Port number where your deployment server listens. Defaults to 8000 (ZenML server default). Hugging Face Spaces will route traffic to this port. Check out [this docs page](https://docs.zenml.io/concepts/steps_and_pipelines/configuration) for more information on how to specify settings. For example, if you wanted to deploy on GPU hardware with persistent storage, you would configure settings as follows: ```python from zenml.integrations.huggingface.deployers import HuggingFaceDeployerSettings huggingface_settings = HuggingFaceDeployerSettings( space_hardware="t4-small", space_storage="small", # private=True is the default for security ) @pipeline( settings={ "deployer": huggingface_settings } ) def my_pipeline(...): ... ``` ### Managing deployments Once deployed, you can manage your deployments using the ZenML CLI: ```shell # List all deployments zenml deployment list # Get deployment status zenml deployment describe # Get deployment logs zenml deployment logs # Delete a deployment zenml deployment delete ``` The deployed pipeline will be available as a Hugging Face Space at: ``` https://huggingface.co/spaces//- ``` By default, the space prefix is `zenml` but this can be configured using the `space_prefix` parameter when registering the deployer. ## Important Requirements ### Secure Secrets and Environment Variables {% hint style="success" %} The Hugging Face deployer handles secrets and environment variables **securely** using Hugging Face's Space Secrets and Variables API. Credentials are **never** written to the Dockerfile. {% endhint %} **How it works:** - Environment variables are set using `HfApi.add_space_variable()` - stored securely by Hugging Face - Secrets are set using `HfApi.add_space_secret()` - encrypted and never exposed in the Space repository - **Nothing is baked into the Dockerfile** - no risk of leaked credentials even in public Spaces **What this means:** - ✅ Safe to use with both private and public Spaces - ✅ Secrets remain encrypted and hidden from view - ✅ Environment variables are managed through HF's secure API - ✅ No credentials exposed in Dockerfile or repository files This secure approach ensures that if you choose to make your Space public (`private=False`), credentials remain protected and are never visible to anyone viewing your Space's repository. ### Container Registry Requirement {% hint style="warning" %} The Hugging Face deployer **requires** a container registry to be part of your ZenML stack. The Docker image must be pre-built and pushed to a **publicly accessible** container registry. {% endhint %} **Why public access is required:** Hugging Face Spaces cannot authenticate with private Docker registries when building Docker Spaces. The platform pulls your Docker image during the build process, which means it needs public access. **Recommended registries:** - [Docker Hub](https://hub.docker.com/) public repositories - [GitHub Container Registry (GHCR)](https://ghcr.io) with public images - Any other public container registry **Example setup with GitHub Container Registry:** ```shell # Register a public container registry zenml container-registry register ghcr_public \ --flavor=default \ --uri=ghcr.io/ # Add it to your stack zenml stack update --container-registry=ghcr_public ``` ### Configuring iframe Embedding (X-Frame-Options) By default, ZenML's deployment server sends an `X-Frame-Options` header that prevents the deployment UI from being embedded in iframes. This causes issues with Hugging Face Spaces, which displays deployments in an iframe. **To fix this**, you must configure your pipeline's `DeploymentSettings` to disable the `X-Frame-Options` header: ```python from zenml import pipeline from zenml.config import DeploymentSettings, SecureHeadersConfig # Configure deployment settings deployment_settings = DeploymentSettings( app_title="My ZenML Pipeline", app_description="ML pipeline deployed to Hugging Face Spaces", app_version="1.0.0", secure_headers=SecureHeadersConfig( xfo=False, # Disable X-Frame-Options to allow iframe embedding server=True, hsts=False, content=True, referrer=True, cache=True, permissions=True, ), cors={ "allow_origins": ["*"], "allow_methods": ["GET", "POST", "OPTIONS"], "allow_headers": ["*"], "allow_credentials": False, }, ) @pipeline( name="my_hf_pipeline", settings={"deployment": deployment_settings} ) def my_pipeline(): # Your pipeline steps here pass ``` Without this configuration, the Hugging Face Spaces UI will show a blank page or errors when trying to display your deployment. ## Additional Resources * [Hugging Face Spaces Documentation](https://huggingface.co/docs/hub/spaces) * [Docker Spaces Guide](https://huggingface.co/docs/hub/spaces-sdks-docker) * [Hugging Face Hardware Options](https://huggingface.co/docs/hub/spaces-gpus) * [ZenML Deployment Concepts](https://docs.zenml.io/concepts/deployment)
ZenML Scarf
--- ### Book/Component Guide/Deployers/Kubernetes (docs/book/component-guide/deployers/kubernetes.md) --- description: Deploying your pipelines to Kubernetes clusters. --- # Kubernetes Deployer [Kubernetes](https://kubernetes.io/) is the industry-standard container orchestration platform for deploying and managing containerized applications at scale. The Kubernetes deployer is a [deployer](./) flavor included in the ZenML Kubernetes integration that deploys your pipelines to any Kubernetes cluster as production-ready services. {% hint style="warning" %} This component is only meant to be used within the context of a [remote ZenML installation](https://docs.zenml.io/getting-started/deploying-zenml/). Usage with a local ZenML setup may lead to unexpected behavior! {% endhint %} ## When to use it You should use the Kubernetes deployer if: * you already have a Kubernetes cluster (EKS, GKE, AKS, or self-managed). * you need fine-grained control over deployment configuration (resources, networking, security). * you want production-grade features like autoscaling, health probes, and high availability. * you need to deploy to on-premises infrastructure or air-gapped environments. * you want to leverage existing Kubernetes expertise and tooling in your organization. * you need to integrate with existing Kubernetes resources (Ingress, NetworkPolicies, ServiceMonitors, etc.). ## Prerequisites To use the Kubernetes deployer, you need: * The ZenML `kubernetes` integration installed: ```shell zenml integration install kubernetes ``` * [Docker](https://www.docker.com) installed and running. * A [remote artifact store](https://docs.zenml.io/stacks/artifact-stores/) as part of your stack. * A [remote container registry](https://docs.zenml.io/stacks/container-registries/) as part of your stack. * A running Kubernetes cluster (version 1.21 or higher recommended) * [Kubernetes cluster access](#kubernetes-access-and-permissions) either via `kubectl` or a service connector. ### Kubernetes access and permissions You have two different options to provide cluster access to the Kubernetes deployer: * use `kubectl` to authenticate locally with your Kubernetes cluster * (recommended) configure [a Kubernetes Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/kubernetes-service-connector) and link it to the Kubernetes deployer stack component. #### Kubernetes Permissions The Kubernetes deployer needs the following permissions in the target namespace: * **Deployments**: `create`, `get`, `list`, `watch`, `update`, `delete` * **Services**: `create`, `get`, `list`, `watch`, `update`, `delete` * **Secrets**: `create`, `get`, `list`, `watch`, `update`, `delete` * **Pods**: `get`, `list`, `watch` (for logs and status) * **Namespaces**: `create`, `get` (if creating namespaces) If using additional resources (Ingress, HPA, NetworkPolicy, etc.), you'll also need permissions for those resource types. #### Configuration use-case: local kubectl with context This configuration assumes you have configured `kubectl` to authenticate with your cluster (i.e. by running `kubectl config use-context `). This is the easiest way to get started: ```shell zenml deployer register \ --flavor=kubernetes \ --kubernetes_context= \ --kubernetes_namespace=zenml-deployments ``` {% hint style="warning" %} This setup is not portable to other machines unless they have the same kubectl context configured. For production and team environments, use a service connector instead. {% endhint %} #### Configuration use-case: Kubernetes Service Connector This is the recommended approach for production and team environments. It makes credentials portable and manageable: ```shell # Register a Kubernetes service connector zenml service-connector register \ --type kubernetes \ --auth-method=token \ --token= \ --server= \ --certificate_authority= \ --resource-type kubernetes-cluster # Register the deployer and link it to the connector zenml deployer register \ --flavor=kubernetes \ --kubernetes_namespace=zenml-deployments \ --connector ``` See the [Kubernetes Service Connector documentation](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/kubernetes-service-connector) for more authentication methods including: * Service account tokens * Kubeconfig files * Cloud provider authentication (EKS, GKE, AKS) #### Configuration use-case: In-cluster deployment If your ZenML server runs inside the same Kubernetes cluster where you want to deploy pipelines, you can use in-cluster authentication: ```shell zenml deployer register \ --flavor=kubernetes \ --incluster=True \ --kubernetes_namespace=zenml-deployments ``` This uses the service account token mounted into the pod running ZenML. ### Configuring the stack With the deployer registered, you can use it in your active stack: ```shell # Register and activate a stack with the new deployer zenml stack register -D ... --set ``` {% hint style="info" %} ZenML will build a Docker image called `/zenml:` and use it to deploy your pipeline as a Kubernetes Deployment with a Service. Check out [this page](https://docs.zenml.io/how-to/customize-docker-builds/) if you want to learn more about how ZenML builds these images and how you can customize them. {% endhint %} You can now [deploy any ZenML pipeline](https://docs.zenml.io/concepts/deployment) using the Kubernetes deployer: ```shell zenml pipeline deploy --name my_deployment my_module.my_pipeline ``` ## Advanced configuration The Kubernetes deployer follows a progressive complexity model, allowing you to start simple and add configuration as needed: ### Level 1: Essential settings (80% of use cases) Most deployments only need these basic settings: ```python from zenml import pipeline, step from zenml.integrations.kubernetes.flavors.kubernetes_deployer_flavor import ( KubernetesDeployerSettings ) from zenml.config import ResourceSettings @step def greet(name: str) -> str: return f"Hello {name}!" # Basic deployment settings settings = { "deployer": KubernetesDeployerSettings( namespace="my-namespace", service_type="LoadBalancer", # or "NodePort", "ClusterIP" service_port=8000, ), "resources": ResourceSettings( cpu_count=1, memory="2GB", min_replicas=1, max_replicas=3, ) } @pipeline(settings=settings) def greet_pipeline(name: str = "World"): greet(name=name) ``` ### Level 2: Production-ready configuration For production deployments, add health probes, labels, and resource limits: ```python settings = { "deployer": KubernetesDeployerSettings( namespace="production", service_type="ClusterIP", # Use with Ingress service_port=8000, # Labels for organization labels={ "environment": "production", "team": "ml-platform", "version": "1.0", }, # Prometheus monitoring annotations={ "prometheus.io/scrape": "true", "prometheus.io/port": "8000", "prometheus.io/path": "/metrics", }, ), "resources": ResourceSettings( cpu_count=2, memory="4GB", min_replicas=2, max_replicas=10, ) } ``` ### Level 3: Additional resources Deploy additional Kubernetes resources alongside your main Deployment and Service (Ingress, HorizontalPodAutoscaler, PodDisruptionBudget, NetworkPolicy, etc.): First, create a YAML file (e.g., `k8s-resources.yaml`) with your additional resources: ```yaml # Ingress for domain-based routing --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: my-ingress namespace: {{namespace}} # ZenML fills this annotations: nginx.ingress.kubernetes.io/rewrite-target: / spec: ingressClassName: nginx rules: - host: myapp.company.com # Your domain http: paths: - path: / pathType: Prefix backend: service: name: {{name}} # ZenML fills this with the Service name port: number: {{settings.service_port}} # ZenML fills this # HorizontalPodAutoscaler for autoscaling --- apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: my-hpa namespace: {{namespace}} spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: {{name}} # ZenML fills this with the Deployment name minReplicas: {{replicas}} # ZenML fills this maxReplicas: 10 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70 # PodDisruptionBudget for high availability --- apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: my-pdb namespace: {{namespace}} spec: minAvailable: 1 selector: matchLabels: managed-by: {{labels['managed-by']}} zenml-deployment-id: {{labels['zenml-deployment-id']}} ``` Then reference this file in your deployment settings: ```python settings = { "deployer": KubernetesDeployerSettings( additional_resources=[ "./k8s-resources.yaml" ], strict_additional_resources=True, # Fail if any resource fails to apply # ... other settings ) } ``` **Available template variables** for use in your YAML files: Core objects (access their properties directly): * `{{deployment}}`: Full DeploymentResponse object - access via `{{deployment.id}}`, `{{deployment.name}}`, etc. * `{{settings}}`: Full KubernetesDeployerSettings object - access via `{{settings.service_port}}`, `{{settings.service_type}}`, etc. Common values: * `{{name}}`: Deployment/Service resource name (use this for Deployment name, Service name in Ingress, HPA target, etc.) * `{{namespace}}`: Kubernetes namespace * `{{labels}}`: Dict of all labels (includes ZenML-managed labels + custom labels) - `{{labels['managed-by']}}`: Always 'zenml' - `{{labels['zenml-deployment-id']}}`: Deployment UUID - `{{labels['zenml-deployment-name']}}`: Human-readable deployment name * `{{replicas}}`: Configured replica count * `{{image}}`: Container image URI * `{{command}}`: Container command * `{{args}}`: Container args * `{{env}}`: Environment variables dict * `{{resources}}`: Resource requests/limits dict * `{{pod_settings}}`: KubernetesPodSettings object (if configured) {% hint style="warning" %} **Important prerequisites:** - **Ingress**: Requires an ingress controller (nginx, traefik, etc.) installed in your cluster - **HPA**: Requires [metrics-server](https://github.com/kubernetes-sigs/metrics-server) installed - **ServiceMonitor**: Requires [Prometheus Operator](https://prometheus-operator.dev/) CRDs installed - **CRDs**: Any custom resources must have their CRDs installed beforehand {% endhint %} ### Level 4: Custom Deployment and Service templates For maximum control, you can completely override the built-in Deployment and Service templates by providing your own Jinja2 templates. This allows you to customize every aspect of the core Kubernetes resources that ZenML creates. **When to use custom templates:** * You need to add features not supported by the standard settings (init containers, sidecar containers, custom volume types) * You want complete control over health probe configuration beyond the provided settings * You need specific Kubernetes features for compliance or security requirements * You're migrating existing Kubernetes manifests to ZenML and want to maintain the exact structure **How it works:** 1. Create a directory for your custom templates (e.g., `~/.zenml/k8s-templates/`) 2. Add one or both of these files to override the built-in templates: - `deployment.yaml.j2` - Override the Deployment resource - `service.yaml.j2` - Override the Service resource 3. Configure the deployer to use your custom templates: ```python settings = { "deployer": KubernetesDeployerSettings( custom_deployment_template_file="~/.zenml/k8s-templates/deployment.yaml.j2", custom_service_template_file="~/.zenml/k8s-templates/service.yaml.j2", # ... other settings ) } ``` **Available template variables:** Your custom templates have access to all the same context variables as the built-in templates: Core objects: * `deployment`: Full DeploymentResponse object * `settings`: Full KubernetesDeployerSettings object (access health probes, ports, etc. via `settings.X`) Common values: * `name`: Deployment/Service resource name * `namespace`: Kubernetes namespace * `image`: Container image URI * `replicas`: Number of replicas * `labels`: Dict of all labels * `command`: Container command (list) * `args`: Container args (list) * `env`: Environment variables dict * `resources`: Resource requests/limits dict (with `requests` and `limits` keys) * `pod_settings`: KubernetesPodSettings object (access volumes, affinity, tolerations, etc.) **Example: Custom deployment template with init container** Create `~/MyProject/k8s-templates/deployment.yaml.j2`: ``` /* Detailed source-code truncated for AI context efficiency. */ ``` {% hint style="info" %} **Tip**: Start with ZenML's built-in templates as a reference. You can find them on GitHub: - [deployment.yaml.j2](https://github.com/zenml-io/zenml/blob/main/src/zenml/integrations/kubernetes/templates/kubernetes/deployment.yaml.j2) - [service.yaml.j2](https://github.com/zenml-io/zenml/blob/main/src/zenml/integrations/kubernetes/templates/kubernetes/service.yaml.j2) Copy and modify them for your needs. {% endhint %} {% hint style="warning" %} When using custom templates, you're responsible for maintaining compatibility with ZenML's deployment lifecycle. Ensure your templates: - Use the correct label selectors (`zenml-deployment-id`, `managed-by: zenml`) for resource tracking - Expose the correct container port (`{{ settings.service_port }}`) - Include health probes for proper deployment tracking - Pass through environment variables (`{{ env }}`) - Use `{{ name }}` for consistent resource naming {% endhint %} ### Complete settings reference For a complete list of all available settings, see the `KubernetesDeployerSettings` class. Here's a comprehensive overview organized by category: **Basic Settings** (common to all Deployers): * `auth_key`: User-defined authentication key for deployment API calls * `generate_auth_key`: Whether to generate a random authentication key * `lcm_timeout`: Maximum time in seconds to wait for deployment lifecycle operations **Essential Settings**: * `namespace`: Kubernetes namespace for the deployment (defaults to deployer's `kubernetes_namespace`) * `service_type`: How to expose the service - `LoadBalancer`, `NodePort`, or `ClusterIP` (default: `LoadBalancer`) * `service_port`: Port to expose on the service (default: `8000`) * `image_pull_policy`: When to pull images - `Always`, `IfNotPresent`, or `Never` (default: `IfNotPresent`) * `labels`: Additional labels to apply to all resources * `annotations`: Annotations to add to pod resources **Container Configuration**: * `command`: Override container command/entrypoint * `args`: Override container args * `service_account_name`: Kubernetes service account for pods * `image_pull_secrets`: List of secret names for pulling private images **Health Probes**: * `readiness_probe_path`: HTTP path for readiness probe (default: `/api/health`) * `readiness_probe_initial_delay`: Initial delay in seconds (default: `10`) * `readiness_probe_period`: Probe interval in seconds (default: `10`) * `readiness_probe_timeout`: Probe timeout in seconds (default: `5`) * `readiness_probe_failure_threshold`: Failures before marking pod not ready (default: `3`) * `liveness_probe_path`: HTTP path for liveness probe (default: `/api/health`) * `liveness_probe_initial_delay`: Initial delay in seconds (default: `30`) * `liveness_probe_period`: Probe interval in seconds (default: `10`) * `liveness_probe_timeout`: Probe timeout in seconds (default: `5`) * `liveness_probe_failure_threshold`: Failures before restarting pod (default: `3`) **Advanced Settings**: * `pod_settings`: Advanced pod configuration (see `KubernetesPodSettings`) * `additional_resources`: List of paths to YAML files with additional K8s resources * `strict_additional_resources`: If `True`, fail deployment if any additional resource fails (default: `True`) * `custom_templates_dir`: Path to directory with custom Jinja2 templates **Internal Settings**: * `wait_for_load_balancer_timeout`: Timeout for LoadBalancer IP assignment (default: `150` seconds, `0` to skip) * `deployment_ready_check_interval`: Interval between readiness checks (default: `2` seconds) Check out [this docs page](https://docs.zenml.io/concepts/steps_and_pipelines/configuration) for more information on how to specify settings. ## Troubleshooting ### Deployment stuck in pending state Check pod events and logs: ```shell # Get deployment info zenml deployment describe my-deployment # Follow logs zenml deployment logs my-deployment -f # Check Kubernetes resources directly kubectl get pods -n kubectl describe pod -n ``` Common causes: * Insufficient cluster resources * Image pull errors (check image_pull_secrets) * Node selector/affinity constraints not satisfied * PersistentVolumeClaim pending ### LoadBalancer not getting external IP If using `LoadBalancer` service type and it stays ``: * Check if your cluster supports LoadBalancer (cloud providers usually do, local clusters usually don't) * For local clusters, use `NodePort` instead * For production without LoadBalancer support, use `ClusterIP` with an Ingress ### Additional resources failing to apply If using `strict_additional_resources=True` and deployment fails: ```shell # Check which resource failed zenml deployment describe my-deployment # Validate resources manually kubectl apply --dry-run=client -f k8s-resources.yaml ``` Common issues: * Missing CRDs (install required operators) * Missing cluster components (metrics-server for HPA, ingress controller for Ingress) * Invalid resource references (check template variables) ### Image pull errors If pods can't pull the container image: * Verify image exists in registry: `docker pull ` * Check `image_pull_secrets` are configured correctly * Verify service account has access to registry * Check image pull policy (`IfNotPresent` vs `Always`) ## Best practices 1. **Use service connectors** in production for portable, manageable credentials 2. **Always configure health probes** for production deployments 3. **Use Ingress with ClusterIP** instead of LoadBalancer for cost and flexibility 4. **Use labels and annotations** for organization, monitoring, and cost tracking 5. **Configure resource limits** to prevent resource exhaustion 6. **Use HPA** for autoscaling based on actual load 7. **Configure PodDisruptionBudget** for high availability during cluster updates 8. **Keep additional resources in version control** alongside your pipeline code
ZenML Scarf
--- ### Book/Component Guide/Deployers/Local (docs/book/component-guide/deployers/local.md) --- description: Deploying pipelines on your local machine as background processes. --- # Local Deployer The local deployer is a [deployer](./) flavor that comes built-in with ZenML and deploys pipelines on your local machine as background processes. ### When to use it The local deployer is part of your default stack when you're first getting started with ZenML. Due to it running locally on your machine, it requires no additional setup and is easy to use and debug. You should use the local deployer if: * you're just getting started with ZenML and want to deploy pipelines without setting up any cloud infrastructure. * you're writing a new pipeline and want to experiment and debug quickly ### How to deploy it The local deployer comes with ZenML and works without any additional setup. ### How to use it To use the local deployer, you can register it and use it in your active stack: ```shell zenml deployer register --flavor=local # Register and activate a stack with the new deployer zenml stack register -D ... --set ``` You can now [deploy any ZenML pipeline](https://docs.zenml.io/concepts/deployment) using the local deployer: ```shell zenml pipeline deploy --name my_deployment my_module.my_pipeline ``` ### Additional configuration For additional configuration of the Local deployer, you can pass the following `LocalDeployerSettings` attributes defined in the `zenml.deployers.local.local_deployer` module when configuring the deployer or defining or deploying your pipeline: * Basic settings common to all Deployers: * `auth_key`: A user-defined authentication key to use to authenticate with deployment API calls. * `generate_auth_key`: Whether to generate and use a random authentication key instead of the user-defined one. * `lcm_timeout`: The maximum time in seconds to wait for the deployment lifecycle management to complete. * Settings specific to the Local deployer: * `port`: A custom port that the deployment server will listen on. Not set by default. * `allocate_port_if_busy`: If True, allocate a free port if the configured `port` is busy or not set. Defaults to True. * `port_range`: The range of ports to search for a free port. Defaults to `(8000, 65535)`. * `address`: The address that the deployment server will listen on. Defaults to `127.0.0.1`. * `blocking`: Whether to run the deployment in the current process instead of running it as a daemon process. Defaults to False. Use this if you want to debug issues with the deployment ASGI application itself. * `auto_reload`: Whether to enable auto-reload for the uvicorn server. This is useful to speed up local development by automatically restarting the server when code changes are detected without requiring a re-provisioning of the entire deployment. Defaults to False. NOTE: the `auto_reload` setting has no effect on changes in the pipeline configuration, step configuration or stack configuration. Check out [this docs page](https://docs.zenml.io/concepts/steps_and_pipelines/configuration) for more information on how to specify settings. For example, if you wanted to specify the port to use for the deployment, you would configure settings as follows: ```python from zenml import step, pipeline from zenml.deployers.local.local_deployer import LocalDeployerSettings @step def greet(name: str) -> str: return f"Hello {name}!" settings = { "deployer": LocalDeployerSettings( port=8000 ) } @pipeline(settings=settings) def greet_pipeline(name: str = "John"): greet(name=name) ```
ZenML Scarf
--- ### Book/Component Guide/Experiment Trackers/README (docs/book/component-guide/experiment-trackers/README.md) --- description: Logging and visualizing ML experiments. icon: clipboard --- # Experiment Trackers Experiment trackers let you track your ML experiments by logging extended information about your models, datasets, metrics, and other parameters and allowing you to browse them, visualize them and compare them between runs. In the ZenML world, every pipeline run is considered an experiment, and ZenML facilitates the storage of experiment results through Experiment Tracker stack components. This establishes a clear link between pipeline runs and experiments. Related concepts: * the Experiment Tracker is an optional type of Stack Component that needs to be registered as part of your ZenML [Stack](https://docs.zenml.io/user-guides/production-guide/understand-stacks). * ZenML already provides versioning and tracking for the pipeline artifacts by storing artifacts in the [Artifact Store](https://docs.zenml.io/stacks/artifact-stores/). ### When to use it ZenML already records information about the artifacts circulated through your pipelines by means of the mandatory [Artifact Store](https://docs.zenml.io/stacks/artifact-stores/). However, these ZenML mechanisms are meant to be used programmatically and can be more difficult to work with without a visual interface. Experiment Trackers on the other hand are tools designed with usability in mind. They include extensive UIs providing users with an interactive and intuitive interface that allows them to browse and visualize the information logged during the ML pipeline runs. You should add an Experiment Tracker to your ZenML stack and use it when you want to augment ZenML with the visual features provided by experiment tracking tools. ### How they experiment trackers slot into the stack Here is an architecture diagram that shows how experiment trackers fit into the overall story of a remote stack. #### Experiment Tracker Flavors Experiment Trackers are optional stack components provided by integrations: | Experiment Tracker | Flavor | Integration | Notes | | ---------------------------------- | --------- | ----------- | ----------------------------------------------------------------------------------------------- | | [Comet](comet.md) | `comet` | `comet` | Add Comet experiment tracking and visualization capabilities to your ZenML pipelines | | [MLflow](mlflow.md) | `mlflow` | `mlflow` | Add MLflow experiment tracking and visualization capabilities to your ZenML pipelines | | [Neptune](neptune.md) | `neptune` | `neptune` | Add Neptune experiment tracking and visualization capabilities to your ZenML pipelines | | [Weights & Biases](wandb.md) | `wandb` | `wandb` | Add Weights & Biases experiment tracking and visualization capabilities to your ZenML pipelines | | [Trackio](trackio.md) | `trackio` | `trackio` | Add Trackio experiment tracking and visualization capabilities to your ZenML pipelines | | [Custom Implementation](custom.md) | _custom_ | | _custom_ | If you would like to see the available flavors of Experiment Tracker, you can use the command: ```shell zenml experiment-tracker flavor list ``` ### How to use it Every Experiment Tracker has different capabilities and uses a different way of logging information from your pipeline steps, but it generally works as follows: * first, you have to configure and add an Experiment Tracker to your ZenML stack * next, you have to explicitly enable the Experiment Tracker for individual steps in your pipeline by decorating them with the included decorator * in your steps, you have to explicitly log information (e.g. models, metrics, data) to the Experiment Tracker same as you would if you were using the tool independently of ZenML * finally, you can access the Experiment Tracker UI to browse and visualize the information logged during your pipeline runs. You can use the following code snippet to get the URL of the experiment tracker UI for the experiment linked to a certain step of your pipeline run: {% hint style="info" %} Stacks can have multiple experiment trackers attached. If you enable tracking with `@step(experiment_tracker=True)`, ZenML activates all attached experiment trackers for that step. If you want to use only one tracker, specify it explicitly by name in the `@step` decorator. {% endhint %} ```python from zenml.client import Client pipeline_run = Client().get_pipeline_run("") step = pipeline_run.steps[""] experiment_tracker_url = step.run_metadata["experiment_tracker_url"].value ``` {% hint style="info" %} Experiment trackers will automatically declare runs as failed if the corresponding ZenML pipeline step fails. {% endhint %} Consult the documentation for the particular [Experiment Tracker flavor](./#experiment-tracker-flavors) that you plan on using or are using in your stack for detailed information about how to use it in your ZenML pipelines.
ZenML Scarf
--- ### Book/Component Guide/Experiment Trackers/Comet (docs/book/component-guide/experiment-trackers/comet.md) --- description: Logging and visualizing experiments with Comet. --- # Comet The Comet Experiment Tracker is an [Experiment Tracker](./) flavor provided with the Comet ZenML integration that uses [the Comet experiment tracking platform](https://www.comet.com/site/products/ml-experiment-tracking/) to log and visualize information from your pipeline steps (e.g., models, parameters, metrics).

A pipeline with a Comet experiment tracker url as metadata

### When would you want to use it? [Comet](https://www.comet.com/site/products/ml-experiment-tracking/) is a popular platform that you would normally use in the iterative ML experimentation phase to track and visualize experiment results. That doesn't mean that it cannot be repurposed to track and visualize the results produced by your automated pipeline runs, as you make the transition towards a more production-oriented workflow. You should use the Comet Experiment Tracker: * if you have already been using Comet to track experiment results for your project and would like to continue doing so as you are incorporating MLOps workflows and best practices in your project through ZenML. * if you are looking for a more visually interactive way of navigating the results produced from your ZenML pipeline runs (e.g., models, metrics, datasets) * if you would like to connect ZenML to Comet to share the artifacts and metrics logged by your pipelines with your team, organization, or external stakeholders You should consider one of the other [Experiment Tracker flavors](./#experiment-tracker-flavors) if you have never worked with Comet before and would rather use another experiment tracking tool that you are more familiar with. ### How do you deploy it? The Comet Experiment Tracker flavor is provided by the Comet ZenML integration. You need to install it on your local machine to be able to register a Comet Experiment Tracker and add it to your stack: ```bash zenml integration install comet -y ``` The Comet Experiment Tracker needs to be configured with the credentials required to connect to the Comet platform using one of the available authentication methods. #### Authentication Methods You need to configure the following credentials for authentication to the Comet platform: * `api_key`: Mandatory API key token of your Comet account. * `project_name`: The name of the project where you're sending the new experiment. If the project is not specified, the experiment is put in the default project associated with your API key. * `workspace`: Optional. The name of the workspace where your project is located. If not specified, the default workspace associated with your API key will be used. {% tabs %} {% tab title="ZenML Secret (Recommended)" %} This method requires you to [configure a ZenML secret](https://docs.zenml.io/getting-started/deploying-zenml/secret-management) to store the Comet tracking service credentials securely. You can create the secret using the `zenml secret create` command: ```bash zenml secret create comet_secret \ --workspace= \ --project_name= \ --api_key= ``` Once the secret is created, you can use it to configure the Comet Experiment Tracker: ```bash # Reference the workspace, project, and api-key in our experiment tracker component zenml experiment-tracker register comet_tracker \ --flavor=comet \ --workspace={{comet_secret.workspace}} \ --project_name={{comet_secret.project_name}} \ --api_key={{comet_secret.api_key}} ... # Register and set a stack with the new experiment tracker zenml stack register custom_stack -e comet_experiment_tracker ... --set ``` {% hint style="info" %} Read more about [ZenML Secrets](https://docs.zenml.io/getting-started/deploying-zenml/secret-management) in the ZenML documentation. {% endhint %} {% endtab %} {% tab title="Basic Authentication" %} This option configures the credentials for the Comet platform directly as stack component attributes. {% hint style="warning" %} This is not recommended for production settings as the credentials won't be stored securely and will be clearly visible in the stack configuration. {% endhint %} ```bash # Register the Comet experiment tracker zenml experiment-tracker register comet_experiment_tracker --flavor=comet \ --workspace= --project_name= --api_key= # Register and set a stack with the new experiment tracker zenml stack register custom_stack -e comet_experiment_tracker ... --set ``` {% endtab %} {% endtabs %}

A stack with the Comet experiment tracker

For more up-to-date information on the Comet Experiment Tracker implementation and its configuration, you can have a look at [the SDK docs for our Comet integration](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-comet.html#zenml.integrations.comet). ### How do you use it? To be able to log information from a ZenML pipeline step using the Comet Experiment Tracker component in the active stack, you need to enable an experiment tracker using the `@step` decorator. Then use Comet logging capabilities as you would normally do, e.g.: ```python from zenml.client import Client experiment_tracker = Client().active_stack.experiment_tracker @step(experiment_tracker=experiment_tracker.name) def my_step(): ... # go through some experiment tracker methods experiment_tracker.log_metrics({"my_metric": 42}) experiment_tracker.log_params({"my_param": "hello"}) # or use the Experiment object directly experiment_tracker.experiment.log_model(...) # or pass the Comet Experiment object into helper methods from comet_ml.integration.sklearn import log_model log_model( experiment=experiment_tracker.experiment, model_name="SVC", model=model, ) ... ``` {% hint style="info" %} Instead of hardcoding an experiment tracker name, you can also use the [Client](https://docs.zenml.io/sdk-reference/zenml/client) to dynamically use the experiment tracker of your active stack, as shown in the example above. {% endhint %} ### Comet UI Comet comes with a web-based UI that you can use to find further details about your tracked experiments. Every ZenML step that uses Comet should create a separate experiment which you can inspect in the Comet UI.

A confusion matrix logged in the Comet UI

A model tracked in the Comet UI

You can find the URL of the Comet experiment linked to a specific ZenML run via the metadata of the step in which the experiment tracker was used: ```python from zenml.client import Client last_run = client.get_pipeline("").last_run trainer_step = last_run.steps[""] tracking_url = trainer_step.run_metadata["experiment_tracker_url"].value print(tracking_url) ```

A pipeline with a Comet experiment tracker url as metadata

Alternatively, you can see an overview of all experiments at `https://www.comet.com/{WORKSPACE_NAME}/{PROJECT_NAME}/experiments/`. {% hint style="info" %} The naming convention of each Comet experiment is `{pipeline_run_name}_{step_name}` (e.g., `comet_example_pipeline-25_Apr_22-20_06_33_535737_my_step`), and each experiment will be tagged with both `pipeline_name` and `pipeline_run_name`, which you can use to group and filter experiments. {% endhint %} ## Full Code Example This section combines all the code from this section into one simple script that you can use to run easily:
Code Example of this Section ``` /* Detailed source-code truncated for AI context efficiency. */ ```
#### Additional configuration For additional configuration of the Comet experiment tracker, you can pass `CometExperimentTrackerSettings` to provide additional tags for your experiments: ```python from zenml.integrations.comet.flavors.comet_experiment_tracker_flavor import ( CometExperimentTrackerSettings, ) comet_settings = CometExperimentTrackerSettings( tags=["some_tag"], run_name="", settings={}, ) @step( experiment_tracker="", settings={ "experiment_tracker": comet_settings } ) def my_step(): ... ``` Check out the [SDK docs](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-comet.html#zenml.integrations.comet) for a full list of available attributes and [this docs page](https://docs.zenml.io/concepts/steps_and_pipelines/configuration) for more information on how to specify settings.
ZenML Scarf
--- ### Book/Component Guide/Experiment Trackers/Custom (docs/book/component-guide/experiment-trackers/custom.md) --- description: Learning how to develop a custom experiment tracker. --- # Develop a custom experiment tracker {% hint style="info" %} Before diving into the specifics of this component type, it is beneficial to familiarize yourself with our [general guide to writing custom component flavors in ZenML](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/implement-a-custom-stack-component). This guide provides an essential understanding of ZenML's component flavor concepts. {% endhint %} {% hint style="warning" %} **Base abstraction in progress!** We are actively working on the base abstraction for the Experiment Tracker, which will be available soon. As a result, their extension is not recommended at the moment. When you are selecting an Experiment Tracker for your stack, you can use one of [the existing flavors](./#experiment-tracker-flavors). If you need to implement your own Experiment Tracker flavor, you can still do so, but keep in mind that you may have to refactor it when the base abstraction is released. {% endhint %} ### Build your own custom experiment tracker If you want to create your own custom flavor for an experiment tracker, you can follow the following steps: 1. Create a class that inherits from the `BaseExperimentTracker` class and implements the abstract methods. 2. If you need any configuration, create a class that inherits from the `BaseExperimentTrackerConfig` class and add your configuration parameters. 3. Bring both the implementation and the configuration together by inheriting from the `BaseExperimentTrackerFlavor` class. Once you are done with the implementation, you can register it through the CLI. Please ensure you **point to the flavor class via dot notation**: ```shell zenml experiment-tracker flavor register ``` For example, if your flavor class `MyExperimentTrackerFlavor` is defined in `flavors/my_flavor.py`, you'd register it by doing: ```shell zenml experiment-tracker flavor register flavors.my_flavor.MyExperimentTrackerFlavor ``` {% hint style="warning" %} ZenML resolves the flavor class by taking the path where you initialized zenml (via `zenml init`) as the starting point of resolution. Therefore, please ensure you follow [the best practice](https://docs.zenml.io/user-guides/best-practices/iac) of initializing zenml at the root of your repository. If ZenML does not find an initialized ZenML repository in any parent directory, it will default to the current working directory, but usually, it's better to not have to rely on this mechanism and initialize zenml at the root. {% endhint %} Afterward, you should see the new flavor in the list of available flavors: ```shell zenml experiment-tracker flavor list ``` {% hint style="warning" %} It is important to draw attention to when and how these base abstractions are coming into play in a ZenML workflow. * The **CustomExperimentTrackerFlavor** class is imported and utilized upon the creation of the custom flavor through the CLI. * The **CustomExperimentTrackerConfig** class is imported when someone tries to register/update a stack component with this custom flavor. Especially, during the registration process of the stack component, the config will be used to validate the values given by the user. As `Config` objects are inherently `pydantic` objects, you can also add your own custom validators here. * The **CustomExperimentTracker** only comes into play when the component is ultimately in use. The design behind this interaction lets us separate the configuration of the flavor from its implementation. This way we can register flavors and components even when the major dependencies behind their implementation are not installed in our local setting (assuming the `CustomExperimentTrackerFlavor` and the `CustomExperimentTrackerConfig` are implemented in a different module/path than the actual `CustomExperimentTracker`). {% endhint %}
ZenML Scarf
--- ### Book/Component Guide/Experiment Trackers/Mlflow (docs/book/component-guide/experiment-trackers/mlflow.md) --- description: Logging and visualizing experiments with MLflow. --- # MLflow The MLflow Experiment Tracker is an [Experiment Tracker](./) flavor provided with the MLflow ZenML integration that uses [the MLflow tracking service](https://mlflow.org/docs/latest/tracking.html) to log and visualize information from your pipeline steps (e.g. models, parameters, metrics). ## When would you want to use it? [MLflow Tracking](https://www.mlflow.org/docs/latest/tracking.html) is a very popular tool that you would normally use in the iterative ML experimentation phase to track and visualize experiment results. That doesn't mean that it cannot be repurposed to track and visualize the results produced by your automated pipeline runs, as you make the transition toward a more production-oriented workflow. You should use the MLflow Experiment Tracker: * if you have already been using MLflow to track experiment results for your project and would like to continue doing so as you are incorporating MLOps workflows and best practices in your project through ZenML. * if you are looking for a more visually interactive way of navigating the results produced from your ZenML pipeline runs (e.g. models, metrics, datasets) * if you or your team already have a shared MLflow Tracking service deployed somewhere on-premise or in the cloud, and you would like to connect ZenML to it to share the artifacts and metrics logged by your pipelines You should consider one of the other [Experiment Tracker flavors](./#experiment-tracker-flavors) if you have never worked with MLflow before and would rather use another experiment tracking tool that you are more familiar with. ## How do you configure it? The MLflow Experiment Tracker flavor is provided by the MLflow ZenML integration, you need to install it on your local machine to be able to register an MLflow Experiment Tracker and add it to your stack: ```shell zenml integration install mlflow -y ``` The MLflow Experiment Tracker can be configured to accommodate the following [MLflow deployment scenarios](https://mlflow.org/docs/latest/tracking.html#common-setups): * [Localhost (default)](https://mlflow.org/docs/latest/tracking.html#common-setups) and [Local Tracking with Local Database](https://mlflow.org/docs/latest/tracking/tutorials/local-database.html): This scenario requires that you use a [local Artifact Store](https://docs.zenml.io/stacks/artifact-stores/local) alongside the MLflow Experiment Tracker in your ZenML stack. The local Artifact Store comes with limitations regarding what other types of components you can use in the same stack. This scenario should only be used to run ZenML locally and is not suitable for collaborative and production settings. No parameters need to be supplied when configuring the MLflow Experiment Tracker, e.g: ```shell # Register the MLflow experiment tracker zenml experiment-tracker register mlflow_experiment_tracker --flavor=mlflow # Register and set a stack with the new experiment tracker zenml stack register custom_stack -e mlflow_experiment_tracker ... --set ``` {% hint style="warning" %} If you use the default local MLflow configuration without setting a `tracking_uri`, ZenML stores new MLflow tracking metadata in a SQLite database at `/mlflow.db` and stores MLflow artifacts in `/mlflow_artifacts`. Earlier ZenML versions used MLflow's file store at `/mlruns` for this default local setup. Existing local runs in that directory are not migrated automatically and won't appear in the new default SQLite-backed MLflow UI. The files remain on disk and can still be inspected separately with MLflow's file-store backend. {% endhint %} * [Remote Experiment Tracking with MLflow Tracking Server](https://mlflow.org/docs/latest/tracking/tutorials/remote-server.html): This scenario assumes that you have already deployed an MLflow Tracking Server enabled with proxied artifact storage access. There is no restriction regarding what other types of components it can be combined with. This option requires [authentication-related parameters](mlflow.md#authentication-methods) to be configured for the MLflow Experiment Tracker. {% hint style="warning" %} Due to a [critical severity vulnerability](https://github.com/advisories/GHSA-xg73-94fp-g449) found in older versions of MLflow, we recommend using MLflow version 2.2.1 or higher. ZenML supports both MLflow 2.x and 3.x versions. {% endhint %} * [Databricks scenario](https://www.databricks.com/product/managed-mlflow): This scenario assumes that you have a Databricks workspace, and you want to use the managed MLflow Tracking server it provides. This option requires [authentication-related parameters](mlflow.md#authentication-methods) to be configured for the MLflow Experiment Tracker. ### Authentication Methods Remote MLflow tracking servers require a `tracking_uri` and exactly one authentication method. Set `tracking_uri` to the URL of a remote MLflow Tracking Server, or to `"databricks"` when using the MLflow Tracking Server managed by Databricks. You can also set `tracking_insecure_tls=True` to skip SSL certificate verification when connecting to the tracking server. The supported authentication methods are: * **Username/password:** set both `tracking_username` and `tracking_password`. Use this for tracking servers that accept basic authentication. * **Token:** set `tracking_token`. Use this for tracking servers that accept bearer token authentication. * **Databricks OAuth M2M:** set `tracking_uri="databricks"`, `databricks_host`, `databricks_client_id`, and `databricks_client_secret`. Use this for Databricks-managed MLflow with a service principal, as described in the section below. For Databricks-managed MLflow, `databricks_host` must be the Databricks workspace URL. The `databricks_client_id` and `databricks_client_secret` fields are only used for Databricks OAuth M2M authentication. #### Databricks OAuth M2M Databricks OAuth machine-to-machine (M2M) authentication authorizes unattended workloads, such as pipelines and automation scripts, to access Databricks resources as a service principal. See the Databricks documentation on [authorizing service principal access with OAuth](https://docs.databricks.com/aws/en/dev-tools/auth/oauth-m2m) for the full setup flow. For ZenML, the Databricks OAuth M2M values map to the MLflow experiment tracker configuration as follows: * Databricks workspace URL: `databricks_host` * Service principal application ID / client ID: `databricks_client_id` * Service principal OAuth secret: `databricks_client_secret` To get these values, create or select a service principal in your Databricks workspace, generate an OAuth secret for it, and copy the displayed client ID and secret value. Make sure the service principal has access to the workspace and to the MLflow experiment path you want to use. {% tabs %} {% tab title="Basic Authentication" %} This option configures the credentials for the MLflow tracking service directly as stack component attributes. {% hint style="warning" %} This is not recommended for production settings as the credentials won't be stored securely and will be clearly visible in the stack configuration. {% endhint %} ```shell # Register the MLflow experiment tracker zenml experiment-tracker register mlflow_experiment_tracker --flavor=mlflow \ --tracking_uri= --tracking_token= # You can also register it like this: # zenml experiment-tracker register mlflow_experiment_tracker --flavor=mlflow \ # --tracking_uri= --tracking_username= --tracking_password= # Register and set a stack with the new experiment tracker zenml stack register custom_stack -e mlflow_experiment_tracker ... --set ``` {% endtab %} {% tab title="Databricks OAuth M2M" %} This option configures authentication to a Databricks-managed MLflow tracking server with a Databricks-managed service principal and OAuth M2M credentials. ```shell # Create a secret with the Databricks service principal OAuth credentials zenml secret create databricks_oauth_secret \ --client_id= \ --client_secret= # Register the MLflow experiment tracker zenml experiment-tracker register mlflow_databricks --flavor=mlflow \ --tracking_uri=databricks \ --databricks_host= \ --databricks_client_id={{databricks_oauth_secret.client_id}} \ --databricks_client_secret={{databricks_oauth_secret.client_secret}} # Register and set a stack with the new experiment tracker zenml stack register custom_stack -e mlflow_databricks ... --set ``` {% endtab %} {% tab title="ZenML Secret (Recommended)" %} This method requires you to [configure a ZenML secret](https://docs.zenml.io/how-to/project-setup-and-management/interact-with-secrets) to store the MLflow tracking service credentials securely. You can create the secret using the `zenml secret create` command: ```shell # Create a secret called `mlflow_secret` with key-value pairs for the # username and password to authenticate with the MLflow tracking server zenml secret create mlflow_secret \ --username= \ --password= ``` Once the secret is created, you can use it to configure the MLflow Experiment Tracker: ```shell # Reference the username and password in our experiment tracker component zenml experiment-tracker register mlflow \ --flavor=mlflow \ --tracking_username={{mlflow_secret.username}} \ --tracking_password={{mlflow_secret.password}} \ ... ``` {% hint style="warning" %} **PowerShell Terminal Note** When using the `zenml experiment-tracker register` command in **PowerShell**, referencing secrets using the `{{secret_name.key}}` syntax without quotes can cause the following error: ``` zenml.exe : The command parameter was already specified. ``` This is a quirk of how PowerShell interprets braces in command-line arguments. To resolve this, enclose the secret references in **double quotes**: ```bash --tracking_username="{{mlflow_secret.username}}" --tracking_password="{{mlflow_secret.password}}" ``` {% endhint %} {% hint style="info" %} Read more about [ZenML Secrets](https://docs.zenml.io/how-to/project-setup-and-management/interact-with-secrets) in the ZenML documentation. {% endhint %} {% endtab %} {% endtabs %} For more, up-to-date information on the MLflow Experiment Tracker implementation and its configuration, you can have a look at [the SDK docs](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-mlflow.html#zenml.integrations.mlflow) . ## How do you use it? To be able to log information from a ZenML pipeline step using the MLflow Experiment Tracker component in the active stack, you need to enable an experiment tracker using the `@step` decorator. Then use MLflow's logging or auto-logging capabilities as you would normally do, e.g.: ```python import mlflow import numpy as np import tensorflow as tf from zenml import step @step(experiment_tracker="") def tf_trainer( x_train: np.ndarray, y_train: np.ndarray, ) -> tf.keras.Model: """Train a neural net from scratch to recognize MNIST digits return our model or the learner""" # compile model mlflow.tensorflow.autolog() # train model # log additional information to MLflow explicitly if needed mlflow.log_param(...) mlflow.log_metric(...) mlflow.log_artifact(...) return model ``` {% hint style="info" %} Instead of hardcoding an experiment tracker name, you can also use the [Client](https://docs.zenml.io/sdk-reference/zenml/client) to dynamically use the experiment tracker of your active stack: ```python from zenml.client import Client experiment_tracker = Client().active_stack.experiment_tracker @step(experiment_tracker=experiment_tracker.name) def tf_trainer(...): ... ``` {% endhint %} ### MLflow UI MLflow comes with its own UI that you can use to find further details about your tracked experiments. You can find the URL of the MLflow experiment linked to a specific ZenML run via the metadata of the step in which the experiment tracker was used: ```python from zenml.client import Client last_run = client.get_pipeline("").last_run trainer_step = last_run.steps[""] tracking_url = trainer_step.run_metadata["experiment_tracker_url"].value print(tracking_url) ``` This will be the URL of the corresponding experiment in your deployed MLflow instance, or the local MLflow tracking URI if you are using ZenML's default local MLflow configuration. {% hint style="info" %} If you are using ZenML's default local MLflow configuration, the MLflow tracking metadata is stored in a SQLite database inside your active local artifact store, and MLflow artifacts are stored next to it. You can use the `mlflow ui` command to start MLflow at [`localhost:5000`](http://localhost:5000/) where you can then explore the UI in your browser. You can find the active artifact store path with `zenml artifact-store describe`. ```bash mlflow ui \ --backend-store-uri sqlite:////mlflow.db \ --default-artifact-root file:/mlflow_artifacts ``` {% endhint %} ### Additional configuration For additional configuration of the MLflow experiment tracker, you can pass `MLFlowExperimentTrackerSettings` to create nested runs or add additional tags to your MLflow runs: ```python import mlflow from zenml.integrations.mlflow.flavors.mlflow_experiment_tracker_flavor import MLFlowExperimentTrackerSettings mlflow_settings = MLFlowExperimentTrackerSettings( nested=True, tags={"key": "value"} ) @step( experiment_tracker="", settings={ "experiment_tracker": mlflow_settings } ) def step_one( data: np.ndarray, ) -> np.ndarray: ... ``` Check out the [SDK docs](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-mlflow.html#zenml.integrations.mlflow) for a full list of available attributes and [this docs page](https://docs.zenml.io/concepts/steps_and_pipelines/configuration) for more information on how to specify settings.
ZenML Scarf
--- ### Book/Component Guide/Experiment Trackers/Neptune (docs/book/component-guide/experiment-trackers/neptune.md) --- description: Logging and visualizing experiments with neptune.ai --- # Neptune {% hint style="warning" %} **Neptune.ai has been acquired by OpenAI** (announced December 2025) and Neptune's standalone services are discontinued as of March 5, 2026. While the ZenML Neptune integration remains functional for the time being, it will be removed soon and we recommend migrating to an alternative experiment tracker such as [MLflow](mlflow.md), [Weights & Biases](wandb.md), or [Comet](comet.md). {% endhint %} The Neptune Experiment Tracker is an [Experiment Tracker](./) flavor provided with the Neptune-ZenML integration that uses neptune.ai to log and visualize information from your pipeline steps (e.g. models, parameters, metrics). ### When would you want to use it? Neptune is a popular tool that you would normally use in the iterative ML experimentation phase to track and visualize experiment results or as a model registry for your production-ready models. Neptune can also track and visualize the results produced by your automated pipeline runs, as you make the transition towards a more production-oriented workflow. You should use the Neptune Experiment Tracker: * if you have already been using neptune.ai to track experiment results for your project and would like to continue doing so as you are incorporating MLOps workflows and best practices in your project through ZenML. * if you are looking for a more visually interactive way of navigating the results produced from your ZenML pipeline runs (e.g. models, metrics, datasets) * if you would like to connect ZenML to neptune.ai to share the artifacts and metrics logged by your pipelines with your team, organization, or external stakeholders You should consider one of the other [Experiment Tracker flavors](./#experiment-tracker-flavors) if you have never worked with neptune.ai before and would rather use another experiment tracking tool that you are more familiar with. ### How do you deploy it? The Neptune Experiment Tracker flavor is provided by the Neptune-ZenML integration. You need to install it on your local machine to be able to register the Neptune Experiment Tracker and add it to your stack: ```shell zenml integration install neptune -y ``` The Neptune Experiment Tracker needs to be configured with the credentials required to connect to Neptune using an API token. ### Authentication Methods You need to configure the following credentials for authentication to Neptune: * `api_token`: API key token of your Neptune account. If left blank, Neptune will attempt to retrieve the token from your environment variables. * `project`: The name of the project where you're sending the new run, in the form "workspace-name/project-name". If the project is not specified, Neptune will attempt to retrieve it from your environment variables. {% tabs %} {% tab title="ZenML Secret (Recommended)" %} This method requires you to [configure a ZenML secret](https://docs.zenml.io/how-to/project-setup-and-management/interact-with-secrets) to store the Neptune tracking service credentials securely. You can create the secret using the `zenml secret create` command: ```shell zenml secret create neptune_secret --api_token= ``` Once the secret is created, you can use it to configure the `neptune` Experiment Tracker: ```shell # Reference the project and api-token in our experiment tracker component zenml experiment-tracker register neptune_experiment_tracker \ --flavor=neptune \ --project= \ --api_token={{neptune_secret.api_token}} ... # Register and set a stack with the new experiment tracker zenml stack register neptune_stack -e neptune_experiment_tracker ... --set ``` {% hint style="info" %} Read more about [ZenML Secrets](https://docs.zenml.io/how-to/project-setup-and-management/interact-with-secrets) in the ZenML documentation. {% endhint %} {% endtab %} {% tab title="Basic Authentication" %} This option configures the credentials for neptune.ai directly as stack component attributes. {% hint style="warning" %} This is not recommended for production settings as the credentials won't be stored securely and will be clearly visible in the stack configuration. {% endhint %} ```shell # Register the Neptune experiment tracker zenml experiment-tracker register neptune_experiment_tracker --flavor=neptune \ --project= --api_token= # Register and set a stack with the new experiment tracker zenml stack register neptune_stack -e neptune_experiment_tracker ... --set ``` {% endtab %} {% endtabs %} For more, up-to-date information on the Neptune Experiment Tracker implementation and its configuration, you can have a look at [the SDK docs](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-neptune.html#zenml.integrations.neptune) . ### How do you use it? To log information from a ZenML pipeline step using the Neptune Experiment Tracker component in the active stack, you need to enable an experiment tracker using the `@step` decorator. Then fetch the [Neptune run object](https://web.archive.org/web/20250311101837/https://docs.neptune.ai/api/run/) and use logging capabilities as you would normally do. For example: ```python from zenml.integrations.neptune.experiment_trackers.run_state import ( get_neptune_run ) from neptune.utils import stringify_unsupported from zenml import get_step_context from sklearn.model_selection import train_test_split from sklearn.svm import SVC from sklearn.datasets import load_iris from zenml import pipeline, step from zenml.client import Client from zenml.integrations.neptune.experiment_trackers import NeptuneExperimentTracker # Get the experiment tracker from the active stack experiment_tracker: NeptuneExperimentTracker = Client().active_stack.experiment_tracker @step(experiment_tracker="neptune_experiment_tracker") def train_model() -> SVC: iris = load_iris() X_train, _, y_train, _ = train_test_split( iris.data, iris.target, test_size=0.2, random_state=42 ) params = { "kernel": "rbf", "C": 1.0, } model = SVC(**params) model.fit(X_train, y_train) # Log the model to Neptune neptune_run = get_neptune_run() neptune_run["parameters"] = params return model ``` {% hint style="info" %} Instead of hardcoding an experiment tracker name, you can also use the [Client](https://docs.zenml.io/sdk-reference/zenml/client) to dynamically use the experiment tracker of your active stack: ```python from zenml.client import Client experiment_tracker = Client().active_stack.experiment_tracker @step(experiment_tracker=experiment_tracker.name) def tf_trainer(...): ... ``` {% endhint %} #### Logging ZenML pipeline and step metadata to the Neptune run You can use the `get_step_context` method to log some ZenML metadata in your Neptune run: ```python from zenml import get_step_context from zenml.integrations.neptune.experiment_trackers.run_state import ( get_neptune_run ) from neptune.utils import stringify_unsupported @step(experiment_tracker="neptune_tracker") def my_step(): neptune_run = get_neptune_run() context = get_step_context() neptune_run["pipeline_metadata"] = stringify_unsupported( context.pipeline_run.get_metadata().dict() ) neptune_run[f"step_metadata/{context.step_name}"] = stringify_unsupported( context.step_run.get_metadata().dict() ) ... ``` #### Adding tags to your Neptune run You can pass a set of tags to the Neptune run by using the `NeptuneExperimentTrackerSettings` class, like in the example below: ```python import numpy as np import tensorflow as tf from zenml import step from zenml.integrations.neptune.experiment_trackers.run_state import ( get_neptune_run, ) from zenml.integrations.neptune.flavors import NeptuneExperimentTrackerSettings neptune_settings = NeptuneExperimentTrackerSettings(tags={"keras", "mnist"}) @step( experiment_tracker="", settings={ "experiment_tracker": neptune_settings } ) def my_step( x_test: np.ndarray, y_test: np.ndarray, model: tf.keras.Model, ) -> float: """Log metadata to Neptune run""" neptune_run = get_neptune_run() ... ``` Check out the [SDK docs](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-neptune.html#zenml.integrations.neptune) for a full list of available attributes ## Neptune UI Neptune comes with a web-based UI that you can use to find further details about your tracked experiments. You can find the URL of the Neptune run linked to a specific ZenML run printed on the console whenever a Neptune run is initialized. You can also find it in the dashboard in the metadata tab of any step that has used the tracker:

A pipeline with a Neptine run linked as metadata

Each pipeline run will be logged as a separate experiment run in Neptune, which you can inspect in the Neptune UI.

A list of Neptune runs from ZenML pipelines

Clicking on one run will reveal further metadata logged within the step:

Details of a Neptune run via a ZenML pipeline

## Full Code Example This section shows an end to end run with the ZenML Neptune integration.
Code Example of this Section ``` /* Detailed source-code truncated for AI context efficiency. */ ```
## Further reading Check [Neptune's docs](https://web.archive.org/web/20250316084453/https://docs.neptune.ai/integrations/zenml/) for further information on how to use this integration and Neptune in general.
ZenML Scarf
--- ### Book/Component Guide/Experiment Trackers/Trackio (docs/book/component-guide/experiment-trackers/trackio.md) --- description: Logging and visualizing experiments with Trackio. --- # Trackio Integration with ZenML ## Overview The Trackio integration for ZenML enables experiment tracking, metric logging, artifact management, and Hugging Face ecosystem publishing directly inside ZenML pipelines. The integration connects ZenML's pipeline orchestration layer with Trackio's lightweight experiment tracking runtime, allowing users to monitor and publish machine learning workflows with minimal operational overhead. ### Supported Features - Local experiment tracking - Hugging Face Space dashboards - Dataset publishing - Artifact logging - Metric visualization - System and GPU telemetry - Static dashboard workflows - Full Hugging Face ecosystem integration The tracker is implemented as a ZenML experiment tracker flavor and lifecycle integration. --- ## How the Integration Works During pipeline execution, the integration follows this workflow: 1. ZenML initializes the active experiment tracker 2. The Trackio integration creates a Trackio run 3. ZenML pipeline metadata is injected automatically 4. Metrics, artifacts, and metadata are logged to Trackio 5. Optional synchronization and publishing actions are executed 6. Run metadata and dashboard URLs are attached back to the ZenML run Internally, the integration hooks into the ZenML step lifecycle using: - `prepare_step_run` - `cleanup_step_run` - `get_step_run_metadata` --- ## Backend Support The integration supports multiple Trackio backend modes through the backend configuration field. | Backend | Description | |---------|-------------| | sqlite | Local lightweight tracking | | space | Hugging Face Space-hosted dashboards | | http | Remote Trackio server | | static | Static deployment mode | This allows the same ZenML pipeline to run locally or publish hosted dashboards without changing pipeline logic. --- ## Configuration The integration exposes two configuration layers: ### Tracker Configuration `TrackioExperimentTrackerConfig` defines backend and deployment behavior. Important fields include: | Field | Description | |-------|-------------| | project_name | Trackio project name | | backend | Backend type | | tracking_uri | Optional backend URI | | local_dir | Local tracking directory | | hf_token | Hugging Face authentication token | | hf_space | Hugging Face Space dashboard | | hf_dataset_repo | Dataset repository | | publish_to_space | Publish dashboards automatically | | publish_to_dataset | Publish logs automatically | ### Runtime Settings `TrackioExperimentTrackerSettings` controls runtime behavior. Supported settings include: | Setting | Description | |---------|-------------| | run_name | Custom run name | | tags | Run tags | | resume | Resume policy | | auto_sync | Automatically sync runs | | auto_freeze | Freeze dashboards | | log_system_metrics | System telemetry | | log_gpu_metrics | GPU telemetry | --- ## Complete Integration Example ### Overview The example pipeline demonstrates a complete Hugging Face + ZenML + Trackio workflow. The pipeline performs: - Dataset loading from Hugging Face Datasets - Sentiment inference using Transformers - Metric logging with Trackio - Artifact saving - Dataset publishing to Hugging Face Hub - Dashboard synchronization ### Active Experiment Tracker The currently active experiment tracker is resolved from the ZenML stack: ```python experiment_tracker = ( Client() .active_stack .experiment_tracker ) ``` This allows the pipeline to remain environment-independent while inheriting the configured experiment tracker automatically. ### Hugging Face Integration Targets ```python # Input dataset consumed by pipeline HF_INPUT_DATASET = ( "dair-ai/emotion" ) # Output dataset containing # Trackio inference logs/results HF_OUTPUT_DATASET_REPO = ( "AINovice2005/" "trackio-sentiment-results-v2" ) # Trackio dashboard Space HF_SPACE_ID = ( "AINovice2005/" "trackio-sentiment-dashboard-v2" ) HF_SPACE_URL = ( "https://huggingface.co/spaces/" f"{HF_SPACE_ID}" ) ``` ### Dataset Loading The pipeline loads an input dataset directly from the Hugging Face Hub: ```python @step def load_input_dataset() -> Dataset: """Load dataset from Hugging Face Hub.""" dataset = load_dataset( HF_INPUT_DATASET, split="train[:350]", ) print( "\nLoaded input dataset:" ) print(HF_INPUT_DATASET) return dataset ``` This step becomes reproducible and version-aware through ZenML. ### Transformers Inference The example initializes a Hugging Face Transformers sentiment pipeline: ```python classifier = hf_pipeline( task="sentiment-analysis", model=( "distilbert-base-uncased-" "finetuned-sst-2-english" ), ) ``` Inference is executed inside the `classify_sentiment` step. Each dataset row is processed individually and converted into structured prediction records. ### Trackio Logging The integration logs metrics directly through Trackio: ```python trackio.log(metrics) ``` The example logs: - Number of samples - Positive predictions - Negative predictions - Confidence scores - Prediction ratios - Min/max confidence Additional metadata is also logged: ```python trackio.log( { "model": ( "distilbert-base-" "uncased-finetuned-" "sst-2-english" ), "hf_input_dataset": ( HF_INPUT_DATASET ), "sample_predictions": ( results[:10] ), } ) ``` ### Artifact Management The pipeline stores prediction outputs and metrics locally before uploading them through Trackio. Artifacts are written as JSON files and uploaded using: ```python predictions_path = ( ARTIFACT_DIR / "predictions.json" ) with open( predictions_path, "w", ) as f: json.dump( results, f, indent=2, ) trackio.save( str(predictions_path) ) ``` This enables artifact persistence alongside experiment metadata. ### Hugging Face Dataset Publishing The processed dataset is published back to the Hugging Face Hub: ```python result_dataset.push_to_hub( HF_OUTPUT_DATASET_REPO, private=False, ) ``` This creates a reproducible dataset output workflow integrated directly into the pipeline. ### Hugging Face Space Dashboard The pipeline also publishes dashboard metadata for a Hugging Face Space: ```python trackio.log( { "space_status": ( "published" ), "space_url": ( HF_SPACE_URL ), } ) ``` ### Synchronization The example explicitly synchronizes the Trackio project after execution: ```python trackio.sync( project=experiment_tracker.config.project_name ) ``` This pushes tracked metadata and artifacts into the configured backend. ### Runtime Initialization Internally, the integration initializes Trackio with ZenML metadata: ```python config = { "zenml": { "pipeline_name": ( info.pipeline.name ), "step_name": ( info.pipeline_step_name ), "run_name": info.run_name, } } ``` The integration dynamically filters unsupported SDK arguments before calling: ```python trackio.init(**filtered_kwargs) ``` This improves compatibility across Trackio versions and backend modes. ### Automatic Cleanup At the end of each step, the integration finalizes the Trackio session using: ```python trackio.finish() ``` Optional synchronization and dashboard freezing can also be enabled through runtime settings. --- ## Full Example Code ``` /* Detailed source-code truncated for AI context efficiency. */ ``` --- ## Summary The Trackio integration extends ZenML with a lightweight experiment tracking layer that supports: - Metric logging - Artifact tracking - Hugging Face publishing - Hosted dashboards - Dataset synchronization - Experiment metadata management The example pipeline demonstrates how ZenML orchestration, Hugging Face tooling, and Trackio tracking can be combined into a single reproducible ML workflow with minimal infrastructure requirements. ### Integration Benefits - **Hugging Face Ecosystem**: Native integration with Datasets, Models, and Spaces. - **Multiple Backends**: Supports local SQLite, Hugging Face Spaces, HTTP servers, and static deployments. - **Automatic Metadata Injection**: Pipeline context (names, run IDs, step names) automatically included. - **Artifact Management**: Seamless artifact persistence and publishing. - **Dashboard Publishing**: Export runs to hosted Hugging Face Spaces dashboards. --- --- ### Book/Component Guide/Experiment Trackers/Vertexai (docs/book/component-guide/experiment-trackers/vertexai.md) --- description: Logging and visualizing experiments with Vertex AI Experiment Tracker. --- # Google Cloud VertexAI Experiment Tracker The Vertex AI Experiment Tracker is an [Experiment Tracker](./) flavor provided with the Vertex AI ZenML integration. It uses the [Vertex AI tracking service](https://cloud.google.com/vertex-ai/docs/experiments/intro-vertex-ai-experiments) to log and visualize information from your pipeline steps (e.g., models, parameters, metrics). ## When would you want to use it? [Vertex AI Experiment Tracker](https://cloud.google.com/vertex-ai/docs/experiments/intro-vertex-ai-experiments) is a managed service by Google Cloud that you would normally use in the iterative ML experimentation phase to track and visualize experiment results. That doesn't mean that it cannot be repurposed to track and visualize the results produced by your automated pipeline runs, as you make the transition toward a more production-oriented workflow. You should use the Vertex AI Experiment Tracker: * if you have already been using Vertex AI to track experiment results for your project and would like to continue doing so as you are incorporating MLOps workflows and best practices in your project through ZenML. * if you are looking for a more visually interactive way of navigating the results produced from your ZenML pipeline runs (e.g. models, metrics, datasets) * if you are building machine learning workflows in the Google Cloud ecosystem and want a managed experiment tracking solution tightly integrated with other Google Cloud services, Vertex AI is a great choice You should consider one of the other [Experiment Tracker flavors](./#experiment-tracker-flavors) if you have never worked with Vertex AI before and would rather use another experiment tracking tool that you are more familiar with, or if you are not using GCP or using other cloud providers. ## How do you configure it? The Vertex AI Experiment Tracker flavor is provided by the GCP ZenML integration, you need to install it on your local machine to be able to register a Vertex AI Experiment Tracker and add it to your stack: ```shell zenml integration install gcp -y ``` ### Configuration Options To properly register the Vertex AI Experiment Tracker, you can provide several configuration options tailored to your needs. Here are the main configurations you may want to set: * `project`: Optional. GCP project name. If `None` it will be inferred from the environment. * `location`: Optional. GCP location where your experiments will be created. If not set defaults to us-central1. * `staging_bucket`: Optional. The default staging bucket to use to stage artifacts. In the form gs://... * `service_account_path`: Optional. A path to the service account credential json file to be used to interact with Vertex AI Experiment Tracker. Please check the [Authentication Methods](vertexai.md#authentication-methods) chapter for more details. With the project, location and staging\_bucket, registering the Vertex AI Experiment Tracker can be done as follows: ```shell # Register the Vertex AI Experiment Tracker zenml experiment-tracker register vertex_experiment_tracker \ --flavor=vertex \ --project= \ --location= \ --staging_bucket=gs:// # Register and set a stack with the new experiment tracker zenml stack register custom_stack -e vertex_experiment_tracker ... --set ``` ### Authentication Methods Integrating and using a Vertex AI Experiment Tracker in your pipelines is not possible without employing some form of authentication. If you're looking for a quick way to get started locally, you can use the _Implicit Authentication_ method. However, the recommended way to authenticate to the Google Cloud Platform is through a [GCP Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/gcp-service-connector). This is particularly useful if you are configuring ZenML stacks that combine the Vertex AI Experiment Tracker with other remote stack components also running in GCP. > **Note**: Regardless of your chosen authentication method, you must grant your account the necessary permissions to use Vertex AI Experiment Tracking. Follow the principle of least privilege: > > **Recommended Approach:** > * `roles/aiplatform.user` role on your project, which allows you to create, manage, and track your experiments within Vertex AI. > * `roles/storage.objectAdmin` role **scoped to specific GCS buckets** rather than project-wide, granting the ability to read and write experiment artifacts, such as models and datasets, to those storage buckets. > > **Alternative - Custom Role with Minimal Permissions:** > For maximum security, create a custom role with only these specific permissions: > - `aiplatform.experiments.create` > - `aiplatform.experiments.get` > - `aiplatform.experiments.list` > - `aiplatform.experiments.update` > - `storage.objects.create` > - `storage.objects.get` > - `storage.objects.list` > - `storage.buckets.get` {% tabs %} {% tab title="Implicit Authentication" %} This configuration method assumes that you have authenticated locally to GCP using the [`gcloud` CLI](https://cloud.google.com/sdk/gcloud) (e.g., by running gcloud auth login). > **Note**: This method is quick for local setups but is unsuitable for team collaborations or production environments due to its lack of portability. We can then register the experiment tracker as follows: ```shell # Register the Vertex AI Experiment Tracker zenml experiment-tracker register \ --flavor=vertex \ --project= \ --location= \ --staging_bucket=gs:// # Register and set a stack with the new experiment tracker zenml stack register custom_stack -e vertex_experiment_tracker ... --set ``` {% endtab %} {% tab title="GCP Service Connector (recommended)" %} To set up the Vertex AI Experiment Tracker to authenticate to GCP, it is recommended to leverage the many features provided by the [GCP Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/gcp-service-connector) such as auto-configuration, best security practices regarding long-lived credentials and reusing the same credentials across multiple stack components. If you don't already have a GCP Service Connector configured in your ZenML deployment, you can register one using the interactive CLI command. You have the option to configure a GCP Service Connector that can be used to access more than one type of GCP resource: ```sh # Register a GCP Service Connector interactively zenml service-connector register --type gcp -i ``` After having set up or decided on a GCP Service Connector to use, you can register the Vertex AI Experiment Tracker as follows: ```shell # Register the Vertex AI Experiment Tracker zenml experiment-tracker register \ --flavor=vertex \ --project= \ --location= \ --staging_bucket=gs:// zenml experiment-tracker connect --connector # Register and set a stack with the new experiment tracker zenml stack register custom_stack -e vertex_experiment_tracker ... --set ``` {% endtab %} {% tab title="GCP Credentials" %} When you register the Vertex AI Experiment Tracker, you can [generate a GCP Service Account Key](https://cloud.google.com/docs/authentication/application-default-credentials#attached-sa), store it in a [ZenML Secret](https://docs.zenml.io/getting-started/deploying-zenml/secret-management) and then reference it in the Experiment Tracker configuration. This method has some advantages over the implicit authentication method: * you don't need to install and configure the GCP CLI on your host * you don't need to care about enabling your other stack components (orchestrators, step operators and model deployers) to have access to the experiment tracker through GCP Service Accounts and Workload Identity * you can combine the Vertex AI Experiment Tracker with other stack components that are not running in GCP For this method, you need to [create a user-managed GCP service account](https://cloud.google.com/iam/docs/service-accounts-create) and then [create a service account key](https://cloud.google.com/iam/docs/keys-create-delete#creating). With the service account key downloaded to a local file, you can register a ZenML secret and reference it in the Vertex AI Experiment Tracker configuration as follows: ```shell # Register the Vertex AI Experiment Tracker and reference the ZenML secret zenml experiment-tracker register \ --flavor=vertex \ --project= \ --location= \ --staging_bucket=gs:// \ --service_account_path=path/to/service_account_key.json # Register and set a stack with the new experiment tracker zenml experiment-tracker connect --connector ``` {% endtab %} {% endtabs %} ## How do you use it? To be able to log information from a ZenML pipeline step using the Vertex AI Experiment Tracker component in the active stack, you need to enable an experiment tracker using the `@step` decorator. Then use Vertex AI's logging or auto-logging capabilities as you would normally do, e.g. Here are two examples demonstrating how to use the experiment tracker: ### Example 1: Logging Metrics Using Built-in Methods This example demonstrates how to log time-series metrics using `aiplatform.log_time_series_metrics` from within a Keras callback, and using `aiplatform.log_metrics` to log specific metrics and `aiplatform.log_params` to log experiment parameters. The logged metrics can then be visualized in the UI of Vertex AI Experiment Tracker and integrated TensorBoard instance. > **Note:** To use the autologging functionality, ensure that the google-cloud-aiplatform library is installed with the Autologging extension. You can do this by running the following command: > > ```bash > pip install google-cloud-aiplatform[autologging] > ``` ```python from google.cloud import aiplatform class VertexAICallback(tf.keras.callbacks.Callback): def on_epoch_end(self, epoch, logs=None): logs = logs or {} metrics = {key: value for key, value in logs.items() if isinstance(value, (int, float))} aiplatform.log_time_series_metrics(metrics=metrics, step=epoch) @step(experiment_tracker="") def train_model( config: TrainerConfig, x_train: np.ndarray, y_train: np.ndarray, x_val: np.ndarray, y_val: np.ndarray, ): aiplatform.autolog() ... # Train the model, using the custom callback to log metrics into experiment tracker model.fit( x_train, y_train, validation_data=(x_test, y_test), epochs=config.epochs, batch_size=config.batch_size, callbacks=[VertexAICallback()] ) ... # Log specific metrics and parameters aiplatform.log_metrics(...) aiplatform.log_params(...) ``` ### Example 2: Uploading TensorBoard Logs This example demonstrates how to use an integrated TensorBoard instance to directly upload training logs. This is particularly useful if you're already using TensorBoard in your projects and want to benefit from its detailed visualizations during training. You can initiate the upload using `aiplatform.start_upload_tb_log` and conclude it with `aiplatform.end_upload_tb_log`. Similar to the first example, you can also log specific metrics and parameters directly. > **Note:** To use TensorBoard logging functionality, ensure you have the `google-cloud-aiplatform` library installed with the TensorBoard extension. You can install it using the following command: > > ```bash > pip install google-cloud-aiplatform[tensorboard] > ``` ```python from google.cloud import aiplatform @step(experiment_tracker="") def train_model( config: TrainerConfig, gcs_path: str, x_train: np.ndarray, y_train: np.ndarray, x_val: np.ndarray, y_val: np.ndarray, ): # get current experiment and run names experiment_tracker = Client().active_stack.experiment_tracker experiment_name = experiment_tracker.experiment_name experiment_run_name = experiment_tracker.run_name # define a TensorBoard callback, logs are written to gcs_path tensorboard_callback = tf.keras.callbacks.TensorBoard( log_dir=gcs_path, histogram_freq=1 ) # start the TensorBoard log upload aiplatform.start_upload_tb_log( tensorboard_experiment_name=experiment_name, logdir=gcs_path, run_name_prefix=f"{experiment_run_name}_", ) model.fit( x_train, y_train, validation_data=(x_test, y_test), epochs=config.epochs, batch_size=config.batch_size, ) ... # end the TensorBoard log upload aiplatform.end_upload_tb_log() aiplatform.log_metrics(...) aiplatform.log_params(...) ``` {% hint style="info" %} Instead of hardcoding an experiment tracker name, you can also use the [Client](https://docs.zenml.io/sdk-reference/zenml/client) to dynamically use the experiment tracker of your active stack: ```python from zenml.client import Client experiment_tracker = Client().active_stack.experiment_tracker @step(experiment_tracker=experiment_tracker.name) def tf_trainer(...): ... ``` {% endhint %} ### Experiment Tracker UI You can find the URL of the Vertex AI experiment linked to a specific ZenML run via the metadata of the step in which the experiment tracker was used: ```python from zenml.client import Client client = Client() last_run = client.get_pipeline("").last_run trainer_step = last_run.steps.get("") tracking_url = trainer_step.run_metadata["experiment_tracker_url"].value print(tracking_url) ``` This will be the URL of the corresponding experiment in Vertex AI Experiment Tracker. Below are examples of the UI for the Vertex AI Experiment Tracker and the integrated TensorBoard instance. **Vertex AI Experiment Tracker UI** **TensorBoard UI** ### Additional configuration For additional configuration of the Vertex AI Experiment Tracker, you can pass `VertexExperimentTrackerSettings` to specify an experiment name or choose previously created TensorBoard instance. > **Note**: By default, Vertex AI will use the default TensorBoard instance in your project if you don't explicitly specify one. ```python import mlflow from zenml.integrations.gcp.flavors.vertex_experiment_tracker_flavor import VertexExperimentTrackerSettings vertexai_settings = VertexExperimentTrackerSettings( experiment="", experiment_tensorboard="TENSORBOARD_RESOURCE_NAME" ) @step( experiment_tracker="", settings={"experiment_tracker": vertexai_settings}, ) def step_one( data: np.ndarray, ) -> np.ndarray: ... ``` Check out [this docs page](https://docs.zenml.io/concepts/steps_and_pipelines/configuration) for more information on how to specify settings.
ZenML Scarf
--- ### Book/Component Guide/Experiment Trackers/Wandb (docs/book/component-guide/experiment-trackers/wandb.md) --- description: Logging and visualizing experiments with Weights & Biases. --- # Weights & Biases The Weights & Biases Experiment Tracker is an [Experiment Tracker](./) flavor provided with the Weights & Biases ZenML integration that uses [the Weights & Biases experiment tracking platform](https://wandb.ai/site/experiment-tracking) to log and visualize information from your pipeline steps (e.g. models, parameters, metrics). ### When would you want to use it? [Weights & Biases](https://wandb.ai/site/experiment-tracking) is a very popular platform that you would normally use in the iterative ML experimentation phase to track and visualize experiment results. That doesn't mean that it cannot be repurposed to track and visualize the results produced by your automated pipeline runs, as you make the transition towards a more production-oriented workflow. You should use the Weights & Biases Experiment Tracker: * if you have already been using Weights & Biases to track experiment results for your project and would like to continue doing so as you are incorporating MLOps workflows and best practices in your project through ZenML. * if you are looking for a more visually interactive way of navigating the results produced from your ZenML pipeline runs (e.g. models, metrics, datasets) * if you would like to connect ZenML to Weights & Biases to share the artifacts and metrics logged by your pipelines with your team, organization, or external stakeholders You should consider one of the other [Experiment Tracker flavors](./#experiment-tracker-flavors) if you have never worked with Weights & Biases before and would rather use another experiment tracking tool that you are more familiar with. ### How do you deploy it? The Weights & Biases Experiment Tracker flavor is provided by the W\&B ZenML integration, you need to install it on your local machine to be able to register a Weights & Biases Experiment Tracker and add it to your stack: ```shell zenml integration install wandb -y ``` The Weights & Biases Experiment Tracker needs to be configured with the credentials required to connect to the Weights & Biases platform using one of the [available authentication methods](wandb.md#authentication-methods). #### Authentication Methods You need to configure the following credentials for authentication to the Weights & Biases platform: * `api_key`: Mandatory API key token of your Weights & Biases account. * `project_name`: The name of the project where you're sending the new run. If the project is not specified, the run is put in an "Uncategorized" project. * `entity`: An entity is a username or team name where you're sending runs. This entity must exist before you can send runs there, so make sure to create your account or team in the UI before starting to log runs. If you don't specify an entity, the run will be sent to your default entity, which is usually your username. {% tabs %} {% tab title="Basic Authentication" %} This option configures the credentials for the Weights & Biases platform directly as stack component attributes. {% hint style="warning" %} This is not recommended for production settings as the credentials won't be stored securely and will be clearly visible in the stack configuration. {% endhint %} ```shell # Register the Weights & Biases experiment tracker zenml experiment-tracker register wandb_experiment_tracker --flavor=wandb \ --entity= --project_name= --api_key= # Register and set a stack with the new experiment tracker zenml stack register custom_stack -e wandb_experiment_tracker ... --set ``` {% endtab %} {% tab title="ZenML Secret (Recommended)" %} This method requires you to [configure a ZenML secret](https://docs.zenml.io/how-to/project-setup-and-management/interact-with-secrets) to store the Weights & Biases tracking service credentials securely. You can create the secret using the `zenml secret create` command: ```shell zenml secret create wandb_secret \ --entity= \ --project_name= --api_key= ``` Once the secret is created, you can use it to configure the wandb Experiment Tracker: ```shell # Reference the entity, project and api-key in our experiment tracker component zenml experiment-tracker register wandb_tracker \ --flavor=wandb \ --entity={{wandb_secret.entity}} \ --project_name={{wandb_secret.project_name}} \ --api_key={{wandb_secret.api_key}} ... ``` {% hint style="info" %} Read more about [ZenML Secrets](https://docs.zenml.io/how-to/project-setup-and-management/interact-with-secrets) in the ZenML documentation. {% endhint %} {% endtab %} {% endtabs %} For more, up-to-date information on the Weights & Biases Experiment Tracker implementation and its configuration, you can have a look at [the SDK docs](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-wandb.html#zenml.integrations.wandb) . ### How do you use it? To be able to log information from a ZenML pipeline step using the Weights & Biases Experiment Tracker component in the active stack, you need to enable an experiment tracker using the `@step` decorator. Then use Weights & Biases logging or auto-logging capabilities as you would normally do, e.g.: ```python import wandb from wandb.integration.keras import WandbCallback @step(experiment_tracker="") def tf_trainer( config: TrainerConfig, x_train: np.ndarray, y_train: np.ndarray, x_val: np.ndarray, y_val: np.ndarray, ) -> tf.keras.Model: ... model.fit( x_train, y_train, epochs=config.epochs, validation_data=(x_val, y_val), callbacks=[ WandbCallback( log_evaluation=True, validation_steps=16, validation_data=(x_val, y_val), ) ], ) metric = ... wandb.log({"": metric}) ``` {% hint style="info" %} Instead of hardcoding an experiment tracker name, you can also use the [Client](https://docs.zenml.io/sdk-reference/zenml/client) to dynamically use the experiment tracker of your active stack: ```python from zenml.client import Client experiment_tracker = Client().active_stack.experiment_tracker @step(experiment_tracker=experiment_tracker.name) def tf_trainer(...): ... ``` {% endhint %} ### Weights & Biases UI Weights & Biases comes with a web-based UI that you can use to find further details about your tracked experiments. Every ZenML step that uses Weights & Biases should create a separate experiment run which you can inspect in the Weights & Biases UI: You can find the URL of the Weights & Biases experiment linked to a specific ZenML run via the metadata of the step in which the experiment tracker was used: ```python from zenml.client import Client last_run = client.get_pipeline("").last_run trainer_step = last_run.steps[""] tracking_url = trainer_step.run_metadata["experiment_tracker_url"].value print(tracking_url) ``` Or on the ZenML dashboard as metadata of a step that uses the tracker: Alternatively, you can see an overview of all experiment runs at https://wandb.ai/{ENTITY\_NAME}/{PROJECT\_NAME}/runs/. {% hint style="info" %} The naming convention of each Weights & Biases experiment run is `{pipeline_run_name}_{step_name}` (e.g. `wandb_example_pipeline-25_Apr_22-20_06_33_535737_tf_evaluator`). By default, ZenML groups W&B runs by the ZenML pipeline run name, adds human-readable tags (`zenml`, the pipeline name, and the pipeline run name), and stores durable ZenML identifiers in the W&B config. {% endhint %} #### Additional configuration For additional configuration of the Weights & Biases experiment tracker, you can pass `WandbExperimentTrackerSettings` to overwrite the [wandb.Settings](https://github.com/wandb/client/blob/master/wandb/sdk/wandb_settings.py#L353) or pass additional tags for your runs: ```python import wandb from zenml.integrations.wandb.flavors.wandb_experiment_tracker_flavor import WandbExperimentTrackerSettings wandb_settings = WandbExperimentTrackerSettings( settings=wandb.Settings(...), tags=["some_tag"], enable_weave=True, # Enable Weave integration ) @step( experiment_tracker="", settings={ "experiment_tracker": wandb_settings } ) def my_step( x_test: np.ndarray, y_test: np.ndarray, model: tf.keras.Model, ) -> float: """Everything in this step is auto-logged""" ... ``` ZenML disables W&B SDK console output by default with settings equivalent to `wandb.Settings(console="off", silent=True)`. W&B writes normal status and progress messages to stderr and uses carriage returns to redraw progress output. ZenML log stores preserve stderr as error-level logs, which can create confusing entries and trigger error-based log alerts even when W&B is working correctly. Metrics logged with `wandb.log(...)` are still sent to W&B. To show W&B SDK console output again, override these settings: ```python wandb_settings = WandbExperimentTrackerSettings( settings=wandb.Settings( console="auto", silent=False, ), ) ``` If you enable W&B SDK console output again, expect W&B login, sync, and progress messages to appear in ZenML step logs. Some of these messages may be stored as error-level logs because they are emitted through stderr by the W&B SDK. ZenML manages common `wandb.init(...)` fields for you. In addition to the W&B entity and project configured on the stack component, `WandbExperimentTrackerSettings` supports: ```python wandb_settings = WandbExperimentTrackerSettings( run_name="optional-display-name", group="optional-group", tags=["team-a"], job_type="training", run_config={"dataset": "customers-v2"}, ) ``` ZenML truncates W&B tags to 64 characters and removes duplicates after truncation, so long tags with the same first 64 characters collapse into one W&B tag. When ZenML metadata is enabled, W&B config receives flat keys such as `zenml_pipeline_name`, `zenml_pipeline_run_name`, `zenml_pipeline_run_id`, `zenml_step_name`, `zenml_latest_step_run_id`, and `zenml_latest_step_run_version`. ZenML dashboard links are added when they can be derived from the active server. These values are written to W&B config, not W&B summary, so metric summaries stay focused on experiment results. To use W&B in a minimal mode without ZenML-added grouping, tags, or config, disable metadata: ```python wandb_settings = WandbExperimentTrackerSettings( enable_zenml_metadata=False, tags=["plain-wandb"], ) ``` By default, ZenML leaves W&B run IDs unset (`run_id_strategy="wandb_generated"`), preserving W&B-generated IDs. The supported `run_id_strategy` values are: | Strategy | Behavior | | --- | --- | | `wandb_generated` | Let W&B generate the run ID. | | `reuse_on_retry` | Use one deterministic W&B run ID for each ZenML pipeline-step invocation, so retries for the same invocation resume the same W&B run. | | `new_on_retry` | Use one deterministic W&B run ID for each ZenML step-run attempt, including retries. | For example, this configuration keeps retries for the same ZenML step invocation attached to the same W&B run: ```python retry_collapsed_settings = WandbExperimentTrackerSettings( run_id_strategy="reuse_on_retry", resume="allow", ) ``` If you prefer one W&B run for every ZenML step-run attempt, including retries, use: ```python attempt_settings = WandbExperimentTrackerSettings( run_id_strategy="new_on_retry", ) ``` When `run_id_strategy="new_on_retry"` is used without an explicit `run_name`, ZenML appends the ZenML step-run version to the W&B display name so retry attempts are easier to distinguish in the W&B UI. ZenML defaults `resume` to `allow` whenever it passes a deterministic or explicit W&B run ID. Advanced users can attach logging to an existing W&B run, or pass unmanaged W&B initialization fields through `init_kwargs`: ```python wandb_settings = WandbExperimentTrackerSettings( run_id="existing-wandb-run-id", init_kwargs={"notes": "Backfill evaluation metrics."}, ) ``` ZenML validates conflicts early: * `run_id` cannot be combined with a non-default `run_id_strategy`. * `resume="never"` cannot be combined with `run_id_strategy="reuse_on_retry"`, because retries intentionally reuse the same W&B run ID. * `resume="must"` requires either an explicit `run_id` or a deterministic `run_id_strategy`. * `run_config` cannot contain `zenml_*` keys, which are reserved for ZenML metadata. * `init_kwargs` cannot override ZenML-managed `wandb.init(...)` keys such as `entity`, `project`, `name`, `id`, `resume`, `group`, `job_type`, `tags`, `config`, or `settings`. ### Using Weights & Biases Weave [Weights & Biases Weave](https://weave-docs.wandb.ai/) is a customizable dashboard interface that allows you to visualize and interact with your machine learning models, data, and results. ZenML provides built-in support for Weave through the `WandbExperimentTrackerSettings`. #### Enabling and Disabling Weave You can enable or disable Weave for specific steps in your pipeline by configuring the `enable_weave` parameter in the `WandbExperimentTrackerSettings` (or setting it when registering the experiment tracker component): ```python import weave from openai import OpenAI from zenml import pipeline, step from zenml.integrations.wandb.flavors.wandb_experiment_tracker_flavor import ( WandbExperimentTrackerSettings, ) # Settings to enable Weave wandb_with_weave_settings = WandbExperimentTrackerSettings( tags=["weave_enabled"], enable_weave=True, # Enable Weave integration ) # Settings to disable Weave wandb_without_weave_settings = WandbExperimentTrackerSettings( tags=["weave_disabled"], enable_weave=False, # Explicitly disable Weave integration ) ``` #### Using Weave with ZenML Steps To use Weave with your ZenML steps, you need to: 1. Configure your `WandbExperimentTrackerSettings` with `enable_weave=True` 2. Apply the `@weave.op()` decorator to your step function 3. Configure your step to use the Weights & Biases experiment tracker with your Weave settings Here's an example: ```python @step( experiment_tracker="wandb_weave", # Your W&B experiment tracker component name settings={"experiment_tracker": wandb_with_weave_settings}, ) @weave.op() # The Weave decorator def my_step_with_weave() -> str: """This step will use Weave for enhanced visualization""" # Your step implementation return "Step with Weave enabled" ``` {% hint style="warning" %} **Important**: The decorator order is critical. The `@weave.op()` decorator must be applied AFTER the `@step` decorator (i.e., closer to the function definition). If you reverse the order, your step won't work correctly. ```python # CORRECT ORDER @step(experiment_tracker="wandb_weave") @weave.op() def correct_order_step(): ... # INCORRECT ORDER - will cause issues @weave.op() @step(experiment_tracker="wandb_weave") def incorrect_order_step(): ... ``` {% endhint %} To explicitly disable Weave for specific steps, while keeping the ability to use the `@weave.op()` decorator: ```python @step( experiment_tracker="wandb_weave", settings={"experiment_tracker": wandb_without_weave_settings}, ) @weave.op() def my_step_without_weave() -> str: """This step will not use Weave even with the @weave.op() decorator""" # Your step implementation return "Step with Weave disabled" ``` #### Weave Initialization Behavior When using Weave with ZenML, there are a few important behaviors to understand: 1. If `enable_weave=True` and a `project_name` is specified in your W\&B experiment tracker, Weave will be initialized with that project name. 2. If `enable_weave=True` but no `project_name` is specified, Weave initialization will be skipped. 3. If `enable_weave=False` and a `project_name` is specified (explicit disabling), Weave will be disabled with `settings={"disabled": True}`. 4. If `enable_weave=False` and no `project_name` is specified, Weave disabling will be skipped. {% hint style="info" %} For more information about Weights & Biases Weave and its capabilities, visit the [Weave documentation](https://docs.wandb.ai/weave). {% endhint %} ## Full Code Example This section shows an end to end run with the ZenML W\&B integration.
Example without Weave ``` /* Detailed source-code truncated for AI context efficiency. */ ```
Example with Weave for LLM Tracing ``` /* Detailed source-code truncated for AI context efficiency. */ ```
Check out the [SDK docs](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-wandb.html#zenml.integrations.wandb) for a full list of available attributes and [this docs page](https://docs.zenml.io/concepts/steps_and_pipelines/configuration) for more information on how to specify settings.
ZenML Scarf
--- ### Book/Component Guide/Feature Stores/README (docs/book/component-guide/feature-stores/README.md) --- icon: database description: Managing data in feature stores. --- # Feature Stores Feature stores allow data teams to serve data via an offline store and an online low-latency store where data is kept in sync between the two. It also offers a centralized registry where features (and feature schemas) are stored for use within a team or wider organization. As a data scientist working on training your model, your requirements for how you access your batch / 'offline' data will almost certainly be different from how you access that data as part of a real-time or online inference setting. Feast solves the problem of developing train-serve skew where those two sources of data diverge from each other. Feature stores are a relatively recent addition to commonly-used machine learning stacks. ### When to use it The feature store is an optional stack component in the ZenML Stack. The feature store as a technology should be used to store the features and inject them into the process on the server side. This includes * Productionalize new features * Reuse existing features across multiple pipelines and models * Achieve consistency between training and serving data (Training Serving Skew) * Provide a central registry of features and feature schemas ### List of available feature stores For production use cases, some more flavors can be found in specific `integrations` modules. In terms of features stores, ZenML features an integration of `feast`. | Feature Store | Flavor | Integration | Notes | |------------------------------------|----------|-------------|--------------------------------------------------------------------------| | [FeastFeatureStore](feast.md) | `feast` | `feast` | Connect ZenML with already existing Feast | | [Custom Implementation](custom.md) | _custom_ | | Extend the feature store abstraction and provide your own implementation | If you would like to see the available flavors for feature stores, you can use the command: ```shell zenml feature-store flavor list ``` ### How to use it The available implementation of the feature store is built on top of the feast integration, which means that using a feature store is no different from what's described on the [feast page: How to use it?](feast.md#how-do-you-use-it).
ZenML Scarf
--- ### Book/Component Guide/Feature Stores/Custom (docs/book/component-guide/feature-stores/custom.md) --- description: Learning how to develop a custom feature store. --- # Develop a Custom Feature Store {% hint style="info" %} Before diving into the specifics of this component type, it is beneficial to familiarize yourself with our [general guide to writing custom component flavors in ZenML](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/implement-a-custom-stack-component). This guide provides an essential understanding of ZenML's component flavor concepts. {% endhint %} Feature stores allow data teams to serve data via an offline store, and an online low-latency store where data is kept in sync between the two. It also offers a centralized registry where features (and feature schemas) are stored for use within a team or wider organization. {% hint style="warning" %} **Base abstraction in progress!** We are actively working on the base abstraction for the feature stores, which will be available soon. As a result, their extension is not possible at the moment. If you would like to use a feature store in your stack, please check the list of already available feature stores down below. {% endhint %}
ZenML Scarf
--- ### Book/Component Guide/Feature Stores/Feast (docs/book/component-guide/feature-stores/feast.md) --- description: Managing data in Feast feature stores. --- # Feast Feast (Feature Store) is an operational data system for managing and serving machine learning features to models in production. Feast is able to serve feature data to models from a low-latency online store (for real-time prediction) or from an offline store (for scale-out batch scoring or model training). ### When would you want to use it? There are two core functions that feature stores enable: * access to data from an offline / batch store for training. * access to online data at inference time. Feast integration currently supports your choice of offline data sources for your online feature serving. We encourage users to check out [Feast's documentation](https://docs.feast.dev/) and [guides](https://docs.feast.dev/how-to-guides/) on how to set up your offline and online data sources via the configuration `yaml` file. {% hint style="info" %} COMING SOON: While the ZenML integration has an interface to access online feature store data, it currently is not usable in production settings with deployed models. We will update the docs when we enable this functionality. {% endhint %} ### How to deploy it? ZenML assumes that users already have a Feast feature store that they just need to connect with. If you don't have a feature store yet, follow the [Feast Documentation](https://docs.feast.dev/how-to-guides/feast-snowflake-gcp-aws/deploy-a-feature-store) to deploy one first. To use the feature store as a ZenML stack component, you also need to install the corresponding `feast` integration in ZenML: ```shell zenml integration install feast ``` Now you can register your feature store as a ZenML stack component and add it into a corresponding stack: ```shell zenml feature-store register feast_store --flavor=feast --feast_repo="" zenml stack register ... -f feast_store ``` ### How do you use it? {% hint style="warning" %} Online data retrieval is possible in a local setting, but we don't currently support using the online data serving in the context of a deployed model or as part of model deployment. We will update this documentation as we develop this feature. {% endhint %} Getting features from a registered and active feature store is possible by creating your own step that interfaces into the feature store: ``` /* Detailed source-code truncated for AI context efficiency. */ ``` {% hint style="warning" %} Note that ZenML's use of Pydantic to serialize and deserialize inputs stored in the ZenML metadata means that we are limited to basic data types. Pydantic cannot handle Pandas `DataFrame`s, for example, or `datetime` values, so in the above code you can see that we have to convert them at various points. {% endhint %} For more information and a full list of configurable attributes of the Feast feature store, check out the [SDK Docs](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-feast.html#zenml.integrations.feast) .
ZenML Scarf
--- ### Book/Component Guide/Image Builders/README (docs/book/component-guide/image-builders/README.md) --- description: Building container images for your ML workflow. icon: box-open --- # Image Builders The image builder is an essential part of most remote MLOps stacks. It is used to build container images such that your machine-learning pipelines and steps can be executed in remote environments. ### When to use it The image builder is needed whenever other components of your stack need to build container images. Currently, this is the case for most of ZenML's remote [orchestrators](https://docs.zenml.io/stacks/orchestrators/) , [step operators](https://docs.zenml.io/stacks/step-operators/), and some [model deployers](https://docs.zenml.io/stacks/model-deployers/). These containerize your pipeline code and therefore require an image builder to build [Docker](https://www.docker.com/) images. ### Image Builder Flavors Out of the box, ZenML comes with a `local` image builder that builds OCI images on your client machine using Docker or Podman, depending on your [global container engine setting](../../how-to/containerization/containerization.md#choosing-the-container-engine). Additional image builders are provided by integrations: | Image Builder | Flavor | Integration | Notes | | ---------------------------------- | -------- | ----------- | ------------------------------------------------------------------------ | | [LocalImageBuilder](local.md) | `local` | _built-in_ | Builds images locally (Docker or Podman). | | [KanikoImageBuilder](kaniko.md) | `kaniko` | `kaniko` | Builds your Docker images in Kubernetes using Kaniko. **Note: Kaniko project was archived in June 2025.** | | [GCPImageBuilder](gcp.md) | `gcp` | `gcp` | Builds your Docker images using Google Cloud Build. | | [AWSImageBuilder](aws.md) | `aws` | `aws` | Builds your Docker images using AWS Code Build. | | [Custom Implementation](custom.md) | _custom_ | | Extend the image builder abstraction and provide your own implementation | If you would like to see the available flavors of image builders, you can use the command: ```shell zenml image-builder flavor list ``` ### How to use it You don't need to directly interact with any image builder in your code. As long as the image builder that you want to use is part of your active [ZenML stack](https://docs.zenml.io/user-guides/production-guide/understand-stacks), it will be used automatically by any component that needs to build container images.
ZenML Scarf
--- ### Book/Component Guide/Image Builders/Aws (docs/book/component-guide/image-builders/aws.md) --- description: Building container images with AWS CodeBuild --- # AWS Image Builder The AWS image builder is an [image builder](./) flavor provided by the ZenML `aws` integration that uses [AWS CodeBuild](https://aws.amazon.com/codebuild) to build container images. ### When to use it You should use the AWS image builder if: * you're **unable** to install or use [Docker](https://www.docker.com) on your client machine. * you're already using AWS. * your stack is mainly composed of other AWS components such as the [S3 Artifact Store](https://docs.zenml.io/stacks/artifact-stores/s3) or the [SageMaker Orchestrator](https://docs.zenml.io/stacks/orchestrators/sagemaker). ### How to deploy it {% hint style="info" %} Would you like to skip ahead and deploy a full ZenML cloud stack already, including the AWS image builder? Check out the[in-browser stack deployment wizard](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/deploy-a-cloud-stack), or [the ZenML AWS Terraform module](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/deploy-a-cloud-stack-with-terraform) for a shortcut on how to deploy & register this stack component. {% endhint %} ### How to use it To use the AWS image builder, you need: * The ZenML `aws` integration installed. If you haven't done so, run: ```shell zenml integration install aws ``` * An [S3 Artifact Store](https://docs.zenml.io/stacks/artifact-stores/s3) where the build context will be uploaded, so AWS CodeBuild can access it. * Recommended: an [AWS container registry](https://docs.zenml.io/stacks/container-registries/aws) where the built image will be pushed. The AWS CodeBuild service can also work with other container registries, but [explicit authentication](aws.md#authentication-methods) must be enabled in this case. * An [AWS CodeBuild project](https://aws.amazon.com/codebuild) created in the AWS account and region where you want to build the Docker images, preferably in the same region as the ECR container registry where images will be pushed (if applicable). The CodeBuild project configuration is largely irrelevant, as ZenML will override most of the default settings for each build according to the [AWS Docker build guide](https://docs.aws.amazon.com/codebuild/latest/userguide/sample-docker-section.html). Some example default configuration values are: * **Source Type**: `Amazon S3` * **Bucket**: The same S3 bucket used by the ZenML S3 Artifact Store. * **S3 folder**: any value (e.g. `codebuild`); * **Environment Type**: `Linux Container` * **Environment Image**: `bentolor/docker-dind-awscli` * **Privileged Mode**: `false` The user must take care that the **Service Role** attached to the CodeBuild project also has the necessary permissions to access the S3 bucket to read objects and the ECR container registry to push images (if applicable): ```json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:GetObject", "s3:GetObjectVersion" ], "Resource": "arn:aws:s3:::/*" }, { "Effect": "Allow", "Action": [ "ecr:BatchGetImage", "ecr:DescribeImages", "ecr:BatchCheckLayerAvailability", "ecr:GetDownloadUrlForLayer", "ecr:InitiateLayerUpload", "ecr:UploadLayerPart", "ecr:CompleteLayerUpload", "ecr:PutImage" ], "Resource": "arn:aws:ecr:::repository/" }, { "Effect": "Allow", "Action": [ "ecr:GetAuthorizationToken" ], "Resource": "*" }, ] } ``` * Recommended: Grant ZenML access to trigger AWS CodeBuild builds by registering an [AWS Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/aws-service-connector) with the proper credentials and permissions, as covered in the [Authentication Methods](aws.md#authentication-methods) section. If not provided, the AWS credentials will be inferred from the environment where the pipeline is triggered. We can register the image builder and use it in our active stack: ```shell zenml image-builder register \ --flavor=aws \ --code_build_project= # Register and activate a stack with the new image builder zenml stack register -i ... --set ``` You also need to set up [authentication](aws.md#authentication-methods) required to access the CodeBuild AWS service. #### Authentication Methods Integrating and using an AWS Image Builder in your pipelines is not possible without employing some form of authentication. If you're looking for a quick way to get started locally, you can use the _Local Authentication_ method. However, the recommended way to authenticate to the AWS cloud platform is through [an AWS Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/aws-service-connector). This is particularly useful if you are configuring ZenML stacks that combine the AWS Image Builder with other remote stack components also running in AWS. {% tabs %} {% tab title="Implicit Authentication" %} This method uses the implicit AWS authentication available _in the environment where the ZenML code is running_. On your local machine, this is the quickest way to configure an AWS Image Builder. You don't need to supply credentials explicitly when you register the AWS Image Builder, as it leverages the local credentials and configuration that the AWS CLI stores on your local machine. However, you will need to install and set up the AWS CLI on your machine as a prerequisite, as covered in [the AWS CLI documentation](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html), before you register the AWS Image Builder. {% hint style="warning" %} Stacks using the AWS Image Builder set up with local authentication are not portable across environments. To make ZenML pipelines fully portable, it is recommended to use [an AWS Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/aws-service-connector) to authenticate your AWS Image Builder to the AWS cloud platform. {% endhint %} {% endtab %} {% tab title="AWS Service Connector (recommended)" %} To set up the AWS Image Builder to authenticate to AWS and access the AWS CodeBuild services, it is recommended to leverage the many features provided by [the AWS Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/aws-service-connector) such as auto-configuration, best security practices regarding long-lived credentials and reusing the same credentials across multiple stack components. If you don't already have an AWS Service Connector configured in your ZenML deployment, you can register one using the interactive CLI command. You also have the option to configure an AWS Service Connector that can be used to access more than just the AWS CodeBuild service: ```sh zenml service-connector register --type aws -i ``` A non-interactive CLI example that leverages [the AWS CLI configuration](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) on your local machine to auto-configure an AWS Service Connector for the AWS CodeBuild service: ```sh zenml service-connector register --type aws --resource-type aws-generic --auto-configure ``` {% code title="Example Command Output" %} ``` $ zenml service-connector register aws-generic --type aws --resource-type aws-generic --auto-configure Successfully registered service connector `aws-generic` with access to the following resources: ┏━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┓ ┃ RESOURCE TYPE │ RESOURCE NAMES ┃ ┠────────────────┼────────────────┨ ┃ 🔶 aws-generic │ eu-central-1 ┃ ┗━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┛ ``` {% endcode %} > **Note**: Please remember to grant the entity associated with your AWS credentials permissions to access the CodeBuild API and to run CodeBuilder builds: > > ```json > { > "Version": "2012-10-17", > "Statement": [ > { > "Effect": "Allow", > "Action": [ > "codebuild:StartBuild", > "codebuild:BatchGetBuilds", > ], > "Resource": "arn:aws:codebuild:::project/" > }, > ] > } > ``` The AWS Service Connector supports [many different authentication methods](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/aws-service-connector#authentication-methods) with different levels of security and convenience. You should pick the one that best fits your use case. If you already have one or more AWS Service Connectors configured in your ZenML deployment, you can check which of them can be used to access generic AWS resources like the one required for your AWS Image Builder by running e.g.: ```sh zenml service-connector list-resources --resource-type aws-generic ``` {% code title="Example Command Output" %} ``` The following 'aws-generic' resources can be accessed by service connectors configured in your workspace: ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┓ ┃ CONNECTOR ID │ CONNECTOR NAME │ CONNECTOR TYPE │ RESOURCE TYPE │ RESOURCE NAMES ┃ ┠──────────────────────────────────────┼────────────────┼────────────────┼────────────────┼────────────────┨ ┃ 7113ba9b-efdd-4a0a-94dc-fb67926e58a1 │ aws-generic │ 🔶 aws │ 🔶 aws-generic │ eu-central-1 ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┛ ``` {% endcode %} After having set up or decided on an AWS Service Connector to use to authenticate to AWS, you can register the AWS Image Builder as follows: ```sh zenml image-builder register \ --flavor=aws \ --code_build_project= \ --connector ``` To connect an AWS Image Builder to an AWS Service Connector at a later point, you can use the following command: ```sh zenml image-builder connect --connector ``` {% code title="Example Command Output" %} ``` $ zenml image-builder connect aws-image-builder --connector aws-generic Successfully connected image builder `aws-image-builder` to the following resources: ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┓ ┃ CONNECTOR ID │ CONNECTOR NAME │ CONNECTOR TYPE │ RESOURCE TYPE │ RESOURCE NAMES ┃ ┠──────────────────────────────────────┼────────────────┼────────────────┼────────────────┼────────────────┨ ┃ 7113ba9b-efdd-4a0a-94dc-fb67926e58a1 │ aws-generic │ 🔶 aws │ 🔶 aws-generic │ eu-central-1 ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┛ ``` {% endcode %} As a final step, you can use the AWS Image Builder in a ZenML Stack: ```sh # Register and set a stack with the new image builder zenml stack register -i ... --set ``` {% endtab %} {% endtabs %} #### Customizing AWS CodeBuild builds The AWS Image Builder can be customized to a certain extent by providing additional configuration options when registering the image builder. The following additional attributes can be set: * `build_image`: The Docker image used to build the Docker image. The default is `bentolor/docker-dind-awscli`, which is a Docker image that includes both Docker-in-Docker and the AWS CLI. {% hint style="info" %} If you are running into Docker Hub rate-limits, it might be a good idea to copy this image to your own container registry and customize the `build_image` attribute to point to your own image. {% endhint %} * `compute_type`: The compute type used for the CodeBuild project. The default is `BUILD_GENERAL1_SMALL`. * `custom_env_vars`: A dictionary of custom environment variables to be set in the CodeBuild project. * `implicit_container_registry_auth`: A boolean flag that indicates whether to use implicit or explicit authentication when authenticating the AWS CodeBuild build to the target container registry: * when this is set to `true` (default), the builds will be configured to use whatever implicit authentication credentials are already available within the build container. As a special case for ECR registries, the service IAM role attached to the CodeBuild project is used to authenticate to the target ECR container registry and therefore the service role must include the necessary permissions to push images to the target ECR registry. * when set to `false`, the credentials attached to the ZenML Container Registry stack component in the active stack will be set as build environment variables and used to authenticate to the target container registry. This is useful when the target container registry is not an ECR registry or when the service role attached to the CodeBuild project does not have the necessary permissions to push images to the target ECR registry. This works best when the ZenML Container Registry stack component is also linked to the external container registry via a Service Connector.
ZenML Scarf
--- ### Book/Component Guide/Image Builders/Custom (docs/book/component-guide/image-builders/custom.md) --- description: Learning how to develop a custom image builder. --- # Develop a Custom Image Builder {% hint style="info" %} Before diving into the specifics of this component type, it is beneficial to familiarize yourself with our [general guide to writing custom component flavors in ZenML](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/implement-a-custom-stack-component). This guide provides an essential understanding of ZenML's component flavor concepts. {% endhint %} ### Base Abstraction The `BaseImageBuilder` is the abstract base class that needs to be subclassed in order to create a custom component that can be used to build Docker images. As image builders can come in many shapes and forms, the base class exposes a deliberately basic and generic interface: ``` /* Detailed source-code truncated for AI context efficiency. */ ``` {% hint style="info" %} This is a slimmed-down version of the base implementation which aims to highlight the abstraction layer. In order to see the full implementation and get the complete docstrings, please check [the source code on GitHub](https://github.com/zenml-io/zenml/blob/main/src/zenml/image\_builders/base\_image\_builder.py) . {% endhint %} ### Build your own custom image builder If you want to create your own custom flavor for an image builder, you can follow the following steps: 1. Create a class that inherits from the `BaseImageBuilder` class and implement the abstract `build` method. This method should use the given build context and build a Docker image with it. If additionally a container registry is passed to the `build` method, the image builder is also responsible for pushing the image there. 2. If you need to provide any configuration, create a class that inherits from the `BaseImageBuilderConfig` class and adds your configuration parameters. 3. Bring both the implementation and the configuration together by inheriting from the `BaseImageBuilderFlavor` class. Make sure that you give a `name` to the flavor through its abstract property. Once you are done with the implementation, you can register it through the CLI. Please ensure you **point to the flavor class via dot notation**: ```shell zenml image-builder flavor register ``` For example, if your flavor class `MyImageBuilderFlavor` is defined in `flavors/my_flavor.py`, you'd register it by doing: ```shell zenml image-builder flavor register flavors.my_flavor.MyImageBuilderFlavor ``` {% hint style="warning" %} ZenML resolves the flavor class by taking the path where you initialized zenml (via `zenml init`) as the starting point of resolution. Therefore, please ensure you follow [the best practice](https://docs.zenml.io/user-guides/best-practices/iac) of initializing zenml at the root of your repository. If ZenML does not find an initialized ZenML repository in any parent directory, it will default to the current working directory, but usually it's better to not have to rely on this mechanism, and initialize zenml at the root. {% endhint %} Afterward, you should see the new flavor in the list of available flavors: ```shell zenml image-builder flavor list ``` {% hint style="warning" %} It is important to draw attention to when and how these base abstractions are coming into play in a ZenML workflow. * The **CustomImageBuilderFlavor** class is imported and utilized upon the creation of the custom flavor through the CLI. * The **CustomImageBuilderConfig** class is imported when someone tries to register/update a stack component with this custom flavor. Especially, during the registration process of the stack component, the config will be used to validate the values given by the user. As `Config` objects are inherently `pydantic` objects, you can also add your own custom validators here. * The **CustomImageBuilder** only comes into play when the component is ultimately in use. The design behind this interaction lets us separate the configuration of the flavor from its implementation. This way we can register flavors and components even when the major dependencies behind their implementation are not installed in our local setting (assuming the `CustomImageBuilderFlavor` and the `CustomImageBuilderConfig` are implemented in a different module/path than the actual `CustomImageBuilder`). {% endhint %} #### Using a custom-build context The `BaseImageBuilder` abstraction uses the `build_context_class` to provide a class that should be used as the build context. In case your custom image builder requires a different build context than the default Docker build context, you can subclass the `BuildContext` class to customize the structure of your build context. In your image builder implementation, you can then overwrite the `build_context_class` property to specify your build context subclass.
ZenML Scarf
--- ### Book/Component Guide/Image Builders/Gcp (docs/book/component-guide/image-builders/gcp.md) --- description: Building container images with Google Cloud Build --- # Google Cloud Image Builder The Google Cloud image builder is an [image builder](./) flavor provided by the ZenML `gcp` integration that uses [Google Cloud Build](https://cloud.google.com/build) to build container images. ### When to use it You should use the Google Cloud image builder if: * you're **unable** to install or use [Docker](https://www.docker.com) on your client machine. * you're already using GCP. * your stack is mainly composed of other Google Cloud components such as the [GCS Artifact Store](https://docs.zenml.io/stacks/artifact-stores/gcp) or the [Vertex Orchestrator](https://docs.zenml.io/stacks/orchestrators/vertex). ### How to deploy it {% hint style="info" %} Would you like to skip ahead and deploy a full ZenML cloud stack already, including the Google Cloud image builder? Check out the[in-browser stack deployment wizard](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/deploy-a-cloud-stack), the [stack registration wizard](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/register-a-cloud-stack), or [the ZenML GCP Terraform module](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/deploy-a-cloud-stack-with-terraform) for a shortcut on how to deploy & register this stack component. {% endhint %} In order to use the ZenML Google Cloud image builder you need to enable Google Cloud Build relevant APIs on the Google Cloud project. ### How to use it To use the Google Cloud image builder, we need: * The ZenML `gcp` integration installed. If you haven't done so, run: ```shell zenml integration install gcp ``` * A [GCP Artifact Store](https://docs.zenml.io/stacks/artifact-stores/gcp) where the build context will be uploaded, so Google Cloud Build can access it. * A [GCP container registry](https://docs.zenml.io/stacks/container-registries/gcp) where the built image will be pushed. * Optionally, the GCP project ID in which you want to run the build and a service account with the needed permissions to run the build. If not provided, then the project ID and credentials will be inferred from the environment. * Optionally, you can change: * the Docker image used by Google Cloud Build to execute the steps to build and push the Docker image. By default, the builder image will be `'gcr.io/cloud-builders/docker'`. * The network to which the container used to build the ZenML pipeline Docker image will be attached. More information: [Cloud build network](https://cloud.google.com/build/docs/build-config-file-schema#network). * The build timeout for the build, and for the blocking operation waiting for the build to finish. More information: [Build Timeout](https://cloud.google.com/build/docs/build-config-file-schema#timeout_2). * The location to run Cloud Build (e.g., `us-central1`, `europe-west1`) when you need regional data residency, lower latency to nearby GCS buckets or Artifact Registry, or to use Cloud Build private pools. {% hint style="info" %} Even if your GCP Service Connector is scoped to a specific region, the GCP Image Builder uses the **global** Cloud Build endpoint by default. To run builds in a specific region, set the `location` parameter on the Image Builder. The Service Connector only supplies authentication and does not influence which Cloud Build region is used. {% endhint %} We can register the image builder and use it in our active stack: ```shell zenml image-builder register \ --flavor=gcp \ --cloud_builder_image= \ --network= \ --build_timeout= \ --location= # Register and activate a stack with the new image builder zenml stack register -i ... --set ``` You also need to set up [authentication](gcp.md#authentication-methods) required to access the Cloud Build GCP services. #### Authentication Methods Integrating and using a GCP Image Builder in your pipelines is not possible without employing some form of authentication. If you're looking for a quick way to get started locally, you can use the _Local Authentication_ method. However, the recommended way to authenticate to the GCP cloud platform is through [a GCP Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/gcp-service-connector). This is particularly useful if you are configuring ZenML stacks that combine the GCP Image Builder with other remote stack components also running in GCP. {% tabs %} {% tab title="Implicit Authentication" %} This method uses the implicit GCP authentication available _in the environment where the ZenML code is running_. On your local machine, this is the quickest way to configure a GCP Image Builder. You don't need to supply credentials explicitly when you register the GCP Image Builder, as it leverages the local credentials and configuration that the Google Cloud CLI stores on your local machine. However, you will need to install and set up the Google Cloud CLI on your machine as a prerequisite, as covered in [the Google Cloud documentation](https://cloud.google.com/sdk/docs/install-sdk) , before you register the GCP Image Builder. {% hint style="warning" %} Stacks using the GCP Image Builder set up with local authentication are not portable across environments. To make ZenML pipelines fully portable, it is recommended to use [a GCP Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/gcp-service-connector) to authenticate your GCP Image Builder to the GCP cloud platform. {% endhint %} {% endtab %} {% tab title="GCP Service Connector (recommended)" %} To set up the GCP Image Builder to authenticate to GCP and access the GCP Cloud Build services, it is recommended to leverage the many features provided by [the GCP Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/gcp-service-connector) such as auto-configuration, best security practices regarding long-lived credentials and reusing the same credentials across multiple stack components. If you don't already have a GCP Service Connector configured in your ZenML deployment, you can register one using the interactive CLI command. You also have the option to configure a GCP Service Connector that can be used to access more than just the GCP Cloud Build service: ```sh zenml service-connector register --type gcp -i ``` A non-interactive CLI example that leverages [the Google Cloud CLI configuration](https://cloud.google.com/sdk/docs/install-sdk) on your local machine to auto-configure a GCP Service Connector for the GCP Cloud Build service: ```sh zenml service-connector register --type gcp --resource-type gcp-generic --resource-name --auto-configure ``` {% code title="Example Command Output" %} ``` $ zenml service-connector register gcp-generic --type gcp --resource-type gcp-generic --auto-configure Successfully registered service connector `gcp-generic` with access to the following resources: ┏━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┓ ┃ RESOURCE TYPE │ RESOURCE NAMES ┃ ┠────────────────┼────────────────┨ ┃ 🔵 gcp-generic │ zenml-core ┃ ┗━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┛ ``` {% endcode %} > **Note**: Please remember to grant the entity associated with your GCP credentials permissions to access the Cloud Build API and to run Cloud Builder jobs. Instead of using the broad `Cloud Build Editor` role, follow the principle of least privilege by creating a custom role with only the required permissions: > - `cloudbuild.builds.create` > - `cloudbuild.builds.get` > - `cloudbuild.builds.list` > - `source.repos.get` (if using Cloud Source Repositories) > - `storage.objects.get`, `storage.objects.create`, and `storage.objects.list` (for accessing build artifacts and build context stored in GCS) > > Alternatively, you can use the predefined [Cloud Build Editor IAM role](https://cloud.google.com/build/docs/iam-roles-permissions#predefined_roles) if broader permissions are acceptable for your use case. The GCP Service Connector supports [many different authentication methods](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/gcp-service-connector#authentication-methods) with different levels of security and convenience. You should pick the one that best fits your use case. If you already have one or more GCP Service Connectors configured in your ZenML deployment, you can check which of them can be used to access generic GCP resources like the GCP Image Builder required for your GCP Image Builder by running e.g.: ```sh zenml service-connector list-resources --resource-type gcp-generic ``` {% code title="Example Command Output" %} ``` The following 'gcp-generic' resources can be accessed by service connectors that you have configured: ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┓ ┃ CONNECTOR ID │ CONNECTOR NAME │ CONNECTOR TYPE │ RESOURCE TYPE │ RESOURCE NAMES ┃ ┠──────────────────────────────────────┼────────────────┼────────────────┼────────────────┼────────────────┨ ┃ bfdb657d-d808-47e7-9974-9ba6e4919d83 │ gcp-generic │ 🔵 gcp │ 🔵 gcp-generic │ zenml-core ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┛ ``` {% endcode %} After having set up or decided on a GCP Service Connector to use to authenticate to GCP, you can register the GCP Image Builder as follows: ```sh zenml image-builder register \ --flavor=gcp \ --cloud_builder_image= \ --network= \ --build_timeout= \ --location= # Connect the GCP Image Builder to GCP via a GCP Service Connector zenml image-builder connect -i ``` A non-interactive version that connects the GCP Image Builder to a target GCP Service Connector: ```sh zenml image-builder connect --connector ``` {% code title="Example Command Output" %} ``` $ zenml image-builder connect gcp-image-builder --connector gcp-generic Successfully connected image builder `gcp-image-builder` to the following resources: ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┓ ┃ CONNECTOR ID │ CONNECTOR NAME │ CONNECTOR TYPE │ RESOURCE TYPE │ RESOURCE NAMES ┃ ┠──────────────────────────────────────┼────────────────┼────────────────┼────────────────┼────────────────┨ ┃ bfdb657d-d808-47e7-9974-9ba6e4919d83 │ gcp-generic │ 🔵 gcp │ 🔵 gcp-generic │ zenml-core ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┛ ``` {% endcode %} As a final step, you can use the GCP Image Builder in a ZenML Stack: ```sh # Register and set a stack with the new image builder zenml stack register -i ... --set ``` {% endtab %} {% tab title="GCP Credentials" %} When you register the GCP Image Builder, you can [generate a GCP Service Account Key](https://cloud.google.com/docs/authentication/application-default-credentials#attached-sa), save it to a local file and then reference it in the Image Builder configuration. This method has the advantage that you don't need to install and configure the GCP CLI on your host, but it's still not as secure as using a GCP Service Connector and the stack component configuration is not portable to other hosts. For this method, you need to [create a user-managed GCP service account](https://cloud.google.com/iam/docs/service-accounts-create), and grant it privileges to access the Cloud Build API and to run Cloud Builder jobs (e.g. the [Cloud Build Editor IAM role](https://cloud.google.com/build/docs/iam-roles-permissions#predefined_roles). With the service account key downloaded to a local file, you can register the GCP Image Builder as follows: ```shell zenml image-builder register \ --flavor=gcp \ --project= \ --service_account_path= \ --cloud_builder_image= \ --network= \ --location= \ --build_timeout= # Register and set a stack with the new image builder zenml stack register -i ... --set ``` {% endtab %} {% endtabs %} ### Caveats As described in this [Google Cloud Build documentation page](https://cloud.google.com/build/docs/build-config-file-schema#network), Google Cloud Build uses containers to execute the build steps which are automatically attached to a network called `cloudbuild` that provides some Application Default Credentials (ADC), that allow the container to be authenticated and therefore use other GCP services. By default, the GCP Image Builder is executing the build command of the ZenML Pipeline Docker image with the option `--network=cloudbuild`, so the ADC provided by the `cloudbuild` network can also be used in the build. This is useful if you want to install a private dependency from a GCP Artifact Registry, but you will also need to use a [custom base parent image](https://docs.zenml.io/how-to/customize-docker-builds/docker-settings-on-a-pipeline) with the [`keyrings.google-artifactregistry-auth`](https://pypi.org/project/keyrings.google-artifactregistry-auth/) installed, so `pip` can connect and authenticate in the private artifact registry to download the dependency. ```dockerfile FROM zenmldocker/zenml:latest RUN pip install keyrings.google-artifactregistry-auth ``` {% hint style="warning" %} The above `Dockerfile` uses `zenmldocker/zenml:latest` as a base image, but is recommended to change the tag to specify the ZenML version and Python version like `0.33.0-py3.10`. {% endhint %}
ZenML Scarf
--- ### Book/Component Guide/Image Builders/Kaniko (docs/book/component-guide/image-builders/kaniko.md) --- description: Building container images with Kaniko. --- # Kaniko Image Builder {% hint style="warning" %} The Kaniko project has been archived as of early June 2025. While existing installations will continue to work, the project is no longer actively maintained. Consider using alternative image builders such as the [Local](local.md), [GCP](gcp.md), or [AWS](aws.md) image builders for your containerization needs. {% endhint %} The Kaniko image builder is an [image builder](./) flavor provided by the ZenML `kaniko` integration that uses [Kaniko](https://github.com/GoogleContainerTools/kaniko) to build container images. ### When to use it You should use the Kaniko image builder if: * you're **unable** to install or use [Docker](https://www.docker.com) on your client machine. * you're familiar with/already using Kubernetes. ### How to deploy it In order to use the Kaniko image builder, you need a deployed Kubernetes cluster. ### How to use it To use the Kaniko image builder, we need: * The ZenML `kaniko` integration installed. If you haven't done so, run ```shell zenml integration install kaniko ``` * [kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl) installed. * A [remote container registry](https://docs.zenml.io/stacks/container-registries/) as part of your stack. * By default, the Kaniko image builder transfers the build context using the Kubernetes API. If you instead want to transfer the build context by storing it in the artifact store, you need to register it with the `store_context_in_artifact_store` attribute set to `True`. In this case, you also need a [remote artifact store](https://docs.zenml.io/stacks/artifact-stores/) as part of your stack. * Optionally, you can change the timeout (in seconds) until the Kaniko pod is running in the orchestrator using the `pod_running_timeout` attribute. We can then register the image builder and use it in our active stack: ```shell zenml image-builder register \ --flavor=kaniko \ --kubernetes_context= [ --pod_running_timeout= ] # Register and activate a stack with the new image builder zenml stack register -i ... --set ``` For more information and a full list of configurable attributes of the Kaniko image builder, check out the [SDK Docs](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-kaniko.html#zenml.integrations.kaniko) . #### Authentication for the container registry and artifact store The Kaniko image builder will create a Kubernetes pod that is running the build. This build pod needs to be able to pull from/push to certain container registries, and depending on the stack component configuration also needs to be able to read from the artifact store: * The pod needs to be authenticated to push to the container registry in your active stack. * In case the [parent image](https://docs.zenml.io/how-to/customize-docker-builds/docker-settings-on-a-pipeline#using-a-custom-parent-image) you use in your `DockerSettings` is stored in a private registry, the pod needs to be authenticated to pull from this registry. * If you configured your image builder to store the build context in the artifact store, the pod needs to be authenticated to read files from the artifact store storage. ZenML is not yet able to handle setting all of the credentials of the various combinations of container registries and artifact stores on the Kaniko build pod, which is you're required to set this up yourself for now. The following section outlines how to handle it in the most straightforward (and probably also most common) scenario, when the Kubernetes cluster you're using for the Kaniko build is hosted on the same cloud provider as your container registry (and potentially the artifact store). For all other cases, check out the [official Kaniko repository](https://github.com/GoogleContainerTools/kaniko) for more information. {% tabs %} {% tab title="AWS" %} * Add permissions to push to ECR by attaching the `EC2InstanceProfileForImageBuilderECRContainerBuilds` policy to your [EKS node IAM role](https://docs.aws.amazon.com/eks/latest/userguide/create-node-role.html). * Configure the image builder to set some required environment variables on the Kaniko build pod: ```shell # register a new image builder with the environment variables zenml image-builder register \ --flavor=kaniko \ --kubernetes_context= \ --env='[{"name": "AWS_SDK_LOAD_CONFIG", "value": "true"}, {"name": "AWS_EC2_METADATA_DISABLED", "value": "true"}]' # or update an existing one zenml image-builder update \ --env='[{"name": "AWS_SDK_LOAD_CONFIG", "value": "true"}, {"name": "AWS_EC2_METADATA_DISABLED", "value": "true"}]' ``` Check out [the Kaniko docs](https://github.com/GoogleContainerTools/kaniko#pushing-to-amazon-ecr) for more information. {% endtab %} {% tab title="GCP" %} * [Enable workload identity](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity#enable_on_cluster) for your cluster * Follow the steps described [here](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity#authenticating_to) to create a Google service account, a Kubernetes service account as well as an IAM policy binding between them. * Grant the Google service account permissions to push to your GCR registry and read from your GCP bucket. * Configure the image builder to run in the correct namespace and use the correct service account: ```shell # register a new image builder with namespace and service account zenml image-builder register \ --flavor=kaniko \ --kubernetes_context= \ --kubernetes_namespace= \ --service_account_name= # --executor_args='["--compressed-caching=false", "--use-new-run=true"]' # or update an existing one zenml image-builder update \ --kubernetes_namespace= \ --service_account_name= ``` Check out [the Kaniko docs](https://github.com/GoogleContainerTools/kaniko#pushing-to-google-gcr) for more information. {% endtab %} {% tab title="Azure" %} * Create a Kubernetes `configmap` for a Docker config that uses the Azure credentials helper: ```shell kubectl create configmap docker-config --from-literal='config.json={ "credHelpers": { "mycr.azurecr.io": "acr-env" } }' ``` * Follow [these steps](https://learn.microsoft.com/en-us/azure/aks/use-managed-identity) to configure your cluster to use a managed identity * Configure the image builder to mount the `configmap` in the Kaniko build pod: ```shell # register a new image builder with the mounted configmap zenml image-builder register \ --flavor=kaniko \ --kubernetes_context= \ --volume_mounts='[{"name": "docker-config", "mountPath": "/kaniko/.docker/"}]' \ --volumes='[{"name": "docker-config", "configMap": {"name": "docker-config"}}]' # --executor_args='["--compressed-caching=false", "--use-new-run=true"]' # or update an existing one zenml image-builder update \ --volume_mounts='[{"name": "docker-config", "mountPath": "/kaniko/.docker/"}]' \ --volumes='[{"name": "docker-config", "configMap": {"name": "docker-config"}}]' ``` Check out [the Kaniko docs](https://github.com/GoogleContainerTools/kaniko#pushing-to-azure-container-registry) for more information. {% endtab %} {% endtabs %} #### Passing additional parameters to the Kaniko build You can pass additional parameters to the Kaniko build by setting the `executor_args` attribute of the image builder. ```shell zenml image-builder register \ --flavor=kaniko \ --kubernetes_context= \ --executor_args='["--label", "key=value"]' # Adds a label to the final image ``` List of some possible additional flags: * `--cache`: Set to `false` to disable caching. Defaults to `true`. * `--cache-dir`: Set the directory where to store cached layers. Defaults to `/cache`. * `--cache-repo`: Set the repository where to store cached layers. * `--cache-ttl`: Set the cache expiration time. Defaults to `24h`. * `--cleanup`: Set to `false` to disable cleanup of the working directory. Defaults to `true`. * `--compressed-caching`: Set to `false` to disable compressed caching. Defaults to `true`. For a full list of possible flags, check out the [Kaniko additional flags](https://github.com/GoogleContainerTools/kaniko#additional-flags)
ZenML Scarf
--- ### Book/Component Guide/Image Builders/Local (docs/book/component-guide/image-builders/local.md) --- description: Building container images locally. --- # Local Image Builder The local image builder is an [image builder](./) flavor that comes built-in with ZenML and uses the [container engine](../../how-to/containerization/containerization.md#choosing-the-container-engine) available on your client machine (**Docker** or **Podman**) to build and push images. ### Container engine selection The local image builder uses [the container engine as the global setting of your ZenML client](../../how-to/containerization/containerization.md#choosing-the-container-engine): * If you do nothing: ZenML auto-detects the container engine available on your machine: it prefers **Docker** when the Docker CLI and daemon are available; otherwise it uses **Podman** if that CLI is installed. Most users with Docker installed therefore get **Docker** by default. * To force an engine: set the environment variable **`ZENML_CONTAINER_ENGINE`** to `docker` or `podman`. {% hint style="info" %} **Docker engine:** builds use the Docker API by default (or the `docker build` CLI if you enable subprocess mode below). Registry credentials for push often come from `$HOME/.docker/config.json`. If your config lives elsewhere, set `DOCKER_CONFIG` to the directory that contains `config.json`. **Podman engine:** builds and pushes use the Podman CLI only; configure registry login with `podman login` (or your stack’s container registry credentials, which ZenML applies per operation). {% endhint %} ### When to use it You should use the local image builder if: * you can run **Docker** or **Podman** on the machine where you execute pipelines * you want to use remote components that require containerization without the additional hassle of configuring infrastructure for an additional component. ### How to deploy it The local image builder comes with ZenML and works without any additional setup. ### How to use it To use the local image builder, you need: * [Docker](https://www.docker.com/) or [Podman](https://podman.io/) installed and working (see [Choosing the Container Engine](../../how-to/containerization/containerization.md#choosing-the-container-engine)), and * Your user authenticated to the container registry in that stack (for example `docker login` or `podman login`, or credentials supplied via your [container registry](../../component-guide/container-registries/README.md) stack component / service connector). We can then register the image builder and use it to create a new stack: ```shell zenml image-builder register --flavor=local # Register and activate a stack with the new image builder zenml stack register -i ... --set ``` #### Use the Docker CLI for builds (subprocess mode) When the active container engine is **Docker**, builds normally go through the Docker API. You can configure the local image builder to call **`docker build`** via a subprocess instead. That helps when you need **Docker BuildKit** options that are not exposed through the Python SDK. When the active engine is **Podman**, builds **always** use the Podman CLI; this setting has no effect. Enable subprocess mode when registering or updating the component: ```shell # Register with subprocess mode enabled zenml image-builder register --flavor=local --use_subprocess_call=true # Or update an existing local image builder zenml image-builder update --use_subprocess_call=true ``` In subprocess mode, the build options passed via `DockerSettings` are handled in the following way: - The `buildargs`, `labels` dictionaries are passed as `--build-arg KEY=VALUE` and `--label KEY=VALUE` respectively - Lists will pass multiple command line arguments with the same name for all values - `True` boolean values means the argument will be passed without any value (`--KEY`) - All other values are converted to a string and passed as `--KEY VALUE` For more information and a full list of configurable attributes of the local image builder, check out the [SDK Docs](https://sdkdocs.zenml.io/latest/core_code_docs/core-image_builders.html#zenml.image_builders.local_image_builder) .
ZenML Scarf
--- ### Book/Component Guide/Log Stores/README (docs/book/component-guide/log-stores/README.md) --- description: Storing and retrieving logs from your ML pipelines. icon: file-lines --- # Log Stores The log store is a stack component responsible for collecting, storing, and retrieving logs generated during pipeline and step execution. It captures everything from standard logging output to print statements and any messages written to stdout/stderr, making it easy to debug and monitor your ML workflows. ### How it works ZenML's log capture system is designed to be comprehensive and non-intrusive. Here's what happens under the hood: 1. **stdout/stderr wrapping**: ZenML wraps the standard output and error streams to capture all printed messages and any output directed to these streams. 2. **Root logger handler**: A custom handler is added to Python's root logger to capture all log messages with proper metadata from loggers that propagate to the root. 3. **Log routing**: All captured messages are routed through a `LoggingContext` to the active log store in your stack. This approach ensures that you don't miss any output from your pipeline steps, including: - Standard Python `logging` messages - `print()` statements - Output from third-party libraries - Messages from subprocesses that write to stdout/stderr ### When to use it The Log Store is automatically used in every ZenML stack. If you don't explicitly configure a log store, ZenML will use an [**Artifact Log Store**](artifact.md) by default, which stores logs in your artifact store. You should consider configuring a dedicated log store when: - You want to use a centralized logging backend like Datadog, Jaeger, Grafana Tempo, Honeycomb, Lightstep or Dash0 for log aggregation and analysis - You need advanced log querying capabilities beyond what file-based storage provides - You're running pipelines at scale and need better log management - You want to integrate with your organization's existing observability infrastructure ### How to use it By default, if no log store is explicitly configured in your stack, ZenML automatically creates an Artifact Log Store that uses your artifact store for log storage. This means logging works out of the box without any additional configuration. To use a different log store, you need to register it and add it to your stack: ```shell # Register a log store (example with Datadog) zenml log-store register \ --flavor=datadog \ --api_key= \ --application_key= # Add it to your stack zenml stack register -a -o -ls --set ``` Once configured, logs are automatically captured during pipeline execution. ### Viewing Logs You can view logs through several methods: 1. **ZenML Dashboard**: Navigate to a pipeline run and view step logs directly in the UI. 2. **Programmatically**: You can fetch logs directly using the log store: ```python from zenml.client import Client client = Client() # Get the run you want logs for run = client.get_pipeline_run("") # Note: The log store must match the one that captured the logs log_store = client.active_stack.log_store log_entries = log_store.fetch(logs_model=run.logs, limit=1000) for entry in log_entries: print(f"[{entry.level}] {entry.message}") ``` 3. **External platforms**: For log stores like Datadog, you can also view logs directly in the platform's native interface. ### Log Store Flavors ZenML provides several log store flavors out of the box: | Log Store | Flavor | Integration | Notes | |------------------------------------|------------|-------------|------------------------------------------------------------------------------------------------| | [ArtifactLogStore](artifact.md) | `artifact` | _built-in_ | Default log store that writes logs to your artifact store. Zero configuration required. | | [OtelLogStore](otel.md) | `otel` | _built-in_ | Generic OpenTelemetry log store for any OTEL-compatible backend. Does not support log fetching.| | [DatadogLogStore](datadog.md) | `datadog` | _built-in_ | Exports logs to Datadog's log management platform with full fetch support. | | [Custom Implementation](custom.md) | _custom_ | | Extend the log store abstraction and provide your own implementation. | If you would like to see the available flavors of log stores, you can use the command: ```shell zenml log-store flavor list ``` {% hint style="info" %} If you're interested in understanding the base abstraction and how log stores work internally, check out the [Develop a Custom Log Store](custom.md) page for a detailed explanation of the architecture. {% endhint %}
ZenML Scarf
--- ### Book/Component Guide/Log Stores/Artifact (docs/book/component-guide/log-stores/artifact.md) --- description: Storing logs in your artifact store. --- # Artifact Log Store The Artifact Log Store is the default log store flavor that comes built-in with ZenML. It stores logs directly in your artifact store, providing a zero-configuration logging solution that works out of the box. {% hint style="warning" %} The Artifact Log Store is ZenML's implicit default. You don't need to register it as a flavor or add it to your stack. When no log store is explicitly configured, ZenML automatically uses an Artifact Log Store to handle logs. This means logging works out of the box with zero configuration. {% endhint %} ### When to use it The Artifact Log Store is ideal when: - You want logging to work without any additional configuration - You prefer to keep all your pipeline data (artifacts and logs) in one place - You don't need advanced log querying capabilities - You're getting started with ZenML and want a simple setup ### How it works The Artifact Log Store leverages OpenTelemetry's batching infrastructure while using a custom exporter that writes logs to your artifact store. Here's what happens during pipeline execution: 1. **Log capture**: All stdout, stderr, and Python logging output is captured and routed to the log store. 2. **Batching**: Logs are collected in batches using OpenTelemetry's `BatchLogRecordProcessor` for efficient processing. 3. **Export**: The `ArtifactLogExporter` writes batched logs to your artifact store as JSON-formatted log files. 4. **Finalization**: When a step completes, logs are finalized (merged if necessary) to ensure they're ready for retrieval. #### Handling Different Filesystem Types The Artifact Log Store handles different artifact store backends intelligently: - **Mutable filesystems** (local, S3, Azure): Logs are appended to a single file per step. - **Immutable filesystems** (GCS): Logs are written as timestamped files in a directory, then merged on finalization. This ensures consistent behavior across all supported artifact store types. ### Environment Variables The Artifact Log Store uses OpenTelemetry's batch processing under the hood. You can tune the batching behavior using these environment variables: | Environment Variable | Default | Description | |------------------------------------------|-----------|-----------------------------------------------------| | `ZENML_LOGS_OTEL_MAX_QUEUE_SIZE` | `100000` | Maximum queue size for batch log processor | | `ZENML_LOGS_OTEL_SCHEDULE_DELAY_MILLIS` | `5000` | Delay between batch exports in milliseconds | | `ZENML_LOGS_OTEL_MAX_EXPORT_BATCH_SIZE` | `5000` | Maximum batch size for exports | | `ZENML_LOGS_OTEL_EXPORT_TIMEOUT_MILLIS` | `15000` | Timeout for each export batch in milliseconds | These defaults are optimized for most use cases. You typically only need to adjust them for high-volume logging scenarios. ### Log format Logs are stored as newline-delimited JSON (NDJSON) files. Each log entry contains the following fields: ```json { "message": "Training model with 1000 samples", "level": "INFO", "timestamp": "2024-01-15T10:30:00.000Z", "name": "my_logger", "filename": "train.py", "lineno": 42, "module": "train", "chunk_index": 0, "total_chunks": 1, "id": "550e8400-e29b-41d4-a716-446655440000" } ``` | Field | Description | |----------------|-----------------------------------------------------------------------------| | `message` | The log message content | | `level` | Log level (DEBUG, INFO, WARN, ERROR, CRITICAL) | | `timestamp` | When the log was created | | `name` | The name of the logger | | `filename` | The source file that generated the log | | `lineno` | The line number in the source file | | `module` | The module that generated the log | | `chunk_index` | Index of this chunk (0 for non-chunked messages) | | `total_chunks` | Total number of chunks (1 for non-chunked messages) | | `id` | Unique identifier for the log entry (used to reassemble chunked messages) | For large messages (>5KB), logs are automatically split into multiple chunks with sequential `chunk_index` values and a shared `id` for reassembly. ### Storage location Logs are stored in the `logs` directory within your artifact store: ``` / └── logs/ ├── .log # For mutable filesystems └── / # For immutable filesystems (GCS) ├── 1705312200.123.log ├── 1705312205.456.log └── 1705312210.789_merged.log ``` ### Best practices 1. **Use the default**: For most use cases, the automatic artifact log store is sufficient. Don't add complexity unless you need it. 2. **Monitor storage**: Logs can accumulate over time. Consider implementing log retention policies for your artifact store. 3. **Large log volumes**: If you're generating very large log volumes, consider using a dedicated log store like Datadog for better scalability and querying. 4. **Sensitive data**: Be mindful of what you log. Avoid logging sensitive information like credentials or PII. For more information and a full list of configurable attributes, check out the [SDK Docs](https://sdkdocs.zenml.io/latest/core_code_docs/core-log_stores.html#zenml.log_stores.artifact.artifact_log_store).
ZenML Scarf
--- ### Book/Component Guide/Log Stores/Custom (docs/book/component-guide/log-stores/custom.md) --- description: Learning how to develop a custom log store. --- # Develop a Custom Log Store {% hint style="info" %} Before diving into the specifics of this component type, it is beneficial to familiarize yourself with our [general guide to writing custom component flavors in ZenML](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/implement-a-custom-stack-component). This guide provides an essential understanding of ZenML's component flavor concepts. {% endhint %} ZenML comes equipped with [Log Store implementations](./#log-store-flavors) that you can use to store logs in your artifact store, export to OpenTelemetry-compatible backends, or send to Datadog. However, if you need to use a different logging backend, you can extend ZenML to provide your own custom Log Store implementation. ### Base Abstraction The log store is responsible for collecting, storing, and retrieving logs during pipeline execution. Let's take a deeper dive into the fundamentals behind its abstraction, namely the `BaseLogStore` class: 1. **Origins**: A `BaseLogStoreOrigin` represents the source of log records (e.g., a step execution). When logging starts, you register an origin with the log store, then emit logs through the log store referencing that origin. When logging ends, you deregister the origin to release resources. 2. **Core methods**: The base class defines four abstract methods that must be implemented: - `emit()`: Process and export a log record for a given origin - `_release_origin()`: Called when logging for an origin is complete (cleanup resources) - `flush()`: Ensure all pending logs are exported - `fetch()`: Retrieve stored logs for display 3. **Thread safety**: The base implementation includes locking mechanisms to ensure thread-safe operation. Here's a simplified view of the base implementation: ``` /* Detailed source-code truncated for AI context efficiency. */ ``` {% hint style="info" %} This is a slimmed-down version of the base implementation. For the full implementation with complete docstrings, check the [SDK docs](https://sdkdocs.zenml.io/latest/core_code_docs/core-log_stores.html#zenml.log_stores.base_log_store). {% endhint %} ### Extending the OTEL Log Store For most custom implementations, you'll want to extend `OtelLogStore` rather than `BaseLogStore` directly. The OTEL Log Store provides: - OpenTelemetry infrastructure (LoggerProvider, BatchLogRecordProcessor; stdlib bridge via `opentelemetry-instrumentation-logging`) - Automatic log batching and retry logic - Standard OTEL log format conversion To create a custom OTEL-based log store, you only need to implement: 1. `get_exporter()`: Return your custom log exporter 2. `fetch()`: Retrieve logs from your backend (optional, raise `NotImplementedError` if not supported) ``` /* Detailed source-code truncated for AI context efficiency. */ ``` ### Creating a Custom Log Exporter If you're using a custom backend, you'll need to implement a log exporter. The exporter receives batches of OpenTelemetry log records and sends them to your backend: ``` /* Detailed source-code truncated for AI context efficiency. */ ``` ### Implementing Log Fetching If your backend supports log retrieval, implement the `fetch()` method to enable log viewing in the ZenML dashboard: ```python def fetch( self, logs_model: LogsResponse, start_time: Optional[datetime] = None, end_time: Optional[datetime] = None, limit: int = 20000, ) -> List[LogEntry]: """Fetch logs from the backend.""" # Query your backend using logs_model.id to filter response = requests.get( f"{self.config.endpoint}/logs", params={ "log_id": str(logs_model.id), "start_time": start_time.isoformat() if start_time else None, "end_time": end_time.isoformat() if end_time else None, "limit": limit, }, headers={"Authorization": f"Bearer {self.config.api_key}"}, ) log_entries = [] for log in response.json()["logs"]: log_entries.append(LogEntry( message=log["message"], level=LoggingLevels[log["severity"].upper()], timestamp=datetime.fromisoformat(log["timestamp"]), name=log.get("logger_name"), filename=log.get("filename"), lineno=log.get("line_number"), )) return log_entries ``` ### Build Your Own Custom Log Store Follow these steps to create and register your custom log store: 1. **Create the implementation**: Implement your log store class, configuration, and flavor as shown above. 2. **Create the exporter** (if needed): Implement a custom `LogRecordExporter` for your backend. 3. **Register the flavor**: Use the CLI to register your custom flavor: ```shell zenml log-store flavor register ``` For example, if your flavor class `MyLogStoreFlavor` is defined in `flavors/my_log_store.py`: ```shell zenml log-store flavor register flavors.my_log_store.MyLogStoreFlavor ``` {% hint style="warning" %} ZenML resolves the flavor class by taking the path where you initialized zenml (via `zenml init`) as the starting point of resolution. Ensure you follow [the best practice](https://docs.zenml.io/user-guides/best-practices/iac) of initializing zenml at the root of your repository. {% endhint %} 4. **Verify registration**: Check that your flavor appears in the list: ```shell zenml log-store flavor list ``` 5. **Register and use your log store**: ```shell zenml log-store register my_logs \ --flavor=my_custom \ --endpoint=https://my-backend.example.com/logs \ --api_key= zenml stack register my_stack -ls my_logs ... --set ``` {% hint style="info" %} **Important timing notes:** - The **CustomLogStoreFlavor** class is imported when registering the flavor via CLI. - The **CustomLogStoreConfig** class is imported when registering/updating a stack component (used for validation). - The **CustomLogStore** class is only imported when the component is actually used. This separation allows you to register flavors even when their dependencies aren't installed locally. {% endhint %} {% hint style="warning" %} **Important**: Log stores are instantiated on the ZenML server to fetch logs for display in the dashboard. This introduces a critical constraint on your implementation. When the ZenML dashboard or API requests logs, the server instantiates the log store and calls its `fetch()` method. This means that there can be **no external dependencies** that aren't already installed on the ZenML server. {% endhint %} ### Best Practices 1. **Extend OtelLogStore**: Unless you have specific requirements, extend `OtelLogStore` to benefit from built-in batching and retry logic. 2. **Handle failures gracefully**: Log export failures shouldn't crash your pipeline. Return `LogRecordExportResult.FAILURE` and log warnings. 3. **Implement retry logic**: For network-based backends, implement retry logic in your exporter. 4. **Use secrets for credentials**: Store API keys and tokens in ZenML secrets, not in the config directly. 5. **Test thoroughly**: Test your implementation with various log volumes and failure scenarios. 6. **Document configuration**: Clearly document all configuration options and their defaults. 7. **Keep fetch() simple**: Remember that `fetch()` runs on the server with limited dependencies. Use only built-in Python libraries and HTTP APIs.
ZenML Scarf
--- ### Book/Component Guide/Log Stores/Datadog (docs/book/component-guide/log-stores/datadog.md) --- description: Exporting logs to Datadog's log management platform. --- # Datadog Log Store The Datadog Log Store is a log store flavor that exports logs to [Datadog's log management platform](https://www.datadoghq.com/product/log-management/). It provides full integration with Datadog, including both log export and retrieval, enabling you to view pipeline logs directly in the ZenML dashboard. ### When would you want to use it? The Datadog Log Store is ideal when: - You're already using Datadog for application monitoring and want to consolidate ML pipeline logs - You need advanced log querying, filtering, and alerting capabilities - You want to correlate ML pipeline logs with other application metrics and traces - You need long-term log retention with Datadog's archiving features - You want to view logs both in the ZenML dashboard and Datadog's native interface ### How it works The Datadog Log Store extends the [OTEL Log Store](otel.md) with Datadog-specific functionality: 1. **Log capture**: All stdout, stderr, and Python logging output is captured during pipeline execution. 2. **OTEL conversion**: Log records are converted to the OpenTelemetry format with ZenML-specific attributes. 3. **Datadog export**: A custom `DatadogLogExporter` sends logs to Datadog's OTLP intake endpoint with proper attribute mapping for Datadog's log structure. 4. **Log retrieval**: The log store uses Datadog's Logs Search API to fetch logs for display in the ZenML dashboard. #### ZenML-specific attributes Each log record includes ZenML metadata that can be used for filtering in Datadog: | Attribute | Description | |-----------------------|------------------------------------------------| | `@zenml.log.id` | Unique identifier for the log stream | | `@zenml.log.source` | Source of the log (step, pipeline, etc.) | | `@zenml.log.uri` | URI where logs are stored (if applicable) | | `@zenml.log_store.id` | ID of the log store component | | `@zenml.log_store.name`| Name of the log store component | | `@zenml.run.id` | Pipeline run ID | | `@zenml.log.id` | Unique identifier for the log stream | | `@zenml.log.source` | Source of the log (step, pipeline, etc.) | | `@zenml.log_store.id` | ID of the log store component | | `@zenml.log_store.name`| Name of the log store component | | `@zenml.user.id` | User ID | | `@zenml.user.name` | User name | | `@zenml.project.id` | Project ID | | `@zenml.project.name` | Project name | | `@zenml.stack.id` | Stack ID | | `@zenml.stack.name` | Stack name | | `@zenml.pipeline.id` | Pipeline ID | | `@zenml.pipeline.name` | Pipeline name | | `@zenml.pipeline.run.id` | Pipeline run ID | | `@zenml.pipeline.run.name` | Pipeline run name | | `@zenml.step.run.id` | Step ID (for step-level logs) | | `@zenml.step.run.name` | Step name (for step-level logs) | | `@zenml.workspace.id` | Workspace ID (when connected to a ZenML Pro workspace) | | `@zenml.workspace.name` | Workspace name (when connected to a ZenML Pro workspace) | ### How to deploy it The Datadog Log Store comes built-in with ZenML. You need: 1. A Datadog account with log management enabled 2. A Datadog API key (for log ingestion) 3. A Datadog Application key (for log retrieval) #### Getting your keys 1. **API Key**: Navigate to **Organization Settings** → **API Keys** in Datadog 2. **Application Key**: Navigate to **Organization Settings** → **Application Keys** in Datadog {% hint style="info" %} Both the API key and Application key are **required** to register a Datadog log store. The API key is used for log ingestion, while the Application key is used for log retrieval (displaying logs in the ZenML dashboard). {% endhint %} ### How to use it #### Basic setup ```shell # Create a secret with your Datadog keys zenml secret create datadog_keys \ --api_key= \ --application_key= # Register the Datadog log store zenml log-store register datadog_logs \ --flavor=datadog \ --api_key='{{datadog_keys.api_key}}' \ --application_key='{{datadog_keys.application_key}}' # Add it to your stack zenml stack register my_stack \ -a my_artifact_store \ -o default \ -ls datadog_logs \ --set ``` #### With a different Datadog site Datadog has multiple regional sites. Specify your site if you're not using the default (`datadoghq.com`): ```shell zenml log-store register datadog_logs \ --flavor=datadog \ --api_key='{{datadog_keys.api_key}}' \ --application_key='{{datadog_keys.application_key}}' \ --site=datadoghq.eu # For EU region ``` Available sites: - `datadoghq.com` (US1 - default) - `us3.datadoghq.com` (US3) - `us5.datadoghq.com` (US5) - `datadoghq.eu` (EU) - `ap1.datadoghq.com` (AP1) #### With a custom service name ```shell zenml log-store register datadog_logs \ --flavor=datadog \ --api_key='{{datadog_keys.api_key}}' \ --application_key='{{datadog_keys.application_key}}' \ --service_name=my-ml-pipelines ``` ### Configuration options | Parameter | Default | Description | |--------------------------|--------------------|----------------------------------------------------| | `api_key` | _required_ | Datadog API key for log ingestion | | `application_key` | _required_ | Datadog Application key for log retrieval | | `site` | `"datadoghq.com"` | Datadog site (e.g., `datadoghq.eu`) | | `service_name` | `"zenml"` | Service name shown in Datadog logs | | `service_version` | ZenML version | Service version shown in Datadog logs | | `max_export_batch_size` | `500` | Maximum batch size (Datadog limit: 1000) | | `max_queue_size` | `100000` | Maximum queue size for batch processor | | `schedule_delay_millis` | `5000` | Delay between batch exports (milliseconds) | | `export_timeout_millis` | `15000` | Timeout for each export batch (milliseconds) | {% hint style="warning" %} Datadog has a maximum batch size limit of 1000 logs per request. The `max_export_batch_size` is capped at this value. {% endhint %} ### Viewing logs #### In ZenML Dashboard Logs are automatically fetched from Datadog when viewing step details in the ZenML dashboard. The dashboard uses Datadog's Logs Search API to retrieve logs filtered by the step's log ID. #### In Datadog Navigate to **Logs** in your Datadog dashboard and use these filters: ``` service:zenml @zenml.pipeline.run.name: ``` Or filter by specific step: ``` service:zenml @zenml.pipeline.run.name: @zenml.step.run.name:my_training_step ``` ### Troubleshooting #### Logs not appearing in Datadog 1. Verify your API key is correct 2. Check that you're looking at the correct Datadog site 3. Ensure the service name filter matches your configuration 4. Allow a few minutes for logs to be indexed #### Logs not appearing in ZenML Dashboard 1. Verify your Application key is correct 2. Ensure the Application key has the `logs_read` scope 3. Check that the Datadog site configuration matches #### Rate limiting If you're hitting Datadog's rate limits: - Increase `schedule_delay_millis` to reduce export frequency - Decrease `max_export_batch_size` for more frequent, smaller batches - Consider log sampling for high-volume pipelines For more information and a full list of configurable attributes, check out the [SDK Docs](https://sdkdocs.zenml.io/latest/core_code_docs/core-log_stores.html#zenml.log_stores.datadog.datadog_log_store).
ZenML Scarf
--- ### Book/Component Guide/Log Stores/Otel (docs/book/component-guide/log-stores/otel.md) --- description: Exporting logs to any OpenTelemetry-compatible backend. --- # OpenTelemetry Log Store The OpenTelemetry (OTEL) Log Store is a log store flavor that exports logs to any OpenTelemetry-compatible backend using the OTLP/HTTP protocol with JSON encoding. Built on the [OpenTelemetry Python SDK](https://opentelemetry.io/docs/languages/python/), it provides maximum flexibility for integrating with your existing observability infrastructure. {% hint style="info" %} This page covers pipeline and step log export through a ZenML stack component. ZenML server observability is configured separately with `ZENML_SERVER_OTEL_EXPORTER_OTLP_ENDPOINT` or the standard `OTEL_EXPORTER_OTLP_ENDPOINT`, which exports server traces, metrics, and logs from the deployed ZenML server process. See the deployment guides for more details. {% endhint %} {% hint style="warning" %} The OTEL Log Store is a **write-only** log store. It can export logs to an OTEL-compatible endpoint, but it cannot fetch logs back for display in the ZenML dashboard. If you need log retrieval capabilities, you can extend this log store and implement the `fetch()` method for your backend. See [Develop a Custom Log Store](custom.md) for details on how to do this. {% endhint %} ### When to use it The OTEL Log Store is ideal when: - You have an existing OpenTelemetry-compatible observability platform (e.g., Jaeger, Grafana Tempo, Honeycomb, Lightstep, Dash0) - You want to consolidate ML pipeline logs with your application logs - You need to export logs to a custom backend that supports OTLP - You're building a custom log ingestion pipeline ### How it works The OTEL Log Store implements the OpenTelemetry logging specification: 1. **Log capture**: All stdout, stderr, and Python logging output is captured during pipeline execution. 2. **OTEL conversion**: Log records are converted to the OpenTelemetry log format with ZenML-specific attributes. 3. **Batching**: Logs are batched using OpenTelemetry's `BatchLogRecordProcessor` for efficient export. 4. **Export**: Batched logs are sent to your configured endpoint using OTLP/HTTP with JSON encoding and optionally, using data compression. #### ZenML-specific attributes Each log record includes ZenML metadata as OTEL attributes: | Attribute | Description | |-----------------------|------------------------------------------------| | `zenml.log.id` | Unique identifier for the log stream | | `zenml.log.source` | Source of the log (step, pipeline, etc.) | | `zenml.log_store.id` | ID of the log store component | | `zenml.log_store.name`| Name of the log store component | | `zenml.user.id` | User ID | | `zenml.user.name` | User name | | `zenml.project.id` | Project ID | | `zenml.project.name` | Project name | | `zenml.stack.id` | Stack ID | | `zenml.stack.name` | Stack name | | `zenml.pipeline.id` | Pipeline ID | | `zenml.pipeline.name` | Pipeline name | | `zenml.pipeline.run.id` | Pipeline run ID | | `zenml.pipeline.run.name` | Pipeline run name | | `zenml.step.run.id` | Step ID (for step-level logs) | | `zenml.step.run.name` | Step name (for step-level logs) | | `zenml.workspace.id` | Workspace ID (when connected to a ZenML Pro workspace) | | `zenml.workspace.name` | Workspace name (when connected to a ZenML Pro workspace) | These attributes enable powerful filtering and querying in your observability platform. ### How to use it You need to have an OpenTelemetry-compatible endpoint ready to receive logs. This could be: - A self-hosted OTEL Collector - A managed observability platform (Grafana Cloud, Honeycomb, etc.) - Any service that accepts OTLP/HTTP with JSON encoding Register the OTEL log store with your endpoint configuration: ```shell # Register an OTEL log store zenml log-store register my_otel_logs \ --flavor=otel \ --endpoint=https://otel-collector.example.com/v1/logs # Add it to your stack zenml stack register my_stack \ -a my_artifact_store \ -o default \ -ls my_otel_logs \ --set ``` #### With authentication headers Most OTEL backends require authentication. You can pass headers using a ZenML secret: ```shell # Create a secret with your API key zenml secret create otel_auth \ --api_key= # Register the log store with the header zenml log-store register my_otel_logs \ --flavor=otel \ --endpoint=https://otel-collector.example.com/v1/logs \ --headers='{"Authorization": "Bearer {{otel_auth.api_key}}"}' ``` #### With TLS certificates For endpoints requiring client certificates: ```shell zenml log-store register my_otel_logs \ --flavor=otel \ --endpoint=https://secure-collector.example.com/v1/logs \ --certificate_file=/path/to/ca.crt \ --client_certificate_file=/path/to/client.crt \ --client_key_file=/path/to/client.key ``` ### Configuration options | Parameter | Default | Description | |----------------------------|--------------------|----------------------------------------------------| | `endpoint` | _required_ | OTLP/HTTP endpoint URL for log ingestion | | `headers` | `None` | Optional headers for authentication | | `certificate_file` | `None` | Path to CA certificate file for TLS verification | | `client_certificate_file` | `None` | Path to client certificate file for mTLS | | `client_key_file` | `None` | Path to client key file for mTLS | | `compression` | `"none"` | Compression type: `"none"`, `"gzip"`, or `"deflate"`| | `service_name` | `"zenml"` | Service name in OTEL resource attributes | | `service_version` | ZenML version | Service version in OTEL resource attributes | | `max_queue_size` | `100000` | Maximum queue size for batch processor | | `schedule_delay_millis` | `5000` | Delay between batch exports (milliseconds) | | `max_export_batch_size` | `5000` | Maximum batch size for exports | | `export_timeout_millis` | `15000` | Timeout for each export batch (milliseconds) | ### Retry behavior The OTEL Log Store includes built-in retry logic for transient failures: - **Retried status codes**: 408, 429, 500, 502, 503, 504 - **Connection retries**: 5 attempts with exponential backoff - **Read retries**: 5 attempts - **Backoff factor**: 0.5 seconds This ensures reliable log delivery even in unstable network conditions. ### Limitations 1. **No log fetching**: The OTEL Log Store cannot retrieve logs for display in the ZenML dashboard. You must use your observability platform's native interface to view logs. 2. **Dashboard integration**: Since logs cannot be fetched, the ZenML dashboard will show "Logs not available" for steps using this log store. 3. **Endpoint compatibility**: Your endpoint must support OTLP/HTTP with JSON encoding. Protobuf-only endpoints are not supported. ### Best practices 1. **Use compression**: Enable `gzip` compression for high-volume logging to reduce network bandwidth. 2. **Tune batch settings**: Adjust `max_queue_size` and `max_export_batch_size` based on your log volume: - High volume: Increase both values - Low latency needs: Decrease `schedule_delay_millis` 3. **Monitor the endpoint**: Ensure your OTEL collector or backend can handle the log volume from your pipelines. 4. **Use secrets for credentials**: Always store API keys and tokens in ZenML secrets, not in plain text. For more information and a full list of configurable attributes, check out the [SDK Docs](https://sdkdocs.zenml.io/latest/core_code_docs/core-log_stores.html#zenml.log_stores.otel.otel_log_store).
ZenML Scarf
--- ### Book/Component Guide/Model Deployers/README (docs/book/component-guide/model-deployers/README.md) --- icon: rocket description: Deploying your models and serve real-time predictions. --- # Model Deployers {% hint style="warning" %} **DEPRECATION NOTICE** The Model Deployer stack component is deprecated in favor of the more flexible [**Deployer**](https://docs.zenml.io/stacks/stack-components/deployers) component and [**Pipeline Deployments**](https://docs.zenml.io/concepts/deployment). The Model Deployer abstraction focused exclusively on single-model serving, but modern ML workflows often require multi-step pipelines with preprocessing, tool integration, and custom business logic. The new Pipeline Deployment paradigm provides: - **Unified approach**: Deploy any pipeline—classical ML inference, agentic workflows, or hybrid systems—as a long-running HTTP service - **Greater flexibility**: Customize your deployment with full FastAPI control, add middleware, custom routes, and even frontend interfaces - **Simpler mental model**: One primitive for all deployment scenarios instead of separate abstractions for models vs. pipelines - **Better extensibility**: Deploy to Docker, AWS App Runner, GCP Cloud Run, and other platforms with consistent patterns **Migration Path**: Instead of using Model Deployer-specific steps, wrap your model inference logic in a regular ZenML pipeline and deploy it using `zenml pipeline deploy`. See the [Pipeline Deployment guide](https://docs.zenml.io/concepts/deployment) for examples of deploying ML models as HTTP services. While Model Deployer integrations remain available for backward compatibility, we strongly recommend migrating to Pipeline Deployments for new projects. {% endhint %} Model Deployment is the process of making a machine learning model available to make predictions and decisions on real-world data. Getting predictions from trained models can be done in different ways depending on the use case, a batch prediction is used to generate predictions for a large amount of data at once, while a real-time prediction is used to generate predictions for a single data point at a time. Model deployers are stack components responsible for serving models on a real-time or batch basis. Online serving is the process of hosting and loading machine-learning models as part of a managed web service and providing access to the models through an API endpoint like HTTP or GRPC. Once deployed, model inference can be triggered at any time, and you can send inference requests to the model through the web service's API and receive fast, low-latency responses. Batch inference or offline inference is the process of making a machine learning model make predictions on a batch of observations. This is useful for generating predictions for a large amount of data at once. The predictions are usually stored as files or in a database for end users or business applications. ### When to use it? The model deployers are optional components in the ZenML stack. They are used to deploy machine learning models to a target environment, either a development (local) or a production (Kubernetes or cloud) environment. The model deployers are mainly used to deploy models for real-time inference use cases. With the model deployers and other stack components, you can build pipelines that are continuously trained and deployed to production. ### How model deployers slot into the stack Here is an architecture diagram that shows how model deployers fit into the overall story of a remote stack. #### Model Deployers Flavors ZenML comes with a `local` MLflow model deployer which is a simple model deployer that deploys models to a local MLflow server. Additional model deployers that can be used to deploy models on production environments are provided by integrations: | Model Deployer | Flavor | Integration | Notes | |------------------------------------|-----------|---------------|------------------------------------------------------------------------------| | [MLflow](mlflow.md) | `mlflow` | `mlflow` | Deploys ML Model locally | | [BentoML](bentoml.md) | `bentoml` | `bentoml` | Build and Deploy ML models locally or for production grade (Cloud, K8s) | | [Seldon Core](seldon.md) | `seldon` | `seldon Core` | Built on top of Kubernetes to deploy models for production grade environment | | [Hugging Face](huggingface.md) | `huggingface` | `huggingface` | Deploys ML model on Hugging Face Inference Endpoints | | [Databricks](databricks.md) | `databricks` | `databricks` | Deploying models to Databricks Inference Endpoints with Databricks | | [vLLM](vllm.md) | `vllm` | `vllm` | Deploys LLM using vLLM locally | | [Custom Implementation](custom.md) | _custom_ | | Extend the Artifact Store abstraction and provide your own implementation | {% hint style="info" %} Every model deployer may have different attributes that must be configured in order to interact with the model serving tool, framework, or platform (e.g. hostnames, URLs, references to credentials, and other client-related configuration parameters). The following example shows the configuration of the MLflow and Seldon Core model deployers: ```shell # Configure MLflow model deployer zenml model-deployer register mlflow --flavor=mlflow # Configure Seldon Core model deployer zenml model-deployer register seldon --flavor=seldon \ --kubernetes_context=zenml-eks --kubernetes_namespace=zenml-workloads \ --base_url=http://abb84c444c7804aa98fc8c097896479d-377673393.us-east-1.elb.amazonaws.com ... ``` {% endhint %} #### The role that a model deployer plays in a ZenML Stack * Seamless Model Deployment: Facilitates the deployment of machine learning models to various serving environments, such as local servers, Kubernetes clusters, or cloud platforms, ensuring that models can be deployed and managed efficiently in accordance with the specific requirements of the serving infrastructure by holds all the stack-related configuration attributes required to interact with the remote model serving tool, service, or platform (e.g. hostnames, URLs, references to credentials, and other client-related configuration parameters). The following are examples of configuring the MLflow and Seldon Core Model Deployers and registering them as a Stack component: ```bash zenml integration install mlflow zenml model-deployer register mlflow --flavor=mlflow zenml stack register local_with_mlflow -m default -a default -o default -d mlflow --set ``` ```bash zenml integration install seldon zenml model-deployer register seldon --flavor=seldon \ --kubernetes_context=zenml-eks --kubernetes_namespace=zenml-workloads \ --base_url=http://abb84c444c7804aa98fc8c097896479d-377673393.us-east-1.elb.amazonaws.com ... zenml stack register seldon_stack -m default -a aws -o default -d seldon ``` * Lifecycle Management: Provides mechanisms for comprehensive lifecycle management of model servers, including the ability to start, stop, and delete model servers, as well as to update existing servers with new model versions, thereby optimizing resource utilization and facilitating continuous delivery of model updates. Some core methods that can be used to interact with the remote model server include: - `deploy_model` - Deploys a model to the serving environment and returns a Service object that represents the deployed model server. - `find_model_server` - Finds and returns a list of Service objects that represent model servers that have been deployed to the serving environment, the `services` are stored in the DB and can be used as a reference to know what and where the model is deployed. - `stop_model_server` - Stops a model server that is currently running in the serving environment. - `start_model_server` - Starts a model server that has been stopped in the serving environment. - `delete_model_server` - Deletes a model server from the serving environment and from the DB. {% hint style="info" %} ZenML uses the Service object to represent a model server that has been deployed to a serving environment. The Service object is saved in the DB and can be used as a reference to know what and where the model is deployed. The Service object consists of 2 main attributes, the `config` and the `status`. The `config` attribute holds all the deployment configuration attributes required to create a new deployment, while the `status` attribute holds the operational status of the deployment, such as the last error message, the prediction URL, and the deployment status. {% endhint %} ```python from zenml.integrations.huggingface.model_deployers import HuggingFaceModelDeployer model_deployer = HuggingFaceModelDeployer.get_active_model_deployer() services = model_deployer.find_model_server( pipeline_name="LLM_pipeline", pipeline_step_name="huggingface_model_deployer_step", model_name="LLAMA-7B", ) if services: if services[0].is_running: print( f"Model server {services[0].config['model_name']} is running at {services[0].status['prediction_url']}" ) else: print(f"Model server {services[0].config['model_name']} is not running") model_deployer.start_model_server(services[0]) else: print("No model server found") service = model_deployer.deploy_model( pipeline_name="LLM_pipeline", pipeline_step_name="huggingface_model_deployer_step", model_name="LLAMA-7B", model_uri="s3://zenprojects/huggingface_model_deployer_step/output/884/huggingface", revision="main", task="text-classification", region="us-east-1", vendor="aws", token="huggingface_token", namespace="zenml-workloads", endpoint_type="public", ) print(f"Model server {service.config['model_name']} is deployed at {service.status['prediction_url']}") ``` #### How to Interact with a model deployer after deployment? When a Model Deployer is part of the active ZenML Stack, it is also possible to interact with it from the CLI to list, start, stop, or delete the model servers that is managed: ``` /* Detailed source-code truncated for AI context efficiency. */ ``` In Python, you can alternatively discover the prediction URL of a deployed model by inspecting the metadata of the step that deployed the model: ```python from zenml.client import Client pipeline_run = Client().get_pipeline_run("") deployer_step = pipeline_run.steps[""] deployed_model_url = deployer_step.run_metadata["deployed_model_url"].value ``` The ZenML integrations that provide Model Deployer stack components also include standard pipeline steps that can directly be inserted into any pipeline to achieve a continuous model deployment workflow. These steps take care of all the aspects of continuously deploying models to an external server and saving the Service configuration into the Artifact Store, where they can be loaded at a later time and re-create the initial conditions used to serve a particular model.
ZenML Scarf
--- ### Book/Component Guide/Model Deployers/Bentoml (docs/book/component-guide/model-deployers/bentoml.md) --- description: Deploying your models locally with BentoML. --- # BentoML BentoML is an open-source framework for machine learning model serving. it can be used to deploy models locally, in a cloud environment, or in a Kubernetes environment. The BentoML Model Deployer is one of the available flavors of the [Model Deployer](./) stack component. Provided with the BentoML integration it can be used to deploy and [manage BentoML models](https://docs.bentoml.org/en/latest/guides/model-store.html#manage-models) or [Bento](https://docs.bentoml.org/en/latest/reference/stores.html#manage-bentos) on a local running HTTP server. {% hint style="warning" %} The BentoML Model Deployer can be used to deploy models for local development and production use cases. There are two paths to deploy Bentos with ZenML, one as a local http server and one as a containerized service. Within the BentoML ecosystem, [Yatai](https://github.com/bentoml/Yatai) and [`bentoctl`](https://github.com/bentoml/bentoctl) are the tools responsible for deploying the Bentos into the Kubernetes cluster and Cloud Platforms. `bentoctl` is deprecated now and might not work with the latest BentoML versions. {% endhint %} ## When to use it? You should use the BentoML Model Deployer to: * Standardize the way you deploy your models to production within your organization. * if you are looking to deploy your models in a simple way, while you are still able to transform your model into a production-ready solution when that time comes. If you are looking to deploy your models with other Kubernetes-based solutions, you can take a look at one of the other [Model Deployer Flavors](./#model-deployers-flavors) available in ZenML. BentoML also allows you to deploy your models in a more complex production-grade setting. [Bentoctl](https://github.com/bentoml/bentoctl) is one of the tools that can help you get there. Bentoctl takes your built Bento from a ZenML pipeline and deploys it with `bentoctl` into a cloud environment such as AWS Lambda, AWS SageMaker, Google Cloud Functions, Google Cloud AI Platform, or Azure Functions. Read more about this in the [From Local to Cloud with `bentoctl` section](bentoml.md#from-local-to-cloud-with-bentoctl). {% hint style="info" %} The `bentoctl` integration implementation is still in progress and will be available soon. The integration will allow you to deploy your models to a specific cloud provider with just a few lines of code using ZenML built-in steps. {% endhint %} ## How do you deploy it? Within ZenML you can quickly get started with BentoML by simply creating Model Deployer Stack Component with the BentoML flavor. To do so you'll need to install the required Python packages on your local machine to be able to deploy your models: ```bash zenml integration install bentoml -y ``` To register the BentoML model deployer with ZenML you need to run the following command: ```bash zenml model-deployer register bentoml_deployer --flavor=bentoml ``` The ZenML integration will provision a local HTTP deployment server as a daemon process that will continue to run in the background to serve the latest models and Bentos. ## How do you use it? The recommended flow to use the BentoML model deployer is to first [create a BentoML Service](bentoml.md#create-a-bentoml-service), then either build a [bento yourself](bentoml.md#build-your-own-bento) or [use the `bento_builder_step`](bentoml.md#zenml-bento-builder-step) to build the model and service into a bento bundle, and finally [deploy the bundle with the `bentoml_model_deployer_step`](bentoml.md#zenml-bentoml-deployer-step). ### Create a BentoML Service The first step to being able to deploy your models and use BentoML is to create a [bento service](https://docs.bentoml.com/en/latest/guides/services.html) which is the main logic that defines how your model will be served. The following example shows how to create a basic bento service that will be used to serve a torch model. Learn more about how to specify the inputs and outputs for the APIs and how to use validators in the [Input and output types BentoML docs](https://docs.bentoml.com/en/latest/guides/iotypes.html) ```python import bentoml from bentoml.validators import DType, Shape from bentoml.io import PILImage import numpy as np import torch from typing import Annotated # Note: SERVICE_NAME and MODEL_NAME would be defined elsewhere # Note: to_numpy() would be a custom function to convert tensors to numpy arrays @bentoml.service( name=SERVICE_NAME, ) class MNISTService: def __init__(self): # load model self.model = bentoml.pytorch.load_model(MODEL_NAME) self.model.eval() @bentoml.api() async def predict_ndarray( self, inp: Annotated[np.ndarray, DType("float32"), Shape((28, 28))] ) -> np.ndarray: inp = np.expand_dims(inp, (0, 1)) output_tensor = await self.model(torch.tensor(inp)) return to_numpy(output_tensor) @bentoml.api() async def predict_image(self, f: PILImage) -> np.ndarray: assert isinstance(f, PILImage) arr = np.array(f) / 255.0 assert arr.shape == (28, 28) arr = np.expand_dims(arr, (0, 1)).astype("float32") output_tensor = await self.model(torch.tensor(arr)) return to_numpy(output_tensor) ``` ### 🏗️ Build your own bento The `bento_builder_step` only exists to make your life easier; you can always build the bento yourself and use it in the deployer step in the next section. A peek into how this step is implemented will give you ideas on how to build such a function yourself. This allows you to have more customization over the bento build process if needed. ```python # 1. use the step context to get the output artifact uri context = get_step_context() # 2. you can save the model and bento uri as part of the bento labels labels = labels or {} labels["model_uri"] = model.uri labels["bento_uri"] = os.path.join( context.get_output_artifact_uri(), DEFAULT_BENTO_FILENAME ) # 3. Load the model from the model artifact model = load_artifact_from_response(model) # 4. Save the model to a BentoML model based on the model type try: module = importlib.import_module(f".{model_type}", "bentoml") module.save_model(model_name, model, labels=labels) except importlib.metadata.PackageNotFoundError: bentoml.picklable_model.save_model( model_name, model, ) # 5. Build the BentoML bundle. You can use any of the parameters supported by the bentos.build function. bento = bentos.build( service=service, models=[model_name], version=version, labels=labels, description=description, include=include, exclude=exclude, python=python, docker=docker, build_ctx=working_dir or source_utils.get_source_root(), ) ``` The `model_name` here should be the name with which your model is saved to BentoML, typically through one of the following commands. More information about the BentoML model store and how to save models there can be found here on the [BentoML docs](https://docs.bentoml.org/en/latest/guides/model-store.html#save-a-model). ```python bentoml.MODEL_TYPE.save_model(model_name, model, labels=labels) # or bentoml.picklable_model.save_model( model_name, model, ) ``` Now, your custom step could look something like this: ```python from zenml import step @step def my_bento_builder(model) -> bento.Bento: ... # Load the model from the model artifact model = load_artifact_from_response(model) # save to bentoml bentoml.pytorch.save_model(model_name, model) # Build the BentoML bundle. You can use any of the parameters supported by the bentos.build function. bento = bentos.build( ... ) return bento ``` You can now use this bento in any way you see fit. ### ZenML Bento Builder step Once you have your bento service defined, we can use the built-in bento builder step to build the bento bundle that will be used to serve the model. The following example shows how can call the built-in bento builder step within a ZenML pipeline. Make sure you have the bento service file in your repository and at the root-level and then use the correct class name in the `service` parameter. ```python from zenml import pipeline, step from zenml.integrations.bentoml.steps import bento_builder_step @pipeline def bento_builder_pipeline(): model = ... bento = bento_builder_step( model=model, model_name="pytorch_mnist", # Name of the model model_type="pytorch", # Type of the model (pytorch, tensorflow, sklearn, xgboost..) service="service.py:CLASS_NAME", # Path to the service file within zenml repo labels={ # Labels to be added to the bento bundle "framework": "pytorch", "dataset": "mnist", "zenml_version": "0.21.1", }, exclude=["data"], # Exclude files from the bento bundle python={ "packages": ["zenml", "torch", "torchvision"], }, # Python package requirements of the model ) ``` The Bento Builder step can be used in any orchestration pipeline that you create with ZenML. The step will build the bento bundle and save it to the used artifact store. Which can be used to serve the model in a local or containerized setting using the BentoML Model Deployer Step, or in a remote setting using the `bentoctl` or Yatai. This gives you the flexibility to package your model in a way that is ready for different deployment scenarios. ### ZenML BentoML Deployer step We have now built our bento bundle, and we can use the built-in `bentoml_model_deployer_step` to deploy the bento bundle to our local HTTP server or to a containerized service running in your local machine. {% hint style="info" %} The `bentoml_model_deployer_step` can only be used in a local environment. But in the case of using containerized deployment, you can use the Docker image created by the `bentoml_model_deployer_step` to deploy your model to a remote environment. It is automatically pushed to your ZenML Stack's container registry. {% endhint %} **Local deployment** The following example shows how to use the `bentoml_model_deployer_step` to deploy the bento bundle to a local HTTP server. ```python from zenml import pipeline, step from zenml.integrations.bentoml.steps import bentoml_model_deployer_step @pipeline def bento_deployer_pipeline(): bento = ... deployed_model = bentoml_model_deployer_step( bento=bento model_name="pytorch_mnist", # Name of the model port=3001, # Port to be used by the http server ) ``` **Containerized deployment** The following example shows how to use the `bentoml_model_deployer_step` to deploy the bento bundle to a [containerized service](https://docs.bentoml.org/en/latest/guides/containerization.html) running in your local machine. Make sure you have the `docker` CLI installed on your local machine to be able to build an image and deploy the containerized service. You can choose to give a name and a tag to the image that will be built and pushed to your ZenML Stack's container registry. By default, the bento tag is used. If you are providing a custom image name, make sure that you attach the right registry name as prefix to the image name, otherwise the image push will fail. ```python from zenml import pipeline, step from zenml.integrations.bentoml.steps import bentoml_model_deployer_step @pipeline def bento_deployer_pipeline(): bento = ... deployed_model = bentoml_model_deployer_step( bento=bento model_name="pytorch_mnist", # Name of the model port=3001, # Port to be used by the http server deployment_type="container", image="my-custom-image", image_tag="my-custom-image-tag", platform="linux/amd64", ) ``` This step: * builds a docker image for the bento and pushes it to the container registry * runs the docker image locally to make it ready for inference You can find the image on your machine by running: ```bash docker images ``` and also the running container by running: ```bash docker ps ``` The image is also pushed to the container registry of your ZenML stack. You can run the image in any environment with a sample command like this: ```bash docker run -it --rm -p 3000:3000 image:image-tag serve ``` ### ZenML BentoML Pipeline examples Once all the steps have been defined, we can create a ZenML pipeline and run it. The bento builder step expects to get the trained model as an input, so we need to make sure either we have a previous step that trains the model and outputs it or loads the model from a previous run. Then the deployer step expects to get the bento bundle as an input, so we need to make sure either we have a previous step that builds the bento bundle and outputs it or load the bento bundle from a previous run or external source. The following example shows how to create a ZenML pipeline that trains a model, builds a bento bundle, creates and runs a docker image for it and pushes it to the container registry. You can then have a different pipeline that retrieves the image and deploys it to a remote environment. ```python # Import the pipeline to use the pipeline decorator from zenml.pipelines import pipeline # Pipeline definition @pipeline def bentoml_pipeline( importer, trainer, evaluator, deployment_trigger, bento_builder, deployer, ): """Link all the steps and artifacts together""" train_dataloader, test_dataloader = importer() model = trainer(train_dataloader) accuracy = evaluator(test_dataloader=test_dataloader, model=model) decision = deployment_trigger(accuracy=accuracy) bento = bento_builder(model=model) deployer(deploy_decision=decision, bento=bento, deployment_type="container") ``` In more complex scenarios, you might want to build a pipeline that trains a model and builds a bento bundle in a remote environment. Then creates a new pipeline that retrieves the bento bundle and deploys it to a local http server, or to a cloud provider. The following example shows a pipeline example that does exactly that. ```python # Import the pipeline to use the pipeline decorator from zenml.pipelines import pipeline # Pipeline definition @pipeline def remote_train_pipeline( importer, trainer, evaluator, bento_builder, ): """Link all the steps and artifacts together""" train_dataloader, test_dataloader = importer() model = trainer(train_dataloader) accuracy = evaluator(test_dataloader=test_dataloader, model=model) bento = bento_builder(model=model) @pipeline def local_deploy_pipeline( bento_loader, deployer, ): """Link all the steps and artifacts together""" bento = bento_loader() deployer(deploy_decision=decision, bento=bento) ``` ### Predicting with the local deployed model Once the model has been deployed we can use the BentoML client to send requests to the deployed model. ZenML will automatically create a BentoML client for you and you can use it to send requests to the deployed model by simply calling the service to predict the method and passing the input data and the API function name. The following example shows how to use the BentoML client to send requests to the deployed model. ```python @step def predictor( inference_data: Dict[str, List], service: BentoMLDeploymentService, ) -> None: """Run an inference request against the BentoML prediction service. Args: service: The BentoML service. data: The data to predict. """ service.start(timeout=10) # should be a NOP if already started for img, data in inference_data.items(): prediction = service.predict("predict_ndarray", np.array(data)) result = to_labels(prediction[0]) rich_print(f"Prediction for {img} is {result}") ``` Deploying and testing locally is a great way to get started and test your model. However, a real-world scenario will most likely require you to deploy your model to a remote environment. You can choose to deploy your model as a container image by setting the `deployment_type` to container in the deployer step and then use the image created in a remote environment. You can also use `bentoctl` or `yatai` to deploy the bento to a cloud environment. ### From Local to Cloud with `bentoctl` {% hint style="warning" %} The `bentoctl` CLI is now deprecated and might not work with the latest BentoML versions. {% endhint %} Bentoctl helps deploy any machine learning models as production-ready API endpoints into the cloud. It is a command line tool that provides a simple interface to manage your BentoML bundles. The `bentoctl` CLI provides a list of operators which are plugins that interact with cloud services, some of these operators are: * [AWS Lambda](https://github.com/bentoml/aws-lambda-deploy) * [AWS SageMaker](https://github.com/bentoml/aws-sagemaker-deploy) * [AWS EC2](https://github.com/bentoml/aws-ec2-deploy) * [Google Cloud Run](https://github.com/bentoml/google-cloud-run-deploy) * [Google Compute Engine](https://github.com/bentoml/google-compute-engine-deploy) * [Azure Container Instances](https://github.com/bentoml/azure-container-instances-deploy) * [Heroku](https://github.com/bentoml/heroku-deploy) You can find more information about the `bentoctl` tool [on the official GitHub repository](https://github.com/bentoml/bentoctl). For more information and a full list of configurable attributes of the BentoML Model Deployer, check out the [SDK Docs](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-bentoml.html#zenml.integrations.bentoml) .
ZenML Scarf
--- ### Book/Component Guide/Model Deployers/Custom (docs/book/component-guide/model-deployers/custom.md) --- description: Learning how to develop a custom model deployer. --- # Develop a Custom Model Deployer {% hint style="info" %} Before diving into the specifics of this component type, it is beneficial to familiarize yourself with our [general guide to writing custom component flavors in ZenML](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/implement-a-custom-stack-component). This guide provides an essential understanding of ZenML's component flavor concepts. {% endhint %} To deploy and manage your trained machine-learning models, ZenML provides a stack component called `Model Deployer`. This component is responsible for interacting with the deployment tool, framework, or platform. When present in a stack, the model deployer can also act as a registry for models that are served with ZenML. You can use the model deployer to list all models that are currently deployed for online inference or filtered according to a particular pipeline run or step, or to suspend, resume or delete an external model server managed through ZenML. ### Base Abstraction In ZenML, the base abstraction of the model deployer is built on top of three major criteria: 1. It needs to ensure efficient deployment and management of models in accordance with the specific requirements of the serving infrastructure, by holding all the stack-related configuration attributes required to interact with the remote model serving tool, service, or platform. 2. It needs to implement the continuous deployment logic necessary to deploy models in a way that updates an existing model server that is already serving a previous version of the same model instead of creating a new model server for every new model version (see the `deploy_model` abstract method). This functionality can be consumed directly from ZenML pipeline steps, but it can also be used outside the pipeline to deploy ad-hoc models. It is also usually coupled with a standard model deployer step, implemented by each integration, that hides the details of the deployment process from the user. 3. It needs to act as a ZenML BaseService registry, where every BaseService instance is used as an internal representation of a remote model server (see the `find_model_server` abstract method). To achieve this, it must be able to re-create the configuration of a BaseService from information that is persisted externally, alongside, or even as part of the remote model server configuration itself. For example, for model servers that are implemented as Kubernetes resources, the BaseService instances can be serialized and saved as Kubernetes resource annotations. This allows the model deployer to keep track of all externally running model servers and to re-create their corresponding BaseService instance representations at any given time. The model deployer also defines methods that implement basic life-cycle management on remote model servers outside the coverage of a pipeline (see `stop_model_server` , `start_model_server` and `delete_model_server`). Putting all these considerations together, we end up with the following interface: ``` /* Detailed source-code truncated for AI context efficiency. */ ``` {% hint style="info" %} This is a slimmed-down version of the base implementation which aims to highlight the abstraction layer. In order to see the full implementation and get the complete docstrings, please check the [SDK docs](https://sdkdocs.zenml.io/latest/core_code_docs/core-model_deployers.html#zenml.model_deployers.base_model_deployer) . {% endhint %} ### Building your own model deployers If you want to create your own custom flavor for a model deployer, you can follow the following steps: 1. Create a class that inherits from the `BaseModelDeployer` class and implements the abstract methods. 2. If you need to provide any configuration, create a class that inherits from the `BaseModelDeployerConfig` class and add your configuration parameters. 3. Bring both the implementation and the configuration together by inheriting from the `BaseModelDeployerFlavor` class. Make sure that you give a `name` to the flavor through its abstract property. 4. Create a service class that inherits from the `BaseService` class and implements the abstract methods. This class will be used to represent the deployed model server in ZenML. Once you are done with the implementation, you can register it through the CLI. Please ensure you **point to the flavor class via dot notation**: ```shell zenml model-deployer flavor register ``` For example, if your flavor class `MyModelDeployerFlavor` is defined in `flavors/my_flavor.py`, you'd register it by doing: ```shell zenml model-deployer flavor register flavors.my_flavor.MyModelDeployerFlavor ``` {% hint style="warning" %} ZenML resolves the flavor class by taking the path where you initialized zenml (via `zenml init`) as the starting point of resolution. Therefore, please ensure you follow [the best practice](https://docs.zenml.io/user-guides/best-practices/iac) of initializing zenml at the root of your repository. If ZenML does not find an initialized ZenML repository in any parent directory, it will default to the current working directory, but usually, it's better to not have to rely on this mechanism and initialize zenml at the root. {% endhint %} Afterward, you should see the new flavor in the list of available flavors: ```shell zenml model-deployer flavor list ``` {% hint style="warning" %} It is important to draw attention to when and how these base abstractions are coming into play in a ZenML workflow. * The **CustomModelDeployerFlavor** class is imported and utilized upon the creation of the custom flavor through the CLI. * The **CustomModelDeployerConfig** class is imported when someone tries to register/update a stack component with this custom flavor. Especially, during the registration process of the stack component, the config will be used to validate the values given by the user. As `Config` objects are inherently `pydantic` objects, you can also add your own custom validators here. * The **CustomModelDeployer** only comes into play when the component is ultimately in use. The design behind this interaction lets us separate the configuration of the flavor from its implementation. This way we can register flavors and components even when the major dependencies behind their implementation are not installed in our local setting (assuming the `CustomModelDeployerFlavor` and the `CustomModelDeployerConfig` are implemented in a different module/path than the actual `CustomModelDeployer`). {% endhint %}
ZenML Scarf
--- ### Book/Component Guide/Model Deployers/Databricks (docs/book/component-guide/model-deployers/databricks.md) --- description: >- Deploying models to Databricks Inference Endpoints with Databricks --- # Databricks Databricks Model Serving or Mosaic AI Model Serving provides a unified interface to deploy, govern, and query AI models. Each model you serve is available as a REST API that you can integrate into your web or client application. This service provides dedicated and autoscaling infrastructure managed by Databricks, allowing you to deploy models without dealing with containers and GPUs. {% hint style="info" %} Databricks Model deployer can be considered as a managed service for deploying models using MLflow, This means you can switch between MLflow and Databricks Model Deployers without changing your pipeline code even for custom complex models. {% endhint %} ## When to use it? You should use Databricks Model Deployer: * You are already using Databricks for your data and ML workloads. * If you want to deploy AI models without dealing with containers and GPUs, Databricks Model Deployer provides a unified interface to deploy, govern, and query models. * Databricks Model Deployer offers dedicated and autoscaling infrastructure managed by Databricks, making it easier to deploy models at scale. * Enterprise security is a priority, and you need to deploy models into secure offline endpoints accessible only via a direct connection to your Virtual Private Cloud (VPCs). * if your goal is to turn your models into production-ready APIs with minimal infrastructure or MLOps involvement. If you are looking for a more easy way to deploy your models locally, you can use the [MLflow Model Deployer](mlflow.md) flavor. ## How to deploy it? The Databricks Model Deployer flavor is provided by the Databricks ZenML integration, so you need to install it on your local machine to be able to deploy your models. You can do this by running the following command: ```bash zenml integration install databricks -y ``` To register the Databricks model deployer with ZenML you need to run the following command: ```bash zenml model-deployer register --flavor=databricks --host= --client_id={{databricks.client_id}} --client_secret={{databricks.client_secret}} ``` {% hint style="info" %} We recommend creating a Databricks service account with the necessary permissions to create and run jobs. You can find more information on how to create a service account [here](https://docs.databricks.com/dev-tools/api/latest/authentication.html). You can generate a client_id and client_secret for the service account and use them to authenticate with Databricks. {% endhint %} We can now use the model deployer in our stack. ```bash zenml stack update --model-deployer= ``` See the [databricks\_model\_deployer\_step](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-databricks.html#zenml.integrations.databricks) for an example of using the Databricks Model Deployer to deploy a model inside a ZenML pipeline step. ## Configuration Within the `DatabricksServiceConfig` you can configure: * `model_name`: The name of the model that will be served, this will be used to identify the model in the Databricks Model Registry. * `model_version`: The version of the model that will be served, this will be used to identify the model in the Databricks Model Registry. * `workload_size`: The size of the workload that the model will be serving. This can be `Small`, `Medium`, or `Large`. * `scale_to_zero_enabled`: A boolean flag to enable or disable the scale to zero feature. * `env_vars`: A dictionary of environment variables to be passed to the model serving container. * `workload_type`: The type of workload that the model will be serving. This can be `CPU`, `GPU_LARGE`, `GPU_MEDIUM`, `GPU_SMALL`, or `MULTIGPU_MEDIUM`. * `endpoint_secret_name`: The name of the secret that will be used to secure the endpoint and authenticate requests. For more information and a full list of configurable attributes of the Databricks Model Deployer, check out the [SDK Docs](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-databricks.html#zenml.integrations.databricks) and Databricks endpoint [code](https://docs.databricks.com/aws/en/machine-learning/model-serving/create-manage-serving-endpoints). ### Run inference on a provisioned inference endpoint The following code example shows how to run inference against a provisioned inference endpoint: ``` /* Detailed source-code truncated for AI context efficiency. */ ``` For more information and a full list of configurable attributes of the Databricks Model Deployer, check out the [SDK Docs](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-databricks.html#zenml.integrations.databricks).
ZenML Scarf
--- ### Book/Component Guide/Model Deployers/Huggingface (docs/book/component-guide/model-deployers/huggingface.md) --- description: >- Deploying models to Huggingface Inference Endpoints with Hugging Face :hugging_face:. --- # Hugging Face Hugging Face Inference Endpoints provides a secure production solution to easily deploy any `transformers`, `sentence-transformers`, and `diffusers` models on a dedicated and autoscaling infrastructure managed by Hugging Face. An Inference Endpoint is built from a model from the [Hub](https://huggingface.co/models). This service provides dedicated and autoscaling infrastructure managed by Hugging Face, allowing you to deploy models without dealing with containers and GPUs. ## When to use it? You should use Hugging Face Model Deployer: * if you want to deploy [Transformers, Sentence-Transformers, or Diffusion models](https://huggingface.co/docs/inference-endpoints/supported\_tasks) on dedicated and secure infrastructure. * if you prefer a fully-managed production solution for inference without the need to handle containers and GPUs. * if your goal is to turn your models into production-ready APIs with minimal infrastructure or MLOps involvement * Cost-effectiveness is crucial, and you want to pay only for the raw compute resources you use. * Enterprise security is a priority, and you need to deploy models into secure offline endpoints accessible only via a direct connection to your Virtual Private Cloud (VPCs). If you are looking for a more easy way to deploy your models locally, you can use the [MLflow Model Deployer](mlflow.md) flavor. ## How to deploy it? The Hugging Face Model Deployer flavor is provided by the Hugging Face ZenML integration, so you need to install it on your local machine to be able to deploy your models. You can do this by running the following command: ```bash zenml integration install huggingface -y ``` To register the Hugging Face model deployer with ZenML you need to run the following command: ```bash zenml model-deployer register --flavor=huggingface --token= --namespace= ``` Here, * `token` parameter is the Hugging Face authentication token. It can be managed through [Hugging Face settings](https://huggingface.co/settings/tokens). * `namespace` parameter is used for listing and creating the inference endpoints. It can take any of the following values, username or organization name or `*` depending on where the inference endpoint should be created. We can now use the model deployer in our stack. ```bash zenml stack update --model-deployer= ``` ## How to use it There are two mechanisms for using the Hugging Face model deployer integration: * Using the pre-built [huggingface\_model\_deployer\_step](https://github.com/zenml-io/zenml/blob/main/src/zenml/integrations/huggingface/steps/huggingface_deployer.py#L35) to deploy a Hugging Face model. * Running batch inference on a deployed Hugging Face model using the [HuggingFaceDeploymentService](https://github.com/zenml-io/zenml/blob/04cdf96576edd8fc615dceb7e0bf549301dc97bd/tests/integration/examples/huggingface/steps/prediction_service_loader/prediction_service_loader.py#L27) If you'd like to see this in action, check out this example of of [a deployment pipeline](https://github.com/zenml-io/zenml/blob/main/tests/integration/examples/huggingface/pipelines/deployment_pipelines/deployment_pipeline.py#L29) and [an inference pipeline](https://github.com/zenml-io/zenml/blob/main/tests/integration/examples/huggingface/pipelines/deployment_pipelines/inference_pipeline.py). ### Deploying a model The pre-built [huggingface\_model\_deployer\_step](https://github.com/zenml-io/zenml/blob/main/src/zenml/integrations/huggingface/steps/huggingface_deployer.py#L35) exposes a [`HuggingFaceServiceConfig`](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-huggingface.html#zenml.integrations.huggingface) that you can use in your pipeline. Here is an example snippet: ```python from zenml import pipeline from zenml.config import DockerSettings from zenml.integrations.constants import HUGGINGFACE from zenml.integrations.huggingface.services import HuggingFaceServiceConfig from zenml.integrations.huggingface.steps import ( huggingface_model_deployer_step, ) docker_settings = DockerSettings( required_integrations=[HUGGINGFACE], ) @pipeline(enable_cache=True, settings={"docker": docker_settings}) def huggingface_deployment_pipeline( model_name: str = "hf", timeout: int = 1200, ): service_config = HuggingFaceServiceConfig(model_name=model_name) # Deployment step huggingface_model_deployer_step( service_config=service_config, timeout=timeout, ) ``` Within the `HuggingFaceServiceConfig` you can configure: * `model_name`: the name of the model in ZenML. * `endpoint_name`: the name of the inference endpoint. We add a prefix `zenml-` and first 8 characters of the service uuid as a suffix to the endpoint name. * `repository`: The repository name in the user’s namespace (`{username}/{model_id}`) or in the organization namespace (`{organization}/{model_id}`) from the Hugging Face hub. * `framework`: The machine learning framework used for the model (e.g. `"custom"`, `"pytorch"` ) * `accelerator`: The hardware accelerator to be used for inference. (e.g. `"cpu"`, `"gpu"`) * `instance_size`: The size of the instance to be used for hosting the model (e.g. `"large"`, `"xxlarge"`) * `instance_type`: Inference Endpoints offers a selection of curated CPU and GPU instances. (e.g. `"c6i"`, `"g5.12xlarge"`) * `region`: The cloud region in which the Inference Endpoint will be created (e.g. `"us-east-1"`, `"eu-west-1"` for `vendor = aws` and `"eastus"` for Microsoft Azure vendor.). * `vendor`: The cloud provider or vendor where the Inference Endpoint will be hosted (e.g. `"aws"`). * `token`: The Hugging Face authentication token. It can be managed through [huggingface settings](https://huggingface.co/settings/tokens). The same token can be passed used while registering the Hugging Face model deployer. * `account_id`: (Optional) The account ID used to link a VPC to a private Inference Endpoint (if applicable). * `min_replica`: (Optional) The minimum number of replicas (instances) to keep running for the Inference Endpoint. Defaults to `0`. * `max_replica`: (Optional) The maximum number of replicas (instances) to scale to for the Inference Endpoint. Defaults to `1`. * `revision`: (Optional) The specific model revision to deploy on the Inference Endpoint for the Hugging Face repository . * `task`: Select a supported [Machine Learning Task](https://huggingface.co/docs/inference-endpoints/supported\_tasks). (e.g. `"text-classification"`, `"text-generation"`) * `custom_image`: (Optional) A custom Docker image to use for the Inference Endpoint. * `namespace`: The namespace where the Inference Endpoint will be created. The same namespace can be passed used while registering the Hugging Face model deployer. * `endpoint_type`: (Optional) The type of the Inference Endpoint, which can be `"protected"`, `"public"` (default) or `"private"`. For more information and a full list of configurable attributes of the Hugging Face Model Deployer, check out the [SDK Docs](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-huggingface.html) and Hugging Face endpoint [code](https://github.com/huggingface/huggingface_hub/blob/5e3b603ccc7cd6523d998e75f82848215abf9415/src/huggingface_hub/hf_api.py#L6957). ### Running inference on a provisioned inference endpoint The following code example shows how to run inference against a provisioned inference endpoint: ``` /* Detailed source-code truncated for AI context efficiency. */ ``` For more information and a full list of configurable attributes of the Hugging Face Model Deployer, check out the [SDK Docs](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-huggingface.html#zenml.integrations.huggingface).
ZenML Scarf
--- ### Book/Component Guide/Model Deployers/Mlflow (docs/book/component-guide/model-deployers/mlflow.md) --- description: Deploying your models locally with MLflow. --- # MLflow The MLflow Model Deployer is one of the available flavors of the [Model Deployer](./) stack component. Provided with the MLflow integration it can be used to deploy and manage [MLflow models](https://www.mlflow.org/docs/latest/python_api/mlflow.deployments.html) on a local running MLflow server. {% hint style="warning" %} The MLflow Model Deployer is only meant for deploying models in development settings, for production model deployment we recommend tools like [BentoML](./bentoml.md) or [Seldon](./seldon.md). {% endhint %} ## When to use it? MLflow is a popular open-source platform for machine learning. It's a great tool for managing the entire lifecycle of your machine learning. One of the most important features of MLflow is the ability to package your model and its dependencies into a single artifact that can be deployed to a variety of deployment targets. You should use the MLflow Model Deployer: * if you want to have an easy way to deploy your models locally and perform real-time predictions using the running MLflow prediction server. * if you are looking to deploy your models in a simple way without the need for a dedicated deployment environment like Kubernetes or advanced infrastructure configuration. If you are looking to deploy your models in a more complex way, you should use one of the other [Model Deployer Flavors](./#model-deployers-flavors) available in ZenML. ## How do you deploy it? The MLflow Model Deployer flavor is provided by the MLflow ZenML integration, so you need to install it on your local machine to be able to deploy your models. You can do this by running the following command: ```bash zenml integration install mlflow -y ``` To register the MLflow model deployer with ZenML you need to run the following command: ```bash zenml model-deployer register mlflow_deployer --flavor=mlflow ``` The ZenML integration will provision a local MLflow deployment server as a daemon process that will continue to run in the background to serve the latest MLflow model. ## How do you use it? ### Deploy a logged model Following [MLflow's documentation](https://mlflow.org/docs/latest/deployment/deploy-model-locally.html#deploy-mlflow-model-as-a-local-inference-server), if we want to deploy a model as a local inference server, we need the model to be logged in the MLflow experiment tracker first. Once the model is logged, we can use the model URI either from the artifact path saved with the MLflow run or using model name and version if a model is registered in the MLflow model registry. In the following examples, we will show how to deploy a model using the MLflow Model Deployer, in two different scenarios: 1. We already know the logged model URI and we want to deploy it as a local inference server. ```python from zenml import pipeline, step, get_step_context from zenml.client import Client @step def deploy_model() -> Optional[MLFlowDeploymentService]: # Deploy a model using the MLflow Model Deployer zenml_client = Client() model_deployer = zenml_client.active_stack.model_deployer mlflow_deployment_config = MLFlowDeploymentConfig( name: str = "mlflow-model-deployment-example", description: str = "An example of deploying a model using the MLflow Model Deployer", pipeline_name: str = get_step_context().pipeline_name, pipeline_step_name: str = get_step_context().step_name, model_uri: str = "runs://model" or "models://", model_name: str = "model", workers: int = 1 mlserver: bool = False timeout: int = DEFAULT_SERVICE_START_STOP_TIMEOUT ) service = model_deployer.deploy_model( config=mlflow_deployment_config, service_type=MLFlowDeploymentService.SERVICE_TYPE ) logger.info(f"The deployed service info: {model_deployer.get_model_server_info(service)}") return service ``` 2. We don't know the logged model URI, since the model was logged in a previous step. We want to deploy the model as a local inference server. ZenML provides set of functionalities that would make it easier to get the model URI from the current run and deploy it. ``` /* Detailed source-code truncated for AI context efficiency. */ ``` #### Configuration Within the `MLFlowDeploymentService` you can configure: * `name`: The name of the deployment. * `description`: The description of the deployment. * `pipeline_name`: The name of the pipeline that deployed the MLflow prediction server. * `pipeline_step_name`: The name of the step that deployed the MLflow prediction server. * `model_name`: The name of the model that is deployed in case of model registry the name must be a valid registered model name. * `model_version`: The version of the model that is deployed in case of model registry the version must be a valid registered model version. * `silent_daemon`: set to True to suppress the output of the daemon (i.e., redirect stdout and stderr to /dev/null). If False, the daemon output will be redirected to a log file. * `blocking`: set to True to run the service in the context of the current process and block until the service is stopped instead of running the service as a daemon process. Useful for operating systems that do not support daemon processes. * `model_uri`: The URI of the model to be deployed. This can be a local file path, a run ID, or a model name and version. * `workers`: The number of workers to be used by the MLflow prediction server. * `mlserver`: If True, the MLflow prediction server will be started as a MLServer instance. The MLServer backend is deprecated by MLflow and is ignored for MLflow 3.13.0 or newer, where ZenML falls back to the built-in MLflow scoring server. * `timeout`: The timeout in seconds to wait for the MLflow prediction server to start or stop. ### Run inference on a deployed model The following code example shows how you can load a deployed model in Python and run inference against it: 1. Load a prediction service deployed in another pipeline ``` /* Detailed source-code truncated for AI context efficiency. */ ``` 2. Within the same pipeline, use the service from previous step to run inference this time using pre-built predict method ```python from typing import Annotated import numpy as np from zenml import step from zenml.integrations.mlflow.services import MLFlowDeploymentService # Use the service for inference @step def predictor( service: MLFlowDeploymentService, data: np.ndarray, ) -> Annotated[np.ndarray, "predictions"]: """Run a inference request against a prediction service""" prediction = service.predict(data) prediction = prediction.argmax(axis=-1) return prediction ``` For more information and a full list of configurable attributes of the MLflow Model Deployer, check out the [SDK Docs](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-mlflow.html#zenml.integrations.mlflow) .
ZenML Scarf
--- ### Book/Component Guide/Model Deployers/Seldon (docs/book/component-guide/model-deployers/seldon.md) --- description: Deploying models to Kubernetes with Seldon Core. --- # Seldon [Seldon Core](https://github.com/SeldonIO/seldon-core) is a production grade source-available model serving platform. It packs a wide range of features built around deploying models to REST/GRPC microservices that include monitoring and logging, model explainers, outlier detectors and various continuous deployment strategies such as A/B testing, canary deployments and more. Seldon Core also comes equipped with a set of built-in model server implementations designed to work with standard formats for packaging ML models that greatly simplify the process of serving models for real-time inference. {% hint style="warning" %} The Seldon Core model deployer integration is currently not supported under **MacOS**. {% endhint %} ## When to use it? [Seldon Core](https://github.com/SeldonIO/seldon-core) is a production-grade source-available model serving platform. It packs a wide range of features built around deploying models to REST/GRPC microservices that include monitoring and logging, model explainers, outlier detectors, and various continuous deployment strategies such as A/B testing, canary deployments, and more. Seldon Core also comes equipped with a set of built-in model server implementations designed to work with standard formats for packaging ML models that greatly simplify the process of serving models for real-time inference. You should use the Seldon Core Model Deployer: * If you are looking to deploy your model on a more advanced infrastructure like Kubernetes. * If you want to handle the lifecycle of the deployed model with no downtime, including updating the runtime graph, scaling, monitoring, and security. * Looking for more advanced API endpoints to interact with the deployed model, including REST and GRPC endpoints. * If you want more advanced deployment strategies like A/B testing, canary deployments, and more. * if you have a need for a more complex deployment process that can be customized by the advanced inference graph that includes custom [TRANSFORMER](https://docs.seldon.ai/seldon-core-2/installation/advanced-configurations/pipeline) and [ROUTER](https://docs.seldon.ai/seldon-core-2/about/concepts). If you are looking for a more easy way to deploy your models locally, you can use the [MLflow Model Deployer](mlflow.md) flavor. ## How to deploy it? ZenML provides a Seldon Core flavor build on top of the Seldon Core Integration to allow you to deploy and use your models in a production-grade environment. In order to use the integration you need to install it on your local machine to be able to register a Seldon Core Model deployer with ZenML and add it to your stack: ```bash zenml integration install seldon -y ``` To deploy and make use of the Seldon Core integration we need to have the following prerequisites: 1. access to a Kubernetes cluster. This can be configured using the `kubernetes_context` configuration attribute to point to a local `kubectl` context or an in-cluster configuration, but the recommended approach is to [use a Service Connector](seldon.md#using-a-service-connector) to link the Seldon Deployer Stack Component to a Kubernetes cluster. 2. Seldon Core needs to be preinstalled and running in the target Kubernetes cluster. Check out the [official Seldon Core installation instructions](https://github.com/SeldonIO/seldon-core/tree/master/examples/auth#demo-setup) or the [EKS installation example below](seldon.md#installing-seldon-core-eg-in-an-eks-cluster). 3. models deployed with Seldon Core need to be stored in some form of persistent shared storage that is accessible from the Kubernetes cluster where Seldon Core is installed (e.g. AWS S3, GCS, Azure Blob Storage, etc.). You can use one of the supported [remote artifact store flavors](https://docs.zenml.io/stacks/artifact-stores/) to store your models as part of your stack. For a smoother experience running Seldon Core with a cloud artifact store, we also recommend configuring explicit credentials for the artifact store. The Seldon Core model deployer knows how to automatically convert those credentials in the format needed by Seldon Core model servers to authenticate to the storage back-end where models are stored. Since the Seldon Model Deployer is interacting with the Seldon Core model server deployed on a Kubernetes cluster, you need to provide a set of configuration parameters. These parameters are: * kubernetes\_context: the Kubernetes context to use to contact the remote Seldon Core installation. If not specified, the active Kubernetes context is used or the in-cluster configuration is used if the model deployer is running in a Kubernetes cluster. The recommended approach is to [use a Service Connector](seldon.md#using-a-service-connector) to link the Seldon Deployer Stack Component to a Kubernetes cluster and to skip this parameter. * kubernetes\_namespace: the Kubernetes namespace where the Seldon Core deployment servers are provisioned and managed by ZenML. If not specified, the namespace set in the current configuration is used. * base\_url: the base URL of the Kubernetes ingress used to expose the Seldon Core deployment servers. In addition to these parameters, the Seldon Core Model Deployer may also require additional configuration to be set up to allow it to authenticate to the remote artifact store or persistent storage service where model artifacts are located. This is covered in the [Managing Seldon Core Authentication](seldon.md#managing-seldon-core-authentication) section. ### Seldon Core Installation Example The following example briefly shows how you can install Seldon in an EKS Kubernetes cluster. It assumes that the EKS cluster itself is already set up and configured with IAM access. For more information or tutorials for other clouds, check out the [official Seldon Core installation instructions](https://github.com/SeldonIO/seldon-core/tree/master/examples/auth#demo-setup). 1. Configure EKS cluster access locally, e.g: ```bash aws eks --region us-east-1 update-kubeconfig --name zenml-cluster --alias zenml-eks ``` 2. Install Istio 1.5.0 (required for the latest Seldon Core version): ```bash curl -L [https://istio.io/downloadIstio](https://istio.io/downloadIstio) | ISTIO_VERSION=1.5.0 sh - cd istio-1.5.0/ bin/istioctl manifest apply --set profile=demo ``` 3. Set up an Istio gateway for Seldon Core: ```bash curl https://raw.githubusercontent.com/SeldonIO/seldon-core/master/notebooks/resources/seldon-gateway.yaml | kubectl apply -f - ``` 4. Install Seldon Core: ```bash helm install seldon-core seldon-core-operator \ --repo https://storage.googleapis.com/seldon-charts \ --set usageMetrics.enabled=true \ --set istio.enabled=true \ --namespace seldon-system ``` 5. Test that the installation is functional ```bash kubectl apply -f iris.yaml ``` with `iris.yaml` defined as follows: ```yaml apiVersion: machinelearning.seldon.io/v1 kind: SeldonDeployment metadata: name: iris-model namespace: default spec: name: iris predictors: - graph: implementation: SKLEARN_SERVER modelUri: gs://seldon-models/v1.14.0-dev/sklearn/iris name: classifier name: default replicas: 1 ``` Then extract the URL where the model server exposes its prediction API: ```bash export INGRESS_HOST=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].hostname}') ``` And use curl to send a test prediction API request to the server: ```bash curl -X POST http://$INGRESS_HOST/seldon/default/iris-model/api/v1.0/predictions \ -H 'Content-Type: application/json' \ -d '{ "data": { "ndarray": [[1,2,3,4]] } }' ``` ### Using a Service Connector To set up the Seldon Core Model Deployer to authenticate to a remote Kubernetes cluster, it is recommended to leverage the many features provided by [the Service Connectors](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/) such as auto-configuration, local client login, best security practices regarding long-lived credentials and fine-grained access control and reusing the same credentials across multiple stack components. Depending on where your target Kubernetes cluster is running, you can use one of the following Service Connectors: * [the AWS Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/aws-service-connector), if you are using an AWS EKS cluster. * [the GCP Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/gcp-service-connector), if you are using a GKE cluster. * [the Azure Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/azure-service-connector), if you are using an AKS cluster. * [the generic Kubernetes Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/kubernetes-service-connector) for any other Kubernetes cluster. If you don't already have a Service Connector configured in your ZenML deployment, you can register one using the interactive CLI command. You have the option to configure a Service Connector that can be used to access more than one Kubernetes cluster or even more than one type of cloud resource: ```sh zenml service-connector register -i ``` A non-interactive CLI example that leverages [the AWS CLI configuration](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) on your local machine to auto-configure an AWS Service Connector targeting a single EKS cluster is: ```sh zenml service-connector register --type aws --resource-type kubernetes-cluster --resource-name --auto-configure ``` {% code title="Example Command Output" %} ``` $ zenml service-connector register eks-zenhacks --type aws --resource-type kubernetes-cluster --resource-id zenhacks-cluster --auto-configure ⠼ Registering service connector 'eks-zenhacks'... Successfully registered service connector `eks-zenhacks` with access to the following resources: ┏━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━┓ ┃ RESOURCE TYPE │ RESOURCE NAMES ┃ ┠───────────────────────┼──────────────────┨ ┃ 🌀 kubernetes-cluster │ zenhacks-cluster ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━┛ ``` {% endcode %} Alternatively, you can configure a Service Connector through the ZenML dashboard: > **Note**: Please remember to grant the entity associated with your cloud credentials permissions to access the Kubernetes cluster and to list accessible Kubernetes clusters. For a full list of permissions required to use a AWS Service Connector to access one or more Kubernetes cluster, please refer to the [documentation for your Service Connector of choice](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/) or read the documentation available in the interactive CLI commands and dashboard. The Service Connectors supports many different authentication methods with different levels of security and convenience. You should pick the one that best fits your use-case. If you already have one or more Service Connectors configured in your ZenML deployment, you can check which of them can be used to access the Kubernetes cluster that you want to use for your Seldon Core Model Deployer by running e.g.: ```sh zenml service-connector list-resources --resource-type kubernetes-cluster ``` {% code title="Example Command Output" %} ``` The following 'kubernetes-cluster' resources can be accessed by service connectors that you have configured: ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃ CONNECTOR ID │ CONNECTOR NAME │ CONNECTOR TYPE │ RESOURCE TYPE │ RESOURCE NAMES ┃ ┠──────────────────────────────────────┼────────────────┼────────────────┼───────────────────────┼───────────────────────────────────────────────┨ ┃ bdf1dc76-e36b-4ab4-b5a6-5a9afea4822f │ eks-zenhacks │ 🔶 aws │ 🌀 kubernetes-cluster │ zenhacks-cluster ┃ ┠──────────────────────────────────────┼────────────────┼────────────────┼───────────────────────┼───────────────────────────────────────────────┨ ┃ b57f5f5c-0378-434c-8d50-34b492486f30 │ gcp-multi │ 🔵 gcp │ 🌀 kubernetes-cluster │ zenml-test-cluster ┃ ┠──────────────────────────────────────┼────────────────┼────────────────┼───────────────────────┼───────────────────────────────────────────────┨ ┃ d6fc6004-eb76-4fd7-8fa1-ec600cced680 │ azure-multi │ 🇦 azure │ 🌀 kubernetes-cluster │ demo-zenml-demos/demo-zenml-terraform-cluster ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ``` {% endcode %} After having set up or decided on a Service Connector to use to connect to the target Kubernetes cluster where Seldon Core is installed, you can register the Seldon Core Model Deployer as follows: ```sh # Register the Seldon Core Model Deployer zenml model-deployer register --flavor=seldon \ --kubernetes_namespace= \ --base_url=http://$INGRESS_HOST # Connect the Seldon Core Model Deployer to the target cluster via a Service Connector zenml model-deployer connect -i ``` A non-interactive version that connects the Seldon Core Model Deployer to a target Kubernetes cluster through a Service Connector: ```sh zenml model-deployer connect --connector --resource-id ``` {% code title="Example Command Output" %} ``` $ zenml model-deployer connect seldon-test --connector gcp-multi --resource-id zenml-test-cluster Successfully connected model deployer `seldon-test` to the following resources: ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━┓ ┃ CONNECTOR ID │ CONNECTOR NAME │ CONNECTOR TYPE │ RESOURCE TYPE │ RESOURCE NAMES ┃ ┠──────────────────────────────────────┼────────────────┼────────────────┼───────────────────────┼────────────────────┨ ┃ b57f5f5c-0378-434c-8d50-34b492486f30 │ gcp-multi │ 🔵 gcp │ 🌀 kubernetes-cluster │ zenml-test-cluster ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━┛ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┛ ``` {% endcode %} A similar experience is available when you configure the Seldon Core Model Deployer through the ZenML dashboard: ### Managing Seldon Core Authentication The Seldon Core Model Deployer requires access to the persistent storage where models are located. In most cases, you will use the Seldon Core model deployer to serve models that are trained through ZenML pipelines and stored in the ZenML Artifact Store, which implies that the Seldon Core model deployer needs to access the Artifact Store. If Seldon Core is already running in the same cloud as the Artifact Store (e.g. S3 and an EKS cluster for AWS, or GCS and a GKE cluster for GCP), there are ways of configuring cloud workloads to have implicit access to other cloud resources like persistent storage without requiring explicit credentials. However, if Seldon Core is running in a different cloud, or on-prem, or if implicit in-cloud workload authentication is not enabled, then you need to configure explicit credentials for the Artifact Store to allow other components like the Seldon Core model deployer to authenticate to it. Every cloud Artifact Store flavor supports some way of configuring explicit credentials and this is documented for each individual flavor in the [Artifact Store documentation](https://docs.zenml.io/stacks/artifact-stores/). When explicit credentials are configured in the Artifact Store, the Seldon Core Model Deployer doesn't need any additional configuration and will use those credentials automatically to authenticate to the same persistent storage service used by the Artifact Store. If the Artifact Store doesn't have explicit credentials configured, then Seldon Core will default to using whatever implicit authentication method is available in the Kubernetes cluster where it is running. For example, in AWS this means using the IAM role attached to the EC2 or EKS worker nodes, and in GCP this means using the service account attached to the GKE worker nodes. {% hint style="warning" %} If the Artifact Store used in combination with the Seldon Core Model Deployer in the same ZenML stack does not have explicit credentials configured, then the Seldon Core Model Deployer might not be able to authenticate to the Artifact Store which will cause the deployed model servers to fail. To avoid this, we recommend that you use Artifact Stores with explicit credentials in the same stack as the Seldon Core Model Deployer. Alternatively, if you're running Seldon Core in one of the cloud providers, you should configure implicit authentication for the Kubernetes nodes. {% endhint %} If you want to use a custom persistent storage with Seldon Core, or if you prefer to manually manage the authentication credentials attached to the Seldon Core model servers, you can use the approach described in the next section. **Advanced: Configuring a Custom Seldon Core Secret** The Seldon Core model deployer stack component allows configuring an additional `secret` attribute that can be used to specify custom credentials that Seldon Core should use to authenticate to the persistent storage service where models are located. This is useful if you want to connect Seldon Core to a persistent storage service that is not supported as a ZenML Artifact Store, or if you don't want to configure or use the same credentials configured for your Artifact Store. The `secret` attribute must be set to the name of [a ZenML secret](https://docs.zenml.io/how-to/project-setup-and-management/interact-with-secrets) containing credentials configured in the format supported by Seldon Core. {% hint style="info" %} This method is not recommended, because it limits the Seldon Core model deployer to a single persistent storage service, whereas using the Artifact Store credentials gives you more flexibility in combining the Seldon Core model deployer with any Artifact Store in the same ZenML stack. {% endhint %} Seldon Core model servers use [`rclone`](https://rclone.org/) to connect to persistent storage services and the credentials that can be configured in the ZenML secret must also be in the configuration format supported by `rclone`. This section covers a few common use cases and provides examples of how to configure the ZenML secret to support them, but for more information on supported configuration options, you can always refer to the [`rclone` documentation for various providers](https://rclone.org/).
Seldon Core Authentication Secret Examples Example of configuring a Seldon Core secret for AWS S3: ```shell zenml secret create s3-seldon-secret \ --rclone_config_s3_type="s3" \ # set to 's3' for S3 storage. --rclone_config_s3_provider="aws" \ # the S3 provider (e.g. aws, Ceph, Minio). --rclone_config_s3_env_auth=False \ # set to true to use implicit AWS authentication from EC2/ECS meta data # (i.e. with IAM roles configuration). Only applies if access_key_id and secret_access_key are blank. --rclone_config_s3_access_key_id="" \ # AWS Access Key ID. --rclone_config_s3_secret_access_key="" \ # AWS Secret Access Key. --rclone_config_s3_session_token="" \ # AWS Session Token. --rclone_config_s3_region="" \ # region to connect to. --rclone_config_s3_endpoint="" \ # S3 API endpoint. # Alternatively for providing key-value pairs, you can utilize the '--values' option by specifying a file path containing # key-value pairs in either JSON or YAML format. # File content example: {"rclone_config_s3_type":"s3",...} zenml secret create s3-seldon-secret \ --values=@path/to/file.json ``` Example of configuring a Seldon Core secret for GCS: ```shell zenml secret create gs-seldon-secret \ --rclone_config_gs_type="google cloud storage" \ # set to 'google cloud storage' for GCS storage. --rclone_config_gs_client_secret="" \ # OAuth client secret. --rclone_config_gs_token="" \ # OAuth Access Token as a JSON blob. --rclone_config_gs_project_number="" \ # project number. --rclone_config_gs_service_account_credentials="" \ #service account credentials JSON blob. --rclone_config_gs_anonymous=False \ # Access public buckets and objects without credentials. # Set to True if you just want to download files and don't configure credentials. --rclone_config_gs_auth_url="" \ # auth server URL. # Alternatively for providing key-value pairs, you can utilize the '--values' option by specifying a file path containing # key-value pairs in either JSON or YAML format. # File content example: {"rclone_config_gs_type":"google cloud storage",...} zenml secret create gs-seldon-secret \ --values=@path/to/file.json ``` Example of configuring a Seldon Core secret for Azure Blob Storage: ```shell zenml secret create az-seldon-secret \ --rclone_config_az_type="azureblob" \ # set to 'azureblob' for Azure Blob Storage. --rclone_config_az_account="" \ # storage Account Name. Leave blank to # use SAS URL or MSI. --rclone_config_az_key="" \ # storage Account Key. Leave blank to # use SAS URL or MSI. --rclone_config_az_sas_url="" \ # SAS URL for container level access # only. Leave blank if using account/key or MSI. --rclone_config_az_use_msi="" \ # use a managed service identity to # authenticate (only works in Azure). --rclone_config_az_client_id="" \ # client ID of the service principal # to use for authentication. --rclone_config_az_client_secret="" \ # client secret of the service # principal to use for authentication. --rclone_config_az_tenant="" \ # tenant ID of the service principal # to use for authentication. # Alternatively for providing key-value pairs, you can utilize the '--values' option by specifying a file path containing # key-value pairs in either JSON or YAML format. # File content example: {"rclone_config_az_type":"azureblob",...} zenml secret create az-seldon-secret \ --values=@path/to/file.json ```
## How do you use it? ### Requirements To run pipelines that deploy models to Seldon, you need the following tools installed locally: * [Docker](https://www.docker.com) * [K3D](https://k3d.io/v5.2.1/#installation) (can be installed by running `curl -s https://raw.githubusercontent.com/rancher/k3d/main/install.sh | bash`). ### Stack Component Registration For registering the model deployer, we need the URL of the Istio Ingress Gateway deployed on the Kubernetes cluster. We can get this URL by running the following command (assuming that the service name is `istio-ingressgateway`, deployed in the `istio-system` namespace): ```bash # For GKE clusters, the host is the GKE cluster IP address. export INGRESS_HOST=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}') # For EKS clusters, the host is the EKS cluster IP hostname. export INGRESS_HOST=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].hostname}') ``` Now register the model deployer: > **Note**: If you chose to configure your own custom credentials to authenticate to the persistent storage service where models are stored, as covered in the [Advanced: Configuring a Custom Seldon Core Secret](seldon.md#managing-seldon-core-authentication) section, you will need to specify a ZenML secret reference when you configure the Seldon Core model deployer below: > > ```shell > zenml model-deployer register seldon_deployer --flavor=seldon \ > --kubernetes_context= \ > --kubernetes_namespace= \ > --base_url=http://$INGRESS_HOST \ > --secret= > ``` ```bash # Register the Seldon Core Model Deployer zenml model-deployer register seldon_deployer --flavor=seldon \ --kubernetes_context= \ --kubernetes_namespace= \ --base_url=http://$INGRESS_HOST \ ``` We can now use the model deployer in our stack. ```bash zenml stack update seldon_stack --model-deployer=seldon_deployer ``` See the [seldon\_model\_deployer\_step](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-seldon.html#zenml.integrations.seldon) for an example of using the Seldon Core Model Deployer to deploy a model inside a ZenML pipeline step. ### Configuration Within the `SeldonDeploymentConfig` you can configure: * `model_name`: the name of the model in the Seldon cluster and in ZenML. * `replicas`: the number of replicas with which to deploy the model * `implementation`: the type of Seldon inference server to use for the model. The implementation type can be one of the following: `TENSORFLOW_SERVER`, `SKLEARN_SERVER`, `XGBOOST_SERVER`, `custom`. * `parameters`: an optional list of parameters (`SeldonDeploymentPredictorParameter`) to pass to the deployment predictor in the form of: * `name` * `type` * `value` * `resources`: the resources to be allocated to the model. This can be configured by passing a `SeldonResourceRequirements` object with the `requests` and `limits` properties. The values for these properties can be a dictionary with the `cpu` and `memory` keys. The values for these keys can be a string with the amount of CPU and memory to be allocated to the model. * `serviceAccount` The name of the Service Account applied to the deployment. For more information and a full list of configurable attributes of the Seldon Core Model Deployer, check out the [SDK Docs](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-seldon.html#zenml.integrations.seldon) . ### Custom Code Deployment ZenML enables you to deploy your pre- and post-processing code into the deployment environment together with the model by defining a custom predict function that will be wrapped in a Docker container and executed on the model deployment server, e.g.: ```python def custom_predict( model: Any, request: Array_Like, ) -> Array_Like: """Custom Prediction function. The custom predict function is the core of the custom deployment, the function is called by the custom deployment class defined for the serving tool. The current implementation requires the function to get the model loaded in the memory and a request with the data to predict. Args: model: The model to use for prediction. request: The prediction response of the model is an array-like format. Returns: The prediction in an array-like format. """ inputs = [] for instance in request: input = np.array(instance) if not isinstance(input, np.ndarray): raise Exception("The request must be a NumPy array") processed_input = pre_process(input) prediction = model.predict(processed_input) postprocessed_prediction = post_process(prediction) inputs.append(postprocessed_prediction) return inputs def pre_process(input: np.ndarray) -> np.ndarray: """Pre process the data to be used for prediction.""" input = input / 255.0 return input[None, :, :] def post_process(prediction: np.ndarray) -> str: """Pre process the data""" classes = [str(i) for i in range(10)] prediction = tf.nn.softmax(prediction, axis=-1) maxindex = np.argmax(prediction.numpy()) return classes[maxindex] ``` {% hint style="info" %} The custom predict function should get the model and the input data as arguments and return the model predictions. ZenML will automatically take care of loading the model into memory and starting the `seldon-core-microservice` that will be responsible for serving the model and running the predict function. {% endhint %} After defining your custom predict function in code, you can use the `seldon_custom_model_deployer_step` to automatically build your function into a Docker image and deploy it as a model server by setting the `predict_function` argument to the path of your `custom_predict` function: ```python from zenml.integrations.seldon.steps import seldon_custom_model_deployer_step from zenml.integrations.seldon.services import SeldonDeploymentConfig from zenml import pipeline @pipeline def seldon_deployment_pipeline(): model = ... seldon_custom_model_deployer_step( model=model, predict_function="", # TODO: path to custom code service_config=SeldonDeploymentConfig( model_name="", # TODO: name of the deployed model replicas=1, implementation="custom", resources=SeldonResourceRequirements( limits={"cpu": "200m", "memory": "250Mi"} ), serviceAccountName="kubernetes-service-account", ), ) ``` #### Advanced Custom Code Deployment with Seldon Core Integration {% hint style="warning" %} Before creating your custom model class, you should take a look at the [custom Python model](https://docs.seldon.ai/seldon-core-2/about/concepts) section of the Seldon Core documentation. {% endhint %} The built-in Seldon Core custom deployment step is a good starting point for deploying your custom models. However, if you want to deploy more than the trained model, you can create your own custom class and a custom step to achieve this. See the [ZenML custom Seldon model class](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-seldon.html#zenml.integrations.seldon) as a reference.
ZenML Scarf
--- ### Book/Component Guide/Model Deployers/Vllm (docs/book/component-guide/model-deployers/vllm.md) --- description: Deploying your LLM locally with vLLM. --- # vLLM [vLLM](https://docs.vllm.ai/en/latest/) is a fast and easy-to-use library for LLM inference and serving. ## When to use it? You should use vLLM Model Deployer: * Deploying Large Language models with state-of-the-art serving throughput creating an OpenAI-compatible API server * Continuous batching of incoming requests * Quantization: GPTQ, AWQ, INT4, INT8, and FP8 * Features such as PagedAttention, Speculative decoding, Chunked pre-fill ## How do you deploy it? The vLLM Model Deployer flavor is provided by the vLLM ZenML integration, so you need to install it on your local machine to be able to deploy your models. You can do this by running the following command: ```bash zenml integration install vllm -y ``` To register the vLLM model deployer with ZenML you need to run the following command: ```bash zenml model-deployer register vllm_deployer --flavor=vllm ``` The ZenML integration will provision a local vLLM deployment server as a daemon process that will continue to run in the background to serve the latest vLLM model. ## How do you use it? If you'd like to see this in action, check out this example of a [deployment pipeline](https://github.com/zenml-io/zenml-projects/blob/79f67ea52c3908b9b33c9a41eef18cb7d72362e8/llm-vllm-deployer/pipelines/deploy_pipeline.py#L25). ### Deploy an LLM The [vllm_model_deployer_step](https://github.com/zenml-io/zenml-projects/blob/79f67ea52c3908b9b33c9a41eef18cb7d72362e8/llm-vllm-deployer/steps/vllm_deployer.py#L32) exposes a `VLLMDeploymentService` that you can use in your pipeline. Here is an example snippet: ```python from zenml import pipeline from typing import Annotated from steps.vllm_deployer import vllm_model_deployer_step from zenml.integrations.vllm.services.vllm_deployment import VLLMDeploymentService @pipeline() def deploy_vllm_pipeline( model: str, timeout: int = 1200, ) -> Annotated[VLLMDeploymentService, "GPT2"]: service = vllm_model_deployer_step( model=model, timeout=timeout, ) return service ``` Here is an [example](https://github.com/zenml-io/zenml-projects/tree/79f67ea52c3908b9b33c9a41eef18cb7d72362e8/llm-vllm-deployer) of running a GPT-2 model using vLLM. #### Configuration Within the `VLLMDeploymentService` you can configure: * `model`: Name or path of the Hugging Face model to use. * `tokenizer`: Name or path of the Hugging Face tokenizer to use. If unspecified, model name or path will be used. * `served_model_name`: The model name(s) used in the API. If not specified, the model name will be the same as the `model` argument. * `trust_remote_code`: Trust remote code from Hugging Face. * `tokenizer_mode`: The tokenizer mode. Allowed choices: ['auto', 'slow', 'mistral'] * `dtype`: Data type for model weights and activations. Allowed choices: ['auto', 'half', 'float16', 'bfloat16', 'float', 'float32'] * `revision`: The specific model version to use. It can be a branch name, a tag name, or a commit id. If unspecified, will use the default version.
ZenML Scarf
--- ### Book/Component Guide/Model Registries/README (docs/book/component-guide/model-registries/README.md) --- icon: table-rows description: Tracking and managing ML models. --- # Model Registries Model registries are centralized storage solutions for managing and tracking machine learning models across various stages of development and deployment. They help track the different versions and configurations of each model and enable reproducibility. By storing metadata such as version, configuration, and metrics, model registries help streamline the management of trained models. In ZenML, model registries are Stack Components that allow for the easy retrieval, loading, and deployment of trained models. They also provide information on the pipeline in which the model was trained and how to reproduce it. ### Model Registry Concepts and Terminology ZenML provides a unified abstraction for model registries through which it is possible to handle and manage the concepts of model groups, versions, and stages in a consistent manner regardless of the underlying registry tool or platform being used. The following concepts are useful to be aware of for this abstraction: * **RegisteredModel**: A logical grouping of models that can be used to track different versions of a model. It holds information about the model, such as its name, description, and tags, and can be created by the user or automatically created by the model registry when a new model is logged. * **RegistryModelVersion**: A specific version of a model identified by a unique version number or string. It holds information about the model, such as its name, description, tags, and metrics, and a reference to the model artifact logged to the model registry. In ZenML, it also holds a reference to the pipeline name, pipeline run ID, and step name. Each model version is associated with a model registration. * **ModelVersionStage**: A model version stage is a state in that a model version can be. It can be one of the following: `None`, `Staging`, `Production`, `Archived`. The model version stage is used to track the lifecycle of a model version. For example, a model version can be in the `Staging` stage while it is being tested and then moved to the `Production` stage once it is ready for deployment. ### When to use it ZenML provides a built-in mechanism for storing and versioning pipeline artifacts through its mandatory Artifact Store. While this is a powerful way to manage artifacts programmatically, it can be challenging to use without a visual interface. Model registries, on the other hand, offer a visual way to manage and track model metadata, particularly when using a remote orchestrator. They make it easy to retrieve and load models from storage, thanks to built-in integrations. A model registry is an excellent choice for interacting with all the models in your pipeline and managing their state in a centralized way. Using a model registry in your stack is particularly useful if you want to interact with all the logged models in your pipeline, or if you need to manage the state of your models in a centralized way and make it easy to retrieve, load, and deploy these models. ### How model registries fit into the ZenML stack Here is an architecture diagram that shows how a model registry fits into the overall story of a remote stack. #### Model Registry Flavors Model Registries are optional stack components provided by integrations: | Model Registry | Flavor | Integration | Notes | | ---------------------------------- | -------- | ----------- | ------------------------------------------ | | [MLflow](mlflow.md) | `mlflow` | `mlflow` | Add MLflow as Model Registry to your stack | | [Custom Implementation](custom.md) | _custom_ | | _custom_ | If you would like to see the available flavors of Model Registry, you can use the command: ```shell zenml model-registry flavor list ``` ### How to use it Model registries are an optional component in the ZenML stack that is tied to the experiment tracker. This means that a model registry can only be used if you are also using an experiment tracker. If you're not using an experiment tracker, you can still store your models in ZenML, but you will need to manually retrieve model artifacts from the artifact store. More information on this can be found in the [documentation on the fetching runs](https://docs.zenml.io/concepts/steps_and_pipelines/). To use model registries, you first need to register a model registry in your stack with the same flavor as your experiment tracker. Then, you can register your trained model in the model registry using one of three methods: * (1) using the built-in step in the pipeline. * (2) using the ZenML CLI to register the model from the command line. * (3) registering the model from the model registry UI. Finally, you can use the model registry to retrieve and load your models for deployment or further experimentation.
ZenML Scarf
--- ### Book/Component Guide/Model Registries/Custom (docs/book/component-guide/model-registries/custom.md) --- description: Learning how to develop a custom model registry. --- # Develop a Custom Model Registry {% hint style="info" %} Before diving into the specifics of this component type, it is beneficial to familiarize yourself with our [general guide to writing custom component flavors in ZenML](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/implement-a-custom-stack-component). This guide provides an essential understanding of ZenML's component flavor concepts. {% endhint %} {% hint style="warning" %} **Base abstraction in progress!** The Model registry stack component is relatively new in ZenML. While it is fully functional, it can be challenging to cover all the ways ML systems deal with model versioning. This means that the API might change in the future. We will keep this page up-to-date with the latest changes. If you are writing a custom model registry flavor, and you found that the base abstraction is lacking or not flexible enough, please let us know by messaging us on [Slack](https://zenml.io/slack), or by opening an issue on [GitHub](https://github.com/zenml-io/zenml/issues/new/choose) {% endhint %} ### Base Abstraction The `BaseModelRegistry` is the abstract base class that needs to be subclassed in order to create a custom component that can be used to register and retrieve models. As model registries can come in many shapes and forms, the base class exposes a deliberately basic and generic interface: ``` /* Detailed source-code truncated for AI context efficiency. */ ``` {% hint style="info" %} This is a slimmed-down version of the base implementation which aims to highlight the abstraction layer. To see the full implementation and get the complete docstrings, please check [the source code on GitHub](https://github.com/zenml-io/zenml/blob/main/src/zenml/model_registries/base_model_registry.py) . {% endhint %} ### Build your own custom model registry If you want to create your own custom flavor for a model registry, you can follow the following steps: 1. Learn more about the core concepts for the model registry [here](./#model-registry-concepts-and-terminology). Your custom model registry will be built on top of these concepts so it helps to be aware of them. 2. Create a class that inherits from `BaseModelRegistry` and implements the abstract methods. 3. Create a `ModelRegistryConfig` class that inherits from `BaseModelRegistryConfig` and adds any additional configuration parameters that you need. 4. Bring the implementation and the configuration together by inheriting from the `BaseModelRegistryFlavor` class. Make sure that you give a `name` to the flavor through its abstract property. Once you are done with the implementation, you can register it through the CLI with the following command: ```shell zenml model-registry flavor register ``` {% hint style="warning" %} It is important to draw attention to how and when these base abstractions are coming into play in a ZenML workflow. * The **CustomModelRegistryFlavor** class is imported and utilized upon the creation of the custom flavor through the CLI. * The **CustomModelRegistryConfig** class is imported when someone tries to register/update a stack component with this custom flavor. Most of all, during the registration process of the stack component, the config will be used to validate the values given by the user. As `Config` objects are `pydantic` objects under the hood, you can also add your own custom validators here. * The **CustomModelRegistry** only comes into play when the component is ultimately in use. The design behind this interaction lets us separate the configuration of the flavor from its implementation. This way we can register flavors and components even when the major dependencies behind their implementation are not installed in our local setting (assuming the `CustomModelRegistryFlavor` and the `CustomModelRegistryConfig` are implemented in a different module/path than the actual `CustomModelRegistry`). {% endhint %} For a full implementation example, please check out the [MLFlowModelRegistry](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-mlflow.html#zenml.integrations.mlflow)
ZenML Scarf
--- ### Book/Component Guide/Model Registries/Mlflow (docs/book/component-guide/model-registries/mlflow.md) --- description: Managing MLFlow logged models and artifacts --- # MLflow Model Registry [MLflow](https://www.mlflow.org/docs/latest/tracking.html) is a popular tool that helps you track experiments, manage models and even deploy them to different environments. ZenML already provides a [MLflow Experiment Tracker](https://docs.zenml.io/stacks/experiment-trackers/mlflow) that you can use to track your experiments, and an [MLflow Model Deployer](https://docs.zenml.io/stacks/model-deployers/mlflow) that you can use to deploy your models locally. The MLflow model registry uses [the MLflow model registry service](https://mlflow.org/docs/latest/model-registry.html) to manage and track ML models and their artifacts and provides a user interface to browse them: ## When would you want to use it? You can use the MLflow model registry throughout your experimentation, QA, and production phases to manage and track machine learning model versions. It is designed to help teams collaborate on model development and deployment, and keep track of which models are being used in which environments. With the MLflow model registry, you can store and manage models, deploy them to different environments, and track their performance over time. This is particularly useful in the following scenarios: * If you are working on a machine learning project and want to keep track of different model versions as they are developed and deployed. * If you need to deploy machine learning models to different environments and want to keep track of which version is being used in each environment. * If you want to monitor and compare the performance of different model versions over time and make data-driven decisions about which models to use in production. * If you want to simplify the process of deploying models either to a production environment or to a staging environment for testing. ## How do you deploy it? The MLflow Experiment Tracker flavor is provided by the MLflow ZenML integration, so you need to install it on your local machine to be able to register an MLflow model registry component. Note that the MLFlow model registry requires [MLFlow Experiment Tracker](https://docs.zenml.io/stacks/experiment-trackers/mlflow) to be present in the stack. ```shell zenml integration install mlflow -y ``` Once the MLflow integration is installed, you can register an MLflow model registry component in your stack: ```shell zenml model-registry register mlflow_model_registry --flavor=mlflow # Register and set a stack with the new model registry as the active stack zenml stack register custom_stack -r mlflow_model_registry ... --set ``` {% hint style="info" %} The MLFlow model registry will automatically use the same configuration as the MLFlow Experiment Tracker. So if you have a remote MLFlow tracking server configured in your stack, the MLFlow model registry will also use the same configuration. {% endhint %} {% hint style="warning" %} Due to a [critical severity vulnerability](https://github.com/advisories/GHSA-xg73-94fp-g449) found in older versions of MLflow, we recommend using MLflow version 2.2.1 or higher. ZenML supports both MLflow 2.x and 3.x versions. {% endhint %} ## How do you use it? There are different ways to use the MLflow model registry. You can use it in your ZenML pipelines with the built-in step, or you can use the ZenML CLI to register your model manually or call the model registry API within a custom step in your pipeline. The following sections show you how to use the MLflow model registry in your ZenML pipelines and with the ZenML CLI: ### Register models inside a pipeline ZenML provides a predefined `mlflow_model_deployer_step` that you can use to register a model in the MLflow model registry which you have previously logged to MLflow: ```python from zenml import pipeline from zenml.integrations.mlflow.steps.mlflow_registry import ( mlflow_register_model_step, ) @pipeline def mlflow_registry_training_pipeline(): model = ... mlflow_register_model_step( model=model, name="tensorflow-mnist-model", ) ``` {% hint style="warning" %} The `mlflow_register_model_step` expects that the `model` it receives has already been logged to MLflow in a previous step. E.g., for a scikit-learn model, you would need to have used `mlflow.sklearn.autolog()` or `mlflow.sklearn.log_model(model)` in a previous step. See the [MLflow experiment tracker documentation](https://docs.zenml.io/stacks/experiment-trackers/mlflow) for more information on how to log models to MLflow from your ZenML steps. {% endhint %} #### List of available parameters When using the `mlflow_register_model_step`, you can set a variety of parameters for fine-grained control over which information is logged with your model: * `name`: The name of the model. This is a required parameter. * `version`: version: The version of the model. * `trained_model_name`: Name of the model artifact in MLflow. * `model_source_uri`: The path to the model. If not provided, the model will be fetched from the MLflow tracking server via the `trained_model_name`. * `description`: A description of the model version. * `metadata`: A list of metadata to associate with the model version. {% hint style="info" %} The `model_source_uri` parameter is the path to the model within the MLflow tracking server. If you are using ZenML's default local MLflow configuration, the path will be something like `file:///.../mlflow_artifacts/3973eabc151c41e6ab98baeb20c5323b/artifacts/model`. If you are using a remote MLflow tracking server, the path will be something like `s3://.../mlruns/667102566783201219/3973eabc151c41e6ab98baeb20c5323b/artifacts/model`. You can find the path of the model in the MLflow UI. Go to the `Artifacts` tab of the run that produced the model and click on the model. The path will be displayed in the URL. {% endhint %} ### Register models via the CLI Sometimes adding a `mlflow_registry_training_pipeline` step to your pipeline might not be the best option for you, as it will register a model in the MLflow model registry every time you run the pipeline. If you want to register your models manually, you can use the `zenml model-registry models register-version` CLI command instead: ```shell zenml model-registry models register-version Tensorflow-model \ --description="A new version of the tensorflow model with accuracy 98.88%" \ -v 1 \ --model-uri="file:///.../mlflow_artifacts/3973eabc151c41e6ab98baeb20c5323b/artifacts/model" \ -m key1 value1 -m key2 value2 \ --zenml-pipeline-name="mlflow_training_pipeline" \ --zenml-step-name="trainer" ``` ### Deploy a registered model After you have registered a model in the MLflow model registry, you can also easily deploy it as a prediction service. Checkout the [MLflow model deployer documentation](https://docs.zenml.io/stacks/model-deployers/mlflow#deploy-from-model-registry) for more information on how to do that. ### Interact with registered models You can also use the ZenML CLI to interact with registered models and their versions. The `zenml model-registry models list` command will list all registered models in the model registry: ```shell $ zenml model-registry models list ┏━━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━┯━━━━━━━━━━┓ ┃ NAME │ DESCRIPTION │ METADATA ┃ ┠────────────────────────┼─────────────┼──────────┨ ┃ tensorflow-mnist-model │ │ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━┷━━━━━━━━━━┛ ``` To list all versions of a specific model, you can use the `zenml model-registry models list-versions REGISTERED_MODEL_NAME` command: ``` /* Detailed source-code truncated for AI context efficiency. */ ``` For more details on a specific model version, you can use the `zenml model-registry models get-version REGISTERED_MODEL_NAME -v VERSION` command: ``` /* Detailed source-code truncated for AI context efficiency. */ ``` Finally, to delete a registered model or a specific model version, you can use the `zenml model-registry models delete REGISTERED_MODEL_NAME` and `zenml model-registry models delete-version REGISTERED_MODEL_NAME -v VERSION` commands respectively. ### MLflow 3.x and Model Stages {% hint style="info" %} **MLflow 3.x Users:** MLflow 3.x introduced [model aliases](https://mlflow.org/docs/latest/model-registry.html#using-registered-model-aliases) as a more flexible replacement for the traditional model stages (None, Staging, Production, Archived). When using ZenML with MLflow 3.x: * ZenML automatically maps stages to aliases: `Staging` → `staging`, `Production` → `champion`, `Archived` → `archived` * The stage transition API still works for backward compatibility, but uses aliases under the hood * Model aliases are stored in the model version metadata and can be viewed in the MLflow UI * You can use multiple aliases per model version for more flexible deployment workflows For more information, see the [MLflow Model Registry documentation](https://mlflow.org/docs/latest/model-registry.html). {% endhint %} Check out the [SDK docs](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-mlflow.html#zenml.integrations.mlflow) to see more about the interface and implementation.
ZenML Scarf
--- ### Book/Component Guide/Orchestrators/README (docs/book/component-guide/orchestrators/README.md) --- description: Orchestrating the execution of ML pipelines. icon: train-track --- # Orchestrators The orchestrator is an essential component in any MLOps stack as it is responsible for running your machine learning pipelines. To do so, the orchestrator provides an environment that is set up to execute the steps of your pipeline. It also makes sure that the steps of your pipeline only get executed once all their inputs (which are outputs of previous steps of your pipeline) are available. {% hint style="info" %} Many of ZenML's remote orchestrators build [Docker](https://www.docker.com/) images in order to transport and execute your pipeline code. If you want to learn more about how Docker images are built by ZenML, check out [this guide](https://docs.zenml.io/how-to/customize-docker-builds/). {% endhint %} ### When to use it The orchestrator is a mandatory component in the ZenML stack. It is used to store all artifacts produced by pipeline runs, and you are required to configure it in all of your stacks. ### Orchestrator Flavors Out of the box, ZenML comes with a `local` orchestrator already part of the default stack that runs pipelines locally. Additional orchestrators are provided by integrations: | Orchestrator | Flavor | Integration | Notes | | ------------------------------------------- | -------------- | ----------------- | ----------------------------------------------------------------------- | | [LocalOrchestrator](local.md) | `local` | _built-in_ | Runs your pipelines locally. | | [LocalDockerOrchestrator](local-docker.md) | `local_docker` | _built-in_ | Runs your pipelines locally using Docker. | | [KubernetesOrchestrator](kubernetes.md) | `kubernetes` | `kubernetes` | Runs your pipelines in Kubernetes clusters. | | [ModalOrchestrator](modal.md) | `modal` | `modal` | Runs your pipelines in Modal Sandboxes. | | [KubeflowOrchestrator](kubeflow.md) | `kubeflow` | `kubeflow` | Runs your pipelines using Kubeflow. | | [VertexOrchestrator](vertex.md) | `vertex` | `gcp` | Runs your pipelines in Vertex AI. | | [SagemakerOrchestrator](sagemaker.md) | `sagemaker` | `aws` | Runs your pipelines in Sagemaker. | | [AzureMLOrchestrator](azureml.md) | `azureml` | `azure` | Runs your pipelines in AzureML. | | [TektonOrchestrator](tekton.md) | `tekton` | `tekton` | Runs your pipelines using Tekton. | | [AirflowOrchestrator](airflow.md) | `airflow` | `airflow` | Runs your pipelines using Airflow. | | [SkypilotAWSOrchestrator](skypilot-vm.md) | `vm_aws` | `skypilot[aws]` | Runs your pipelines in AWS VMs using SkyPilot | | [SkypilotGCPOrchestrator](skypilot-vm.md) | `vm_gcp` | `skypilot[gcp]` | Runs your pipelines in GCP VMs using SkyPilot | | [SkypilotAzureOrchestrator](skypilot-vm.md) | `vm_azure` | `skypilot[azure]` | Runs your pipelines in Azure VMs using SkyPilot | | [SSHOrchestrator](ssh.md) | `ssh` | `ssh` | Runs your pipelines on a remote host via SSH and Docker Compose. | | [HyperAIOrchestrator](hyperai.md) | `hyperai` | `hyperai` | _Deprecated_ — use the SSH orchestrator. Runs your pipeline in HyperAI.ai instances. | | [Custom Implementation](custom.md) | _custom_ | | Extend the orchestrator abstraction and provide your own implementation | If you would like to see the available flavors of orchestrators, you can use the command: ```shell zenml orchestrator flavor list ``` ### How to use it You don't need to directly interact with any ZenML orchestrator in your code. As long as the orchestrator that you want to use is part of your active [ZenML stack](https://docs.zenml.io/user-guides/production-guide/understand-stacks), using the orchestrator is as simple as executing a Python file that [runs a ZenML pipeline](https://docs.zenml.io/user-guides/starter-guide/starter-project): ```shell python file_that_runs_a_zenml_pipeline.py ``` #### Inspecting Runs in the Orchestrator UI If your orchestrator comes with a separate user interface (for example Kubeflow, Airflow, Vertex), you can get the URL to the orchestrator UI of a specific pipeline run using the following code snippet: ```python from zenml.client import Client pipeline_run = Client().get_pipeline_run("") orchestrator_url = pipeline_run.run_metadata["orchestrator_url"].value ``` #### Specifying per-step resources If your steps require the orchestrator to execute them on specific hardware, you can specify them on your steps as described [here](https://docs.zenml.io/concepts/steps_and_pipelines/configuration). If your orchestrator of choice or the underlying hardware doesn't support this, you can also take a look at [step operators](https://docs.zenml.io/stacks/step-operators/).
ZenML Scarf
--- ### Book/Component Guide/Orchestrators/Airflow (docs/book/component-guide/orchestrators/airflow.md) --- description: Orchestrating your pipelines to run on Airflow. --- # Airflow Orchestrator ZenML pipelines can be executed natively as [Airflow](https://airflow.apache.org/) DAGs. This brings together the power of the Airflow orchestration with the ML-specific benefits of ZenML pipelines. Each ZenML step runs in a separate Docker container which is scheduled and started using Airflow. {% hint style="warning" %} If you're going to use a remote deployment of Airflow, you'll also need a [remote ZenML deployment](https://docs.zenml.io/getting-started/deploying-zenml/). {% endhint %} ### When to use it You should use the Airflow orchestrator if * you're looking for a proven production-grade orchestrator. * you're already using Airflow. * you want to run your pipelines locally. * you're willing to deploy and maintain Airflow. ### How to deploy it The Airflow orchestrator can be used to run pipelines locally as well as remotely. In the local case, no additional setup is necessary. There are many options to use a deployed Airflow server: * Use [the ZenML GCP Terraform module](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/deploy-a-cloud-stack-with-terraform) which includes a [Google Cloud Composer](https://cloud.google.com/composer) component. * Use a managed deployment of Airflow such as [Google Cloud Composer](https://cloud.google.com/composer) , [Amazon MWAA](https://aws.amazon.com/managed-workflows-for-apache-airflow/), or [Astronomer](https://www.astronomer.io/). * Deploy Airflow manually. Check out the official [Airflow docs](https://airflow.apache.org/docs/apache-airflow/stable/production-deployment.html) for more information. If you're not using the ZenML GCP Terraform module to deploy Airflow, there are some additional Python packages that you'll need to install in the Python environment of your Airflow server: * `pydantic~=2.11.1`: The Airflow DAG files that ZenML creates for you require Pydantic to parse and validate configuration files. * `apache-airflow-providers-docker` or `apache-airflow-providers-cncf-kubernetes`, depending on which Airflow operator you'll be using to run your pipeline steps. Check out [this section](airflow.md#using-different-airflow-operators) for more information on supported operators. ### How to use it To use the Airflow orchestrator, we need: * [Docker](https://docs.docker.com/get-docker/) installed and running. * The orchestrator registered and part of our active stack: ```shell zenml orchestrator register \ --flavor=airflow \ --local=True # set this to `False` if using a remote Airflow deployment # Register and activate a stack with the new orchestrator zenml stack register -o ... --set ``` {% tabs %} {% tab title="Local" %} Due to dependency conflicts, we need to install the Python packages to start a local Airflow server in a separate Python environment. ```bash # Create a fresh virtual environment in which we install the Airflow server dependencies python -m venv airflow_server_environment source airflow_server_environment/bin/activate # Install the Airflow server dependencies pip install "apache-airflow==3.0.6" "apache-airflow-providers-docker==4.4.0" "pydantic~=2.11.1" ``` Before starting the local Airflow server, we can set a few environment variables to configure it: * `AIRFLOW_HOME`: This variable defines the location where the Airflow server stores its database and configuration files. The default value is `~/airflow`. * `AIRFLOW__CORE__DAGS_FOLDER`: This variable defines the location where the Airflow server looks for DAG files. The default value is `/dags`. * `AIRFLOW__DAG_PROCESSOR__REFRESH_INTERVAL`: This variable controls how often the Airflow dag processor checks for new or updated DAGs. By default, the dag processor will check for new DAGs every 300 seconds. This variable can be used to increase or decrease the frequency of the checks. {% hint style="warning" %} When running this on MacOS, you might need to set the `no_proxy` environment variable to prevent crashes due to a bug in Airflow (see [this page](https://github.com/apache/airflow/issues/28487) for more information): ```bash export no_proxy=* ``` {% endhint %} We can now start the local Airflow server by running the following command: ```bash # Switch to the Python environment that has Airflow installed before running this command airflow standalone ``` This command will start up an Airflow server on your local machine. During the startup, it will print a username and password which you can use to log in to the Airflow UI [here](http://0.0.0.0:8080). We can now switch back the Python environment in which ZenML is installed and run a pipeline: ```shell # Switch to the Python environment that has ZenML installed before running this command python file_that_runs_a_zenml_pipeline.py ``` This call will produce a `.zip` file containing a representation of your ZenML pipeline for Airflow. The location of this `.zip` file will be in the logs of the command above. We now need to copy this file to the Airflow DAGs directory, from where the local Airflow server will load it and run your pipeline (It might take a few seconds until the pipeline shows up in the Airflow UI). To figure out the DAGs directory, we can run `airflow config get-value core DAGS_FOLDER` while having our Python environment with the Airflow installation active. To make this process easier, we can configure our ZenML Airflow orchestrator to automatically copy the `.zip` file to this directory for us. To do so, run the following command: ```bash # Switch to the Python environment that has ZenML installed before running this command zenml orchestrator update --dag_output_dir= ``` Now that we've set this up, running a pipeline in Airflow is as simple as just running the Python file: ```shell # Switch to the Python environment that has ZenML installed before running this command python file_that_runs_a_zenml_pipeline.py ``` {% endtab %} {% tab title="Remote" %} When using the Airflow orchestrator with a remote deployment, you'll additionally need: * A remote ZenML server deployed to the cloud. See the [deployment guide](https://docs.zenml.io/getting-started/deploying-zenml/) for more information. * A deployed Airflow server. See the [deployment section](airflow.md#how-to-deploy-it) for more information. * A [remote artifact store](https://docs.zenml.io/stacks/artifact-stores/) as part of your stack. * A [remote container registry](https://docs.zenml.io/stacks/container-registries/) as part of your stack. In the remote case, the Airflow orchestrator works differently than other ZenML orchestrators. Executing a python file which runs a pipeline by calling `pipeline.run()` will not actually run the pipeline, but instead will create a `.zip` file containing an Airflow representation of your ZenML pipeline. In one additional step, you need to make sure this zip file ends up in the [DAGs directory](https://airflow.apache.org/docs/apache-airflow/stable/concepts/overview.html#architecture-overview) of your Airflow deployment. {% endtab %} {% endtabs %} {% hint style="info" %} ZenML will build a Docker image called `/zenml:` which includes your code and use it to run your pipeline steps in Airflow. Check out [this page](https://docs.zenml.io/how-to/customize-docker-builds/) if you want to learn more about how ZenML builds these images and how you can customize them. {% endhint %} #### Scheduling You can [schedule pipeline runs](https://docs.zenml.io/user-guides/tutorial/managing-scheduled-pipelines) on Airflow similarly to other orchestrators. However, note that**Airflow schedules always need to be set in the past**, e.g.,: ```python from datetime import datetime, timedelta from zenml.config.schedule import Schedule scheduled_pipeline = fashion_mnist_pipeline.with_options( schedule=Schedule( start_time=datetime.now() - timedelta(hours=1), # start in the past end_time=datetime.now() + timedelta(hours=1), interval_second=timedelta(minutes=15), # run every 15 minutes catchup=False, ) ) scheduled_pipeline() ``` #### Airflow UI Airflow comes with its own UI that you can use to find further details about your pipeline runs, such as the logs of your steps. For local Airflow, you can find the Airflow UI at [http://localhost:8080](http://localhost:8080) by default. {% hint style="info" %} If you cannot see the Airflow UI credentials in the console, you can find the password in `/simple_auth_manager_passwords.json.generated`. `AIRFLOW_HOME` will usually be `~/airflow` unless you've manually configured it with the `AIRFLOW_HOME` environment variable. You can always run `airflow info` to figure out the directory for the active environment. {% endhint %} #### Additional configuration For additional configuration of the Airflow orchestrator, you can pass `AirflowOrchestratorSettings` when defining or running your pipeline. Check out the [SDK docs](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-airflow.html#zenml.integrations.airflow) for a full list of available attributes and [this docs page](https://docs.zenml.io/concepts/steps_and_pipelines/yaml_configuration/) for more information on how to specify settings. #### Enabling CUDA for GPU-backed hardware Note that if you wish to use this orchestrator to run steps on a GPU, you will need to follow [the instructions on this page](https://docs.zenml.io/user-guides/tutorial/distributed-training/) to ensure that it works. It requires adding some extra settings customization and is essential to enable CUDA for the GPU to give its full acceleration. #### Using different Airflow operators Airflow operators specify how a step in your pipeline gets executed. As ZenML relies on Docker images to run pipeline steps, only operators that support executing a Docker image work in combination with ZenML. Airflow comes with two operators that support this: * the `DockerOperator` runs the Docker images for executing your pipeline steps on the same machine that your Airflow server is running on. For this to work, the server environment needs to have the `apache-airflow-providers-docker` package installed. * the `KubernetesPodOperator` runs the Docker image on a pod in the Kubernetes cluster that the Airflow server is deployed to. For this to work, the server environment needs to have the `apache-airflow-providers-cncf-kubernetes` package installed. You can specify which operator to use and additional arguments to it as follows: ```python from zenml import pipeline, step from zenml.integrations.airflow.flavors.airflow_orchestrator_flavor import AirflowOrchestratorSettings airflow_settings = AirflowOrchestratorSettings( operator="docker", # or "kubernetes_pod" # Dictionary of arguments to pass to the operator __init__ method operator_args={} ) # Using the operator for a single step @step(settings={"orchestrator": airflow_settings}) def my_step(...): # Using the operator for all steps in your pipeline @pipeline(settings={"orchestrator": airflow_settings}) def my_pipeline(...): ``` {% hint style="info" %} If you're using `apache-airflow-providers-cncf-kubernetes>=10.0.0`, the import of the Kubernetes pod operator changed, and you'll need to specify the operator like this: ```python airflow_settings = AirflowOrchestratorSettings( operator="airflow.providers.cncf.kubernetes.operators.pod.KubernetesPodOperator" ) ``` {% endhint %} **Custom operators** If you want to use any other operator to run your steps, you can specify the `operator` in your `AirflowSettings` as a path to the python operator class: ```python from zenml.integrations.airflow.flavors.airflow_orchestrator_flavor import AirflowOrchestratorSettings airflow_settings = AirflowOrchestratorSettings( # This could also be a reference to one of your custom classes. # e.g. `my_module.MyCustomOperatorClass` as long as the class # is importable in your Airflow server environment operator="airflow.providers.docker.operators.docker.DockerOperator", # Dictionary of arguments to pass to the operator __init__ method operator_args={} ) ``` **Custom DAG generator file** To run a pipeline in Airflow, ZenML creates a Zip archive that contains two files: * A JSON configuration file that the orchestrator creates. This file contains all the information required to create the Airflow DAG to run the pipeline. * A Python file that reads this configuration file and actually creates the Airflow DAG. We call this file the `DAG generator` and you can find the implementation [here](https://github.com/zenml-io/zenml/blob/main/src/zenml/integrations/airflow/orchestrators/dag_generator.py) . If you need more control over how the Airflow DAG is generated, you can provide a custom DAG generator file using the setting `custom_dag_generator`. This setting will need to reference a Python module that can be imported into your active Python environment. It will additionally need to contain the same classes (`DagConfiguration` and `TaskConfiguration`) and constants (`ENV_ZENML_AIRFLOW_RUN_ID`, `ENV_ZENML_LOCAL_STORES_PATH` and `CONFIG_FILENAME`) as the [original module](https://github.com/zenml-io/zenml/blob/main/src/zenml/integrations/airflow/orchestrators/dag_generator.py) . For this reason, we suggest starting by copying the original and modifying it according to your needs. Check out our docs on how to apply settings to your pipelines [here](https://docs.zenml.io/concepts/steps_and_pipelines/yaml_configuration/). For more information and a full list of configurable attributes of the Airflow orchestrator, check out the [SDK Docs](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-airflow.html#zenml.integrations.airflow) .
ZenML Scarf
--- ### Book/Component Guide/Orchestrators/Azureml (docs/book/component-guide/orchestrators/azureml.md) --- description: Orchestrating your pipelines to run on AzureML. --- # AzureML Orchestrator [AzureML](https://azure.microsoft.com/en-us/products/machine-learning) is a cloud-based orchestration service provided by Microsoft, that enables data scientists, machine learning engineers, and developers to build, train, deploy, and manage machine learning models. It offers a comprehensive and integrated environment that supports the entire machine learning lifecycle, from data preparation and model development to deployment and monitoring. ## When to use it You should use the AzureML orchestrator if: * you're already using Azure. * you're looking for a proven production-grade orchestrator. * you're looking for a UI in which you can track your pipeline runs. * you're looking for a managed solution for running your pipelines. ## How it works The ZenML AzureML orchestrator implementation uses [the Python SDK v2 of AzureML](https://learn.microsoft.com/en-gb/python/api/overview/azure/ai-ml-readme?view=azure-python) to allow our users to build their Machine Learning pipelines. For each ZenML step, it creates an AzureML [CommandComponent](https://learn.microsoft.com/en-us/python/api/azure-ai-ml/azure.ai.ml.entities.commandcomponent?view=azure-python) and brings them together in a pipeline. ## How to deploy it {% hint style="info" %} Would you like to skip ahead and deploy a full ZenML cloud stack already, including an AzureML orchestrator? Check out the [in-browser stack deployment wizard](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/deploy-a-cloud-stack), the [stack registration wizard](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/register-a-cloud-stack), or [the ZenML Azure Terraform module](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/deploy-a-cloud-stack-with-terraform) for a shortcut on how to deploy & register this stack component. {% endhint %} In order to use an AzureML orchestrator, you need to first deploy [ZenML to the cloud](https://docs.zenml.io/getting-started/deploying-zenml/). It would be recommended to deploy ZenML in the same region as you plan on using for AzureML, but it is not necessary to do so. You must ensure that you are [connected to the remote ZenML server](https://docs.zenml.io/how-to/manage-zenml-server/connecting-to-zenml/connect-in-with-your-user-interactive) before using this stack component. ## How to use it In order to use the AzureML orchestrator, you need: * The ZenML `azure` integration installed. If you haven't done so, run: ```shell zenml integration install azure ``` * [Docker](https://www.docker.com) installed and running or a remote image builder in your stack. * A [remote artifact store](https://docs.zenml.io/stacks/artifact-stores/) as part of your stack. * A [remote container registry](https://docs.zenml.io/stacks/container-registries/) as part of your stack. * An [Azure resource group equipped with an AzureML workspace](https://learn.microsoft.com/en-us/azure/machine-learning/quickstart-create-resources?view=azureml-api-2) to run your pipeline on. There are two ways of authenticating your orchestrator with AzureML: 1. **Default Authentication** simplifies the authentication process while developing your workflows that deploy to Azure by combining credentials used in Azure hosting environments and credentials used in local development. 2. **Service Principal Authentication (recommended)** is using the concept of service principals on Azure to allow you to connect your cloud components with proper authentication. For this method, you will need to [create a service principal on Azure](https://learn.microsoft.com/en-us/azure/developer/python/sdk/authentication-on-premises-apps?tabs=azure-portal), assign it the correct permissions and use it to [register a ZenML Azure Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/azure-service-connector). ```bash zenml service-connector register --type azure -i zenml orchestrator connect -c ``` ## Docker For each pipeline run, ZenML will build a Docker image called`/zenml:` which includes your code and use it to run your pipeline steps in AzureML. Check out[this page](https://docs.zenml.io/how-to/customize-docker-builds/) if you want to learn more about how ZenML builds these images and how you can customize them. ## AzureML UI Each AzureML workspace comes equipped with an Azure Machine Learning studio. Here you can inspect, manage, and debug your pipelines and steps. Double-clicking any of the steps on this view will open up the overview page for that specific step. Here you can check the configuration of the component and its execution logs. ## Settings The ZenML AzureML orchestrator comes with a dedicated class called`AzureMLOrchestratorSettings` for configuring its settings, and it controls the compute resources used for pipeline execution in AzureML. Currently, it supports three different modes of operation. ### 1. Serverless Compute (Default) * Set `mode` to `serverless`. * Other parameters are ignored. **Example:** ```python from zenml import step, pipeline from zenml.integrations.azure.flavors import AzureMLOrchestratorSettings azureml_settings = AzureMLOrchestratorSettings( mode="serverless" # It's the default behavior ) @step def example_step() -> int: return 3 @pipeline(settings={"orchestrator": azureml_settings}) def pipeline(): example_step() pipeline() ``` ### 2. Compute Instance * Set `mode` to `compute-instance`. * Requires a `compute_name`. * If a compute instance with the same name exists, it uses the existing compute instance and ignores other parameters. (It will throw a warning if the provided configuration does not match the existing instance.) * If a compute instance with the same name doesn't exist, it creates a new compute instance with the `compute_name`. For this process, you can specify `size` and `idle_type_before_shutdown_minutes`. **Example:** ```python from zenml import step, pipeline from zenml.integrations.azure.flavors import AzureMLOrchestratorSettings azureml_settings = AzureMLOrchestratorSettings( mode="compute-instance", compute_name="my-gpu-instance", # Will fetch or create this instance size="Standard_NC6s_v3", # Using a NVIDIA Tesla V100 GPU idle_time_before_shutdown_minutes=20, ) @step def example_step() -> int: return 3 @pipeline(settings={"orchestrator": azureml_settings}) def pipeline(): example_step() pipeline() ``` ### 3. Compute Cluster * Set `mode` to `compute-cluster`. * Requires a `compute_name`. * If a compute cluster with the same name exists, it uses existing cluster, ignores other parameters. (It will throw a warning if the provided * configuration does not match the existing cluster.) * If a compute cluster with the same name doesn't exist, it creates a new compute cluster. Additional parameters can be used for configuring this process. **Example:** ```python from zenml import step, pipeline from zenml.integrations.azure.flavors import AzureMLOrchestratorSettings azureml_settings = AzureMLOrchestratorSettings( mode="compute-cluster", compute_name="my-gpu-cluster", # Will fetch or create this instance size="Standard_NC6s_v3", # Using a NVIDIA Tesla V100 GPU tier="Dedicated", # Can be set to either "Dedicated" or "LowPriority" min_instances=2, max_instances=10, idle_time_before_scaledown_down=60, ) @step def example_step() -> int: return 3 @pipeline(settings={"orchestrator": azureml_settings}) def my_pipeline(): example_step() my_pipeline() ``` {% hint style="info" %} In order to learn more about the supported sizes for compute instances and clusters, you can check [the AzureML documentation](https://learn.microsoft.com/en-us/azure/machine-learning/concept-compute-target?view=azureml-api-2#supported-vm-series-and-sizes). {% endhint %} ### Run pipelines on a schedule The AzureML orchestrator supports running pipelines on a schedule using its [JobSchedules](https://learn.microsoft.com/en-us/azure/machine-learning/how-to-schedule-pipeline-job?view=azureml-api-2\&tabs=python). Both cron expression and intervals are supported. ```python from zenml import pipeline from zenml.config.schedule import Schedule @pipeline def my_pipeline(): ... # Run a pipeline every 5th minute my_pipeline = my_pipeline.with_options( schedule=Schedule(cron_expression="*/5 * * * *") ) my_pipeline() ``` Once you run the pipeline with a schedule, you can find the schedule and the corresponding run under the `All Schedules` tab `Jobs` in the jobs page on AzureML. {% hint style="warning" %} Note that ZenML only gets involved to schedule a run, but maintaining the lifecycle of the schedule is the responsibility of the user. That means, if you want to cancel a schedule that you created on AzureML, you will have to do it through the Azure UI. {% endhint %}
ZenML Scarf
--- ### Book/Component Guide/Orchestrators/Custom (docs/book/component-guide/orchestrators/custom.md) --- description: Learning how to develop a custom orchestrator. --- # Develop a custom orchestrator {% hint style="info" %} Before diving into the specifics of this component type, it is beneficial to familiarize yourself with our [general guide to writing custom component flavors in ZenML](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/implement-a-custom-stack-component). This guide provides an essential understanding of ZenML's component flavor concepts. {% endhint %} ### Base Implementation ZenML aims to enable orchestration with any orchestration tool. This is where the `BaseOrchestrator` comes into play. It abstracts away many of the ZenML-specific details from the actual implementation and exposes a simplified interface: ``` /* Detailed source-code truncated for AI context efficiency. */ ``` {% hint style="info" %} This is a slimmed-down version of the base implementation which aims to highlight the abstraction layer. In order to see the full implementation and get the complete docstrings, please check [the source code on GitHub](https://github.com/zenml-io/zenml/blob/main/src/zenml/orchestrators/base\_orchestrator.py) . {% endhint %} ### Build your own custom orchestrator If you want to create your own custom flavor for an orchestrator, you can follow the following steps: 1. Create a class that inherits from the `BaseOrchestrator` class and implement the abstract `submit_pipeline(...)` and `get_orchestrator_run_id()` methods. 2. If you need to provide any configuration, create a class that inherits from the `BaseOrchestratorConfig` class and add your configuration parameters. 3. Bring both the implementation and the configuration together by inheriting from the `BaseOrchestratorFlavor` class. Make sure that you give a `name` to the flavor through its abstract property. Once you are done with the implementation, you can register it through the CLI. Please ensure you **point to the flavor class via dot notation**: ```shell zenml orchestrator flavor register ``` For example, if your flavor class `MyOrchestratorFlavor` is defined in `flavors/my_flavor.py`, you'd register it by doing: ```shell zenml orchestrator flavor register flavors.my_flavor.MyOrchestratorFlavor ``` {% hint style="warning" %} ZenML resolves the flavor class by taking the path where you initialized zenml (via `zenml init`) as the starting point of resolution. Therefore, please ensure you follow [the best practice](https://docs.zenml.io/user-guides/best-practices/iac) of initializing zenml at the root of your repository. If ZenML does not find an initialized ZenML repository in any parent directory, it will default to the current working directory, but usually, it's better to not have to rely on this mechanism and initialize zenml at the root. {% endhint %} Afterward, you should see the new flavor in the list of available flavors: ```shell zenml orchestrator flavor list ``` {% hint style="warning" %} It is important to draw attention to when and how these base abstractions are coming into play in a ZenML workflow. * The **CustomOrchestratorFlavor** class is imported and utilized upon the creation of the custom flavor through the CLI. * The **CustomOrchestratorConfig** class is imported when someone tries to register/update a stack component with this custom flavor. Especially, during the registration process of the stack component, the config will be used to validate the values given by the user. As `Config` object are inherently `pydantic` objects, you can also add your own custom validators here. * The **CustomOrchestrator** only comes into play when the component is ultimately in use. The design behind this interaction lets us separate the configuration of the flavor from its implementation. This way we can register flavors and components even when the major dependencies behind their implementation are not installed in our local setting (assuming the `CustomOrchestratorFlavor` and the `CustomOrchestratorConfig` are implemented in a different module/path than the actual `CustomOrchestrator`). {% endhint %} ## Implementation guide 1. **Create your orchestrator class:** This class should either inherit from `BaseOrchestrator`, or more commonly from `ContainerizedOrchestrator`. If your orchestrator uses container images to run code, you should inherit from `ContainerizedOrchestrator` which handles building all Docker images for the pipeline to be executed. If your orchestator does not use container images, you'll be responsible that the execution environment contains all the necessary requirements and code files to run the pipeline. 2. **Implement the `submit_pipeline(...)` method:** This method is responsible for submitting the pipeline run or schedule. In most cases, this means converting the pipeline into a format that your orchestration backend understands and submitting it. To do so, you should: * Loop over all steps of the pipeline and configure your orchestration tool to run the correct command and arguments in the correct Docker image * Make sure the passed environment variables are set when the container is run * Make sure the containers are running in the correct order * If you want to store any metadata for the run or schedule, return it as part of the `SubmissionResult`. * If your orchestrator is configured to run synchronous, make sure to return a `wait_for_completion` closure in the `SubmissionResult`. Check out the [code sample](custom.md#code-sample) below for more details on how to fetch the Docker image, command, arguments and step order. 3. **Implement the `get_orchestrator_run_id()` method:** This must return a ID that is different for each pipeline run, but identical if called from within Docker containers running different steps of the same pipeline run. If your orchestrator is based on an external tool like Kubeflow or Airflow, it is usually best to use an unique ID provided by this tool. {% hint style="info" %} To see a full end-to-end worked example of a custom orchestrator, [see here](https://github.com/zenml-io/zenml-plugins/tree/main/how\_to\_custom\_orchestrator). {% endhint %} ### Optional features There are some additional optional features that your orchestrator can implement: * **Running pipelines on a schedule**: if your orchestrator supports running pipelines on a schedule, make sure to handle `deployment.schedule` if it exists. If your orchestrator does not support schedules, you should either log a warning and or even raise an exception in case the user tries to schedule a pipeline. * **Specifying hardware resources**: If your orchestrator supports setting resources like CPUs, GPUs or memory for the pipeline or specific steps, make sure to handle the values defined in `step.config.resource_settings`. See the code sample below for additional helper methods to check whether any resources are required from your orchestrator. ### Code sample ``` /* Detailed source-code truncated for AI context efficiency. */ ``` {% hint style="info" %} To see a full end-to-end worked example of a custom orchestrator, [see here](https://github.com/zenml-io/zenml-plugins/tree/main/how\_to\_custom\_orchestrator). {% endhint %} ### Enabling CUDA for GPU-backed hardware Note that if you wish to use your custom orchestrator to run steps on a GPU, you will need to follow [the instructions on this page](https://docs.zenml.io/user-guides/tutorial/distributed-training) to ensure that it works. It requires adding some extra settings customization and is essential to enable CUDA for the GPU to give its full acceleration.
ZenML Scarf
--- ### Book/Component Guide/Orchestrators/Databricks (docs/book/component-guide/orchestrators/databricks.md) --- description: Orchestrating your pipelines to run on Databricks. --- # Databricks Orchestrator [Databricks](https://www.databricks.com/) is a unified data analytics platform that combines the best of data warehouses and data lakes to offer an integrated solution for big data processing and machine learning. It provides a collaborative environment for data scientists, data engineers, and business analysts to work together on data projects. Databricks offers optimized performance and scalability for big data workloads. The Databricks orchestrator is an orchestrator flavor provided by the ZenML databricks integration that allows you to run your pipelines on Databricks. This integration enables you to leverage Databricks' powerful distributed computing capabilities and optimized environment for your ML pipelines within the ZenML framework. If you only want to run selected steps on Databricks while keeping the overall pipeline on another orchestrator, use the [Databricks step operator](../step-operators/databricks.md) instead. {% hint style="warning" %} The following features are currently in Alpha and may be subject to change. We recommend using them in a controlled environment and providing feedback to the ZenML team. {% endhint %} ### When to use it You should use the Databricks orchestrator if: * you're already using Databricks for your data and ML workloads. * you want to leverage Databricks' powerful distributed computing capabilities for your ML pipelines. * you're looking for a managed solution that integrates well with other Databricks services. * you want to take advantage of Databricks' optimization for big data processing and machine learning. ### Prerequisites You will need to do the following to start using the Databricks orchestrator: * An active Databricks workspace. See the cloud-specific setup guides: * [AWS](https://docs.databricks.com/en/getting-started/onboarding-account.html) * [Azure](https://learn.microsoft.com/en-us/azure/databricks/getting-started/#--create-an-azure-databricks-workspace) * [GCP](https://docs.gcp.databricks.com/en/getting-started/index.html) * A Databricks account or service account with permission to create and run jobs. ## How it works When you run a pipeline with the Databricks orchestrator, ZenML builds a Python wheel from your project and uploads it to Databricks. ZenML then uses the Databricks SDK to create a job whose tasks mirror your pipeline steps and their upstream dependencies. The job uses the cluster settings configured on the orchestrator, including Spark version, worker count or autoscaling, node type, and any Spark configuration. When Databricks starts the job, each task installs the uploaded wheel and executes the corresponding ZenML step entrypoint. {% hint style="info" %} The orchestrator keeps uploaded wheel packages under `/Workspace/Shared/.zenml` after a successful job submission because Databricks job definitions, scheduled runs, and manual re-runs keep referencing those workspace files. Clean up old wheel directories from that workspace path according to your team's retention policy when you no longer need to re-run those jobs. {% endhint %} ### How to use it To use the Databricks orchestrator, you first need to register it and add it to your stack. Before registering the orchestrator, you need to install the Databricks integration by running the following command: ```shell zenml integration install databricks ``` This installs the required dependencies, including `databricks-sdk`. Once the integration is installed, register the orchestrator and configure authentication: ```shell zenml orchestrator register databricks_orchestrator --flavor=databricks --host="https://xxxxx.x.azuredatabricks.net" --client_id={{databricks.client_id}} --client_secret={{databricks.client_secret}} ``` {% hint style="info" %} We recommend creating a Databricks service account with the necessary permissions to create and run jobs. You can find more information on how to create a service account [here](https://docs.databricks.com/dev-tools/api/latest/authentication.html). You can generate a client_id and client_secret for the service account and use them to authenticate with Databricks. {% endhint %} ```shell # Add the orchestrator to your stack zenml stack register databricks_stack -o databricks_orchestrator ... --set ``` You can now run any ZenML pipeline using the Databricks orchestrator: ```shell python run.py ``` ### Databricks UI Databricks comes with its own UI that you can use to find further details about your pipeline runs, such as the logs of your steps. For any runs executed on Databricks, you can get the URL to the Databricks UI in Python using the following code snippet: ```python from zenml.client import Client pipeline_run = Client().get_pipeline_run("") orchestrator_url = pipeline_run.run_metadata["orchestrator_url"].value ``` ### Run pipelines on a schedule The Databricks orchestrator supports running pipelines on a schedule using its [native scheduling capability](https://docs.databricks.com/en/workflows/jobs/schedule-jobs.html). **How to schedule a pipeline** ```python from zenml.config.schedule import Schedule # Run a pipeline every 5th minute pipeline_instance.run( schedule=Schedule( cron_expression="*/5 * * * *" ) ) ``` {% hint style="warning" %} The Databricks orchestrator only supports the `cron_expression`, in the `Schedule` object, and will ignore all other parameters supplied to define the schedule. {% endhint %} {% hint style="warning" %} The Databricks orchestrator requires an IANA timezone ID to be configured through `schedule_timezone` in the orchestrator settings (see below for more information on how to set orchestrator settings). {% endhint %} **How to delete a scheduled pipeline** ZenML creates the Databricks schedule, but you manage its lifecycle in Databricks. To cancel a scheduled Databricks pipeline, delete the schedule in Databricks via the UI or CLI. ### Additional configuration For additional configuration of the Databricks orchestrator, you can pass `DatabricksOrchestratorSettings` which allows you to change the Spark version, number of workers, node type, autoscale settings, Spark configuration, Spark environment variables, schedule timezone, init scripts, and Docker image settings. Init scripts must use DBFS paths that start with `dbfs:/`. If you configure Docker registry authentication, provide both `docker_image_username` and `docker_image_password`. ```python from zenml.integrations.databricks.flavors.databricks_orchestrator_flavor import DatabricksOrchestratorSettings databricks_settings = DatabricksOrchestratorSettings( spark_version="15.3.x-scala2.12", num_workers=3, node_type_id="Standard_D4s_v5", policy_id=POLICY_ID, spark_conf={}, spark_env_vars={}, init_scripts=["dbfs:/scripts/install_dependencies.sh"], schedule_timezone="America/Los_Angeles", ) ``` Use `num_workers` for fixed-size clusters. For autoscaling clusters, omit `num_workers` and set `autoscale`, for example `autoscale=(2, 3)`. These settings can then be specified on either pipeline-level or step-level: ```python # Either specify on pipeline-level @pipeline( settings={ "orchestrator": databricks_settings, } ) def my_pipeline(): ... ``` #### Tagging Databricks resources You can apply tags to Databricks resources for cost allocation, governance, and project tracking using two settings: * `custom_tags`: Applied to the underlying cluster resources (e.g., AWS EC2 instances, EBS volumes). Maximum 45 tags. * `job_tags`: Applied to the Databricks job itself and forwarded as cluster tags. Maximum 25 tags. By default, Databricks autoscaling uses `(0, 1)` worker bounds. This intentionally permits driver-only clusters while still allowing one worker when needed. ```python from zenml.integrations.databricks.flavors.databricks_orchestrator_flavor import DatabricksOrchestratorSettings databricks_settings = DatabricksOrchestratorSettings( spark_version="15.3.x-scala2.12", num_workers=3, node_type_id="Standard_D4s_v5", custom_tags={"cost_center": "ml-team", "environment": "production"}, job_tags={"project": "recommendation-engine", "owner": "data-team"}, ) ``` To use GPU-backed clusters, set `spark_version` and `node_type_id` to GPU-enabled values: ```python from zenml.integrations.databricks.flavors.databricks_orchestrator_flavor import DatabricksOrchestratorSettings databricks_settings = DatabricksOrchestratorSettings( spark_version="15.3.x-gpu-ml-scala2.12", node_type_id="Standard_NC24ads_A100_v4", policy_id=POLICY_ID, autoscale=(1, 2), ) ``` With these settings, the orchestrator uses a GPU-enabled Spark version and node type. #### Enabling CUDA for GPU-backed hardware If your steps need CUDA, follow the [distributed training guide](https://docs.zenml.io/user-guides/tutorial/distributed-training) to configure the required dependencies and runtime settings.
ZenML Scarf
Check out the [SDK docs](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-databricks.html#zenml.integrations.databricks) for all configurable attributes and [this docs page](https://docs.zenml.io/concepts/steps_and_pipelines/configuration) for more information on how to specify settings. --- ### Book/Component Guide/Orchestrators/Hyperai (docs/book/component-guide/orchestrators/hyperai.md) --- description: Orchestrating your pipelines to run on HyperAI.ai instances. --- # HyperAI Orchestrator [HyperAI](https://www.hyperai.ai) is a cutting-edge cloud compute platform designed to make AI accessible for everyone. The HyperAI orchestrator is an [orchestrator](./) flavor that allows you to easily deploy your pipelines on HyperAI instances. {% hint style="warning" %} **Deprecated:** the HyperAI orchestrator is deprecated in favor of the generic [SSH orchestrator](ssh.md), which uses the same Docker Compose execution model, is not tied to HyperAI, and additionally supports dynamic pipelines. See [Migrating from HyperAI](ssh.md#migrating-from-hyperai). It still works but will not receive new features and will be removed in a future release — please migrate to the SSH orchestrator. {% endhint %} {% hint style="warning" %} This component is only meant to be used within the context of a [remote ZenML deployment scenario](https://docs.zenml.io/getting-started/deploying-zenml/). Usage with a local ZenML deployment may lead to unexpected behavior! {% endhint %} ### When to use it You should use the HyperAI orchestrator if: * you're looking for a managed solution for running your pipelines. * you're a HyperAI customer. ### Prerequisites You will need to do the following to start using the HyperAI orchestrator: * Have a running HyperAI instance. It must be accessible from the internet (or at least from the IP addresses of your ZenML users) and allow SSH key based access (passwords are not supported). * Ensure that a recent version of Docker is installed. This version must include Docker Compose, meaning that the command `docker compose` works. * Ensure that the appropriate [NVIDIA Driver](https://www.nvidia.com/en-us/drivers/unix/) is installed on the HyperAI instance (if not already installed by the HyperAI team). * Ensure that the [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html) is installed and configured on the HyperAI instance. Note that it is possible to omit installing the NVIDIA Driver and NVIDIA Container Toolkit. However, you will then be unable to use the GPU from within your ZenML pipeline. Additionally, you will then need to disable GPU access within the container when configuring the Orchestrator component, or the pipeline will not start correctly. ## How it works The HyperAI orchestrator works with Docker Compose, which can be used to construct machine learning pipelines. Under the hood, it creates a Docker Compose file which it then deploys and executes on the configured HyperAI instance. For each ZenML pipeline step, it creates a service in this file. It uses the `service_completed_successfully` condition to ensure that pipeline steps will only run if their connected upstream steps have successfully finished. If configured for it, the HyperAI orchestrator will connect the HyperAI instance to the stack's container registry to ensure a smooth transfer of Docker images. ### Scheduled pipelines [Scheduled pipelines](https://docs.zenml.io/user-guides/tutorial/managing-scheduled-pipelines) are supported by the HyperAI orchestrator. Currently, the HyperAI orchestrator supports the following inputs to `Schedule`: * Cron expressions via `cron_expression`. When pipeline runs are scheduled, they are added as a crontab entry on the HyperAI instance. Use this when you want pipelines to run in intervals. Using cron expressions assumes that `crontab` is available on your instance and that its daemon is running. * Scheduled runs via `run_once_start_time`. When pipeline runs are scheduled this way, they are added as an `at` entry on the HyperAI instance. Use this when you want pipelines to run just once and at a specified time. This assumes that `at` is available on your instance. ### How to deploy it To use the HyperAI orchestrator, you must configure a HyperAI Service Connector in ZenML and link it to the HyperAI orchestrator component. The service connector contains credentials with which ZenML connects to the HyperAI instance. Additionally, the HyperAI orchestrator must be used in a stack that contains a container registry and an image builder. ### How to use it To use the HyperAI orchestrator, we must configure a HyperAI Service Connector first using one of its supported authentication methods. For example, for authentication with an RSA-based key, create the service connector as follows: ```shell zenml service-connector register --type=hyperai --auth-method=rsa-key --base64_ssh_key= --hostnames=,,.., --username= ``` Hostnames are either DNS resolvable names or IP addresses. For example, if you have two servers - one at `1.2.3.4` and another at `4.3.2.1`, you could provide them as `--hostnames=1.2.3.4,4.3.2.1`. Optionally, it is possible to provide a passphrase for the key (`--ssh_passphrase`). Following registering the service connector, we can register the orchestrator and use it in our active stack: ```shell zenml orchestrator register --flavor=hyperai # Register and activate a stack with the new orchestrator zenml stack register -o ... --set ``` You can now run any ZenML pipeline using the HyperAI orchestrator: ```shell python file_that_runs_a_zenml_pipeline.py ``` #### Enabling CUDA for GPU-backed hardware Note that if you wish to use this orchestrator to run steps on a GPU, you will need to follow [the instructions on this page](https://docs.zenml.io/user-guides/tutorial/distributed-training/) to ensure that it works. It requires adding some extra settings customization and is essential to enable CUDA for the GPU to give its full acceleration.
ZenML Scarf
--- ### Book/Component Guide/Orchestrators/Kubeflow (docs/book/component-guide/orchestrators/kubeflow.md) --- description: Orchestrating your pipelines to run on Kubeflow. --- # Kubeflow Orchestrator The Kubeflow orchestrator is an [orchestrator](./) flavor provided by the ZenML `kubeflow` integration that uses [Kubeflow Pipelines](https://www.kubeflow.org/docs/components/pipelines/overview/) to run your pipelines. {% hint style="warning" %} This component is only meant to be used within the context of a [remote ZenML deployment scenario](https://docs.zenml.io/getting-started/deploying-zenml/). Usage with a local ZenML deployment may lead to unexpected behavior! {% endhint %} ### When to use it You should use the Kubeflow orchestrator if: * you're looking for a proven production-grade orchestrator. * you're looking for a UI in which you can track your pipeline runs. * you're already using Kubernetes or are not afraid of setting up and maintaining a Kubernetes cluster. * you're willing to deploy and maintain Kubeflow Pipelines on your cluster. ### How to deploy it To run ZenML pipelines on Kubeflow, you'll need to set up a Kubernetes cluster and deploy Kubeflow Pipelines on it. This can be done in a variety of ways, depending on whether you want to use a cloud provider or your own infrastructure: {% tabs %} {% tab title="AWS" %} * Have an existing AWS [EKS cluster](https://docs.aws.amazon.com/eks/latest/userguide/create-cluster.html) set up. * Make sure you have the [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) set up. * Download and [install](https://kubernetes.io/docs/tasks/tools/) `kubectl` and configure it to talk to your EKS cluster using the following command: ```powershell aws eks --region REGION update-kubeconfig --name CLUSTER_NAME ``` * [Install](https://www.kubeflow.org/docs/components/pipelines/operator-guides/installation/#deploying-kubeflow-pipelines) Kubeflow Pipelines onto your cluster. * ( optional) [set up an AWS Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/aws-service-connector) to grant ZenML Stack Components easy and secure access to the remote EKS cluster. {% endtab %} {% tab title="GCP" %} * Have an existing GCP [GKE cluster](https://cloud.google.com/kubernetes-engine/docs/quickstart) set up. * Make sure you have the [Google Cloud CLI](https://cloud.google.com/sdk/docs/install-sdk) set up first. * Download and [install](https://kubernetes.io/docs/tasks/tools/) `kubectl` and [configure](https://cloud.google.com/kubernetes-engine/docs/how-to/cluster-access-for-kubectl) it to talk to your GKE cluster using the following command: ```powershell gcloud container clusters get-credentials CLUSTER_NAME ``` * [Install](https://www.kubeflow.org/docs/distributions/gke/deploy/overview/) Kubeflow Pipelines onto your cluster. * ( optional) [set up a GCP Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/gcp-service-connector) to grant ZenML Stack Components easy and secure access to the remote GKE cluster. {% endtab %} {% tab title="Azure" %} * Have an existing [AKS cluster](https://azure.microsoft.com/en-in/services/kubernetes-service/#documentation) set up. * Make sure you have the [`az` CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli) set up first. * Download and [install](https://kubernetes.io/docs/tasks/tools/) `kubectl` and ensure that it talks to your AKS cluster using the following command: ```powershell az aks get-credentials --resource-group RESOURCE_GROUP --name CLUSTER_NAME ``` * [Install](https://www.kubeflow.org/docs/components/pipelines/operator-guides/installation/#deploying-kubeflow-pipelines) Kubeflow Pipelines onto your cluster. {% hint style="info" %} Since Kubernetes v1.19, AKS has shifted to [`containerd`](https://docs.microsoft.com/en-us/azure/aks/cluster-configuration#container-settings). However, the workflow controller installed with the Kubeflow installation has `Docker` set as the default runtime. In order to make your pipelines work, you have to change the value to one of the options listed [here](https://argoproj.github.io/argo-workflows/workflow-executors/#workflow-executors), preferably `k8sapi`. This change has to be made by editing the `containerRuntimeExecutor` property of the `ConfigMap` corresponding to the workflow controller. Run the following commands to first know what config map to change and then to edit it to reflect your new value: ``` kubectl get configmap -n kubeflow kubectl edit configmap CONFIGMAP_NAME -n kubeflow # This opens up an editor that can be used to make the change. ``` {% endhint %} {% endtab %} {% tab title="Other Kubernetes" %} * Have an existing Kubernetes cluster set up. * Download and [install](https://kubernetes.io/docs/tasks/tools/) `kubectl` and configure it to talk to your Kubernetes cluster. * [Install](https://www.kubeflow.org/docs/components/pipelines/operator-guides/installation/#deploying-kubeflow-pipelines) Kubeflow Pipelines onto your cluster. * ( optional) [set up a Kubernetes Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/kubernetes-service-connector) to grant ZenML Stack Components easy and secure access to the remote Kubernetes cluster. This is especially useful if your Kubernetes cluster is remotely accessible, as this enables other ZenML users to use it to run pipelines without needing to configure and set up `kubectl` on their local machines. {% endtab %} {% endtabs %} {% hint style="info" %} If one or more of the deployments are not in the `Running` state, try increasing the number of nodes in your cluster. {% endhint %} {% hint style="warning" %} If you're installing Kubeflow Pipelines manually, make sure the Kubernetes service is called exactly `ml-pipeline`. This is a requirement for ZenML to connect to your Kubeflow Pipelines deployment. {% endhint %} ### How to use it To use the Kubeflow orchestrator, we need: * A Kubernetes cluster with Kubeflow pipelines installed. See the [deployment section](kubeflow.md#how-to-deploy-it) for more information. * A ZenML server deployed remotely where it can be accessed from the Kubernetes cluster. See the [deployment guide](https://docs.zenml.io/getting-started/deploying-zenml/) for more information. * The ZenML `kubeflow` integration installed. If you haven't done so, run ```shell zenml integration install kubeflow ``` * [Docker](https://www.docker.com) installed and running (unless you are using a remote [Image Builder](https://docs.zenml.io/stacks/image-builders/) in your ZenML stack). * [kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl) installed (optional, see below) {% hint style="info" %} If you are using a single-tenant Kubeflow installed in a Kubernetes cluster managed by a cloud provider like AWS, GCP or Azure, it is recommended that you set up [a Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/service-connectors-guide) and use it to connect ZenML Stack Components to the remote Kubernetes cluster. This guarantees that your Stack is fully portable on other environments and your pipelines are fully reproducible. {% endhint %} * The name of your Kubernetes context which points to your remote cluster. Run `kubectl config get-contexts` to see a list of available contexts. **NOTE**: this is no longer required if you are using [a Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/service-connectors-guide) to connect your Kubeflow Orchestrator Stack Component to the remote Kubernetes cluster. * A [remote artifact store](https://docs.zenml.io/stacks/artifact-stores/) as part of your stack. * A [remote container registry](https://docs.zenml.io/stacks/container-registries/) as part of your stack. We can then register the orchestrator and use it in our active stack. This can be done in two ways: 1. If you have [a Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/service-connectors-guide) configured to access the remote Kubernetes cluster, you no longer need to set the `kubernetes_context` attribute to a local `kubectl` context. In fact, you don't need the local Kubernetes CLI at all. You can [connect the stack component to the Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/service-connectors-guide#connect-stack-components-to-resources) instead: ```shell # List all available Kubernetes clusters that can be accessed by service connectors zenml service-connector list-resources --resource-type kubernetes-cluster -e # Register the Kubeflow orchestrator and connect it to the remote Kubernetes cluster zenml orchestrator register --flavor kubeflow --connector --resource-id # Register a new stack with the orchestrator zenml stack register -o -a -c ... # Add other stack components as needed ``` The following example demonstrates how to register the orchestrator and connect it to a remote Kubernetes cluster using a Service Connector: ``` /* Detailed source-code truncated for AI context efficiency. */ ``` 2. if you don't have a Service Connector on hand and you don't want to [register one](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/service-connectors-guide#register-service-connectors), the local Kubernetes `kubectl` client needs to be configured with a configuration context pointing to the remote cluster. The `kubernetes_context` must also be configured with the value of that context: ```shell zenml orchestrator register --flavor=kubeflow --kubernetes_context= # Register a new stack with the orchestrator zenml stack register -o -a -c ... # Add other stack components as needed ``` {% hint style="info" %} ZenML will build a Docker image called `/zenml:` which includes all required software dependencies and use it to run your pipeline steps in Kubeflow. Check out [this page](https://docs.zenml.io/how-to/customize-docker-builds/) if you want to learn more about how ZenML builds these images and how you can customize them. {% endhint %} You can now run any ZenML pipeline using the Kubeflow orchestrator: ```shell python file_that_runs_a_zenml_pipeline.py ``` #### Kubeflow UI Kubeflow comes with its own UI that you can use to find further details about your pipeline runs, such as the logs of your steps. For any runs executed on Kubeflow, you can get the URL to the Kubeflow UI in Python using the following code snippet: ```python from zenml.client import Client pipeline_run = Client().get_pipeline_run("") orchestrator_url = pipeline_run.run_metadata["orchestrator_url"] ``` #### Additional configuration For additional configuration of the Kubeflow orchestrator, you can pass `KubeflowOrchestratorSettings` which allows you to configure (among others) the following attributes: * `client_args`: Arguments to pass when initializing the KFP client. * `user_namespace`: The user namespace to use when creating experiments and runs. * `pod_settings`: Node selectors, affinity, and tolerations to apply to the Kubernetes Pods running your pipeline. These can be either specified using the Kubernetes model objects or as dictionaries. ```python from zenml.integrations.kubeflow.flavors.kubeflow_orchestrator_flavor import KubeflowOrchestratorSettings from kubernetes.client.models import V1Toleration kubeflow_settings = KubeflowOrchestratorSettings( client_args={}, user_namespace="my_namespace", pod_settings={ "affinity": { "nodeAffinity": { "requiredDuringSchedulingIgnoredDuringExecution": { "nodeSelectorTerms": [ { "matchExpressions": [ { "key": "node.kubernetes.io/name", "operator": "In", "values": ["my_powerful_node_group"], } ] } ] } } }, "tolerations": [ V1Toleration( key="node.kubernetes.io/name", operator="Equal", value="", effect="NoSchedule" ) ] } ) @pipeline( settings={ "orchestrator": kubeflow_settings } ) ... ``` Check out the [SDK docs](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-kubeflow.html#zenml.integrations.kubeflow) for a full list of available attributes and [this docs page](https://docs.zenml.io/concepts/steps_and_pipelines/configuration) for more information on how to specify settings. #### Enabling CUDA for GPU-backed hardware Note that if you wish to use this orchestrator to run steps on a GPU, you will need to follow [the instructions on this page](https://docs.zenml.io/user-guides/tutorial/distributed-training/) to ensure that it works. It requires adding some extra settings customization and is essential to enable CUDA for the GPU to give its full acceleration. ### Important Note for Multi-Tenancy Deployments Kubeflow has a notion of [multi-tenancy](https://www.kubeflow.org/docs/components/multi-tenancy/overview/) built into its deployment. Kubeflow's multi-user isolation simplifies user operations because each user only views and edited the Kubeflow components and model artifacts defined in their configuration. Using the ZenML Kubeflow orchestrator on a multi-tenant deployment without any settings will result in the following error: ```shell HTTP response body: {"error":"Invalid input error: Invalid resource references for experiment. ListExperiment requires filtering by namespace.","code":3,"message":"Invalid input error: Invalid resource references for experiment. ListExperiment requires filtering by namespace.","details":[{"@type":"type.googleapis.com/api.Error","error_message":"Invalid resource references for experiment. ListExperiment requires filtering by namespace.","error_details":"Invalid input error: Invalid resource references for experiment. ListExperiment requires filtering by namespace."}]} ``` In order to get it to work, we need to leverage the `KubeflowOrchestratorSettings` referenced above. By setting the namespace option, and by passing in the right authentication credentials to the Kubeflow Pipelines Client, we can make it work. First, when registering your Kubeflow orchestrator, please make sure to include the `kubeflow_hostname` parameter. The `kubeflow_hostname` **must end with the `/pipeline` post-fix**. ```shell zenml orchestrator register \ --flavor=kubeflow \ --kubeflow_hostname= # e.g. https://mykubeflow.example.com/pipeline ``` Then, ensure that you use the pass the right settings before triggering a pipeline run. The following snippet will prove useful: ```python import requests from zenml.client import Client from zenml.integrations.kubeflow.flavors.kubeflow_orchestrator_flavor import ( KubeflowOrchestratorSettings, ) NAMESPACE = "namespace_name" # This is the user namespace for the profile you want to use USERNAME = "admin" # This is the username for the profile you want to use PASSWORD = "abc123" # This is the password for the profile you want to use # Use client_username and client_password and ZenML will automatically fetch a session cookie kubeflow_settings = KubeflowOrchestratorSettings( client_username=USERNAME, client_password=PASSWORD, user_namespace=NAMESPACE ) # You can also pass the cookie in `client_args` directly # kubeflow_settings = KubeflowOrchestratorSettings( # client_args={"cookies": session_cookie}, user_namespace=NAMESPACE # ) @pipeline( settings={ "orchestrator": kubeflow_settings } ) : ... if "__name__" == "__main__": # Run the pipeline ``` Note that the above is also currently not tested on all Kubeflow versions, so there might be further bugs with older Kubeflow versions. In this case, please reach out to us on [Slack](https://zenml.io/slack). #### Using secrets in settings The above example encoded the username and password in plain text as settings. You can also set them as secrets. ```shell zenml secret create kubeflow_secret \ --username=admin \ --password=abc123 ``` And then you can use them in code: ```python # Use client_username and client_password and ZenML will automatically fetch a session cookie kubeflow_settings = KubeflowOrchestratorSettings( client_username="{{kubeflow_secret.username}}", # secret reference client_password="{{kubeflow_secret.password}}", # secret reference user_namespace="namespace_name" ) ``` See full documentation of using ZenML secrets [here](https://docs.zenml.io/how-to/project-setup-and-management/interact-with-secrets). For more information and a full list of configurable attributes of the Kubeflow orchestrator, check out the [SDK Docs](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-kubeflow.html#zenml.integrations.kubeflow) .
ZenML Scarf
--- ### Book/Component Guide/Orchestrators/Kubernetes (docs/book/component-guide/orchestrators/kubernetes.md) --- description: Orchestrating your pipelines to run on Kubernetes clusters. --- # Kubernetes Orchestrator Using the ZenML `kubernetes` integration, you can orchestrate and scale your ML pipelines on a [Kubernetes](https://kubernetes.io/) cluster without writing a single line of Kubernetes code. The orchestrator uses standard Kubernetes APIs and works on managed cloud clusters (EKS, GKE, AKS), enterprise platforms like Red Hat OpenShift and SUSE Rancher, and self-managed clusters on-premises or in your own cloud. This Kubernetes-native orchestrator is a minimalist, lightweight alternative to other distributed orchestrators like Airflow or Kubeflow. Overall, the Kubernetes orchestrator is quite similar to the Kubeflow orchestrator in that it runs each pipeline step in a separate Kubernetes pod. However, the orchestration of the different pods is not done by Kubeflow but by a separate master pod that orchestrates the step execution via topological sort. Compared to Kubeflow, this means that the Kubernetes-native orchestrator is faster and much simpler since you do not need to install and maintain Kubeflow on your cluster. The Kubernetes-native orchestrator is an ideal choice for teams in need of distributed orchestration that do not want to go with a fully-managed offering. {% hint style="warning" %} This component is only meant to be used within the context of a [remote ZenML deployment scenario](https://docs.zenml.io/getting-started/deploying-zenml/). Usage with a local ZenML deployment may lead to unexpected behavior! {% endhint %} ## When to use it You should use the Kubernetes orchestrator if: * you already have a Kubernetes cluster—managed cloud, Red Hat OpenShift, SUSE Rancher, or self-managed on-premises. * you're looking for a lightweight way of running your pipelines on Kubernetes. * you're not willing to maintain [Kubeflow Pipelines](kubeflow.md) on your Kubernetes cluster. * you're not interested in paying for managed solutions like [Vertex](vertex.md). ## How to deploy it The Kubernetes orchestrator requires a Kubernetes cluster (version 1.21 or higher recommended). There are many ways to deploy a Kubernetes cluster using different cloud providers or on your custom infrastructure, and we can't possibly cover all of them, but you can check out our [production guide](https://docs.zenml.io/user-guides/production-guide). If the Kubernetes cluster is deployed remotely, another prerequisite is to deploy and connect to a [remote ZenML server](https://docs.zenml.io/getting-started/deploying-zenml/). ## How to use it To use the Kubernetes orchestrator, we need: * The ZenML `kubernetes` integration installed. If you haven't done so, run ```shell zenml integration install kubernetes ``` * [Docker](https://www.docker.com) installed and running. * A [remote artifact store](../artifact-stores/README.md) as part of your stack. * A [remote container registry](../container-registries/README.md) as part of your stack. * A Kubernetes cluster [deployed](kubernetes.md#how-to-deploy-it) (version 1.21 or higher recommended) * [kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl) installed and the name of the Kubernetes configuration context which points to the target cluster (i.e. run`kubectl config get-contexts` to see a list of available contexts) . This is optional (see below). {% hint style="info" %} It is recommended that you set up [a Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/service-connectors-guide) and use it to connect ZenML Stack Components to the remote Kubernetes cluster, especially If you are using a Kubernetes cluster managed by a cloud provider like AWS, GCP or Azure, This guarantees that your Stack is fully portable on other environments and your pipelines are fully reproducible. {% endhint %} We can then register the orchestrator and use it in our active stack. This can be done in two ways: 1. If you have [a Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/service-connectors-guide) configured to access the remote Kubernetes cluster, you no longer need to set the `kubernetes_context` attribute to a local `kubectl` context. In fact, you don't need the local Kubernetes CLI at all. You can [connect the stack component to the Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/service-connectors-guide#connect-stack-components-to-resources) instead: ``` /* Detailed source-code truncated for AI context efficiency. */ ``` 2. if you don't have a Service Connector on hand and you don't want to [register one](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/service-connectors-guide#register-service-connectors) , the local Kubernetes `kubectl` client needs to be configured with a configuration context pointing to the remote cluster. The `kubernetes_context` stack component must also be configured with the value of that context: ```shell zenml orchestrator register \ --flavor=kubernetes \ --kubernetes_context= # Register and activate a stack with the new orchestrator zenml stack register -o ... --set ``` {% hint style="info" %} ZenML will build a Docker image called `/zenml:` which includes your code and use it to run your pipeline steps in Kubernetes. Check out [this page](https://docs.zenml.io/how-to/customize-docker-builds/) if you want to learn more about how ZenML builds these images and how you can customize them. {% endhint %} You can now run any ZenML pipeline using the Kubernetes orchestrator: ```shell python file_that_runs_a_zenml_pipeline.py ``` If all went well, you should now see the logs of all Kubernetes pods in your terminal, and when running `kubectl get pods -n zenml`, you should also see that a pod was created in your cluster for each pipeline step. ### Interacting with pods via kubectl For debugging, it can sometimes be handy to interact with the Kubernetes pods directly via kubectl. To make this easier, we have added the following labels to all pods: * `run`: the name of the ZenML run. * `pipeline`: the name of the ZenML pipeline associated with this run. E.g., you can use these labels to manually delete all pods related to a specific pipeline: ```shell kubectl delete pod -n zenml -l pipeline=kubernetes_example_pipeline ``` ZenML sanitizes these label values before sending them to Kubernetes. Invalid characters are replaced, leading/trailing punctuation is removed, and values are truncated to fit Kubernetes label limits. In practice, a pipeline called `My GPU Pipeline!!!` will not appear as that exact string in a label selector. If a selector using the raw name does not match anything, inspect one of the created pods first: ```shell kubectl get pods -n zenml --show-labels ``` Then copy the sanitized label value from Kubernetes. ### Additional configuration Some configuration options for the Kubernetes orchestrator can only be set through the orchestrator config when you register it (and cannot be changed per-run or per-step through the settings): - **`incluster`** (default: False): If `True`, the orchestrator will attempt to load the in-cluster Kubernetes configuration and run the pipeline inside the same cluster it is running in, ignoring the `kubernetes_context`. If this fails, the orchestrator will fall back to using the linked service connector or the configured `kubernetes_context` configuration if provided, in that order. - **`kubernetes_context`**: The name of the Kubernetes context to use for running pipelines (ignored if using a service connector or `incluster`). - **`kubernetes_namespace`** (default: "zenml"): The Kubernetes namespace to use for running the pipelines. The namespace must already exist in the Kubernetes cluster. In that namespace, it will automatically create a Kubernetes service account called `zenml-service-account` and grant it `edit` RBAC role in that namespace. - **`local`** (default: False): If `True`, the orchestrator assumes it is connected to a local Kubernetes cluster and enables additional validations and operations for local development. - **`skip_local_validations`** (default: False): If `True`, skips the local validations that would otherwise be performed when `local` is set. - **`parallel_step_startup_waiting_period`**: How long (in seconds) to wait between starting parallel steps, useful for distributing server load in highly parallel pipelines. - **`pass_zenml_token_as_secret`** (default: False): By default, the Kubernetes orchestrator will pass a short-lived API token to authenticate to the ZenML server as an environment variable as part of the Pod manifest. If you want this token to be stored in a Kubernetes secret instead, set `pass_zenml_token_as_secret=True` when registering your orchestrator. If you do so, make sure the service connector that you configure for your has permissions to create Kubernetes secrets. Additionally, the service account used for the Pods running your pipeline must have permissions to delete secrets, otherwise the cleanup will fail and you'll be left with orphaned secrets. The following configuration options can be set either through the orchestrator config or overridden using `KubernetesOrchestratorSettings` (at the pipeline or step level): - **`synchronous`** (default: True): If `True`, the client waits for all steps to finish; if `False`, the pipeline runs asynchronously. - **`timeout`** (default: 0): How many seconds to wait for synchronous runs. `0` means to wait indefinitely. - **`stream_step_logs`** (default: True): If `True`, the orchestrator pod will stream the logs of the step pods. - **`service_account_name`**: The name of a Kubernetes service account to use for running the pipelines. If configured, it must point to an existing service account in the default or configured `namespace` that has associated RBAC roles granting permissions to create and manage pods in that namespace. This can also be configured as an individual pipeline setting in addition to the global orchestrator setting. - **`step_pod_service_account_name`**: Name of the service account to use for the step pods. - **`privileged`** (default: False): If the container should be run in privileged mode. - **`pod_settings`**: Node selectors, labels, affinity, and tolerations, secrets, environment variables, image pull secrets, the scheduler name and additional arguments to apply to the Kubernetes Pods running the steps of your pipeline. These can be either specified using the Kubernetes model objects or as dictionaries. - **`orchestrator_pod_settings`**: Node selectors, labels, affinity, tolerations, secrets, environment variables and image pull secrets to apply to the Kubernetes Pod that is responsible for orchestrating the pipeline and starting the other Pods. These can be either specified using the Kubernetes model objects or as dictionaries. - If you're specifying `init_containers` as part of the `additional_pod_spec_args` of the pod settings, you can use an `"{{ image }}"` placeholder string. This placeholder will be replaced by the image that is also used to run the orchestration or step container. - **`pod_name_prefix`**: Prefix for the pod names. A random suffix and the step name will be appended to create unique pod names. - **`pod_startup_timeout`** (default: 600): The maximum time to wait for a pending step pod to start (in seconds). The orchestrator will delete the pending pod after this time has elapsed and raise an error. If configured, the `pod_failure_retry_delay` and `pod_failure_backoff` settings will also be used to calculate the delay between retries. - **`pod_failure_max_retries`** (default: 3): The maximum number of retries to create a step pod that fails to start. - **`pod_failure_retry_delay`** (default: 10): The delay (in seconds) between retries to create a step pod that fails to start. - **`pod_failure_backoff`** (default: 1.0): The backoff factor for pod failure retries and pod startup retries. - **`backoff_limit_margin`** (default 0): The value to add to the backoff limit in addition to the [step retries](../../how-to/steps-pipelines/advanced_features.md#automatic-step-retries). The retry configuration defined on the step defines the maximum number of retries that the server will accept for a step. For this orchestrator, this controls how often the job running the step will try to start the step pod. There are some circumstances however where the job will start the pod, but the pod doesn't actually get to the point of running the step. That means the server will not receive the maximum amount of retry requests, which in turn causes other inconsistencies like wrong step statuses. To mitigate this, this attribute allows to add a margin to the backoff limit. This means that the job will retry the pod startup for the configured amount of times plus the margin, which increases the chance of the server receiving the maximum amount of retry requests. - **`fail_on_container_waiting_reasons`**: List of container waiting reasons that should cause the job to fail immediately. This should be set to a list of nonrecoverable reasons, which if found in any `pod.status.containerStatuses[*].state.waiting.reason` of a job pod, should cause the job to fail immediately. - **`job_monitoring_interval`** (default 3): The interval in seconds to monitor the job. Each interval is used to check for container issues and streaming logs for the job pods. - **`max_parallelism`**: By default the Kubernetes orchestrator immediately spins up a pod for every step that can run already because all its upstream steps have finished. For pipelines with many parallel steps, it can be desirable to limit the amount of parallel steps in order to reduce the load on the Kubernetes cluster. This option can be used to specify the maximum amount of steps pods that can be running at any time. - **`successful_jobs_history_limit`**, **`failed_jobs_history_limit`**, **`ttl_seconds_after_finished`**: Control the cleanup behavior of jobs and pods created by the orchestrator. - **`concurrency_policy`**: CronJob concurrency policy for scheduled pipelines. Controls whether concurrent job executions are allowed. Valid values: `Allow` (Kubernetes default), `Forbid`, `Replace`. Only applies when a pipeline has a cron schedule. - **`starting_deadline_seconds`**: CronJob starting deadline in seconds for scheduled pipelines. If a scheduled run misses its trigger time, it can still start within this window. Only applies when a pipeline has a cron schedule. Note: this is different from `active_deadline_seconds`, which limits how long a *running* job can execute. - **`prevent_orchestrator_pod_caching`** (default: False): If `True`, the orchestrator pod will not try to compute cached steps before starting the step pods. #### Kubernetes permissions and service accounts For production setups, use separate identities for: 1. the identity that starts the orchestrator job (typically via a linked Service Connector), 2. the service account used by the orchestrator pod, 3. the service account used by step pods. Using one service account for all three works, but it is broader than necessary. **Starter identity (Service Connector / kubeconfig identity)** Minimum permissions to start a non-scheduled orchestrator run: - `batch/jobs`: `create` Common optional permissions: - For synchronous startup monitoring from the submitter: - `batch/jobs`: `get` - `core/pods`: `list` - `core/pods/log`: `get` - For scheduled pipelines (CronJobs): - `batch/cronjobs`: `create`, `patch`, `delete` - If `pass_zenml_token_as_secret=True`: - `core/secrets`: `create`, `patch`, `delete` - If you let ZenML auto-create the default `zenml-service-account`: - `core/serviceaccounts`: `create` - `rbac.authorization.k8s.io/rolebindings`: `create` **Orchestrator pod service account (`service_account_name`)** The orchestrator pod launches and monitors step jobs. It needs: - `batch/jobs`: `create`, `get`, `list`, `patch`, `delete` - `core/pods`: `get`, `list` - `core/pods/log`: `get` If `pass_zenml_token_as_secret=True`, also grant: - `core/secrets`: `delete` **Step pod service account (`step_pod_service_account_name`)** Step containers do not need Kubernetes API access for normal execution in this orchestrator flow. Unless your step code explicitly calls the Kubernetes API, you can keep this account with no additional Kubernetes RBAC grants. ``` /* Detailed source-code truncated for AI context efficiency. */ ``` ### Define settings on the step level You can also define settings on the step level, which will override the settings defined at the pipeline level. This is helpful when you want to run a specific step with a different configuration like affinity for more powerful hardware or a different Kubernetes service account. Learn more about the hierarchy of settings [here](https://docs.zenml.io/concepts/steps_and_pipelines/configuration). ```python k8s_settings = KubernetesOrchestratorSettings( pod_settings={ "node_selectors": { "cloud.google.com/gke-nodepool": "gpu-pool", }, "tolerations": [ V1Toleration( key="gpu", operator="Equal", value="present", effect="NoSchedule" ), ] } ) @step(settings={"orchestrator": k8s_settings}) def train_model(data: dict) -> None: ... @pipeline() def simple_ml_pipeline(parameter: int): ... ``` This code will now run the `train_model` step on a GPU-enabled node in the `gpu-pool` node pool while the rest of the pipeline can run on ordinary nodes. Check out the [SDK docs](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-kubernetes.html#zenml.integrations.kubernetes) for a full list of available attributes and [this docs page](https://docs.zenml.io/concepts/steps_and_pipelines/configuration) for more information on how to specify settings. For more information and a full list of configurable attributes of the Kubernetes orchestrator, check out the [SDK Docs](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-kubernetes.html#zenml.integrations.kubernetes) . ### Enabling CUDA for GPU-backed hardware Note that if you wish to use this orchestrator to run steps on a GPU, you will need to follow [the instructions on this page](https://docs.zenml.io/user-guides/tutorial/distributed-training/) to ensure that it works. It requires adding some extra settings customization and is essential to enable CUDA for the GPU to give its full acceleration. ### Running scheduled pipelines with Kubernetes The Kubernetes orchestrator supports scheduling pipelines through Kubernetes CronJobs. This feature allows you to run your pipelines on a recurring schedule without manual intervention. #### How scheduling works When you add a schedule to a pipeline running on the Kubernetes orchestrator, ZenML: 1. Creates a Kubernetes CronJob resource instead of a regular Pod 2. Configures the CronJob to use the same container image, command, and settings as your pipeline 3. Sets the CronJob's schedule field to match your provided cron expression The Kubernetes scheduler then takes over and handles executing your pipeline on schedule. #### Setting up a scheduled pipeline You can add a schedule to your pipeline using the `Schedule` class: ```python from zenml.config.schedule import Schedule from zenml import pipeline @pipeline() def my_kubernetes_pipeline(): # Your pipeline steps here ... # Create a schedule using a cron expression schedule = Schedule(cron_expression="5 2 * * *") # Runs at 2:05 AM daily # Attach the schedule to your pipeline scheduled_pipeline = my_kubernetes_pipeline.with_options(schedule=schedule) # Run the pipeline once to register the schedule scheduled_pipeline() ``` #### Customizing CronJob behavior You can customize the CronJob configuration using `KubernetesOrchestratorSettings`. This is useful when your cluster has specific policies for CronJob resources: ```python from zenml.integrations.kubernetes.flavors import KubernetesOrchestratorSettings k8s_settings = KubernetesOrchestratorSettings( concurrency_policy="Forbid", # Prevent concurrent job executions starting_deadline_seconds=20, # Missed-schedule start window successful_jobs_history_limit=2, # Keep last 2 successful jobs failed_jobs_history_limit=1, # Keep last 1 failed job active_deadline_seconds=180, # Job runtime limit (3 minutes) orchestrator_job_backoff_limit=2, # Max retries before marking failed ttl_seconds_after_finished=3600, # Cleanup completed jobs after 1 hour pod_stop_grace_period=90, # Graceful shutdown window ) scheduled_pipeline = my_kubernetes_pipeline.with_options( schedule=schedule, settings={"orchestrator.kubernetes": k8s_settings}, ) scheduled_pipeline() ``` {% hint style="info" %} `starting_deadline_seconds` controls how late a CronJob can start after its scheduled time (missed-schedule window), while `active_deadline_seconds` limits how long a running job can execute (runtime timeout). These are independent settings that apply at different stages of the job lifecycle. {% endhint %} Cron expressions follow the standard format (`minute hour day-of-month month day-of-week`): * `"0 * * * *"` - Run hourly at the start of the hour * `"0 0 * * *"` - Run daily at midnight * `"0 0 * * 0"` - Run weekly on Sundays at midnight * `"0 0 1 * *"` - Run monthly on the 1st at midnight #### Verifying your scheduled pipeline To check that your pipeline has been scheduled correctly: 1. Using the ZenML CLI: ```shell zenml pipeline schedule list ``` 2. Using kubectl to check the created CronJob: ```shell kubectl get cronjobs -n zenml kubectl describe cronjob -n zenml ``` The CronJob name will be based on your pipeline name with a random suffix for uniqueness. #### Managing scheduled pipelines To view your scheduled jobs and their status: ```shell # List all CronJobs kubectl get cronjobs -n zenml ``` To update a schedule's cron expression: ```bash zenml pipeline schedule update --cron-expression='0 4 * * *' ``` #### Pausing and resuming a scheduled pipeline You can temporarily pause a scheduled pipeline without deleting it using the deactivate command. This sets the CronJob's `suspend` field to `true`, preventing any new executions while preserving the CronJob resource: ```bash # Pause the schedule (sets suspend=true on the CronJob) zenml pipeline schedule deactivate # Resume the schedule (sets suspend=false on the CronJob) zenml pipeline schedule activate ``` You can verify the suspend status using kubectl: ```shell kubectl get cronjob -n zenml -o jsonpath='{.spec.suspend}' ``` #### Deleting a scheduled pipeline When you no longer need a scheduled pipeline, you can delete the schedule. By default, deletion archives the schedule (soft delete), which preserves references in historical pipeline runs: ```bash # Archive the schedule (soft delete - default) # This removes the CronJob from Kubernetes and archives the schedule in ZenML zenml pipeline schedule delete # Permanently delete the schedule (hard delete) # This removes the CronJob and permanently deletes all schedule references zenml pipeline schedule delete --hard ``` #### Troubleshooting If your scheduled pipeline isn't running as expected: 1. Verify the CronJob exists and has the correct schedule: ```shell kubectl get cronjob -n zenml ``` 2. Check the CronJob's recent events and status: ```shell kubectl describe cronjob -n zenml ``` 3. Look at logs from recent job executions: ```shell kubectl logs job/ -n zenml ``` Common issues include incorrect cron expressions, insufficient permissions for the service account, or resource constraints. For a tutorial on how to work with schedules in ZenML, check out our ['Managing Scheduled Pipelines'](https://docs.zenml.io/user-guides/tutorial/managing-scheduled-pipelines) docs page. ## Best practices for highly parallel pipelines If you're trying to run pipelines with multiple parallel steps, there are some configuration options that you can tweak to ensure the best possible performance: - Ensure you enable [retries for your steps](../../how-to/steps-pipelines/advanced_features.md#automatic-step-retries) in case something doesn't work - Add a `backoff_limit_margin` to deal with unexpected Kubernetes evictions/preemptions - Limit the amount of maximum parallel steps using the `max_parallelism` setting - Disable streaming step logs using the `stream_step_logs` setting. All steps will have their logs tracked individually, so streaming them to the orchestrator pod is often unnecessary and can slow things down if your steps are logging a lot.
ZenML Scarf
--- ### Book/Component Guide/Orchestrators/Lightning (docs/book/component-guide/orchestrators/lightning.md) --- description: Orchestrating your pipelines to run on Lightning AI. --- # Lightning AI Orchestrator [Lightning AI Studio](https://lightning.ai/) is a platform that simplifies the development and deployment of AI applications. The Lightning AI orchestrator is an integration provided by ZenML that allows you to run your pipelines on Lightning AI's infrastructure, leveraging its scalable compute resources and managed environment. {% hint style="warning" %} This component is only meant to be used within the context of a [remote ZenML deployment scenario](https://docs.zenml.io/getting-started/deploying-zenml/). Usage with a local ZenML deployment may lead to unexpected behavior! {% endhint %} ## When to use it * You are looking for a fast and easy way to run your pipelines on GPU instances * You're already using Lightning AI for your machine learning projects * You want to leverage Lightning AI's managed infrastructure for running your pipelines * You're looking for a solution that simplifies the deployment and scaling of your ML workflows * You want to take advantage of Lightning AI's optimizations for machine learning workloads ## How to deploy it To use the [Lightning AI Studio](https://lightning.ai/) orchestrator, you need to have a Lightning AI account and the necessary credentials. You don't need to deploy any additional infrastructure, as the orchestrator will use Lightning AI's managed resources. ## How it works The Lightning AI orchestrator is a ZenML orchestrator that runs your pipelines on Lightning AI's infrastructure. When you run a pipeline with the Lightning AI orchestrator, ZenML will archive your current ZenML repository and upload it to the Lightning AI studio. Once the code is archived, using `lightning-sdk`, ZenML will create a new stduio in Lightning AI and upload the code to it. Then ZenML runs list of commands via `studio.run()` to prepare for the pipeline run (e.g. installing dependencies, setting up the environment). Finally, ZenML will run the pipeline on Lightning AI's infrastructure. * You can always use an already existing studio by specifying the `main_studio_name` in the `LightningOrchestratorSettings`. * The orchestartor supports a async mode, which means that the pipeline will be run in the background and you can check the status of the run in the ZenML Dashboard or the Lightning AI Studio. * You can specify a list of custom commands that will be executed before running the pipeline. This can be useful for installing dependencies or setting up the environment. * The orchestrator supports both CPU and GPU machine types. You can specify the machine type in the `LightningOrchestratorSettings`. ## How to use it To use the Lightning AI orchestrator, you need: * The ZenML `lightning` integration installed. If you haven't done so, run ```shell zenml integration install lightning ``` * A [remote artifact store](https://docs.zenml.io/stacks/artifact-stores/) as part of your stack. * [Lightning AI credentials](lightning.md#lightning-ai-credentials) ### Lightning AI credentials You will need the following credentials to use the Lightning AI orchestrator: * `LIGHTNING_USER_ID`: Your Lightning AI user ID * `LIGHTNING_API_KEY`: Your Lightning AI API key * `LIGHTNING_USERNAME`: Your Lightning AI username (optional) * `LIGHTNING_TEAMSPACE`: Your Lightning AI teamspace (optional) * `LIGHTNING_ORG`: Your Lightning AI organization (optional) To find these credentials, log in to your [Lightning AI](https://lightning.ai/) account and click on your avatar in the top right corner. Then click on "Global Settings". There are some tabs you can click on the left hand side. Click on the one that says "Keys" and you will see two ways to get your credentials. The 'Login via CLI' will give you the `LIGHTNING_USER_ID` and `LIGHTNING_API_KEY`. You can set these credentials as environment variables or you can set them when registering the orchestrator: ```shell zenml orchestrator register lightning_orchestrator \ --flavor=lightning \ --user_id= \ --api_key= \ --username= \ # optional --teamspace= \ # optional --organization= # optional ``` We can then register the orchestrator and use it in our active stack: ```bash # Register and activate a stack with the new orchestrator zenml stack register lightning_stack -o lightning_orchestrator ... --set ``` You can configure the orchestrator at pipeline level, using the `orchestrator` parameter. ```python from zenml.integrations.lightning.flavors.lightning_orchestrator_flavor import LightningOrchestratorSettings lightning_settings = LightningOrchestratorSettings( main_studio_name="my_studio", machine_type="cpu", async_mode=True, custom_commands=["pip install -r requirements.txt", "do something else"] ) @pipeline( settings={ "orchestrator.lightning": lightning_settings } ) def my_pipeline(): ... ``` {% hint style="info" %} ZenML will archive the current zenml repository (the code within the path where you run `zenml init`) and upload it to the Lightning AI studio. For this reason you need make sure that you have run `zenml init` in the same repository root directory where you are running your pipeline. {% endhint %} {% hint style="info" %} The `custom_commands` attribute allows you to specify a list of shell commands that will be executed before running the pipeline. This can be useful for installing dependencies or setting up the environment, The commands will be executed in the root directory of the uploaded and extracted ZenML repository. {% endhint %} You can now run any ZenML pipeline using the Lightning AI orchestrator: ```shell python file_that_runs_a_zenml_pipeline.py ``` ### Lightning AI UI Lightning AI provides its own UI where you can monitor and manage your running applications, including the pipelines orchestrated by ZenML. For any runs executed on Lightning AI, you can get the URL to the Lightning AI UI in Python using the following code snippet: ```python from zenml.client import Client pipeline_run = Client().get_pipeline_run("") orchestrator_url = pipeline_run.run_metadata["orchestrator_url"].value ``` ### Additional configuration For additional configuration of the Lightning AI orchestrator, you can pass `LightningOrchestratorSettings` which allows you to configure various aspects of the Lightning AI execution environment: ```python from zenml.integrations.lightning.flavors.lightning_orchestrator_flavor import LightningOrchestratorSettings lightning_settings = LightningOrchestratorSettings( main_studio_name="my_studio", machine_type="cpu", async_mode=True, custom_commands=["pip install -r requirements.txt", "do something else"] ) ``` These settings can then be specified on either a pipeline-level or step-level: ```python # Either specify on pipeline-level @pipeline( settings={ "orchestrator.lightning": lightning_settings } ) def my_pipeline(): ... # OR specify settings on step-level @step( settings={ "orchestrator.lightning": lightning_settings } ) def my_step(): ... ``` Check out the [SDK docs](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-lightning.html#zenml.integrations.lightning) for a full list of available attributes and [this docs page](https://docs.zenml.io/concepts/steps_and_pipelines/configuration) for more information on how to specify settings. To use GPUs with the Lightning AI orchestrator, you need to specify a GPU-enabled machine type in your settings: ```python lightning_settings = LightningOrchestratorSettings( machine_type="gpu", # or `A10G` e.g. ) ``` Make sure to check [Lightning AI's documentation](https://lightning.ai/docs/overview/studios/change-gpus) for the available GPU-enabled machine types and their specifications.
ZenML Scarf
--- ### Book/Component Guide/Orchestrators/Local Docker (docs/book/component-guide/orchestrators/local-docker.md) --- description: Orchestrating your pipelines to run in Docker. --- # Local Docker Orchestrator The local Docker orchestrator is an [orchestrator](./) flavor that comes built-in with ZenML and runs your pipelines locally using Docker. ### When to use it You should use the local Docker orchestrator if: * you want the steps of your pipeline to run locally in isolated environments. * you want to debug issues that happen when running your pipeline in Docker containers without waiting and paying for remote infrastructure. ### How to deploy it To use the local Docker orchestrator, you only need to have [Docker](https://www.docker.com/) installed and running. ### How to use it To use the local Docker orchestrator, we can register it and use it in our active stack: ```shell zenml orchestrator register --flavor=local_docker # Register and activate a stack with the new orchestrator zenml stack register -o ... --set ``` You can now run any ZenML pipeline using the local Docker orchestrator: ```shell python file_that_runs_a_zenml_pipeline.py ``` #### Additional configuration For additional configuration of the Local Docker orchestrator, you can pass `LocalDockerOrchestratorSettings` when defining or running your pipeline. Check out the [SDK docs](https://sdkdocs.zenml.io/latest/core_code_docs/core-orchestrators.html#zenml.orchestrators.local_docker) for a full list of available attributes and [this docs page](https://docs.zenml.io/concepts/steps_and_pipelines/configuration) for more information on how to specify settings. A full list of what can be passed in via the `run_args` can be found [in the Docker Python SDK documentation](https://docker-py.readthedocs.io/en/stable/containers.html). For more information and a full list of configurable attributes of the local Docker orchestrator, check out the [SDK Docs](https://sdkdocs.zenml.io/latest/core_code_docs/core-orchestrators.html#zenml.orchestrators.local_docker) . For example, if you wanted to specify the CPU count available for the Docker image (note: only configurable for Windows), you could write a simple pipeline like the following: ```python from zenml import step, pipeline from zenml.orchestrators.local_docker.local_docker_orchestrator import ( LocalDockerOrchestratorSettings, ) @step def return_one() -> int: return 1 settings = { "orchestrator": LocalDockerOrchestratorSettings( run_args={"cpu_count": 3} ) } @pipeline(settings=settings) def simple_pipeline(): return_one() ``` #### Enabling CUDA for GPU-backed hardware Note that if you wish to use this orchestrator to run steps on a GPU, you will need to follow [the instructions on this page](https://docs.zenml.io/user-guides/tutorial/distributed-training/) to ensure that it works. It requires adding some extra settings customization and is essential to enable CUDA for the GPU to give its full acceleration.
ZenML Scarf
--- ### Book/Component Guide/Orchestrators/Local (docs/book/component-guide/orchestrators/local.md) --- description: Orchestrating your pipelines to run locally. --- # Local Orchestrator The local orchestrator is an [orchestrator](./) flavor that comes built-in with ZenML and runs your pipelines locally. ### When to use it The local orchestrator is part of your default stack when you're first getting started with ZenML. Due to it running locally on your machine, it requires no additional setup and is easy to use and debug. You should use the local orchestrator if: * you're just getting started with ZenML and want to run pipelines without setting up any cloud infrastructure. * you're writing a new pipeline and want to experiment and debug quickly ### How to deploy it The local orchestrator comes with ZenML and works without any additional setup. ### How to use it To use the local orchestrator, we can register it and use it in our active stack: ```shell zenml orchestrator register --flavor=local # Register and activate a stack with the new orchestrator zenml stack register -o ... --set ``` You can now run any ZenML pipeline using the local orchestrator: ```shell python file_that_runs_a_zenml_pipeline.py ``` For more information and a full list of configurable attributes of the local orchestrator, check out the [SDK Docs](https://sdkdocs.zenml.io/latest/core_code_docs/core-orchestrators.html#zenml.orchestrators.local) .
ZenML Scarf
--- ### Book/Component Guide/Orchestrators/Modal (docs/book/component-guide/orchestrators/modal.md) --- description: Orchestrating your pipelines to run in Modal Sandboxes. --- # Modal Orchestrator The Modal orchestrator runs ZenML pipelines on [Modal](https://modal.com) using Modal Sandboxes. A Modal Sandbox is a remote container process: ZenML builds a Docker image for your pipeline, pushes it to your stack's container registry, and asks Modal to start that image in Modal's cloud infrastructure. {% hint style="info" %} Everywhere on this page, "sandbox" refers to Modal's Sandbox compute primitive (`modal.Sandbox`) — a Modal product concept, not a ZenML stack component. {% endhint %} The important runtime story is: 1. You start a ZenML pipeline run from your machine or CI system. 2. ZenML submits one Modal **orchestration sandbox** for the pipeline run. 3. That orchestration sandbox starts and monitors the step sandboxes that actually run your step code. 4. Step artifacts are read from and written to your ZenML artifact store. 5. ZenML stores the Modal sandbox IDs in run metadata so the run can be monitored and stopped later. This makes Modal useful when you want the whole pipeline, not just selected steps, to run away from your local machine while still using Modal's fast startup and resource selection. {% hint style="warning" %} This component is meant for stacks that use a ZenML server reachable from Modal. If your run depends on a local ZenML store or localhost-only services, the Modal sandbox will not be able to call back to your machine. {% endhint %} ## When to use it You should use the Modal orchestrator if: * you want your entire ZenML pipeline to run on Modal instead of locally. * your steps need Modal CPU, memory, GPU, cloud, or region settings. * you want both static and dynamic ZenML pipelines to run on Modal. * you already have remote ZenML stack components for artifacts, images, and code execution. If you only want a few selected steps to run on Modal while the rest of the pipeline is orchestrated somewhere else, use the [Modal step operator](../step-operators/modal.md) instead. ## Requirements To use the Modal orchestrator, you need: * The ZenML `modal` integration installed: ```shell zenml integration install modal ``` * A [Modal account](https://modal.com/signup). * Modal authentication configured either with `modal setup`, Modal environment variables, or the `token_id` and `token_secret` fields on the ZenML orchestrator component. * Docker installed and running where ZenML builds images. * A [remote artifact store](../artifact-stores/README.md). Modal cannot read artifacts from a local directory on your laptop. * A [remote container registry](../container-registries/README.md). Modal pulls the Docker image from this registry. * An [image builder](../image-builders/README.md) in your stack so ZenML can build the image that Modal runs. The stack validator rejects local artifact stores, local container registries, and stack components that expose local filesystem paths. The failure case is concrete: the Modal sandbox starts in Modal's infrastructure, asks for `/Users/you/project/artifacts/...`, and that path only exists on your machine. A remote artifact store and remote registry avoid that problem. {% hint style="warning" %} Modal imports images from a registry as `linux/amd64` images. If you build locally on Apple Silicon, configure your ZenML Docker settings to build `linux/amd64` images before using Modal. See the Docker build warning in the [Modal step operator docs](../step-operators/modal.md) for an example configuration. {% endhint %} ## How to use it Register the orchestrator and add it to a stack with a remote artifact store, remote container registry, and image builder: ```shell zenml orchestrator register --flavor=modal \ --token_id= \ --token_secret= zenml stack register \ -o \ -a \ -c \ -i \ ... \ --set ``` If you already authenticated Modal with `modal setup`, or you provide Modal credentials through Modal's normal environment variables, you can omit `token_id` and `token_secret`. If you configure one of those fields on the orchestrator component, you must configure both. Then run your pipeline as usual: ```shell python run_pipeline.py ``` ZenML builds and pushes the image, submits the orchestration sandbox to Modal, and records the Modal sandbox metadata on the ZenML pipeline run. ## Static and dynamic pipeline behavior For a **static pipeline**, ZenML submits one Modal orchestration sandbox. Inside that sandbox, ZenML follows the step dependency graph and starts one child Modal sandbox for each step once its upstream steps have completed. If a step can be loaded from cache, the controller records the cached step run instead of starting a new Modal sandbox for that step. Static Modal pipelines support all three ZenML execution modes: `FAIL_FAST`, `STOP_ON_FAILURE`, and `CONTINUE_ON_FAILURE`. For a **dynamic pipeline**, ZenML submits one Modal orchestration sandbox that runs ZenML's dynamic pipeline entrypoint. Dynamic Modal pipelines still follow ZenML's current dynamic pipeline limitation: `CONTINUE_ON_FAILURE` behaves like `STOP_ON_FAILURE`, so after a dynamic step fails, ZenML does not keep launching later independent dynamic steps. Dynamic steps can run in two ways: * Steps that need isolation run as child Modal sandboxes. This includes steps with step-level resource settings or step-level Docker settings that differ from the pipeline image. Steps that use a step operator are isolated too, but they run on that step operator's infrastructure instead of a child Modal sandbox. * Steps that do not need isolation can run inside the dynamic orchestration sandbox process. In both modes, ZenML stores the orchestration sandbox ID on the pipeline run and child sandbox IDs on step runs. These metadata entries are what ZenML uses later for status checks and stop requests. ## Configuring Modal authentication The Modal orchestrator has optional `token_id` and `token_secret` fields. When both are configured, ZenML creates an explicit Modal SDK client from those credentials and passes that client to Modal SDK calls. When they are not configured, ZenML does not pass an explicit client. Modal then uses its normal authentication behavior, for example credentials from `modal setup`, Modal environment variables, or `~/.modal.toml`. ZenML also passes the short-lived ZenML server token needed by the runtime into Modal as a Modal Secret instead of a plain sandbox environment variable. Regular runtime environment variables are passed as normal sandbox environment variables. ## Configuring resources Use `ModalOrchestratorSettings` together with ZenML `ResourceSettings` to select Modal resources: ```python from zenml import pipeline, step from zenml.config import ResourceSettings from zenml.integrations.modal.flavors import ModalOrchestratorSettings gpu_step_settings = ModalOrchestratorSettings( gpu="A100", # GPU type, e.g. "T4" or "A100" # region="us-east-1", # optional; availability depends on your Modal plan # cloud="aws", # optional; availability depends on your Modal plan ) step_resources = ResourceSettings( cpu_count=2, memory="32GB", gpu_count=1, ) @step( settings={ "orchestrator": gpu_step_settings, "resources": step_resources, } ) def train_model() -> None: ... @pipeline def training_pipeline() -> None: train_model() ``` Orchestrator-wide options such as `modal_environment`, `timeout`, and `synchronous` can be set in a `ModalOrchestratorSettings` object passed at the pipeline level when you need them. {% hint style="warning" %} Set GPU types at the step level, not the pipeline level. Pipeline-level orchestrator settings configure the orchestration sandbox and are inherited by every step, so a pipeline-level `gpu` attaches a GPU to all step sandboxes and to the controller — where it sits idle while you pay for it, unless your dynamic pipeline runs GPU step code inside the orchestration sandbox process. {% endhint %} The resource mapping is: | ZenML setting | Modal sandbox setting | | --- | --- | | `ResourceSettings.cpu_count` | `cpu` | | `ResourceSettings.memory` | `memory`, converted to MB and rounded up | | `ResourceSettings.gpu_count` + `ModalOrchestratorSettings.gpu` | `gpu`, for example `"A100"` or `"A100:2"` | | `ModalOrchestratorSettings.timeout` | `timeout` in seconds | | `ModalOrchestratorSettings.cloud` | `cloud` | | `ModalOrchestratorSettings.region` | `region` | Important details: * If `gpu_count > 0`, you must also set `ModalOrchestratorSettings.gpu`. ZenML needs the count and the Modal GPU type to build the Modal `gpu` argument. * If `gpu` is set and `gpu_count` is omitted, Modal uses one GPU of that type. * If `gpu` is set and `gpu_count=0`, ZenML runs on CPU and logs a warning that the GPU type was ignored. * Pipeline-level resources apply to the orchestration sandbox. * Static step-level resources apply to the child sandbox for that step. * Dynamic step-level resources cause the step to run as an isolated child sandbox, and the resources apply to that child sandbox. The `modal_environment` setting selects the Modal environment used for `modal.App.lookup(..., environment_name=...)`. It is separate from ZenML runtime environment variables. ## Synchronous and asynchronous runs By default, `synchronous=True`. In that mode, the process that submitted the run waits until the Modal orchestration sandbox finishes. If the controller detects failed child step sandboxes, it exits with a non-zero code, and ZenML reports the run as failed to the submitting process. If you set `synchronous=False`, ZenML submits the orchestration sandbox and returns after the sandbox has started successfully: ```python from zenml.integrations.modal.flavors import ModalOrchestratorSettings settings = { "orchestrator": ModalOrchestratorSettings(synchronous=False), } ``` The pipeline still runs on Modal. You can monitor it from ZenML because the pipeline run metadata contains the Modal orchestration sandbox ID. ## Stopping runs Stop behavior depends on the pipeline type and whether the stop is graceful or forceful. For a graceful stop of a static Modal run, ZenML marks the pipeline run as `STOPPING`. The orchestration sandbox notices that status, stops scheduling new step sandboxes, lets already-running child sandboxes finish, and then publishes the final run status. For a force stop of a static Modal run, ZenML terminates the Modal sandboxes it knows about: 1. ZenML refreshes the pipeline run metadata and reads known child sandbox IDs from step metadata and pipeline-run fallback metadata. 2. ZenML terminates each known child sandbox that is still running. 3. ZenML terminates the orchestration sandbox. 4. ZenML refreshes the run metadata once more and terminates any child sandbox IDs that appeared during cleanup. 5. If a sandbox has already finished, ZenML leaves it alone. For dynamic Modal runs, graceful stop requests use the same termination flow as force stop requests. ZenML terminates known child sandboxes and the orchestration sandbox, refreshes run metadata, and then terminates any late child sandboxes that appeared while cleanup was running. ## Current v1 limitations The first version of the Modal orchestrator has a few intentional limits: * Scheduled pipelines are not supported yet. Static and dynamic submissions with schedules are rejected. * The orchestrator does not handle step retries internally. ZenML's normal step retry behavior still applies. * Client-side caching is disabled for the orchestrator component. The static controller can still reuse already-cached steps when the ZenML run logic marks a step as cached. * Stop and status behavior depends on Modal sandbox IDs stored in ZenML metadata. If those IDs are missing, ZenML can only report the last known run or step status and may not be able to terminate already-started sandboxes. For more details on the available settings, see the [Modal integration SDK docs](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-modal.html).
ZenML Scarf
--- ### Book/Component Guide/Orchestrators/Sagemaker (docs/book/component-guide/orchestrators/sagemaker.md) --- description: Orchestrating your pipelines to run on Amazon Sagemaker. --- # AWS Sagemaker Orchestrator [Sagemaker Pipelines](https://aws.amazon.com/sagemaker/pipelines) is a serverless ML workflow tool running on AWS. It is an easy way to quickly run your code in a production-ready, repeatable cloud orchestrator that requires minimal setup without provisioning and paying for standby compute. {% hint style="warning" %} This component is only meant to be used within the context of a [remote ZenML deployment scenario](https://docs.zenml.io/getting-started/deploying-zenml/). Usage with a local ZenML deployment may lead to unexpected behavior! {% endhint %} ## When to use it You should use the Sagemaker orchestrator if: * you're already using AWS. * you're looking for a proven production-grade orchestrator. * you're looking for a UI in which you can track your pipeline runs. * you're looking for a managed solution for running your pipelines. * you're looking for a serverless solution for running your pipelines. ## How it works The ZenML Sagemaker orchestrator works with [Sagemaker Pipelines](https://aws.amazon.com/sagemaker/pipelines), which can be used to construct machine learning pipelines. Under the hood, for each ZenML pipeline step, it creates a SageMaker `PipelineStep`, which contains a Sagemaker Processing or Training job. ## How to deploy it {% hint style="info" %} Would you like to skip ahead and deploy a full ZenML cloud stack already, including a Sagemaker orchestrator? Check out the[in-browser stack deployment wizard](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/deploy-a-cloud-stack), the [stack registration wizard](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/register-a-cloud-stack), or [the ZenML AWS Terraform module](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/deploy-a-cloud-stack-with-terraform) for a shortcut on how to deploy & register this stack component. {% endhint %} In order to use a Sagemaker AI orchestrator, you need to first deploy [ZenML to the cloud](https://docs.zenml.io/getting-started/deploying-zenml/). It would be recommended to deploy ZenML in the same region as you plan on using for Sagemaker, but it is not necessary to do so. You must ensure that you are connected to the remote ZenML server before using this stack component. The only other thing necessary to use the ZenML Sagemaker orchestrator is enabling the relevant permissions for your particular role. ## How to use it To use the Sagemaker orchestrator, we need: * The ZenML `aws` and `s3` integrations installed. If you haven't done so, run ```shell zenml integration install aws s3 ``` * [Docker](https://www.docker.com) installed and running. * A [remote artifact store](https://docs.zenml.io/stacks/artifact-stores/) as part of your stack (configured with an `authentication_secret` attribute). * A [remote container registry](https://docs.zenml.io/stacks/container-registries/) as part of your stack. * An IAM role with specific SageMaker permissions following the principle of least privilege (see [Required IAM Permissions](#required-iam-permissions) below) as well as `sagemaker.amazonaws.com` added as a Principal Service. Avoid using the broad `AmazonSageMakerFullAccess` managed policy in production environments. * The local client (whoever is running the pipeline) will also need specific permissions to launch SageMaker jobs (see [Required IAM Permissions](#required-iam-permissions) below for the minimal required permissions). * If you want to use schedules, you also need to set up the correct roles, permissions and policies covered [here](sagemaker.md#required-iam-permissions-for-schedules). There are three ways you can authenticate your orchestrator and link it to the IAM role you have created: {% tabs %} {% tab title="Authentication via Service Connector" %} The recommended way to authenticate your SageMaker orchestrator is by registering an [AWS Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/aws-service-connector) and connecting it to your SageMaker orchestrator. If you plan to use scheduled pipelines, ensure the credentials used by the service connector have the necessary EventBridge and IAM permissions listed in the [Required IAM Permissions](sagemaker.md#required-iam-permissions) section: ```shell zenml service-connector register --type aws -i zenml orchestrator register \ --flavor=sagemaker \ --execution_role= zenml orchestrator connect --connector zenml stack register -o ... --set ``` {% endtab %} {% tab title="Explicit Authentication" %} Instead of creating a service connector, you can also configure your AWS authentication credentials directly in the orchestrator. If you plan to use scheduled pipelines, ensure these credentials have the necessary EventBridge and IAM permissions listed in the [Required IAM Permissions](sagemaker.md#required-iam-permissions) section: ```shell zenml orchestrator register \ --flavor=sagemaker \ --execution_role= \ --aws_access_key_id=... --aws_secret_access_key=... --region=... zenml stack register -o ... --set ``` See the [`SagemakerOrchestratorConfig` SDK Docs](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-aws.html#zenml.integrations.aws) for more information on available configuration options. {% endtab %} {% tab title="Implicit Authentication" %} If you neither connect your orchestrator to a service connector nor configure credentials explicitly, ZenML will try to implicitly authenticate to AWS via the `default` profile in your local [AWS configuration file](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html). If you plan to use scheduled pipelines, ensure this profile has the necessary EventBridge and IAM permissions listed in the [Required IAM Permissions](sagemaker.md#required-iam-permissions) section: ```shell zenml orchestrator register \ --flavor=sagemaker \ --execution_role= zenml stack register -o ... --set python run.py # Authenticates with `default` profile in `~/.aws/config` ``` {% endtab %} {% endtabs %} ## Required IAM Permissions Instead of using the broad `AmazonSageMakerFullAccess` managed policy, follow the principle of least privilege by creating custom policies with only the required permissions: ### Execution Role Permissions (for SageMaker jobs) Create a custom policy for the execution role that SageMaker will assume: ```json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "sagemaker:CreateProcessingJob", "sagemaker:DescribeProcessingJob", "sagemaker:StopProcessingJob", "sagemaker:CreateTrainingJob", "sagemaker:DescribeTrainingJob", "sagemaker:StopTrainingJob" ], "Resource": "*" }, { "Effect": "Allow", "Action": [ "s3:GetObject", "s3:PutObject", "s3:DeleteObject", "s3:ListBucket" ], "Resource": [ "arn:aws:s3:::your-bucket-name", "arn:aws:s3:::your-bucket-name/*" ] }, { "Effect": "Allow", "Action": [ "ecr:BatchCheckLayerAvailability", "ecr:GetDownloadUrlForLayer", "ecr:BatchGetImage" ], "Resource": "*" }, { "Effect": "Allow", "Action": [ "ecr:GetAuthorizationToken" ], "Resource": "*" }, { "Effect": "Allow", "Action": [ "logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents", "logs:GetLogEvents" ], "Resource": "*" } ] } ``` ### Client Permissions (for pipeline submission) Create a custom policy for the client/user submitting pipelines and training/processing jobs: ```json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "sagemaker:CreatePipeline", "sagemaker:StartPipelineExecution", "sagemaker:StopPipelineExecution", "sagemaker:DescribePipeline", "sagemaker:DescribePipelineExecution", "sagemaker:ListPipelineExecutions", "sagemaker:ListPipelineExecutionSteps", "sagemaker:UpdatePipeline", "sagemaker:DeletePipeline" "sagemaker:CreateProcessingJob", "sagemaker:DescribeProcessingJob", "sagemaker:StopProcessingJob", "sagemaker:CreateTrainingJob", "sagemaker:DescribeTrainingJob", "sagemaker:StopTrainingJob" ], "Resource": "*" }, { "Effect": "Allow", "Action": "iam:PassRole", "Resource": "arn:aws:iam::ACCOUNT-ID:role/EXECUTION-ROLE-NAME", "Condition": { "StringEquals": { "iam:PassedToService": "sagemaker.amazonaws.com" } } } ] } ``` Replace `ACCOUNT-ID` and `EXECUTION-ROLE-NAME` with your actual values. {% hint style="info" %} ZenML will build a Docker image called `/zenml:` which includes your code and use it to run your pipeline steps in Sagemaker. Check out [this page](https://docs.zenml.io/how-to/customize-docker-builds/) if you want to learn more about how ZenML builds these images and how you can customize them. {% endhint %} You can now run any ZenML static pipeline or dynamic pipeline using the Sagemaker orchestrator: ```shell python run.py ``` If all went well, you should now see the following output: ``` Steps can take 5-15 minutes to start running when using the Sagemaker Orchestrator. Your orchestrator 'sagemaker' is running remotely. Note that the pipeline run will only show up on the ZenML dashboard once the first step has started executing on the remote infrastructure. ``` {% hint style="warning" %} If it is taking more than 15 minutes for your run to show up, it might be that a setup error occurred in SageMaker before the pipeline could be started. Checkout the [Debugging SageMaker Pipelines](sagemaker.md#debugging-sagemaker-pipelines) section for more information on how to debug this. {% endhint %} ### Sagemaker UI Sagemaker comes with its own UI that you can use to find further details about your pipeline runs, such as the logs of your steps. To access the Sagemaker Pipelines UI, you will have to launch Sagemaker Studio via the AWS Sagemaker UI. Make sure that you are launching it from within your desired AWS region. Once the Studio UI has launched, click on the 'Pipeline' button on the left side. From there you can view the pipelines that have been launched via ZenML: If you are running dynamic pipelines, you can access the training/processing jobs in the SageMaker UI by clicking on the 'Jobs' button on the left side. From there you can view the jobs that have been launched via ZenML. A training job will be created for each dynamic pipeline run and for each step in the dynamic pipeline marked to run as an isolated step. ### Debugging SageMaker Pipelines If your SageMaker pipeline encounters an error before the first ZenML step starts, the ZenML run will not appear in the ZenML dashboard. In such cases, use the [SageMaker UI](sagemaker.md#sagemaker-ui) to review the error message and logs. Here's how: * Open the corresponding pipeline in the SageMaker UI as shown in the [SageMaker UI Section](sagemaker.md#sagemaker-ui), * Open the execution, * Click on the failed step in the pipeline graph, * Go to the 'Output' tab to see the error message or to 'Logs' to see the logs. Alternatively, for a more detailed view of log messages during SageMaker pipeline executions, consider using [Amazon CloudWatch](https://aws.amazon.com/cloudwatch/): * Search for 'CloudWatch' in the AWS console search bar. * Navigate to 'Logs > Log groups.' * Open the '/aws/sagemaker/ProcessingJobs' log group. * Here, you can find log streams for each step of your SageMaker pipeline executions. ### Configuration at pipeline or step level When running your ZenML pipeline with the Sagemaker orchestrator, the configuration set when configuring the orchestrator as a ZenML component will be used by default. However, it is possible to provide additional configuration at the pipeline or step level. This allows you to run whole pipelines or individual steps with alternative configurations. For example, this allows you to run the training process with a heavier, GPU-enabled instance type, while running other steps with lighter instances. Additional configuration for the Sagemaker orchestrator can be passed via `SagemakerOrchestratorSettings`. Here, it is possible to configure `processor_args`, which is a dictionary of arguments for the Processor. For available arguments, see the [Sagemaker documentation](https://sagemaker.readthedocs.io/en/v2/api/training/processing.html#sagemaker.processing.Processor) . Currently, it is not possible to provide custom configuration for the following attributes: * `image_uri` * `instance_count` * `sagemaker_session` * `entrypoint` * `base_job_name` * `environment` For example, settings can be provided and applied in the following way: ```python from zenml import step from zenml.integrations.aws.flavors.sagemaker_orchestrator_flavor import ( SagemakerOrchestratorSettings ) sagemaker_orchestrator_settings = SagemakerOrchestratorSettings( instance_type="ml.m5.large", volume_size_in_gb=30, environment={"MY_ENV_VAR": "my_value"} ) @step(settings={"orchestrator": sagemaker_orchestrator_settings}) def my_step() -> None: pass ``` For example, if your ZenML component is configured to use `ml.c5.xlarge` with 400GB additional storage by default, all steps will use it except for the step above, which will use `ml.t3.medium` (for Processing Steps) or `ml.m5.xlarge` (for Training Steps) with 30GB additional storage. See the next section for details on how ZenML decides which Sagemaker Step type to use. Check out [this docs page](https://docs.zenml.io/concepts/steps_and_pipelines/configuration) for more information on how to specify settings in general. For more information and a full list of configurable attributes of the Sagemaker orchestrator, check out the [SDK Docs](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-aws.html#zenml.integrations.aws) . ### Using Warm Pools for your pipelines [Warm Pools in SageMaker](https://docs.aws.amazon.com/sagemaker/latest/dg/train-warm-pools.html) can significantly reduce the startup time of your pipeline steps, leading to faster iterations and improved development efficiency. This feature keeps compute instances in a "warm" state, ready to quickly start new jobs. To enable Warm Pools, use the [`SagemakerOrchestratorSettings`](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-aws.html#zenml.integrations.aws) class: ```python from zenml.integrations.aws.flavors.sagemaker_orchestrator_flavor import SagemakerOrchestratorSettings sagemaker_orchestrator_settings = SagemakerOrchestratorSettings( keep_alive_period_in_seconds = 300, # 5 minutes, default value ) ``` This configuration keeps instances warm for 5 minutes after each job completes, allowing subsequent jobs to start faster if initiated within this timeframe. The reduced startup time can be particularly beneficial for iterative development processes or frequently run pipelines. If you prefer not to use Warm Pools, you can explicitly disable them: ```python from zenml.integrations.aws.flavors.sagemaker_orchestrator_flavor import SagemakerOrchestratorSettings sagemaker_orchestrator_settings = SagemakerOrchestratorSettings( keep_alive_period_in_seconds = None, ) ``` By default, the SageMaker orchestrator uses Training Steps where possible, which can offer performance benefits and better integration with SageMaker's training capabilities. To disable this behavior: ```python from zenml.integrations.aws.flavors.sagemaker_orchestrator_flavor import SagemakerOrchestratorSettings sagemaker_orchestrator_settings = SagemakerOrchestratorSettings( use_training_step = False ) ``` These settings allow you to fine-tune your SageMaker orchestrator configuration, balancing between faster startup times with Warm Pools and more control over resource usage. By optimizing these settings, you can potentially reduce overall pipeline runtime and improve your development workflow efficiency. #### S3 data access in ZenML steps In Sagemaker jobs, it is possible to [access data that is located in S3](https://docs.aws.amazon.com/sagemaker/latest/dg/model-access-training-data.html). Similarly, it is possible to write data from a job to a bucket. The ZenML Sagemaker orchestrator supports this via the `SagemakerOrchestratorSettings` and hence at component, pipeline, and step levels. **Import: S3 -> job** Importing data can be useful when large datasets are available in S3 for training, for which manual copying can be cumbersome. Sagemaker supports `File` (default) and `Pipe` mode, with which data is either fully copied before the job starts or piped on the fly. See the Sagemaker documentation referenced above for more information about these modes. Note that data import and export can be used jointly with `processor_args` for maximum flexibility. A simple example of importing data from S3 to the Sagemaker job is as follows: ```python from zenml.integrations.aws.flavors.sagemaker_orchestrator_flavor import ( SagemakerOrchestratorSettings ) sagemaker_orchestrator_settings = SagemakerOrchestratorSettings( input_data_s3_mode="File", input_data_s3_uri="s3://some-bucket-name/folder" ) ``` In this case, data will be available at `/opt/ml/processing/input/data` within the job. It is also possible to split your input over channels. This can be useful if the dataset is already split in S3, or maybe even located in different buckets. ```python from zenml.integrations.aws.flavors.sagemaker_orchestrator_flavor import ( SagemakerOrchestratorSettings ) sagemaker_orchestrator_settings = SagemakerOrchestratorSettings( input_data_s3_mode="File", input_data_s3_uri={ "train": "s3://some-bucket-name/training_data", "val": "s3://some-bucket-name/validation_data", "test": "s3://some-other-bucket-name/testing_data" } ) ``` Here, the data will be available in `/opt/ml/processing/input/data/train`, `/opt/ml/processing/input/data/val` and `/opt/ml/processing/input/data/test`. In the case of using `Pipe` for `input_data_s3_mode`, a file path specifying the pipe will be available as per the description written [here](https://docs.aws.amazon.com/sagemaker/latest/dg/model-access-training-data.html#model-access-training-data-input-modes) . An example of using this pipe file within a Python script can be found [here](https://github.com/aws/amazon-sagemaker-examples/blob/main/advanced_functionality/pipe_bring_your_own/train.py) . **Export: job -> S3** Data from within the job (e.g. produced by the training process, or when preprocessing large data) can be exported as well. The structure is highly similar to that of importing data. Copying data to S3 can be configured with `output_data_s3_mode`, which supports `EndOfJob` (default) and `Continuous`. In the simple case, data in `/opt/ml/processing/output/data` will be copied to S3 at the end of a job: ```python from zenml.integrations.aws.flavors.sagemaker_orchestrator_flavor import ( SagemakerOrchestratorSettings ) sagemaker_orchestrator_settings = SagemakerOrchestratorSettings( output_data_s3_mode="EndOfJob", output_data_s3_uri="s3://some-results-bucket-name/results" ) ``` In a more complex case, data in `/opt/ml/processing/output/data/metadata` and `/opt/ml/processing/output/data/checkpoints` will be written away continuously: ```python from zenml.integrations.aws.flavors.sagemaker_orchestrator_flavor import ( SagemakerOrchestratorSettings ) sagemaker_orchestrator_settings = SagemakerOrchestratorSettings( output_data_s3_mode="Continuous", output_data_s3_uri={ "metadata": "s3://some-results-bucket-name/metadata", "checkpoints": "s3://some-results-bucket-name/checkpoints" } ) ``` {% hint style="warning" %} Using multichannel output or output mode except `EndOfJob` will make it impossible to use TrainingStep and also Warm Pools. See corresponding section of this document for details. {% endhint %} ### Tagging SageMaker Pipeline Executions and Jobs The SageMaker orchestrator allows you to add tags to your pipeline executions and individual jobs. Here's how you can apply tags at both the pipeline and step levels: ```python from zenml import pipeline, step from zenml.integrations.aws.flavors.sagemaker_orchestrator_flavor import ( SagemakerOrchestratorSettings ) # Define settings for the pipeline pipeline_settings = SagemakerOrchestratorSettings( pipeline_tags={ "project": "my-ml-project", "environment": "production", } ) # Define settings for a specific step step_settings = SagemakerOrchestratorSettings( tags={ "step": "data-preprocessing", "owner": "data-team" } ) @step(settings={"orchestrator": step_settings}) def preprocess_data(): # Your preprocessing code here pass @pipeline(settings={"orchestrator": pipeline_settings}) def my_training_pipeline(): preprocess_data() # Other steps... # Run the pipeline my_training_pipeline() ``` In this example: * The `pipeline_tags` are applied to the entire SageMaker pipeline object. SageMaker automatically applies the pipeline\_tags to all its associated jobs. * The `tags` in `step_settings` are applied to the specific SageMaker job for the `preprocess_data` step. This approach allows for more granular tagging, giving you flexibility in how you categorize and manage your SageMaker resources. You can view and manage these tags in the AWS Management Console, CLI, or API calls related to your SageMaker resources. ### Enabling CUDA for GPU-backed hardware Note that if you wish to use this orchestrator to run steps on a GPU, you will need to follow [the instructions on this page](https://docs.zenml.io/user-guides/tutorial/distributed-training/) to ensure that it works. It requires adding some extra settings customization and is essential to enable CUDA for the GPU to give its full acceleration. ### Scheduling Pipelines {% hint style="warning" %} The SageMaker orchestrator does not support scheduling for dynamic pipelines yet. {% endhint %} The SageMaker orchestrator supports running pipelines on a schedule using SageMaker's native scheduling capabilities. You can configure schedules in three ways: * Using a cron expression * Using a fixed interval * Running once at a specific time ```python from datetime import datetime, timedelta from zenml import pipeline from zenml.config.schedule import Schedule # Using a cron expression (runs every 5 minutes) @pipeline def my_scheduled_pipeline(): # Your pipeline steps here pass my_scheduled_pipeline.with_options( schedule=Schedule(cron_expression="0/5 * * * ? *") )() # Using an interval (runs every 2 hours) @pipeline def my_interval_pipeline(): # Your pipeline steps here pass my_interval_pipeline.with_options( schedule=Schedule( start_time=datetime.now(), interval_second=timedelta(hours=2) ) )() # Running once at a specific time @pipeline def my_one_time_pipeline(): # Your pipeline steps here pass my_one_time_pipeline.with_options( schedule=Schedule(run_once_start_time=datetime(2024, 12, 31, 23, 59)) )() ``` When you deploy a scheduled pipeline, ZenML will: 1. Create a SageMaker Pipeline Schedule with the specified configuration 2. Configure the pipeline as the target for the schedule 3. Enable automatic execution based on the schedule {% hint style="info" %} If you run the same pipeline with a schedule multiple times, the existing schedule will **not** be updated with the new settings. Rather, ZenML will create a new SageMaker pipeline and attach a new schedule to it. The user must manually delete the old pipeline and their attached schedule using the AWS CLI or API (`aws scheduler delete-schedule `). See details here: [SageMaker Pipeline Schedules](https://docs.aws.amazon.com/sagemaker/latest/dg/pipeline-eventbridge.html) {% endhint %} #### Required IAM Permissions for schedules When using scheduled pipelines, you need to ensure your IAM role has the correct permissions and trust relationships. You can set this up by either defining an explicit `scheduler_role` in your orchestrator configuration or you can adjust the role that you are already using on the client side to manage Sagemaker pipelines. ```bash # When registering the orchestrator zenml orchestrator register sagemaker-orchestrator \ --flavor=sagemaker \ --scheduler_role=arn:aws:iam::123456789012:role/my-scheduler-role # Or updating an existing orchestrator zenml orchestrator update sagemaker-orchestrator \ --scheduler_role=arn:aws:iam::123456789012:role/my-scheduler-role ``` {% hint style="info" %} The IAM role that you are using on the client side can come from multiple sources depending on how you configured your orchestrator, such as explicit credentials, a service connector or an implicit authentication. If you are using a service connector, keep in mind, this only works with authentication methods that involve IAM roles (IAM role, Implicit authentication). LINK {% endhint %} This is particularly useful when: * You want to use different roles for creating pipelines and scheduling them * Your organization's security policies require separate roles for different operations * You need to grant specific permissions only to the scheduling operations 1. **Trust Relationships** Your `scheduler_role` (or your client role if you did not configure a `scheduler_role`) needs to be assumed by the EventBridge Scheduler service: ```json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "", "Service": [ "scheduler.amazonaws.com" ] }, "Action": "sts:AssumeRole" } ] } ``` 2. **Required IAM Permissions for the client role** In addition to permissions needed to manage pipelines, the role on the client side also needs the following permissions to create schedules on EventBridge: ```json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "scheduler:ListSchedules", "scheduler:GetSchedule", "scheduler:CreateSchedule", "scheduler:UpdateSchedule", "scheduler:DeleteSchedule" ], "Resource": "*" }, { "Effect": "Allow", "Action": "iam:PassRole", "Resource": "arn:aws:iam::*:role/*", "Condition": { "StringLike": { "iam:PassedToService": "scheduler.amazonaws.com" } } } ] } ``` Or you can use the `AmazonEventBridgeSchedulerFullAccess` managed policy. These permissions enable: * Creation and management of Pipeline Schedules * Setting up trust relationships between services * Managing IAM policies required for the scheduled execution * Cleanup of resources when schedules are removed Without these permissions, the scheduling functionality will fail. Make sure to configure them before attempting to use scheduled pipelines. 3. **Required IAM Permissions for the `scheduler_role`** The `scheduler_role` requires the same permissions as the client role (that would run the pipeline in a non-scheduled case) to launch and manage SageMaker jobs. Use the same custom client permissions policy shown in the [Required IAM Permissions](#required-iam-permissions) section above instead of the broad `AmazonSageMakerFullAccess` managed policy.
ZenML Scarf
--- ### Book/Component Guide/Orchestrators/Skypilot Vm (docs/book/component-guide/orchestrators/skypilot-vm.md) --- description: Orchestrating your pipelines to run on VMs using SkyPilot. --- # Skypilot VM Orchestrator The SkyPilot VM Orchestrator is an integration provided by ZenML that allows you to provision and manage virtual machines (VMs) on any cloud provider supported by the [SkyPilot framework](https://skypilot.readthedocs.io/en/latest/index.html). This integration is designed to simplify the process of running machine learning workloads on the cloud, offering cost savings, high GPU availability, and managed execution. We recommend using the SkyPilot VM Orchestrator if you need access to GPUs for your workloads, but don't want to deal with the complexities of managing cloud infrastructure or expensive managed solutions. {% hint style="warning" %} This component is only meant to be used within the context of a [remote ZenML deployment scenario](https://docs.zenml.io/getting-started/deploying-zenml/). Usage with a local ZenML deployment may lead to unexpected behavior! {% endhint %} ## When to use it You should use the SkyPilot VM Orchestrator if: * you want to maximize cost savings by leveraging spot VMs and auto-picking the cheapest VM/zone/region/cloud. * you want to ensure high GPU availability by provisioning VMs in all zones/regions/clouds you have access to. * you don't need a built-in UI of the orchestrator. (You can still use ZenML's Dashboard to view and monitor your pipelines/artifacts.) * you're not willing to maintain Kubernetes-based solutions or pay for managed solutions like [Sagemaker](sagemaker.md). ## How it works The orchestrator leverages the SkyPilot framework to handle the provisioning and scaling of VMs. It automatically manages the process of launching VMs for your pipelines, with support for both on-demand and managed spot VMs. While you can select the VM type you want to use, the orchestrator also includes an optimizer that automatically selects the cheapest VM/zone/region/cloud for your workloads. Finally, the orchestrator includes an autostop feature that cleans up idle clusters, preventing unnecessary cloud costs. {% hint style="info" %} You can configure the SkyPilot VM Orchestrator to use a specific VM type, and resources for each step of your pipeline can be configured individually. Read more about how to configure step-specific resources [here](skypilot-vm.md#configuring-step-specific-resources). {% endhint %} {% hint style="warning" %} The SkyPilot VM Orchestrator does not currently support the ability to [schedule pipelines runs](https://docs.zenml.io/user-guides/tutorial/managing-scheduled-pipelines) {% endhint %} {% hint style="info" %} All ZenML pipeline runs are executed using Docker containers within the VMs provisioned by the orchestrator. For that reason, you may need to configure your pipeline settings with `docker_run_args=["--gpus=all"]` to enable GPU support in the Docker container. {% endhint %} {% hint style="info" %} SkyPilot also ships an optional API server with a web dashboard. You can start it with `sky api start`, then run `sky api info` and open the `/dashboard` path of the returned base URL. ZenML does not integrate with this dashboard, but you can use it alongside the ZenML dashboard. {% endhint %} ## How to deploy it You don't need to do anything special to deploy the SkyPilot VM Orchestrator. As the SkyPilot integration itself takes care of provisioning VMs, you can simply use the orchestrator as you would any other ZenML orchestrator. However, you will need to ensure that you have the appropriate permissions to provision VMs on your cloud provider of choice and to configure your SkyPilot orchestrator accordingly using the [service connectors](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/service-connectors-guide) feature. {% hint style="info" %} The SkyPilot VM Orchestrator currently only supports the AWS, GCP, Azure, Lambda Labs and Kubernetes platforms. {% endhint %} ## How to use it To use the SkyPilot VM Orchestrator, you need: * [Docker](https://www.docker.com) installed and running. * A [remote artifact store](https://docs.zenml.io/stacks/artifact-stores/) as part of your stack. * A [remote container registry](https://docs.zenml.io/stacks/container-registries/) as part of your stack. * A [remote ZenML deployment](https://docs.zenml.io/getting-started/deploying-zenml/). * The appropriate permissions to provision VMs on your cloud provider of choice. * A [service connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/service-connectors-guide) configured to authenticate with your cloud provider of choice. {% tabs %} {% tab title="AWS" %} We need first to install the SkyPilot integration for AWS and the AWS connectors extra, using the following commands: ```shell # Installs dependencies for Skypilot AWS, AWS Container Registry, and S3 Artifact Store pip install "zenml[connectors-aws]" zenml integration install aws skypilot_aws # We recommend using the --uv option here ``` To provision VMs on AWS, your VM Orchestrator stack component needs to be configured to authenticate with [AWS Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/aws-service-connector). To configure the AWS Service Connector, you need to register a new service connector configured with AWS credentials that have at least the minimum permissions required by SkyPilot as documented [here](https://skypilot.readthedocs.io/en/latest/cloud-setup/cloud-permissions/aws.html). First, check that the AWS service connector type is available using the following command: ```shell zenml service-connector list-types --type aws ``` ```shell ┏━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━┯━━━━━━━┯━━━━━━━━┓ ┃ NAME │ TYPE │ RESOURCE TYPES │ AUTH METHODS │ LOCAL │ REMOTE ┃ ┠───────────────────────┼────────┼───────────────────────┼──────────────────┼───────┼────────┨ ┃ AWS Service Connector │ 🔶 aws │ 🔶 aws-generic │ implicit │ ✅ │ ➖ ┃ ┃ │ │ 📦 s3-bucket │ secret-key │ │ ┃ ┃ │ │ 🌀 kubernetes-cluster │ sts-token │ │ ┃ ┃ │ │ 🐳 docker-registry │ iam-role │ │ ┃ ┃ │ │ │ session-token │ │ ┃ ┃ │ │ │ federation-token │ │ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━┷━━━━━━━┷━━━━━━━━┛ ``` Next, configure a service connector using the CLI or the dashboard with the AWS credentials. For example, the following command uses the local AWS CLI credentials to auto-configure the service connector: ```shell zenml service-connector register aws-skypilot-vm --type aws --region=us-east-1 --auto-configure ``` This will automatically configure the service connector with the appropriate credentials and permissions to provision VMs on AWS. You can then use the service connector to configure your registered VM Orchestrator stack component using the following command: ```shell # Register the orchestrator zenml orchestrator register --flavor vm_aws # Connect the orchestrator to the service connector zenml orchestrator connect --connector aws-skypilot-vm # Register and activate a stack with the new orchestrator zenml stack register -o ... --set ``` {% endtab %} {% tab title="GCP" %} We need first to install the SkyPilot integration for GCP and the GCP extra for ZenML, using the following two commands: ```shell pip install "zenml[connectors-gcp]" zenml integration install gcp skypilot_gcp ``` To provision VMs on GCP, your VM Orchestrator stack component needs to be configured to authenticate with [GCP Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/gcp-service-connector) To configure the GCP Service Connector, you need to register a new service connector, but first let's check the available service connectors types using the following command: ```shell zenml service-connector list-types --type gcp ``` ```shell ┏━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━┯━━━━━━━┯━━━━━━━━┓ ┃ NAME │ TYPE │ RESOURCE TYPES │ AUTH METHODS │ LOCAL │ REMOTE ┃ ┠───────────────────────┼────────┼───────────────────────┼─────────────────┼───────┼────────┨ ┃ GCP Service Connector │ 🔵 gcp │ 🔵 gcp-generic │ implicit │ ✅ │ ➖ ┃ ┃ │ │ 📦 gcs-bucket │ user-account │ │ ┃ ┃ │ │ 🌀 kubernetes-cluster │ service-account │ │ ┃ ┃ │ │ 🐳 docker-registry │ oauth2-token │ │ ┃ ┃ │ │ │ impersonation │ │ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━┷━━━━━━━┷━━━━━━━━┛ ``` For this example we will configure a service connector using the `user-account` auth method. But before we can do that, we need to login to GCP using the following command: ```shell gcloud auth application-default login ``` This will open a browser window and ask you to login to your GCP account. Once you have logged in, you can register a new service connector using the following command: ```shell # We want to use --auto-configure to automatically configure the service connector with the appropriate credentials and permissions to provision VMs on GCP. zenml service-connector register gcp-skypilot-vm -t gcp --auth-method user-account --auto-configure # using generic resource type requires disabling the generation of temporary tokens zenml service-connector update gcp-skypilot-vm --generate_temporary_tokens=False ``` This will automatically configure the service connector with the appropriate credentials and permissions to provision VMs on GCP. You can then use the service connector to configure your registered VM Orchestrator stack component using the following commands: ```shell # Register the orchestrator zenml orchestrator register --flavor vm_gcp # Connect the orchestrator to the service connector zenml orchestrator connect --connector gcp-skypilot-vm # Register and activate a stack with the new orchestrator zenml stack register -o ... --set ``` {% hint style="warning" %} If you are running a pipeline, where at least one step has different Skypilot settings than the pipeline, the orchestrator will try to run this step in a separate VM. In order to do this properly, you will need to provide it with a parent image through your DockerSettings where both `ZenML` and `gcloud` CLI is installed (currently not available in the default ZenML parent image). docker_settings = DockerSettings(parent_image="your/custom-image:with-zenml-and-gcloud") {% endhint %} {% endtab %} {% tab title="Azure" %} We need first to install the SkyPilot integration for Azure and the extra requirements that are needed from additional Azure components, using the following two commands {% hint style="warning" %} Currently, the ZenML Skypilot integration is **pip-incompatible** with the ZenML Azure integration, therefore executing `zenml integration install azure skypilot_azure` will not work. Since working with a skypilot stack requires you to use a remote artifact store and container registry, please install the requirements of these components with pip to avoid any installation problems. {% endhint %} ```shell pip install "zenml[connectors-azure]" adlfs azure-mgmt-containerservice azure-storage-blob ``` {% hint style="warning" %} If you would like to use `uv` to install the stack requirements for an Azure Skypilot Stack, you need to use `python_package_installer_args={"prerelease": "allow"}`: ```python docker_settings = DockerSettings( python_package_installer_args={"prerelease": "allow"}, ) @pipeline(settings={"docker": docker_settings}) def basic_pipeline(): ... ``` {% endhint %} To provision VMs on Azure, your VM Orchestrator stack component needs to be configured to authenticate with [Azure Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/azure-service-connector) To configure the Azure Service Connector, you need to register a new service connector, but first let's check the available service connectors types using the following command: ```shell zenml service-connector list-types --type azure ``` ```shell ┏━━━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━┯━━━━━━━┯━━━━━━━━┓ ┃ NAME │ TYPE │ RESOURCE TYPES │ AUTH METHODS │ LOCAL │ REMOTE ┃ ┠─────────────────────────┼───────────┼───────────────────────┼───────────────────┼───────┼────────┨ ┃ Azure Service Connector │ 🇦 azure │ 🇦 azure-generic │ implicit │ ✅ │ ➖ ┃ ┃ │ │ 📦 blob-container │ service-principal │ │ ┃ ┃ │ │ 🌀 kubernetes-cluster │ access-token │ │ ┃ ┃ │ │ 🐳 docker-registry │ │ │ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━┷━━━━━━━┷━━━━━━━━┛ zenml service-connector register azure-skypilot-vm -t azure --auth-method access-token --auto-configure ``` This will automatically configure the service connector with the appropriate credentials and permissions to provision VMs on Azure. You can then use the service connector to configure your registered VM Orchestrator stack component using the following commands: ```shell # Register the orchestrator zenml orchestrator register --flavor vm_azure # Connect the orchestrator to the service connector zenml orchestrator connect --connector azure-skypilot-vm # Register and activate a stack with the new orchestrator zenml stack register -o ... --set ``` {% endtab %} {% tab title="Lambda Labs" %} Lambda Labs is a cloud provider that offers GPU instances for machine learning workloads. Unlike the major cloud providers, with Lambda Labs we don't need to configure a service connector to authenticate with the cloud provider. Instead, we can directly use API keys to authenticate with the Lambda Labs API. ```shell zenml integration install skypilot_lambda ``` Once the integration is installed, we can register the orchestrator with the following command: ```shell # For more secure and recommended way, we will register the API key as a secret zenml secret create lambda_api_key --scope user --api_key= # Register the orchestrator zenml orchestrator register --flavor vm_lambda --api_key={{lambda_api_key.api_key}} # Register and activate a stack with the new orchestrator zenml stack register -o ... --set ``` {% hint style="info" %} The Lambda Labs orchestrator does not support some of the features like `job_recovery`, `disk_tier`, `image_id`, `zone`, `idle_minutes_to_autostop`, `disk_size`, `use_spot`. It is recommended not to use these features with the Lambda Labs orchestrator and not to use [step-specific settings](skypilot-vm.md#configuring-step-specific-resources). {% endhint %} {% hint style="warning" %} While testing the orchestrator, we noticed that the Lambda Labs orchestrator does not support the `down` flag. This means the orchestrator will not automatically tear down the cluster after all jobs finish. We recommend manually tearing down the cluster after all jobs finish to avoid unnecessary costs. {% endhint %} {% endtab %} {% tab title="Kubernetes" %} We need first to install the SkyPilot integration for Kubernetes, using the following two commands: ```shell zenml integration install skypilot_kubernetes ``` To provision skypilot on kubernetes cluster, your orchestrator stack components needs to be configured to authenticate with a[Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/service-connectors-guide). To configure the Service Connector, you need to register a new service connector configured with the appropriate credentials and permissions to access the K8s cluster. You can then use the service connector to configure your registered the Orchestrator stack component using the following command: First, check that the Kubernetes service connector type is available using the following command: ```shell zenml service-connector list-types --type kubernetes ``` ```shell ┏━━━━━━━━━━━━┯━━━━━━━━━━━━┯━━━━━━━━━━━━┯━━━━━━━━━━━┯━━━━━━━┯━━━━━━━━┓ ┃ │ │ RESOURCE │ AUTH │ │ ┃ ┃ NAME │ TYPE │ TYPES │ METHODS │ LOCAL │ REMOTE ┃ ┠────────────┼────────────┼────────────┼───────────┼───────┼────────┨ ┃ Kubernetes │ 🌀 │ 🌀 │ password │ ✅ │ ✅ ┃ ┃ Service │ kubernetes │ kubernetes │ token │ │ ┃ ┃ Connector │ │ -cluster │ │ │ ┃ ┗━━━━━━━━━━━━┷━━━━━━━━━━━━┷━━━━━━━━━━━━┷━━━━━━━━━━━┷━━━━━━━┷━━━━━━━━┛ ``` Next, configure a service connector using the CLI or the dashboard with the Kubernetes credentials. For example, the following command uses the local Kubernetes CLI credentials to auto-configure the service connector: ```shell zenml service-connector register kubernetes-skypilot --type kubernetes -i ``` This will automatically configure the service connector with the appropriate credentials and permissions to access the Kubernetes cluster. You can then use the service connector to configure your registered VM Orchestrator stack component using the following command: ```shell # Register the orchestrator zenml orchestrator register --flavor sky_kubernetes # Connect the orchestrator to the service connector zenml orchestrator connect --connector kubernetes-skypilot # Register and activate a stack with the new orchestrator zenml stack register -o ... --set ``` {% hint style="warning" %} Some of the features like `job_recovery`, `disk_tier`, `image_id`, `zone`, `idle_minutes_to_autostop`, `disk_size`, `use_spot` are not supported by the Kubernetes orchestrator. It is recommended not to use these features with the Kubernetes orchestrator and not to use [step-specific settings](skypilot-vm.md#configuring-step-specific-resources). {% endhint %} {% endtab %} {% endtabs %} #### Additional Configuration For additional configuration of the Skypilot orchestrator, you can pass `Settings` depending on which cloud you are using which allows you to configure (among others) the following attributes: * `instance_type`: The instance type to use. * `cpus`: The number of CPUs required for the task. If a string, must be a string of the form `'2'` or `'2+'`, where the `+` indicates that the task requires at least 2 CPUs. * `memory`: The amount of memory in GiB required. If a string, must be a string of the form `'16'` or `'16+'`, where the `+` indicates that the task requires at least 16 GB of memory. * `accelerators`: The accelerators required. If a string, must be a string of the form `'V100'` or `'V100:2'`, where the `:2` indicates that the task requires 2 V100 GPUs. If a dict, must be a dict of the form `{'V100': 2}` or `{'tpu-v2-8': 1}`. * `accelerator_args`: Accelerator-specific arguments. For example, `{'tpu_vm': True, 'runtime_version': 'tpu-vm-base'}` for TPUs. * `use_spot`: Whether to use spot instances. If None, defaults to False. * `job_recovery`: The spot recovery strategy to use for the managed spot to recover the cluster from preemption. Read more about the available strategies [here](https://skypilot.readthedocs.io/en/latest/reference/api.html?highlight=instance_type#resources) * `region`: The cloud region to use. * `zone`: The cloud zone to use within the region. * `image_id`: The image ID to use. If a string, must be a string of the image id from the cloud, such as AWS: `'ami-1234567890abcdef0'`, GCP: `'projects/my-project-id/global/images/my-image-name'`; Or, a image tag provided by SkyPilot, such as AWS: `'skypilot:gpu-ubuntu-2004'`. If a dict, must be a dict mapping from region to image ID. * `disk_size`: The size of the OS disk in GiB. * `disk_tier`: The disk performance tier to use. If None, defaults to `'medium'`. * `cluster_name`: Name of the cluster to create/reuse. If None, auto-generate a name. SkyPilot uses term `cluster` to refer to a group or a single VM that are provisioned to execute the task. The cluster name is used to identify the cluster and to determine whether to reuse an existing cluster or create a new one. * `retry_until_up`: Whether to retry launching the cluster until it is up. * `idle_minutes_to_autostop`: Automatically stop the cluster after this many minutes of idleness, i.e., no running or pending jobs in the cluster's job queue. Idleness gets reset whenever setting-up/running/pending jobs are found in the job queue. Setting this flag schedules an autostop after the launch completes. If not set, the cluster will not be autostopped. * `down`: Tear down the cluster after all jobs finish (successfully or abnormally). If `idle_minutes_to_autostop` is also set, the cluster will be torn down after the specified idle time. Note that if errors occur during provisioning/data syncing/setting up, the cluster will not be torn down for debugging purposes. * `infra`: SkyPilot infrastructure selector (e.g., `"aws/us-east-1"`, `"gcp/us-central1-a"`, `"k8s/my-cluster-ctx"`). Mutually exclusive with explicit cloud/region/zone selection. * `num_nodes`: Number of nodes for multi-node jobs. * `network_tier`: Network performance tier where supported by the cloud provider. Valid values are `'standard'` (default) and `'best'`. * `stream_logs`: If True, show the logs in the terminal as they are generated while the cluster is running. * `docker_run_args`: Additional arguments to pass to the `docker run` command. For example, `['--gpus=all']` to use all GPUs available on the VM. * `ports`: Ports to expose. Could be an integer, a range, or a list of integers and ranges. All ports will be exposed to the public internet. * `labels`: Labels to apply to instances as key-value pairs. These are mapped to cloud-specific implementations (instance tags in AWS, instance labels in GCP, etc.). * `any_of`: List of candidate resources to try in order of preference based on cost (determined by the SkyPilot optimizer). * `ordered`: List of candidate resources to try in the specified order. * `workdir`: Working directory on the local machine to sync to the VM. This is synced to `~/sky_workdir` inside the VM. * `task_name`: Human-readable task name shown in SkyPilot for display purposes. * `file_mounts`: File mounts configuration to make local or cloud storage paths available inside the remote cluster. * `envs`: Environment variables for the task. Accessible in the VMs that Skypilot launches, not in Docker containers that the steps and pipeline is running on. * `task_settings`: Dictionary of arbitrary settings forwarded to `sky.Task()`. This allows passing future parameters added by SkyPilot without requiring updates to ZenML. * `resources_settings`: Dictionary of arbitrary settings forwarded to `sky.Resources()`. This allows passing future parameters added by SkyPilot without requiring updates to ZenML. * `launch_settings`: Dictionary of arbitrary settings forwarded to `sky.launch()`. This allows passing future parameters added by SkyPilot without requiring updates to ZenML. The following code snippets show how to configure the orchestrator settings for each cloud provider: {% tabs %} {% tab title="AWS" %} **Code Example:** ```python from zenml.integrations.skypilot_aws.flavors.skypilot_orchestrator_aws_vm_flavor import SkypilotAWSOrchestratorSettings skypilot_settings = SkypilotAWSOrchestratorSettings( cpus="2", memory="16", accelerators="V100:2", accelerator_args={"tpu_vm": True, "runtime_version": "tpu-vm-base"}, use_spot=True, job_recovery={ "strategy": "failover", "max_restarts_on_errors": 3, }, region="us-west-1", zone="us-west1-a", image_id="ami-1234567890abcdef0", disk_size=100, disk_tier="high", cluster_name="my_cluster", retry_until_up=True, idle_minutes_to_autostop=60, down=True, stream_logs=True, docker_run_args=["--gpus=all"] ) @pipeline( settings={ "orchestrator": skypilot_settings } ) ``` {% endtab %} {% tab title="GCP" %} **Code Example:** ```python from zenml.integrations.skypilot_gcp.flavors.skypilot_orchestrator_gcp_vm_flavor import SkypilotGCPOrchestratorSettings skypilot_settings = SkypilotGCPOrchestratorSettings( cpus="2", memory="16", accelerators="V100:2", accelerator_args={"tpu_vm": True, "runtime_version": "tpu-vm-base"}, use_spot=True, job_recovery={ "strategy": "failover", "max_restarts_on_errors": 3, }, region="us-west1", zone="us-west1-a", image_id="ubuntu-pro-2004-focal-v20231101", disk_size=100, disk_tier="high", cluster_name="my_cluster", retry_until_up=True, idle_minutes_to_autostop=60, down=True, stream_logs=True, docker_run_args=["--gpus=all"] ) @pipeline( settings={ "orchestrator": skypilot_settings } ) ``` {% endtab %} {% tab title="Azure" %} **Code Example:** ```python from zenml.integrations.skypilot_azure.flavors.skypilot_orchestrator_azure_vm_flavor import SkypilotAzureOrchestratorSettings skypilot_settings = SkypilotAzureOrchestratorSettings( cpus="2", memory="16", accelerators="V100:2", accelerator_args={"tpu_vm": True, "runtime_version": "tpu-vm-base"}, use_spot=True, job_recovery={ "strategy": "failover", "max_restarts_on_errors": 3, }, region="West Europe", image_id="Canonical:0001-com-ubuntu-server-jammy:22_04-lts-gen2:latest", disk_size=100, disk_tier="high", cluster_name="my_cluster", retry_until_up=True, idle_minutes_to_autostop=60, down=True, stream_logs=True, docker_run_args=["--gpus=all"] ) @pipeline( settings={ "orchestrator": skypilot_settings } ) ``` {% endtab %} {% tab title="Lambda" %} **Code Example:** ```python from zenml.integrations.skypilot_lambda import SkypilotLambdaOrchestratorSettings skypilot_settings = SkypilotLambdaOrchestratorSettings( instance_type="gpu_1x_h100_pcie", cluster_name="my_cluster", retry_until_up=True, idle_minutes_to_autostop=60, down=True, stream_logs=True, docker_run_args=["--gpus=all"] ) @pipeline( settings={ "orchestrator": skypilot_settings } ) ``` {% endtab %} {% tab title="Kubernetes" %} **Code Example:** ```python from zenml.integrations.skypilot_kubernetes.flavors.skypilot_orchestrator_kubernetes_vm_flavor import SkypilotKubernetesOrchestratorSettings skypilot_settings = SkypilotKubernetesOrchestratorSettings( cpus="2", memory="16", accelerators="V100:2", cluster_name="my_cluster", retry_until_up=True, stream_logs=True, docker_run_args=["--gpus=all"] ) @pipeline( settings={ "orchestrator": skypilot_settings } ) ``` {% endtab %} {% endtabs %} One of the key features of the SkyPilot VM Orchestrator is the ability to run each step of a pipeline on a separate VM with its own specific settings. This allows for fine-grained control over the resources allocated to each step, ensuring that each part of your pipeline has the necessary compute power while optimizing for cost and efficiency. ## Configuring Step-Specific Resources The SkyPilot VM Orchestrator allows you to configure resources for each step individually. This means you can specify different VM types, CPU and memory requirements, and even use spot instances for certain steps while using on-demand instances for others. If no step-specific settings are specified, the orchestrator will use the resources specified in the orchestrator settings for each step and run the entire pipeline in one VM. If step-specific settings are specified, an orchestrator VM will be spun up first, which will subsequently spin out new VMs dependent on the step settings. You can disable this behavior by setting the `disable_step_based_settings` parameter to `True` in the orchestrator configuration, using the following command: ```shell zenml orchestrator update --disable_step_based_settings=True ``` Here's an example of how to configure specific resources for a step for the AWS cloud: ```python from zenml.integrations.skypilot_aws.flavors.skypilot_orchestrator_aws_vm_flavor import SkypilotAWSOrchestratorSettings # Settings for a specific step that requires more resources high_resource_settings = SkypilotAWSOrchestratorSettings( instance_type='t2.2xlarge', cpus=8, memory=32, use_spot=False, region='us-east-1', # ... other settings ) @step(settings={"orchestrator": high_resource_settings}) def my_resource_intensive_step(): # Step implementation pass ``` {% hint style="warning" %} When configuring pipeline or step-specific resources, you can use the `settings` parameter to specifically target the orchestrator flavor you want to use `orchestrator.STACK_COMPONENT_FLAVOR` and not orchestrator component name `orchestrator.STACK_COMPONENT_NAME`. For example, if you want to configure resources for the `vm_gcp` flavor, you can use `settings={"orchestrator": ...}`. {% endhint %} By using the `settings` parameter, you can tailor the resources for each step according to its specific needs. This flexibility allows you to optimize your pipeline execution for both performance and cost. Check out the [SDK docs](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-skypilot.html#zenml.integrations.skypilot) for a full list of available attributes and [this docs page](https://docs.zenml.io/concepts/steps_and_pipelines/configuration) for more information on how to specify settings.
ZenML Scarf
--- ### Book/Component Guide/Orchestrators/Ssh (docs/book/component-guide/orchestrators/ssh.md) --- description: Orchestrating your pipelines on a remote host via SSH and Docker. --- # SSH Orchestrator The SSH orchestrator is an [orchestrator](./) flavor that runs your pipelines on a remote Linux host over SSH, using Docker Compose. It is a generic, vendor-neutral alternative to managed orchestrators: if you have a machine reachable over SSH that can run Docker, you can use it as a ZenML orchestrator. It is the spiritual successor to the [HyperAI orchestrator](hyperai.md) — it uses the same Compose-based execution model but is not tied to any particular cloud provider, and it adds support for [dynamic pipelines](https://docs.zenml.io/how-to/steps-pipelines/dynamic_pipelines). {% hint style="info" %} The SSH orchestrator supersedes the [HyperAI orchestrator](hyperai.md), which is now deprecated. New stacks should use the SSH orchestrator; existing HyperAI users can migrate by registering an `ssh` orchestrator pointed at the same host (see [Migrating from HyperAI](#migrating-from-hyperai) below). {% endhint %} {% hint style="warning" %} This component is only meant to be used within the context of a [remote ZenML deployment scenario](https://docs.zenml.io/getting-started/deploying-zenml/). Usage with a local ZenML deployment may lead to unexpected behavior! {% endhint %} ### When to use it You should use the SSH orchestrator if: * you have a **dedicated remote machine** (e.g. a GPU workstation or on-prem server) reachable over SSH and want to run full pipelines on it. * you want a simple, self-hosted orchestrator without Kubernetes or a cloud-managed service. * you are migrating away from the deprecated HyperAI orchestrator. If you only want to offload *individual* compute-heavy steps to a remote host while keeping a different orchestrator, consider the sibling [SSH step operator](../step-operators/ssh.md) instead. The two share the same connection layer and can target the same host. ### Prerequisites You will need the following to use the SSH orchestrator: * A remote Linux host reachable over SSH from the machine submitting the pipeline, with **SSH key-based** access (passwords are not supported). * A recent version of **Docker** including Docker Compose (the `docker compose` command must work) and the SSH user must be able to run it (typically a member of the `docker` group). * For GPU pipelines: the appropriate [NVIDIA Driver](https://www.nvidia.com/en-us/drivers/unix/) and the [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html) installed on the host. If GPUs are not available, set `gpu_enabled=False` (or per step via settings), otherwise the pipeline will not start correctly. ## How it works The SSH orchestrator connects to the remote host with [paramiko](https://www.paramiko.org/), writes a Docker Compose file into a per-run directory under `remote_workdir`, and launches it with `docker compose up`. It has two execution paths: * **Static pipelines** — one Compose service per step, wired together with `depends_on` (`service_completed_successfully`) so a step only runs once its upstream steps finish. The remote `docker compose up` runs the whole DAG. This mirrors the HyperAI execution model. * **Dynamic pipelines** — a single Compose service runs the *orchestrator image*, which executes ZenML's dynamic runner on the host. That runner launches each isolated step as its own OS **subprocess** (not a thread) so steps are independently accounted and can be preempted — required for resource pools and fail-fast execution. If `authenticate_docker` is enabled, the orchestrator runs `docker login` on the remote host using the submitted stack's container registry credentials before launching, so private images can be pulled. {% hint style="info" %} The SSH orchestrator does not manage schedules. To run a pipeline on a schedule, trigger it directly from your own cron job or CI; submitting a pipeline with a ZenML schedule is rejected with a clear error. {% endhint %} ### How to deploy it The SSH orchestrator connects to an existing host; it does not provision infrastructure. Beyond the [prerequisites](#prerequisites) above, the orchestrator must be used in a stack that contains a **container registry** and an **image builder**. SSH credentials are configured directly on the orchestrator. ### How to use it Install the ZenML `ssh` integration: ```shell zenml integration install ssh ``` Register the orchestrator with the connection details for your host and use it in your active stack: ```shell zenml orchestrator register \ --flavor=ssh \ --hostname= \ --username= \ --ssh_key_path= # Register and activate a stack with the new orchestrator zenml stack register -o ... --set ``` Instead of `ssh_key_path` (a path on the submitting machine) you can store the key content in a [ZenML secret](https://docs.zenml.io/getting-started/deploying-zenml/secret-management) and reference it via `ssh_private_key` (and `ssh_key_passphrase` if the key is encrypted). You can now run any ZenML pipeline using the SSH orchestrator: ```shell python file_that_runs_a_zenml_pipeline.py ``` #### Configuration options Key configuration fields (set at registration time): * `hostname`, `port`, `username` — how to reach the host. * `ssh_key_path` / `ssh_private_key` / `ssh_key_passphrase` — key-based authentication. * `verify_host_key` / `known_hosts_path` — host-key verification (on by default). * `remote_workdir` — base directory on the host for per-run files (default `/tmp/zenml-ssh`). * `docker_binary` — path to the Docker binary on the host (default `docker`). * `authenticate_docker` — run `docker login` on the host before launching (default `False`). * `cleanup_old_files` — remove run directories older than seven days before each launch (default `True`). * `gpu_enabled` — request all NVIDIA GPUs for step containers. This is also a per-step setting, so you can mix CPU and GPU steps in one pipeline. Bind mounts can be configured per pipeline or per step via settings: ```python from zenml.integrations.ssh.flavors.ssh_orchestrator_flavor import ( SSHOrchestratorSettings, ) settings = SSHOrchestratorSettings( mounts={"/data/datasets": "/datasets"}, gpu_enabled=False, ) ``` #### Enabling CUDA for GPU-backed hardware If you wish to run steps on a GPU, follow [the instructions on this page](https://docs.zenml.io/user-guides/tutorial/distributed-training/) to ensure CUDA is enabled inside the container. This requires some extra settings customization and is essential to give the GPU its full acceleration. ### Migrating from HyperAI The HyperAI orchestrator is deprecated in favor of the SSH orchestrator. Since both connect over SSH and run Docker Compose on the host, migration is straightforward: register an `ssh` orchestrator pointed at the same host and SSH credentials, then switch your stack to it. ```shell zenml orchestrator register \ --flavor=ssh \ --hostname= \ --username= \ --ssh_key_path= zenml stack update -o ``` The main difference from HyperAI is that the SSH orchestrator uses the generic SSH integration and additionally supports dynamic pipelines.
ZenML Scarf
--- ### Book/Component Guide/Orchestrators/Tekton (docs/book/component-guide/orchestrators/tekton.md) --- description: Orchestrating your pipelines to run on Tekton. --- # Tekton Orchestrator [Tekton](https://tekton.dev/) is a powerful and flexible open-source framework for creating CI/CD systems, allowing developers to build, test, and deploy across cloud providers and on-premise systems. {% hint style="warning" %} This component is only meant to be used within the context of a [remote ZenML deployment scenario](https://docs.zenml.io/getting-started/deploying-zenml/). Usage with a local ZenML deployment may lead to unexpected behavior! {% endhint %} ### When to use it You should use the Tekton orchestrator if: * you're looking for a proven production-grade orchestrator. * you're looking for a UI in which you can track your pipeline runs. * you're already using Kubernetes or are not afraid of setting up and maintaining a Kubernetes cluster. * you're willing to deploy and maintain Tekton Pipelines on your cluster. ### How to deploy it You'll first need to set up a Kubernetes cluster and deploy Tekton Pipelines: {% tabs %} {% tab title="AWS" %} * A remote ZenML server. See the [deployment guide](https://docs.zenml.io/getting-started/deploying-zenml/) for more information. * Have an existing AWS [EKS cluster](https://docs.aws.amazon.com/eks/latest/userguide/create-cluster.html) set up. * Make sure you have the [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) set up. * Download and [install](https://kubernetes.io/docs/tasks/tools/) `kubectl` and configure it to talk to your EKS cluster using the following command: ```powershell aws eks --region REGION update-kubeconfig --name CLUSTER_NAME ``` * [Install](https://tekton.dev/docs/pipelines/install/) Tekton Pipelines onto your cluster. {% endtab %} {% tab title="GCP" %} * A remote ZenML server. See the [deployment guide](https://docs.zenml.io/getting-started/deploying-zenml/) for more information. * Have an existing GCP [GKE cluster](https://cloud.google.com/kubernetes-engine/docs/quickstart) set up. * Make sure you have the [Google Cloud CLI](https://cloud.google.com/sdk/docs/install-sdk) set up first. * Download and [install](https://kubernetes.io/docs/tasks/tools/) `kubectl` and [configure](https://cloud.google.com/kubernetes-engine/docs/how-to/cluster-access-for-kubectl) it to talk to your GKE cluster using the following command: ```powershell gcloud container clusters get-credentials CLUSTER_NAME ``` * [Install](https://tekton.dev/docs/pipelines/install/) Tekton Pipelines onto your cluster. {% endtab %} {% tab title="Azure" %} * A remote ZenML server. See the [deployment guide](https://docs.zenml.io/getting-started/deploying-zenml/) for more information. * Have an existing [AKS cluster](https://azure.microsoft.com/en-in/services/kubernetes-service/#documentation) set up. * Make sure you have the [`az` CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli) set up first. * Download and [install](https://kubernetes.io/docs/tasks/tools/) `kubectl` and it to talk to your AKS cluster using the following command: ```powershell az aks get-credentials --resource-group RESOURCE_GROUP --name CLUSTER_NAME ``` * [Install](https://tekton.dev/docs/pipelines/install/) Tekton Pipelines onto your cluster. {% endtab %} {% endtabs %} {% hint style="info" %} If one or more of the deployments are not in the `Running` state, try increasing the number of nodes in your cluster. {% endhint %} {% hint style="warning" %} ZenML has only been tested with Tekton Pipelines >=0.38.3 and may not work with previous versions. {% endhint %} ### How to use it To use the Tekton orchestrator, we need: * The ZenML `tekton` integration installed. If you haven't done so, run ```shell zenml integration install tekton -y ``` * [Docker](https://www.docker.com) installed and running. * Tekton pipelines deployed on a remote cluster. See the [deployment section](tekton.md#how-to-deploy-it) for more information. * The name of your Kubernetes context which points to your remote cluster. Run `kubectl config get-contexts` to see a list of available contexts. * A [remote artifact store](https://docs.zenml.io/stacks/artifact-stores/) as part of your stack. * A [remote container registry](https://docs.zenml.io/stacks/container-registries/) as part of your stack. * [kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl) installed and the name of the Kubernetes configuration context which points to the target cluster (i.e. run`kubectl config get-contexts` to see a list of available contexts). This is optional (see below). {% hint style="info" %} It is recommended that you set up [a Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/service-connectors-guide) and use it to connect ZenML Stack Components to the remote Kubernetes cluster, especially If you are using a Kubernetes cluster managed by a cloud provider like AWS, GCP or Azure, This guarantees that your Stack is fully portable on other environments and your pipelines are fully reproducible. {% endhint %} We can then register the orchestrator and use it in our active stack. This can be done in two ways: 1. If you have [a Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/service-connectors-guide) configured to access the remote Kubernetes cluster, you no longer need to set the `kubernetes_context` attribute to a local `kubectl` context. In fact, you don't need the local Kubernetes CLI at all. You can [connect the stack component to the Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/service-connectors-guide#connect-stack-components-to-resources) instead: ``` /* Detailed source-code truncated for AI context efficiency. */ ``` 2. if you don't have a Service Connector on hand and you don't want to [register one](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/service-connectors-guide#register-service-connectors) , the local Kubernetes `kubectl` client needs to be configured with a configuration context pointing to the remote cluster. The `kubernetes_context` stack component must also be configured with the value of that context: ```shell zenml orchestrator register --flavor=tekton --kubernetes_context= # Register and activate a stack with the new orchestrator zenml stack register -o ... --set ``` {% hint style="info" %} ZenML will build a Docker image called `/zenml:` which includes your code and use it to run your pipeline steps in Tekton. Check out [this page](https://docs.zenml.io/how-to/customize-docker-builds/) if you want to learn more about how ZenML builds these images and how you can customize them. {% endhint %} You can now run any ZenML pipeline using the Tekton orchestrator: ```shell python file_that_runs_a_zenml_pipeline.py ``` #### Tekton UI Tekton comes with its own UI that you can use to find further details about your pipeline runs, such as the logs of your steps. To find the Tekton UI endpoint, we can use the following command: ```bash kubectl get ingress -n tekton-pipelines -o jsonpath='{.items[0].spec.rules[0].host}' ``` #### Additional configuration For additional configuration of the Tekton orchestrator, you can pass `TektonOrchestratorSettings` which allows you to configure node selectors, affinity, and tolerations to apply to the Kubernetes Pods running your pipeline. These can be either specified using the Kubernetes model objects or as dictionaries. ```python from zenml.integrations.tekton.flavors.tekton_orchestrator_flavor import TektonOrchestratorSettings from kubernetes.client.models import V1Toleration tekton_settings = TektonOrchestratorSettings( pod_settings={ "affinity": { "nodeAffinity": { "requiredDuringSchedulingIgnoredDuringExecution": { "nodeSelectorTerms": [ { "matchExpressions": [ { "key": "node.kubernetes.io/name", "operator": "In", "values": ["my_powerful_node_group"], } ] } ] } } }, "tolerations": [ V1Toleration( key="node.kubernetes.io/name", operator="Equal", value="", effect="NoSchedule" ) ] } ) ``` If your pipelines steps have certain hardware requirements, you can specify them as `ResourceSettings`: ```python resource_settings = ResourceSettings(cpu_count=8, memory="16GB") ``` These settings can then be specified on either pipeline-level or step-level: ```python # Either specify on pipeline-level @pipeline( settings={ "orchestrator": tekton_settings, "resources": resource_settings, } ) def my_pipeline(): ... # OR specify settings on step-level @step( settings={ "orchestrator": tekton_settings, "resources": resource_settings, } ) def my_step(): ... ``` Check out the [SDK docs](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-tekton.html#zenml.integrations.tekton) for a full list of available attributes and [this docs page](https://docs.zenml.io/concepts/steps_and_pipelines/configuration) for more information on how to specify settings. For more information and a full list of configurable attributes of the Tekton orchestrator, check out the [SDK Docs](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-tekton.html#zenml.integrations.tekton) . #### Enabling CUDA for GPU-backed hardware Note that if you wish to use this orchestrator to run steps on a GPU, you will need to follow [the instructions on this page](https://docs.zenml.io/user-guides/tutorial/distributed-training/) to ensure that it works. It requires adding some extra settings customization and is essential to enable CUDA for the GPU to give its full acceleration.
ZenML Scarf
--- ### Book/Component Guide/Orchestrators/Vertex (docs/book/component-guide/orchestrators/vertex.md) --- description: Orchestrating your pipelines to run on Vertex AI. --- # Google Cloud VertexAI Orchestrator [Vertex AI Pipelines](https://cloud.google.com/vertex-ai/docs/pipelines/introduction) is a serverless ML workflow tool running on the Google Cloud Platform. It is an easy way to quickly run your code in a production-ready, repeatable cloud orchestrator that requires minimal setup without provisioning and paying for standby compute. {% hint style="warning" %} This component is only meant to be used within the context of a [remote ZenML deployment scenario](https://docs.zenml.io/getting-started/deploying-zenml/). Usage with a local ZenML deployment may lead to unexpected behavior! {% endhint %} ## When to use it You should use the Vertex orchestrator if: * you're already using GCP. * you're looking for a proven production-grade orchestrator. * you're looking for a UI in which you can track your pipeline runs. * you're looking for a managed solution for running your pipelines. * you're looking for a serverless solution for running your pipelines. ## How to deploy it {% hint style="info" %} Would you like to skip ahead and deploy a full ZenML cloud stack already, including a Vertex AI orchestrator? Check out the[in-browser stack deployment wizard](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/deploy-a-cloud-stack), the [stack registration wizard](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/register-a-cloud-stack), or [the ZenML GCP Terraform module](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/deploy-a-cloud-stack-with-terraform) for a shortcut on how to deploy & register this stack component. {% endhint %} In order to use a Vertex AI orchestrator, you need to first deploy [ZenML to the cloud](https://docs.zenml.io/getting-started/deploying-zenml/). It would be recommended to deploy ZenML in the same Google Cloud project as where the Vertex infrastructure is deployed, but it is not necessary to do so. You must ensure that you are connected to the remote ZenML server before using this stack component. The only other thing necessary to use the ZenML Vertex orchestrator is enabling Vertex-relevant APIs on the Google Cloud project. ## How to use it To use the Vertex orchestrator, we need: * The ZenML `gcp` integration installed. If you haven't done so, run ```shell zenml integration install gcp ``` * [Docker](https://www.docker.com) installed and running. * A [remote artifact store](https://docs.zenml.io/stacks/artifact-stores/) as part of your stack. * A [remote container registry](https://docs.zenml.io/stacks/container-registries/) as part of your stack. * [GCP credentials with proper permissions](vertex.md#gcp-credentials-and-permissions) * The GCP project ID and location in which you want to run your Vertex AI pipelines. ### GCP credentials and permissions This part is without doubt the most involved part of using the Vertex orchestrator. In order to run pipelines on Vertex AI, you need to have a GCP user account and/or one or more GCP service accounts set up with proper permissions, depending on whether you wish to practice [the principle of least privilege](https://cloud.google.com/iam/docs/using-iam-securely) and distribute permissions across multiple service accounts. You also have three different options to provide credentials to the orchestrator: * use the [`gcloud` CLI](https://cloud.google.com/sdk/gcloud) to authenticate locally with GCP * configure the orchestrator to use a [service account key file](https://cloud.google.com/iam/docs/creating-managing-service-account-keys) to authenticate with GCP by setting the `service_account_path` parameter in the orchestrator configuration. * (recommended) configure [a GCP Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/gcp-service-connector) with GCP credentials and then link the Vertex AI Orchestrator stack component to the Service Connector. This section [explains the different components and GCP resources](vertex.md#vertex-ai-pipeline-components) involved in running a Vertex AI pipeline and what permissions they need, then provides instructions for three different configuration use-cases: 1. [use the local `gcloud` CLI configured with your GCP user account](vertex.md#configuration-use-case-local-gcloud-cli-with-user-account), including the ability to schedule pipelines 2. [use a GCP Service Connector and a single service account](vertex.md#configuration-use-case-gcp-service-connector-with-single-service-account) with all permissions, including the ability to schedule pipelines 3. [use a GCP Service Connector and multiple service accounts](vertex.md#configuration-use-case-gcp-service-connector-with-different-service-accounts) for different permissions, including the ability to schedule pipelines #### Vertex AI pipeline components To understand what accounts you need to provision and why, let's look at the different components of the Vertex orchestrator: 1. _the ZenML client environment_ is the environment where you run the ZenML code responsible for building the pipeline Docker image and submitting the pipeline to Vertex AI, among other things. This is usually your local machine or some other environment used to automate running pipelines, like a CI/CD job. This environment needs to be able to authenticate with GCP and needs to have the necessary permissions to create a job in Vertex Pipelines, (e.g. [the `Vertex AI User` role](https://cloud.google.com/vertex-ai/docs/general/access-control#aiplatform.user)). If you are planning to [run pipelines on a schedule](vertex.md#run-pipelines-on-a-schedule), _the ZenML client environment_ also needs additional permissions: * the [`Storage Object Creator Role`](https://cloud.google.com/iam/docs/understanding-roles#storage.objectCreator) to be able to write the pipeline JSON file to the artifact store directly (NOTE: not needed if the Artifact Store is configured with credentials or is linked to Service Connector) 2. _the Vertex AI pipeline environment_ is the GCP environment in which the pipeline steps themselves are running in GCP. The Vertex AI pipeline runs in the context of a GCP service account which we'll call here _the workload service account_. _The workload service account_ can be explicitly configured in the orchestrator configuration via the `workload_service_account` parameter. If it is omitted, the orchestrator will use [the Compute Engine default service account](https://cloud.google.com/compute/docs/access/service-accounts#default_service_account) for the GCP project in which the pipeline is running. This service account needs to have the following permissions: * permissions to run a Vertex AI pipeline, (e.g. [the `Vertex AI Service Agent` role](https://cloud.google.com/vertex-ai/docs/general/access-control#aiplatform.serviceAgent)). As you can see, there can be dedicated service accounts involved in running a Vertex AI pipeline. That's two service accounts if you also use a service account to authenticate to GCP in _the ZenML client environment_. However, you can keep it simple and use the same service account everywhere. #### Configuration use-case: local `gcloud` CLI with user account This configuration use-case assumes you have configured the [`gcloud` CLI](https://cloud.google.com/sdk/gcloud) to authenticate locally with your GCP account (i.e. by running `gcloud auth login`). It also assumes the following: * your GCP account has permissions to create a job in Vertex Pipelines, (e.g. [the `Vertex AI User` role](https://cloud.google.com/vertex-ai/docs/general/access-control#aiplatform.user)). * [the Compute Engine default service account](https://cloud.google.com/compute/docs/access/service-accounts#default_service_account) for the GCP project in which the pipeline is running is updated with additional permissions required to run a Vertex AI pipeline, (e.g. [the `Vertex AI Service Agent` role](https://cloud.google.com/vertex-ai/docs/general/access-control#aiplatform.serviceAgent)). This is the easiest way to configure the Vertex AI Orchestrator, but it has the following drawbacks: * the setup is not portable on other machines and reproducible by other users. * it uses the Compute Engine default service account, which is not recommended, given that it has a lot of permissions by default and is used by many other GCP services. We can then register the orchestrator as follows: ```shell zenml orchestrator register \ --flavor=vertex \ --project= \ --location= \ --synchronous=true ``` #### Configuration use-case: GCP Service Connector with single service account This use-case assumes you have already configured a GCP service account with the following permissions: * permissions to create a job in Vertex Pipelines, (e.g. [the `Vertex AI User` role](https://cloud.google.com/vertex-ai/docs/general/access-control#aiplatform.user)). * permissions to run a Vertex AI pipeline, (e.g. [the `Vertex AI Service Agent` role](https://cloud.google.com/vertex-ai/docs/general/access-control#aiplatform.serviceAgent)). * the [Storage Object Creator Role](https://cloud.google.com/iam/docs/understanding-roles#storage.objectCreator) to be able to write the pipeline JSON file to the artifact store directly. It also assumes you have already created a service account key for this service account and downloaded it to your local machine (e.g. in a `connectors-vertex-ai.json` file). This is not recommended if you are conscious about security. The principle of least privilege is not applied here and the environment in which the pipeline steps are running has many permissions that it doesn't need. ```shell zenml service-connector register --type gcp --auth-method=service-account --project_id= --service_account_json=@connectors-vertex-ai.json --resource-type gcp-generic zenml orchestrator register \ --flavor=vertex \ --location= \ --synchronous=true \ --workload_service_account=@.iam.gserviceaccount.com zenml orchestrator connect --connector ``` #### Configuration use-case: GCP Service Connector with different service accounts This setup applies the principle of least privilege by using different service accounts with the minimum of permissions needed for [the different components involved in running a Vertex AI pipeline](vertex.md#vertex-ai-pipeline-components). It also uses a GCP Service Connector to make the setup portable and reproducible. This configuration is a best-in-class setup that you would normally use in production, but it requires a lot more work to prepare. {% hint style="info" %} This setup involves creating and configuring several GCP service accounts, which is a lot of work and can be error prone. If you don't really need the added security, you can use [the GCP Service Connector with a single service account](vertex.md#configuration-use-case-gcp-service-connector-with-single-service-account) instead. {% endhint %} The following GCP service accounts are needed: 1. a "client" service account that has the following permissions: * permissions to create a job in Vertex Pipelines, (e.g. [the `Vertex AI User` role](https://cloud.google.com/vertex-ai/docs/general/access-control#aiplatform.user)). * permissions to create a Google Cloud Function (e.g. with the [`Cloud Functions Developer Role`](https://cloud.google.com/functions/docs/reference/iam/roles#cloudfunctions.developer)). * the [Storage Object Creator Role](https://cloud.google.com/iam/docs/understanding-roles#storage.objectCreator) to be able to write the pipeline JSON file to the artifact store directly (NOTE: not needed if the Artifact Store is configured with credentials or is linked to Service Connector). 2. a "workload" service account that has permissions to run a Vertex AI pipeline, (e.g. [the `Vertex AI Service Agent` role](https://cloud.google.com/vertex-ai/docs/general/access-control#aiplatform.serviceAgent)). {% hint style="info" %} **Alternative: Custom Roles for Maximum Security** For even more granular control, you can create custom roles instead of using the predefined roles: **Client Service Account Custom Permissions:** - `aiplatform.pipelineJobs.create` - `aiplatform.pipelineJobs.get` - `aiplatform.pipelineJobs.list` - `cloudfunctions.functions.create` - `storage.objects.create` (for artifact store access) **Workload Service Account Custom Permissions:** - `aiplatform.customJobs.create` - `aiplatform.customJobs.get` - `aiplatform.customJobs.list` - `storage.objects.get` - `storage.objects.create` This provides the absolute minimum permissions required for Vertex AI pipeline operations. {% endhint %} A key is also needed for the "client" service account. You can create a key for this service account and download it to your local machine (e.g. in a `connectors-vertex-ai-client.json` file). With all the service accounts and the key ready, we can register [the GCP Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/gcp-service-connector) and Vertex AI orchestrator as follows: ```shell zenml service-connector register --type gcp --auth-method=service-account --project_id= --service_account_json=@connectors-vertex-ai-client.json --resource-type gcp-generic zenml orchestrator register \ --flavor=vertex \ --location= \ --synchronous=true \ --workload_service_account=@.iam.gserviceaccount.com zenml orchestrator connect --connector ``` ### Configuring the stack With the orchestrator registered, we can use it in our active stack: ```shell # Register and activate a stack with the new orchestrator zenml stack register -o ... --set ``` {% hint style="info" %} ZenML will build a Docker image called `/zenml:` which includes your code and use it to run your pipeline steps in Vertex AI. Check out [this page](https://docs.zenml.io/how-to/customize-docker-builds/) if you want to learn more about how ZenML builds these images and how you can customize them. {% endhint %} You can now run any ZenML pipeline using the Vertex orchestrator: ```shell python file_that_runs_a_zenml_pipeline.py ``` ### Vertex UI Vertex comes with its own UI that you can use to find further details about your pipeline runs, such as the logs of your steps. For any runs executed on Vertex, you can get the URL to the Vertex UI in Python using the following code snippet: ```python from zenml.client import Client pipeline_run = Client().get_pipeline_run("") orchestrator_url = pipeline_run.run_metadata["orchestrator_url"] ``` ### Run pipelines on a schedule The Vertex Pipelines orchestrator supports running pipelines on a schedule using its [native scheduling capability](https://cloud.google.com/vertex-ai/docs/pipelines/schedule-pipeline-run). **How to schedule a pipeline** ```python from datetime import datetime, timedelta from zenml import pipeline from zenml.config.schedule import Schedule @pipeline def first_pipeline(): ... # Run a pipeline every 5th minute first_pipeline = first_pipeline.with_options( schedule=Schedule( cron_expression="*/5 * * * *" ) ) first_pipeline() @pipeline def second_pipeline(): ... # Run a pipeline every hour # starting in one day from now and ending in three days from now second_pipeline = second_pipeline.with_options( schedule=Schedule( cron_expression="0 * * * *", start_time=datetime.now() + timedelta(days=1), end_time=datetime.now() + timedelta(days=3), ) ) second_pipeline() ``` {% hint style="warning" %} The Vertex orchestrator only supports the `cron_expression`, `start_time` (optional) and `end_time` (optional) parameters in the `Schedule` object, and will ignore all other parameters supplied to define the schedule. {% endhint %} The `start_time` and `end_time` timestamp parameters are both optional and are to be specified in local time. They define the time window in which the pipeline runs will be triggered. If they are not specified, the pipeline will run indefinitely. The `cron_expression` parameter [supports timezones](https://cloud.google.com/vertex-ai/docs/reference/rest/v1beta1/projects.locations.schedules). For example, the expression `TZ=Europe/Paris 0 10 * * *` will trigger runs at 10:00 in the Europe/Paris timezone. **How to update/delete a scheduled pipeline** Note that ZenML only gets involved to schedule a run, but maintaining the lifecycle of the schedule is the responsibility of the user. In order to cancel a scheduled Vertex pipeline, you need to manually delete the schedule in VertexAI (via the UI or the CLI). Here is an example (WARNING: Will delete all schedules if you run this): ``` /* Detailed source-code truncated for AI context efficiency. */ ``` ### Additional configuration For additional configuration of the Vertex orchestrator, you can pass `VertexOrchestratorSettings` which allows you to configure labels for your Vertex Pipeline jobs or specify which GPU to use. ```python from zenml.integrations.gcp.flavors.vertex_orchestrator_flavor import ( VertexOrchestratorSettings ) vertex_settings = VertexOrchestratorSettings(labels={"key": "value"}) ``` If your pipelines steps have certain hardware requirements, you can specify them as `ResourceSettings`: ```python from zenml.config import ResourceSettings resource_settings = ResourceSettings(cpu_count=8, memory="16GB") ``` To run your pipeline (or some steps of it) on a GPU, you will need to set both a node selector and the GPU count as follows: ```python from zenml import step, pipeline from zenml.config import ResourceSettings from zenml.integrations.gcp.flavors.vertex_orchestrator_flavor import ( VertexOrchestratorSettings ) vertex_settings = VertexOrchestratorSettings( pod_settings={ "node_selectors": { "cloud.google.com/gke-accelerator": "NVIDIA_TESLA_A100" }, } ) resource_settings = ResourceSettings(gpu_count=1) # Either specify settings on step-level @step( settings={ "orchestrator": vertex_settings, "resources": resource_settings, } ) def my_step(): ... # OR specify on pipeline-level @pipeline( settings={ "orchestrator": vertex_settings, "resources": resource_settings, } ) def my_pipeline(): ... ``` You can find available accelerator types [here](https://cloud.google.com/vertex-ai/docs/training/configure-compute#specifying_gpus). ### Using Custom Job Parameters For more advanced hardware configuration, you can use `VertexCustomJobParameters` to customize each step's execution environment. This allows you to specify detailed requirements like boot disk size, accelerator type, machine type, and more without needing a separate step operator. ```python from zenml.integrations.gcp.vertex_custom_job_parameters import ( VertexCustomJobParameters, ) from zenml import step, pipeline from zenml.integrations.gcp.flavors.vertex_orchestrator_flavor import ( VertexOrchestratorSettings ) # Create settings with a larger boot disk (1TB) large_disk_settings = VertexOrchestratorSettings( custom_job_parameters=VertexCustomJobParameters( boot_disk_size_gb=1000, # 1TB disk boot_disk_type="pd-standard", # Standard persistent disk (cheaper) machine_type="n1-standard-8" ) ) # Create settings with GPU acceleration gpu_settings = VertexOrchestratorSettings( custom_job_parameters=VertexCustomJobParameters( accelerator_type="NVIDIA_TESLA_A100", accelerator_count=1, machine_type="n1-standard-8", boot_disk_size_gb=200 # Larger disk for GPU workloads ) ) # Step that needs a large disk but no GPU @step(settings={"orchestrator": large_disk_settings}) def data_processing_step(): # Process large datasets that require a lot of disk space ... # Step that needs GPU acceleration @step(settings={"orchestrator": gpu_settings}) def training_step(): # Train ML model using GPU ... # Define pipeline that uses both steps @pipeline() def my_pipeline(): data = data_processing_step() model = training_step(data) ... ``` You can also specify these parameters at pipeline level to apply them to all steps: ```python @pipeline( settings={ "orchestrator": VertexOrchestratorSettings( custom_job_parameters=VertexCustomJobParameters( boot_disk_size_gb=500, # 500GB disk for all steps machine_type="n1-standard-4" ) ) } ) def my_pipeline(): ... ``` The `VertexCustomJobParameters` supports the following common configuration options: | Parameter | Description | |-----------|-------------| | boot_disk_size_gb | Size of the boot disk in GB (default: 100) | | boot_disk_type | Type of disk ("pd-standard", "pd-ssd", etc.) | | machine_type | Machine type for computation (e.g., "n1-standard-4") | | accelerator_type | Type of accelerator (e.g., "NVIDIA_TESLA_T4", "NVIDIA_TESLA_A100") | | accelerator_count | Number of accelerators to attach | | service_account | Service account to use for the job | | persistent_resource_id | ID of persistent resource for faster job startup | #### Advanced Custom Job Parameters For advanced scenarios, you can use `additional_training_job_args` to pass additional parameters directly to the underlying Google Cloud Pipeline Components library: ```python @step( settings={ "orchestrator": VertexOrchestratorSettings( custom_job_parameters=VertexCustomJobParameters( machine_type="n1-standard-8", # Advanced parameters passed directly to create_custom_training_job_from_component additional_training_job_args={ "timeout": "86400s", # 24 hour timeout "network": "projects/12345/global/networks/my-vpc", "enable_web_access": True, "reserved_ip_ranges": ["192.168.0.0/16"], "base_output_directory": "gs://my-bucket/outputs", "labels": {"team": "ml-research", "project": "image-classification"} } ) ) } ) def my_advanced_step(): ... ``` These advanced parameters are passed directly to the Google Cloud Pipeline Components library's [`create_custom_training_job_from_component`](https://google-cloud-pipeline-components.readthedocs.io/en/google-cloud-pipeline-components-2.19.0/api/v1/custom_job.html#v1.custom_job.create_custom_training_job_from_component) function. This approach lets you access new features of the Google API without requiring ZenML updates. {% hint style="warning" %} If you specify parameters in `additional_training_job_args` that are also defined as explicit attributes (like `machine_type` or `boot_disk_size_gb`), the values in `additional_training_job_args` will override the explicit values. For example: ```python VertexCustomJobParameters( machine_type="n1-standard-4", # This will be overridden additional_training_job_args={ "machine_type": "n1-standard-16" # This takes precedence } ) ``` The resulting machine type will be "n1-standard-16". When this happens, ZenML will log a warning at runtime to alert you of the parameter override, which helps avoid confusion about which configuration values are actually being used. {% endhint %} {% hint style="info" %} When using `custom_job_parameters`, ZenML automatically applies certain configurations from your orchestrator: - **Network Configuration**: If you've set `network` in your Vertex orchestrator configuration, it will be automatically applied to all custom jobs unless you explicitly override it in `additional_training_job_args`. - **Encryption Specification**: If you've set `encryption_spec_key_name` in your orchestrator configuration, it will be applied to custom jobs for consistent encryption. - **Service Account**: For non-persistent resource jobs, if no service account is specified in the custom job parameters, the `workload_service_account` from the orchestrator configuration will be used. This inheritance mechanism ensures consistent configuration across your pipeline steps, maintaining connectivity to GCP resources (like databases), security settings, and compute resources without requiring manual specification for each step. {% endhint %} For a complete list of parameters supported by the underlying function, refer to the [Google Pipeline Components SDK V1 docs](https://google-cloud-pipeline-components.readthedocs.io/en/google-cloud-pipeline-components-2.19.0/api/v1/custom_job.html#v1.custom_job.create_custom_training_job_from_component). Note that when using custom job parameters with `persistent_resource_id`, you must always specify a `service_account` as well. {% hint style="info" %} The `additional_training_job_args` field provides future-proofing for your ZenML pipelines. If Google adds new parameters to their API, you can immediately use them without waiting for ZenML updates. This is especially useful for accessing new hardware configurations, networking features, or security settings as they become available. {% endhint %} ### Enabling CUDA for GPU-backed hardware Note that if you wish to use this orchestrator to run steps on a GPU, you will need to follow [the instructions on this page](https://docs.zenml.io/user-guides/tutorial/distributed-training/) to ensure that it works. It requires adding some extra settings customization and is essential to enable CUDA for the GPU to give its full acceleration. ### Using Persistent Resources for Faster Development When developing ML pipelines that use Vertex AI, the startup time for each step can be significant since Vertex needs to provision new compute resources for each run. To speed up development iterations, you can use Vertex AI's [Persistent Resources](https://cloud.google.com/vertex-ai/docs/training/persistent-resource-overview) feature, which keeps compute resources warm between runs. To use persistent resources with the Vertex orchestrator, you first need to create a persistent resource using the GCP Cloud UI, or by [following instructions in the GCP docs](https://cloud.google.com/vertex-ai/docs/training/persistent-resource-create). Next, you'll need to configure your orchestrator to run on the persistent resource. This can be done either through the dashboard or CLI in which case it applies to all pipelines that will be run using this orchestrator, or dynamically in code for a specific pipeline or even just single steps. {% hint style="warning" %} Note that a service account with permissions to access the persistent resource is mandatory, so make sure to always include it in the configuration: {% endhint %} #### Configure the orchestrator using the CLI ```bash # You can also use `zenml orchestrator update` zenml orchestrator register -f vertex --custom_job_parameters='{"persistent_resource_id": "", "service_account": "", "machine_type": "n1-standard-4", "boot_disk_type": "pd-standard"}' ``` #### Configure the orchestrator using the dashboard Navigate to the `Stacks` section in your ZenML dashboard and either create a new Vertex orchestrator or update an existing one. During the creation/update, set the persistent resource ID and other values in the `custom_job_parameters` attribute. #### Configure the orchestrator dynamically in code ```python from zenml.integrations.gcp.vertex_custom_job_parameters import ( VertexCustomJobParameters, ) from zenml.integrations.gcp.flavors.vertex_orchestrator_flavor import ( VertexOrchestratorSettings ) # Configure for the pipeline which applies to all steps @pipeline( settings={ "orchestrator": VertexOrchestratorSettings( custom_job_parameters=VertexCustomJobParameters( persistent_resource_id="", service_account="", machine_type="n1-standard-4", boot_disk_type="pd-standard" ) ) } ) def my_pipeline(): ... # Configure for a single step @step( settings={ "orchestrator": VertexOrchestratorSettings( custom_job_parameters=VertexCustomJobParameters( persistent_resource_id="", service_account="", machine_type="n1-standard-4", boot_disk_type="pd-standard" ) ) } ) def my_step(): ... ``` If you need to explicitly specify that no persistent resource should be used, set `persistent_resource_id` to an empty string: ```python @step( settings={ "orchestrator": VertexOrchestratorSettings( custom_job_parameters=VertexCustomJobParameters( persistent_resource_id="", # Explicitly not using a persistent resource boot_disk_size_gb=1000, # Set a large disk machine_type="n1-standard-8" ) ) } ) def my_step(): ... ``` Using a persistent resource is particularly useful when you're developing locally and want to iterate quickly on steps that need cloud resources. The startup time of the job can be extremely quick. {% hint style="warning" %} When using persistent resources (`persistent_resource_id` specified), you **must** always include a `service_account`. Conversely, when explicitly setting `persistent_resource_id=""` to avoid using persistent resources, ZenML will automatically set the service account to an empty string to avoid Vertex API errors - so don't set the service account in this case. {% endhint %} {% hint style="warning" %} Remember that persistent resources continue to incur costs as long as they're running, even when idle. Make sure to monitor your usage and configure appropriate idle timeout periods. {% endhint %}
ZenML Scarf
--- ### Book/Component Guide/Sandboxes/README (docs/book/component-guide/sandboxes/README.md) --- description: Isolated code execution for AI agents and tool-use loops. icon: box-archive --- # Sandboxes A **Sandbox** is a stack component that provides an isolated environment (container, microVM, or pod) in which a ZenML step can execute code. It's primarily designed for AI-agent workloads: an agent running inside a step uses the active stack's Sandbox to execute generated code as a tool, possibly across many turns of an agent loop. A Sandbox is fundamentally different from a [Step Operator](../step-operators/README.md): - A **Step Operator** runs the step itself on a remote backend. - A **Sandbox** is a tool the step *consumes*. The step still runs wherever the orchestrator placed it, and you can run code in the sandbox from within your step code. The two compose: a step running on, say, the SageMaker step operator can still grab the active stack's Sandbox and use it for code execution. ## Concepts | Term | What it is | |---|---| | **Sandbox component** (`BaseSandbox`) | The stack-component entity. Long-lived, configured once, registered in your stack. One Sandbox component can mint many live Sessions. | | **Sandbox Session** (`SandboxSession`) | A live, bounded interaction with a single isolated environment. Has an `id`, accepts many `exec` calls, can be snapshotted, is explicitly closed. | | **Sandbox Process** (`SandboxProcess`) | A handle to one running command inside a Session. Exposes line-delimited stdout/stderr iterators, `wait()` for the exit code, `kill()` to terminate. | | **Sandbox Snapshot** (`SandboxSnapshot`) | A serializable, provider-specific handle to a captured Session state. Round-trips through `session.create_snapshot()` and `sandbox.restore(snapshot)`. | ## How to use it from a step A Sandbox is reached for from inside a step via the active stack: ```python from zenml import step from zenml.client import Client @step def agent_step(prompt: str) -> str: sandbox = Client().active_stack.sandbox if sandbox is None: raise RuntimeError("No sandbox configured in the active stack.") with sandbox.create_session() as session: process = session.exec(["python", "-c", "print(1 + 1)"]) output = "".join(process.stdout()) process.wait() return output ``` If your stack contains more than one Sandbox component, address them by name through `Client().active_stack.sandboxes` (a `Dict[str, BaseSandbox]`). The `.sandbox` accessor returns the default (first attached). ### Streaming output `SandboxProcess.stdout()` and `stderr()` yield strings one line at a time as the underlying command produces them — the standard pattern for streaming an agent's tool output back to a UI or to the step logs. ### Snapshots, restore, and attach A Session can optionally be snapshotted (provider-dependent — not all backends support full state capture). The returned `SandboxSnapshot` is a Pydantic model that's safe to persist as a ZenML artifact: ```python snap = session.create_snapshot() zenml.save_artifact(snap, name="agent_checkpoint") # ... later, possibly in a different pipeline run: snap = zenml.load_artifact("agent_checkpoint") session = stack.sandbox.restore(snap) # new session, state restored ``` `restore` always returns a *new* Session (fresh `id`); the original Session is unaffected. For the common subagent / cross-pipeline reuse case where the original Session is still alive, use `attach()` instead — no snapshot required, the parent just persists the session id as an artifact and the child reconnects to it: ```python # Parent step session_id = session.id zenml.save_artifact(session_id, name="agent_session") # Child step (possibly in a child pipeline run) session = stack.sandbox.attach(zenml.load_artifact("agent_session")) ``` ### Closing vs destroying - `session.close()` releases the local handle. The sandbox **keeps running** on the provider until its TTL expires. Use this when a subagent or follow-up step might still want to `attach` to the same Session. - `session.destroy()` terminates the sandbox on the provider. After this, the `id` is invalid and `attach()` will fail. ZenML does **not** auto-close Sessions on step exit. Either use a `with` block (`__exit__` calls `close()`) or call `close()` / `destroy()` explicitly. ## Configuration ### Per-step settings Step writers can configure sandbox behavior on individual `@step` invocations via `BaseSandboxSettings` (or a flavor-specific subclass): | Setting | Purpose | |---|---| | `sandbox_environment` | Environment variables to set inside the Session. | Flavors that boot a container or microVM add their own settings (for example image selection and a provider TTL) on a flavor-specific `BaseSandboxSettings` subclass. ### Sandbox logs Sandbox stdout/stderr automatically lands on the active step as a dedicated `sandbox:` log source. Each `session.exec(...)` writes a `$ ` marker, then the process output (stdout at INFO, stderr at ERROR), then a trailing `OK`/`FAIL exit code in s` marker — reads like a shell session. ZenML's own step-level Python logger calls stay in the regular `step` source: the sandbox source is dedicated to actual sandbox-execution events, no false attribution. Multi-session steps don't clobber: each session's metadata (flavor, dashboard URL when the flavor exposes one) is keyed by session id (`sandbox..flavor`, `sandbox..dashboard_url`). ## Security considerations Every value you put in a Session's environment (`sandbox_environment`) is **readable by code running inside the Session**. If you run LLM-generated code in the Sandbox and care about credential isolation, treat the Session environment as visible to the agent. ## Available flavors - **[Local](local.md)** — subprocess-based; **no isolation**; built-in. Intended for examples, unit tests, and development against the abstraction. - **[Kubernetes](kubernetes.md)** — pod-backed sessions executed through Kubernetes exec. ## Develop a custom Sandbox To build a flavor for a new backend, subclass `BaseSandbox`, `SandboxSession`, `SandboxProcess`, and optionally `SandboxSnapshot`. The minimal required surface is: - `BaseSandbox.create_session(settings=None) -> SandboxSession` - `SandboxSession.exec(...) -> SandboxProcess` - `SandboxSession.close()` - `SandboxProcess.stdout()` / `stderr()` / `wait()` / `kill()` / `exit_code` Everything else (`attach`, `create_snapshot`, `restore`, `aexec`, `upload_file`, `download_file`, `destroy`) is opt-in — the base raises `NotImplementedError` and you override only what your backend supports. Register your flavor via the standard `Integration.flavors()` hook. --- ### Book/Component Guide/Sandboxes/Kubernetes (docs/book/component-guide/sandboxes/kubernetes.md) --- description: Pod-backed sandbox sessions for isolated command execution on Kubernetes. --- # Kubernetes Sandbox The Kubernetes sandbox flavor creates one Kubernetes pod per sandbox session and executes each `session.exec(...)` command inside that pod using Kubernetes `exec`. ## How to register ```bash zenml integration install kubernetes zenml sandbox register k8s-sb --flavor=kubernetes --connector= zenml stack update --sandbox k8s-sb ``` The [Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/service-connectors-guide) must provide access to a Kubernetes cluster. Alternatively, you can register the sandbox without a connector: ```bash zenml sandbox register k8s-sb --flavor=kubernetes --kubernetes_context= ``` In this case, the sandbox uses your local kubeconfig credentials. `kubernetes_context` selects the kubeconfig context to use and defaults to the active one. ## Additional configuration Some configuration options for the Kubernetes sandbox can only be set through the sandbox config when you register it (and cannot be changed per-session through the settings): - **`incluster`** (default: False): If `True`, the sandbox will load the in-cluster Kubernetes configuration and create session pods in the same cluster it is running in, ignoring the `kubernetes_context`. - **`kubernetes_context`**: The name of the Kubernetes context to use for creating session pods (ignored if using a service connector or `incluster`). - **`kubernetes_namespace`** (default: "zenml"): The Kubernetes namespace in which session pods are created. The namespace must already exist in the Kubernetes cluster. The following configuration options can be set either through the sandbox config or overridden using `KubernetesSandboxSettings`: - **`sandbox_environment`**: Environment variables to set in session pods and command executions. - **`image`** (default: "python:3.11-slim"): The container image used for session pods. The image must provide `/bin/sh` and `base64`, so distroless images are not supported. - **`pod_settings`**: Node selectors, labels, affinity, tolerations, secrets, environment variables, image pull secrets, the scheduler name and additional arguments to apply to the session pods. These can be either specified using the Kubernetes model objects or as dictionaries. - **`service_account_name`**: The name of a Kubernetes service account to use for session pods. If not configured, pods use the namespace default service account. - **`automount_service_account_token`** (default: False): If `True`, a Kubernetes API token is mounted into session pods, allowing code running in the sandbox to call the Kubernetes API with the service account's RBAC permissions. - **`privileged`** (default: False): If the container should be run in privileged mode. - **`pod_startup_timeout`** (default: 120): The maximum time (in seconds) to wait for a session pod to become running. - **`api_request_timeout`**: Timeout (in seconds) for Kubernetes API requests. If not set, client defaults are used. ## Security model This flavor is designed to make safer defaults possible, but cluster policy remains the primary security boundary. ### ZenML-side safeguards (default behavior) - Service account token is not mounted in sandbox pods (`automount_service_account_token=False`). - `sandbox_environment` values are visible to executed code. Do not inject secrets you do not want sandbox code to read. For untrusted code, set `service_account_name` to a dedicated least-privilege service account instead of relying on the namespace default. ### Required cluster-admin controls For untrusted LLM code, configure at least: - **Least-privilege RBAC** for sandbox service accounts. - **Pod Security Admission** (or equivalent policy engine) to forbid privileged containers and host namespace escapes. - **Runtime hardening policy** (e.g. Kyverno/Gatekeeper) to enforce non-root, read-only root filesystem, seccomp, and no privilege escalation where required. - **Network policies** with explicit egress/ingress rules (default-deny plus allow-list). - **ResourceQuota / LimitRange** to constrain runaway compute and memory usage. - **Image policy controls** (trusted registry, signature/provenance checks if available). Without these controls, sandbox pods may still reach internal services or external networks based on cluster defaults. ## Feature set | Feature | Kubernetes | Notes | |---|---|---| | `create_session()` | ✅ | Creates a pod and waits until it is running. | | `exec()` | ✅ | Uses Kubernetes exec websocket streaming. | | Streaming output | ✅ | Stdout/stderr stream line-by-line through `SandboxProcess`. | | Sandbox log forwarding | ✅ | Output is forwarded into `sandbox:` step logs. | | `destroy()` | ✅ | Deletes the backing pod. | | `attach(session_id)` | ✅ | Re-attaches to a running session pod. | | `snapshot()` / `restore()` | ❌ | Not implemented. | | `upload_file` / `download_file` | ✅ | Transfers file content through Kubernetes exec. Uploads are limited to 256 MiB per file. | ## Attaching to a running session `attach(session_id)` re-creates a session handle for an existing session pod. Attaching fails if the pod does not exist, is not running, or was created by a different sandbox component. {% hint style="warning" %} An attached session shares the pod with the original session handle. Calling `destroy()` on either handle deletes the pod, after which `exec()` calls on the other handle fail. {% endhint %} ## Lifecycle behavior - `close()` closes only the local session handle. It does not delete the pod. - `destroy()` deletes the backing pod and then closes the handle. - `kill()` only stops streaming output. Kubernetes exec does not propagate signals on disconnect, so the command keeps running inside the pod until the pod is deleted. --- ### Book/Component Guide/Sandboxes/Local (docs/book/component-guide/sandboxes/local.md) --- description: A no-isolation subprocess sandbox for development and examples. --- # Local Sandbox The local sandbox flavor runs commands as `subprocess.Popen` on the same machine as the step, with the same OS user, full filesystem access, and full network access. **It provides no isolation.** It is intended for: - Quickstart / examples that should work without a Modal or other cloud account. - Unit tests of code that consumes the `BaseSandbox` interface. - Local development against the abstraction. It is **not** intended for running untrusted LLM-generated code. For real isolation, use a different sandbox flavor. ## How to register ```bash zenml sandbox register local-sb --flavor=local zenml stack update --sandbox local-sb ``` ## Settings The Local sandbox inherits `BaseSandboxSettings` and adds `forward_env`. - `sandbox_environment`: explicit environment variables to set inside the subprocess. - `forward_env`: which parent-process variables are forwarded into the session. Set `forward_env=True` to forward the entire parent environment, `False` to forward nothing, or pass a list of names to forward exactly those. It defaults to a set that keeps command resolution and UTF-8 stdio working: `PATH`, `HOME`, `LANG`, `LC_ALL`, `TMPDIR`, `TERM`. Secrets in the parent environment stay isolated from the session unless `forward_env` is `True` or names them explicitly. `sandbox_environment` is layered on top and overrides forwarded variables on a key collision. ## What it doesn't support | Feature | Local | Notes | |---|---|---| | `snapshot()` / `restore()` | ❌ | No state-capture primitive locally. | | `attach(session_id)` | ❌ | Sessions are bound to the parent process; not re-attachable. | | `upload_file` / `download_file` | ❌ | Files live in the session workdir already; use plain Python file I/O. | | Streaming output | ✅ | `subprocess.PIPE` line-buffered. | | Sandbox log forwarding | ✅ | Each `exec` emits a `$ ` marker plus stdout/stderr lines + an `OK`/`FAIL exit code ` trailer into the step's `sandbox:` log source. | | `destroy()` | ✅ | Delegates to `close()`; removes the session workdir. | ## When to use it Use the local flavor when: - You want to try the Sandbox abstraction without setting up infrastructure. - You're running an example that demonstrates the agent / sandbox split but doesn't care about isolation. - You're writing a unit test that exercises a tool that consumes a `SandboxSession`. Otherwise, use a containerized flavor. --- ### Book/Component Guide/Sandboxes/Modal (docs/book/component-guide/sandboxes/modal.md) --- description: Running agent code in Modal sandboxes. --- # Modal Sandbox [Modal](https://modal.com) provides fast-booting, ephemeral compute containers ideal for running LLM-generated code as part of an AI-agent step. The Modal Sandbox flavor wraps Modal's `Sandbox` / `ContainerProcess` / `Image` primitives in ZenML's [Sandbox](README.md) interface, so an agent inside a step can `exec()` commands, stream output, snapshot the filesystem, reattach by id, and tear down — all without leaving ZenML. ### When to use it Use the Modal Sandbox flavor when: - Your agent needs sub-second cold starts for ephemeral code execution. - You want filesystem snapshots that boot back into a fresh `Sandbox` on demand. - You're already on Modal (e.g. using the [Modal step operator](../step-operators/modal.md)) and want to share infrastructure with your sandbox workloads. ### How to deploy it 1. [Sign up for a Modal account](https://modal.com/signup). 2. Install the Modal CLI and authenticate: ```bash pip install modal && modal setup ``` 3. Install the ZenML Modal integration: ```bash zenml integration install modal ``` ### How to register the component ```bash zenml sandbox register my-modal-sandbox \ --flavor=modal \ --app_name=my-agent-app ``` Component-level `--env` and ZenML-stored `--secret` values are injected into your **step process** environment (where your agent framework runs), not into the sandbox container — see the [Environment Variables docs](https://docs.zenml.io/concepts/environment-variables#configuring-environment-variables-on-stack-components). Env vars for code running *inside* the sandbox go through the `sandbox_environment` setting instead: ```bash zenml sandbox register my-modal-sandbox --flavor=modal ... \ --env='{"LOG_LEVEL": "debug"}' \ --secret=openai_creds ``` Then attach it to your stack: ```bash zenml stack update --sandbox my-modal-sandbox ``` ### Settings reference `ModalSandboxSettings` (override on individual `@step` decorations): | Field | Purpose | |---|---| | `image` | Docker image to boot the Sandbox from. Any registry reference Modal can pull, e.g. `python:3.11-slim` (the default) or `my-registry/my-image:tag`. | | `sandbox_environment` | Env vars to set in the Session, injected at Sandbox create time. | | `timeout` | Sandbox lifetime in seconds, forwarded to Modal's `Sandbox.create(timeout=)`. Also honored on `restore()`. | | `cpu` | CPU cores requested for the sandbox, e.g. `2`. Modal's default allocation when unset. | | `memory` | Memory requested for the sandbox, e.g. `"2GB"`. Modal's default allocation when unset. | | `gpu` | Modal GPU type, e.g. `"A100"`, `"H100"`, `"T4"`. | | `region` | Modal region (e.g. `"us-east"`). Enterprise/Team plans. | | `cloud` | Cloud provider (e.g. `"aws"`, `"gcp"`). Enterprise/Team plans. | | `modal_environment` | Modal environment used for the App lookup. | `ModalSandboxConfig` additionally holds the component-level fields set at registration time: `app_name` (the Modal App that hosts the Sessions) and the optional `token_id` / `token_secret` pair for explicit Modal credentials (otherwise Modal's ambient auth from `modal setup` is used). Sandbox resources are configured only via `ModalSandboxSettings` (`cpu`, `memory`, `gpu` type string, `region`, `cloud`); cpu/memory follow Modal's defaults when unset. The step's `ResourceSettings` are intentionally **not** mirrored into the sandbox: the orchestrator already provisions those resources for the step itself, and the sandbox would double them. Example: ```python from zenml import step from zenml.integrations.modal.flavors import ModalSandboxSettings @step( settings={ "sandbox.modal": ModalSandboxSettings(gpu="A100", timeout=900), }, ) def agent_step(...): ... ``` Sandbox stdout/stderr automatically lands on the active step under a dedicated `sandbox:` log source — see the [base sandbox docs](README.md#sandbox-logs) for the format. The `sandbox..dashboard_url` step-metadata entry is rendered as a clickable link to the Modal sandbox. ### Using it from a step ```python from zenml import step from zenml.client import Client @step def agent_step(prompt: str) -> str: sandbox = Client().active_stack.sandbox session = sandbox.create_session() try: process = session.exec(["python", "-c", "print(2 + 2)"]) out = "".join(process.stdout()) process.wait() return out finally: session.destroy() ``` Call `session.destroy()` when you're done, or the sandbox keeps billing until the `timeout` TTL kicks in as the backstop. Use `with sandbox.create_session() as session:` (which only calls `close()`, keeping the sandbox alive for a later `attach()`) when you *want* the sandbox to outlive the handle — but don't combine it with `destroy()`: if `destroy()` fails, it deliberately leaves the handle open for a retry, and the `with` block's exit would close it anyway. ### Snapshots and restore Modal supports **filesystem-only** snapshots (`sandbox.snapshot_filesystem()`). Memory and live process state are not captured. ```python from zenml import save_artifact, load_artifact snap = session.create_snapshot() # SandboxSnapshot save_artifact(snap, name="agent_checkpoint") # later — possibly in a different pipeline run: snap = load_artifact("agent_checkpoint") session = stack.sandbox.restore(snap) # boots a new Sandbox from the stored Image ``` If your agent needs to preserve in-memory state across runs, prefer [`attach()`](README.md#snapshots-restore-and-attach) (reconnect to a still-live Session by id) over `restore()` (which boots a fresh Session every time). ### Caveats - Modal sandbox env vars remain **readable from inside the Session** by any code the agent runs. The [Sandbox Auth Proxy pattern](README.md#security-considerations) is on the roadmap; until it lands, treat sandbox env as agent-visible. - `session.close()` invalidates the local handle (further use raises `SandboxSessionClosedError`) but does **not** stop the Modal Sandbox — it keeps running until `session.destroy()` or the `timeout` TTL. Use `sandbox.attach(session_id)` for a fresh handle. - `process.kill()` terminates the **whole Sandbox**, not just the one command — Modal exposes no per-command kill. - `restore()` always returns a **new** Sandbox with a new id. The original `id` is unaffected; if you need id stability across runs, use `attach()` with a persisted session id instead. --- ### Book/Component Guide/Service Connectors/Auth Management (docs/book/component-guide/service-connectors/auth-management.md) --- description: >- Connect your ZenML deployment to a cloud provider and other infrastructure services and resources. icon: plug --- # Introduction A production-grade MLOps platform involves interactions between a diverse combination of third-party libraries and external services sourced from various different vendors. One of the most daunting hurdles in building and operating an MLOps platform composed of multiple components is configuring and maintaining uninterrupted and secured access to the infrastructure resources and services that it consumes. In layman's terms, your pipeline code needs to "connect" to a handful of different services to run successfully and do what it's designed to do. For example, it might need to connect to a private AWS S3 bucket to read and store artifacts, a Kubernetes cluster to execute steps with Kubeflow or Tekton, and a private GCR container registry to build and store container images. ZenML makes this possible by allowing you to configure authentication information and credentials embedded directly into your Stack Components, but this doesn't scale well when you have more than a few Stack Components and has many other disadvantages related to usability and security. Gaining access to infrastructure resources and services requires knowledge about the different authentication and authorization mechanisms and involves configuring and maintaining valid credentials. It gets even more complicated when these different services need to access each other. For instance, the Kubernetes container running your pipeline step needs access to the S3 bucket to store artifacts or needs to access a cloud service like AWS SageMaker, VertexAI, or AzureML to run a CPU/GPU intensive task like training a model. The challenge comes from _setting up and implementing proper authentication and authorization_ with the best security practices in mind, while at the same time _keeping this complexity away from the day-to-day routines_ of coding and running pipelines. The hard-to-swallow truth is there is no single standard that unifies all authentication and authorization-related matters or a single, well-defined set of security best practices that you can follow. However, with ZenML you get the next best thing, an abstraction that keeps the complexity of authentication and authorization away from your code and makes it easier to tackle them: _the ZenML Service Connectors_.

Service Connectors abstract away complexity and implement security best practices

## A representative use-case The range of features covered by Service Connectors is extensive and going through the entire [Service Connector Guide](../../how-to/stack-components/service_connectors.md) can be overwhelming. If all you want is to get a quick overview of how Service Connectors work and what they can do for you, this section is for you. This is a representative example of how you would use a Service Connector to connect ZenML to a cloud service. This example uses [the AWS Service Connector](../../how-to/stack-components/service_connectors.md) to connect ZenML to an AWS S3 bucket and then link [an S3 Artifact Store Stack Component](https://docs.zenml.io/stacks/artifact-stores/s3) to it. Some details about the current alternatives to using Service Connectors and their drawbacks are provided below. Feel free to skip them if you are already familiar with them or just want to get to the good part.
Alternatives to Service Connectors There are quicker alternatives to using a Service Connector to link an S3 Artifact Store to a private AWS S3 bucket. Let's lay them out first and then explain why using a Service Connector is the better option: 1. the authentication information can be embedded directly into the Stack Component, although this is not recommended for security reasons: ```shell zenml artifact-store register s3 --flavor s3 --path=s3://BUCKET_NAME --key=AWS_ACCESS_KEY --secret=AWS_SECRET_KEY ``` 2. [a ZenML secret](https://docs.zenml.io/getting-started/deploying-zenml/secret-management) can hold the AWS credentials and then be referenced in the S3 Artifact Store configuration attributes: ```shell zenml secret create aws --aws_access_key_id=AWS_ACCESS_KEY --aws_secret_access_key=AWS_SECRET_KEY zenml artifact-store register s3 --flavor s3 --path=s3://BUCKET_NAME --key='{{aws.aws_access_key_id}}' --secret='{{aws.aws_secret_access_key}}' ``` 3. an even better version is to reference the secret itself in the S3 Artifact Store configuration: ```shell zenml secret create aws --aws_access_key_id=AWS_ACCESS_KEY --aws_secret_access_key=AWS_SECRET_KEY zenml artifact-store register s3 --flavor s3 --path=s3://BUCKET_NAME --authentication_secret=aws ``` All these options work, but they have many drawbacks: * first of all, not all Stack Components support referencing secrets in their configuration attributes, so this is not a universal solution. * some Stack Components, like those linked to Kubernetes clusters, rely on credentials being set up on the machine where the pipeline is running, which makes pipelines less portable and more difficult to set up. In other cases, you also need to install and set up cloud-specific SDKs and CLIs to be able to use the Stack Component. * people configuring and using Stack Components linked to cloud resources need to be given access to cloud credentials, or even provision the credentials themselves, which requires access to the cloud provider platform and knowledge about how to do it. * in many cases, you can only configure long-lived credentials directly in Stack Components. This is a security risk because they can inadvertently grant access to key resources and services to a malicious party if they are compromised. Implementing a process that rotates credentials regularly is a complex task that requires a lot of effort and maintenance. * Stack Components don't implement any kind of verification regarding the validity and permission of configured credentials. If the credentials are invalid or if they lack the proper permissions to access the remote resource or service, you will only find this out later, when running a pipeline will fail at runtime. * ultimately, given that different Stack Component flavors rely on the same type of resource or cloud provider, it is not good design to duplicate the logic that handles authentication and authorization in each Stack Component implementation. These drawbacks are addressed by Service Connectors.
Without Service Connectors, credentials are stored directly in the Stack Component configuration or ZenML Secret and are directly used in the runtime environment. The Stack Component implementation is directly responsible for validating credentials, authenticating and connecting to the infrastructure service. This is illustrated in the following diagram: When Service Connectors are involved in the authentication and authorization process, they can act as brokers. The credentials validation and authentication process takes place on the ZenML server. In most cases, the main credentials never have to leave the ZenML server as the Service Connector automatically converts them into short-lived credentials with a reduced set of privileges and issues these credentials to clients. Furthermore, multiple Stack Components of different flavors can use the same Service Connector to access different types or resources with the same credentials: In working with Service Connectors, the first step is usually _finding out what types of resources you can connect ZenML to_. Maybe you have already planned out the infrastructure options for your MLOps platform and are looking to find out whether ZenML can accommodate them. Or perhaps you want to use a particular Stack Component flavor in your Stack and are wondering whether you can use a Service Connector to connect it to external resources. Listing the available Service Connector Types will give you a good idea of what you can do with Service Connectors: ```sh zenml service-connector list-types ``` {% code title="Example Command Output" %} ``` /* Detailed source-code truncated for AI context efficiency. */ ``` {% endcode %} Service Connector Types are also displayed in the dashboard during the configuration of a new Service Connector: The cloud provider of choice for our example is AWS and we're looking to hook up an S3 bucket to an S3 Artifact Store Stack Component. We'll use the AWS Service Connector Type.
Interactive structured docs with Service Connector Types A lot more is hidden behind a Service Connector Type than a name and a simple list of resource types. Before using a Service Connector Type to configure a Service Connector, you probably need to understand what it is, what it can offer and what are the supported authentication methods and their requirements. All this can be accessed on-site directly through the CLI or in the dashboard. Some examples are included here. Showing information about the AWS Service Connector Type: ```sh zenml service-connector describe-type aws ``` {% code title="Example Command Output" %} ``` /* Detailed source-code truncated for AI context efficiency. */ ``` {% endcode %} Dashboard equivalent: AWS Service Connector Type Details Fetching details about the S3 bucket resource type: ```sh zenml service-connector describe-type aws --resource-type s3-bucket ``` {% code title="Example Command Output" %} ``` /* Detailed source-code truncated for AI context efficiency. */ ``` {% endcode %} Displaying information about the AWS Session Token authentication method: ```sh zenml service-connector describe-type aws --auth-method session-token ``` {% code title="Example Command Output" %} ``` /* Detailed source-code truncated for AI context efficiency. */ ``` {% endcode %}
Not all Stack Components support being linked to a Service Connector. This is indicated in the flavor description of each Stack Component. Our example uses the S3 Artifact Store, which does support it: ```sh $ zenml artifact-store flavor describe s3 Configuration class: S3ArtifactStoreConfig [...] This flavor supports connecting to external resources with a Service Connector. It requires a 's3-bucket' resource. You can get a list of all available connectors and the compatible resources that they can access by running: 'zenml service-connector list-resources --resource-type s3-bucket' If no compatible Service Connectors are yet registered, you can register a new one by running: 'zenml service-connector register -i' ``` The second step is _registering a Service Connector_ that effectively enables ZenML to authenticate to and access one or more remote resources. This step is best handled by someone with some infrastructure knowledge, but there are sane defaults and auto-detection mechanisms built into most Service Connectors that can make this a walk in the park even for the uninitiated. For our simple example, we're registering an AWS Service Connector with AWS credentials _automatically lifted up from your local host_, giving ZenML access to the same resources that you can access from your local machine through the AWS CLI. This step assumes the AWS CLI is already installed and set up with credentials on your machine (e.g. by running `aws configure`). ```sh zenml service-connector register aws-s3 --type aws --auto-configure --resource-type s3-bucket ``` {% code title="Example Command Output" %} ``` ⠼ Registering service connector 'aws-s3'... Successfully registered service connector `aws-s3` with access to the following resources: ┏━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃ RESOURCE TYPE │ RESOURCE NAMES ┃ ┠───────────────┼───────────────────────────────────────┨ ┃ 📦 s3-bucket │ s3://aws-ia-mwaa-715803424590 ┃ ┃ │ s3://zenbytes-bucket ┃ ┃ │ s3://zenfiles ┃ ┃ │ s3://zenml-demos ┃ ┃ │ s3://zenml-generative-chat ┃ ┃ │ s3://zenml-public-datasets ┃ ┃ │ s3://zenml-public-swagger-spec ┃ ┗━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ``` {% endcode %} The CLI validates and shows all S3 buckets that can be accessed with the auto-discovered credentials. {% hint style="info" %} The ZenML CLI provides an interactive way of registering Service Connectors. Just use the `-i` command line argument and follow the interactive guide: ``` zenml service-connector register -i ``` {% endhint %}
What happens during auto-configuration A quick glance into the Service Connector configuration that was automatically detected gives a better idea of what happened: ```sh zenml service-connector describe aws-s3 ``` {% code title="Example Command Output" %} ``` /* Detailed source-code truncated for AI context efficiency. */ ``` {% endcode %} The AWS Service Connector discovered and lifted the AWS Secret Key that was configured on the local machine and securely stored it in the [Secrets Store](https://docs.zenml.io/getting-started/deploying-zenml/secret-management). Moreover, the following security best practice is automatically enforced by the AWS connector: the AWS Secret Key will be kept hidden on the ZenML Server and the clients will never use it directly to gain access to any AWS resources. Instead, the AWS Service Connector will generate short-lived security tokens and distribute those to clients. It will also take care of issuing new tokens when those expire. This is identifiable from the `session-token` authentication method and the session duration configuration attributes. One way to confirm this is to ask ZenML to show us the exact configuration that a Service Connector client would see, but this requires us to pick an S3 bucket for which temporary credentials can be generated: ```sh zenml service-connector describe aws-s3 --resource-id s3://zenfiles ``` {% code title="Example Command Output" %} ``` /* Detailed source-code truncated for AI context efficiency. */ ``` {% endcode %} As can be seen, this configuration is of a temporary STS AWS token that will expire in 12 hours. The AWS Secret Key is not visible on the client side.
The next step in this journey is _configuring and connecting one (or more) Stack Components to a remote resource_ via the Service Connector registered in the previous step. This is as easy as saying "_I want this S3 Artifact Store to use the `s3://my-bucket` S3 bucket_" and doesn't require any knowledge whatsoever about the authentication mechanisms or even the provenance of those resources. The following example creates an S3 Artifact store and connects it to an S3 bucket with the earlier connector: ```sh zenml artifact-store register s3-zenfiles --flavor s3 --path=s3://zenfiles zenml artifact-store connect s3-zenfiles --connector aws-s3 ``` {% code title="Example Command Output" %} ``` $ zenml artifact-store register s3-zenfiles --flavor s3 --path=s3://zenfiles Successfully registered artifact_store `s3-zenfiles`. $ zenml artifact-store connect s3-zenfiles --connector aws-s3 Successfully connected artifact store `s3-zenfiles` to the following resources: ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┓ ┃ CONNECTOR ID │ CONNECTOR NAME │ CONNECTOR TYPE │ RESOURCE TYPE │ RESOURCE NAMES ┃ ┠──────────────────────────────────────┼────────────────┼────────────────┼───────────────┼────────────────┨ ┃ 96a92154-4ec7-4722-bc18-21eeeadb8a4f │ aws-s3 │ 🔶 aws │ 📦 s3-bucket │ s3://zenfiles ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┛ ``` {% endcode %} {% hint style="info" %} The ZenML CLI provides an even easier and more interactive way of connecting a stack component to an external resource. Just pass the `-i` command line argument and follow the interactive guide: ``` zenml artifact-store register s3-zenfiles --flavor s3 --path=s3://zenfiles zenml artifact-store connect s3-zenfiles -i ``` {% endhint %} The S3 Artifact Store Stack Component we just connected to the infrastructure is now ready to be used in a stack to run a pipeline: ```sh zenml stack register s3-zenfiles -o default -a s3-zenfiles --set ``` A simple pipeline could look like this: ```python from zenml import step, pipeline @step def simple_step_one() -> str: """Simple step one.""" return "Hello World!" @step def simple_step_two(msg: str) -> None: """Simple step two.""" print(msg) @pipeline def simple_pipeline() -> None: """Define single step pipeline.""" message = simple_step_one() simple_step_two(msg=message) if __name__ == "__main__": simple_pipeline() ``` Save this as `run.py` and run it with the following command: ```sh python run.py ``` {% code title="Example Command Output" %} ``` Running pipeline simple_pipeline on stack s3-zenfiles (caching enabled) Step simple_step_one has started. Step simple_step_one has finished in 1.065s. Step simple_step_two has started. Hello World! Step simple_step_two has finished in 5.681s. Pipeline run simple_pipeline-2023_06_15-19_29_42_159831 has finished in 12.522s. Dashboard URL: http://127.0.0.1:8237/default/pipelines/8267b0bc-9cbd-42ac-9b56-4d18275bdbb4/runs ``` {% endcode %} This example is just a simple demonstration of how to use Service Connectors to connect ZenML Stack Components to your infrastructure. The range of features and possibilities is much larger. ZenML ships with built-in Service Connectors able to connect and authenticate to AWS, GCP, and Azure and offers many different authentication methods and security best practices. Follow the resources below for more information.
🪄 The complete guide to Service ConnectorsEverything you need to know to unlock the power of Service Connectors in your project.https://docs.zenml.io/stacks/service-connectors/auth-management
Security Best PracticesBest practices concerning the various authentication methods implemented by Service Connectors.https://docs.zenml.io/stacks/service-connectors/best-security-practices
🐋 Docker Service ConnectorUse the Docker Service Connector to connect ZenML to a generic Docker container registry.https://docs.zenml.io/stacks/service-connectors/connector-types/docker-service-connector
🌀 Kubernetes Service ConnectorUse the Kubernetes Service Connector to connect ZenML to a generic Kubernetes cluster.https://docs.zenml.io/stacks/service-connectors/connector-types/kubernetes-service-connector
🔶 AWS Service ConnectorUse the AWS Service Connector to connect ZenML to AWS cloud resources.https://docs.zenml.io/stacks/service-connectors/connector-types/aws-service-connector
🔵 GCP Service ConnectorUse the GCP Service Connector to connect ZenML to GCP cloud resources.https://docs.zenml.io/stacks/service-connectors/connector-types/gcp-service-connector
🅰️ Azure Service ConnectorUse the Azure Service Connector to connect ZenML to Azure cloud resources.https://docs.zenml.io/stacks/service-connectors/connector-types/azure-service-connector
🤖 HyperAI Service ConnectorUse the HyperAI Service Connector to connect ZenML to HyperAI resources.https://docs.zenml.io/stacks/service-connectors/connector-types/hyperai-service-connector
ZenML Scarf
--- ### Book/Component Guide/Service Connectors/Connector Types/README (docs/book/component-guide/service-connectors/connector-types/README.md) --- icon: album-collection --- # Connector Types
ZenML Scarf
--- ### Book/Component Guide/Service Connectors/Connector Types/Kubernetes Service Connector (docs/book/component-guide/service-connectors/connector-types/kubernetes-service-connector.md) --- description: Configuring Kubernetes Service Connectors to connect ZenML to Kubernetes clusters. --- # Kubernetes Service Connector The ZenML Kubernetes service connector facilitates authenticating and connecting to a Kubernetes cluster. The connector can be used to access to any generic Kubernetes cluster by providing pre-authenticated Kubernetes python clients to Stack Components that are linked to it and also allows configuring the local Kubernetes CLI (i.e. `kubectl`). ## Prerequisites The Kubernetes Service Connector is part of the Kubernetes ZenML integration. You can either install the entire integration or use a pypi extra to install it independently of the integration: * `pip install "zenml[connectors-kubernetes]"` installs only prerequisites for the Kubernetes Service Connector Type * `zenml integration install kubernetes` installs the entire Kubernetes ZenML integration A local Kubernetes CLI (i.e. `kubectl` ) and setting up local `kubectl` configuration contexts is not required to access Kubernetes clusters in your Stack Components through the Kubernetes Service Connector. ```shell $ zenml service-connector list-types --type kubernetes ``` ``` ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━┯━━━━━━━┯━━━━━━━━┓ ┃ NAME │ TYPE │ RESOURCE TYPES │ AUTH METHODS │ LOCAL │ REMOTE ┃ ┠──────────────────────────────┼───────────────┼───────────────────────┼──────────────┼───────┼────────┨ ┃ Kubernetes Service Connector │ 🌀 kubernetes │ 🌀 kubernetes-cluster │ password │ ✅ │ ✅ ┃ ┃ │ │ │ token │ │ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━┷━━━━━━━┷━━━━━━━━┛ ``` ## Resource Types The Kubernetes Service Connector only supports authenticating to and granting access to a generic Kubernetes cluster. This type of resource is identified by the `kubernetes-cluster` Resource Type. The resource name is a user-friendly cluster name configured during registration. ## Authentication Methods Two authentication methods are supported: 1. username and password. This is not recommended for production purposes. 2. authentication token with or without client certificates. For Kubernetes clusters that use neither username and password nor authentication tokens, such as local K3D clusters, the authentication token method can be used with an empty token. {% hint style="warning" %} This Service Connector does not support generating short-lived credentials from the credentials configured in the Service Connector. In effect, this means that the configured credentials will be distributed directly to clients and used to authenticate to the target Kubernetes API. It is recommended therefore to use API tokens accompanied by client certificates if possible. {% endhint %} ## Auto-configuration The Kubernetes Service Connector allows fetching credentials from the local Kubernetes CLI (i.e. `kubectl`) during registration. The current Kubernetes kubectl configuration context is used for this purpose. The following is an example of lifting Kubernetes credentials granting access to a GKE cluster: ```sh zenml service-connector register kube-auto --type kubernetes --auto-configure ``` {% code title="Example Command Output" %} ```text Successfully registered service connector `kube-auto` with access to the following resources: ┏━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┓ ┃ RESOURCE TYPE │ RESOURCE NAMES ┃ ┠───────────────────────┼────────────────┨ ┃ 🌀 kubernetes-cluster │ 35.185.95.223 ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┛ ``` {% endcode %} ```sh zenml service-connector describe kube-auto ``` {% code title="Example Command Output" %} ``` /* Detailed source-code truncated for AI context efficiency. */ ``` {% endcode %} {% hint style="info" %} Credentials auto-discovered and lifted through the Kubernetes Service Connector might have a limited lifetime, especially if the target Kubernetes cluster is managed through a 3rd party authentication provider such a GCP or AWS. Using short-lived credentials with your Service Connectors could lead to loss of connectivity and other unexpected errors in your pipeline. {% endhint %} ## Local client provisioning This Service Connector allows configuring the local Kubernetes client (i.e. `kubectl`) with credentials: ```sh zenml service-connector login kube-auto ``` {% code title="Example Command Output" %} ```text ⠦ Attempting to configure local client using service connector 'kube-auto'... Cluster "35.185.95.223" set. ⠇ Attempting to configure local client using service connector 'kube-auto'... ⠏ Attempting to configure local client using service connector 'kube-auto'... Updated local kubeconfig with the cluster details. The current kubectl context was set to '35.185.95.223'. The 'kube-auto' Kubernetes Service Connector was used to successfully configure the local Kubernetes cluster client/SDK. ``` {% endcode %} ## Stack Components use The Kubernetes Service Connector can be used in Orchestrator and Model Deployer stack component flavors that rely on Kubernetes clusters to manage their workloads. This allows Kubernetes container workloads to be managed without the need to configure and maintain explicit Kubernetes `kubectl` configuration contexts and credentials in the target environment and in the Stack Component.
ZenML Scarf
--- ### Book/Component Guide/Service Connectors/Connector Types/Oauth2 Service Connector (docs/book/component-guide/service-connectors/connector-types/oauth2-service-connector.md) --- description: Configuring the OAuth2 Service Connector to connect ZenML to OAuth2-protected HTTP APIs. --- # OAuth2 Service Connector The ZenML OAuth2 Service Connector authenticates to an HTTP API using OAuth2 and provides connector consumers with a pre-authenticated `requests` session that has a bearer token set on the `Authorization` header. The client credentials and refresh token methods exchange long-lived credentials for a short-lived access token, so that consumers only ever receive a temporary token and never the underlying client secret or refresh token. ```shell zenml service-connector list-types --type oauth2 ``` ``` ┏━━━━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━┯━━━━━━━━┓ ┃ NAME │ TYPE │ RESOURCE TYPES │ AUTH METHODS │ LOCAL │ REMOTE ┃ ┠──────────────────────────┼───────────┼────────────────┼──────────────────────────────────────────┼───────┼────────┨ ┃ OAuth2 Service Connector │ 🔐 oauth2 │ 🌐 oauth2-api │ client-credentials, refresh-token, token │ ✅ │ ✅ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━┷━━━━━━━━┛ ``` ## Prerequisites No Python packages are required for this Service Connector. All prerequisites are included in the base ZenML Python package. ## Resource Types The OAuth2 Service Connector supports a single resource type, `oauth2-api`, which represents an HTTP API protected by OAuth2. The resource name is the base URL of the API, for example `https://api.example.com`. This resource type does not support multiple instances. A single connector grants access to a single API, identified by the configured `api_url`. ## Authentication Methods The connector supports three authentication methods. Two of them exchange long-lived credentials for a temporary access token at the OAuth2 token endpoint and hand consumers only the temporary token. The third lets you supply an access token directly. ### Client credentials Use the OAuth2 client credentials grant to authenticate as a service identity, for machine-to-machine access with no user context. Configure the token endpoint, the client ID and secret, and optionally a scope and audience. ```sh zenml service-connector register my-api --type oauth2 --auth-method client-credentials \ --api_url=https://api.example.com \ --token_url=https://auth.example.com/oauth/token \ --client_id= \ --client_secret= \ --scope="read write" ``` The client secret stays on the connector. Every consumer receives a freshly minted access token instead. ### Refresh token Use the OAuth2 refresh token grant to authenticate as a user-delegated identity. The connector will exchange the refresh token for access tokens as needed. The client secret is optional, so public clients are supported. ```sh zenml service-connector register my-api --type oauth2 --auth-method refresh-token \ --api_url=https://api.example.com \ --token_url=https://auth.example.com/oauth/token \ --client_id= \ --refresh_token= ``` {% hint style="warning" %} Some providers issue a new refresh token on every exchange and invalidate the previous one. These rotating refresh tokens are currently not supported. {% endhint %} ### Token Supply a pre-obtained OAuth2 access token directly. The connector uses it as-is and does not refresh it. ```sh zenml service-connector register my-api --type oauth2 --auth-method token \ --api_url=https://api.example.com \ --access_token= ``` If you know when the token expires, set the connector expiration when registering so that ZenML stops using it once it is no longer valid. ## Client authentication method For the client credentials and refresh token methods, ZenML sends the client ID and secret to the token endpoint using HTTP Basic authentication by default (`client_secret_basic`). Some providers instead expect the client credentials in the request body (`client_secret_post`). Select the method with `client_auth_method`: ```sh zenml service-connector register my-api --type oauth2 --auth-method client-credentials \ --api_url=https://api.example.com \ --token_url=https://auth.example.com/oauth/token \ --client_id= \ --client_secret= \ --client_auth_method=client_secret_post ``` For public refresh token clients that have no secret, this setting has no effect, since the client ID is always sent in the request body. ## Custom token request parameters and headers Some providers require extra parameters or headers on the token request, such as a `resource` identifier for Azure AD or a vendor-specific flag. The client credentials and refresh token methods accept two optional dictionaries for this: - `token_params` adds form parameters to the token request body. - `token_headers` adds headers to the token request. The parameters `grant_type`, `client_id`, `client_secret`, `refresh_token`, `scope` and `audience`, as well as the `Authorization` header, are managed by the connector and cannot be overwritten through these options. ## Token expiration For the client credentials and refresh token methods, the lifetime of a minted access token is dictated by the OAuth2 server and read from the `expires_in` field of the token response. ZenML always respects the server value and does not let you request a different lifetime. If the server does not return an expiration, ZenML treats the token as non-expiring and keeps using it until the API rejects it. ## Auto-configuration {% hint style="info" %} This Service Connector does not support auto-discovery and extraction of authentication credentials from the local environment. {% endhint %} ## Local client provisioning This Service Connector does not configure a local client. OAuth2 is consumed directly through the authenticated `requests` session returned by the connector. ## How to use No built-in Stack Component requires the `oauth2-api` resource type. This connector is intended for your use as an easy way to get credentials for accessing an OAuth2 authenticated API. A consumer obtains an authenticated `requests` session from the connector and uses it to call the API: ```python from zenml.client import Client client = Client() # Get a Service Connector client for the API connector_client = client.get_service_connector_client( name_id_or_prefix="my-api", resource_type="oauth2-api", ) # Get a pre-authenticated requests Session from the Service Connector client session = connector_client.connect() # Call the API using the temporary token that was issued to the client response = session.get("https://api.example.com/v1/resource") ``` --- ### Book/Component Guide/Step Operators/README (docs/book/component-guide/step-operators/README.md) --- description: Executing individual steps in specialized environments. icon: arrow-progress --- # Step Operators The step operator enables the execution of individual pipeline steps in specialized runtime environments that are optimized for certain workloads. These specialized environments can give your steps access to resources like GPUs or distributed processing frameworks like [Spark](https://spark.apache.org/). {% hint style="info" %} **Comparison to orchestrators:** The [orchestrator](https://docs.zenml.io/stacks/orchestrators/) is a mandatory stack component that is responsible for executing all steps of a pipeline in the correct order and providing additional features such as scheduling pipeline runs. The step operator on the other hand is used to only execute individual steps of the pipeline in a separate environment in case the environment provided by the orchestrator is not feasible. {% endhint %} ### When to use it A step operator should be used if one or more steps of a pipeline require resources that are not available in the runtime environments provided by the [orchestrator](https://docs.zenml.io/stacks/orchestrators/). An example would be a step that trains a computer vision model and requires a GPU to run in a reasonable time, combined with a [Kubeflow orchestrator](https://docs.zenml.io/stacks/orchestrators/kubeflow) running on a Kubernetes cluster that does not contain any GPU nodes. In that case, it makes sense to include a step operator like [SageMaker](sagemaker.md), [Vertex](vertex.md), or [AzureML](azureml.md) to execute the training step with a GPU. ### Step Operator Flavors Step operators to execute steps on one of the big cloud providers are provided by the following ZenML integrations: | Step Operator | Flavor | Integration | Dynamic Pipelines | Notes | | ---------------------------------- | ------------ | ------------ | ----------------- | ------------------------------------------------------------------------ | | [AzureML](azureml.md) | `azureml` | `azure` | ✅ | Uses AzureML to execute steps | | [Baseten](baseten.md) | `baseten` | `baseten` | ✅ | Uses Baseten training jobs, incl. multi-node, to execute steps | | [Databricks](databricks.md) | `databricks` | `databricks` | ✅ | Uses Databricks one-time jobs to execute selected steps | | [Kubernetes](kubernetes.md) | `kubernetes` | `kubernetes` | ✅ | Uses Kubernetes Pods to execute steps | | [Modal](modal.md) | `modal` | `modal` | ✅ | Uses Modal to execute steps | | [SageMaker](sagemaker.md) | `sagemaker` | `aws` | ✅ | Uses SageMaker to execute steps | | [Run:AI](runai.md) | `runai` | `runai` | ✅ | Uses Run:AI to execute steps | | [SSH](ssh.md) | `ssh` | `ssh` | ❌ | Runs steps on a remote host via SSH + Docker | | [Spark](spark-kubernetes.md) | `spark` | `spark` | ❌ | Uses Spark on Kubernetes to execute steps in a distributed manner | | [Vertex](vertex.md) | `vertex` | `gcp` | ✅ | Uses Vertex AI to execute steps | | [Custom Implementation](custom.md) | _custom_ | | | Extend the step operator abstraction and provide your own implementation | If you would like to see the available flavors of step operators, you can use the command: ```shell zenml step-operator flavor list ``` ### How to use it You don't need to directly interact with any ZenML step operator in your code. As long as the step operator that you want to use is part of your active [ZenML stack](https://docs.zenml.io/user-guides/production-guide/understand-stacks), you can simply specify it in the `@step` decorator of your step. {% hint style="info" %} Stacks can have multiple step operators attached. If you use `@step(step_operator=True)`, ZenML uses the default attached step operator, which is the first step operator in the stack. If you want to use a non-default step operator, specify it explicitly by name in the `@step` decorator. {% endhint %} ```python from zenml import step @step(step_operator=True) def my_step(...) -> ...: ... ``` #### Specifying per-step resources If your steps require additional hardware resources, you can specify them on your steps as described [here](https://docs.zenml.io/user-guides/tutorial/distributed-training/). #### Enabling CUDA for GPU-backed hardware Note that if you wish to use step operators to run steps on a GPU, you will need to follow [the instructions on this page](https://docs.zenml.io/user-guides/tutorial/distributed-training/) to ensure that it works. It requires adding some extra settings customization and is essential to enable CUDA for the GPU to give its full acceleration.
ZenML Scarf
--- ### Book/Component Guide/Step Operators/Azureml (docs/book/component-guide/step-operators/azureml.md) --- description: Executing individual steps in AzureML. --- # AzureML [AzureML](https://azure.microsoft.com/en-us/products/machine-learning/) offers specialized compute instances to run your training jobs and has a comprehensive UI to track and manage your models and logs. ZenML's AzureML step operator allows you to submit individual steps to be run on AzureML compute instances. ### When to use it You should use the AzureML step operator if: * one or more steps of your pipeline require computing resources (CPU, GPU, memory) that are not provided by your orchestrator. * you have access to AzureML. If you're using a different cloud provider, take a look at the [SageMaker](sagemaker.md) or [Vertex](vertex.md) step operators. ### How to deploy it {% hint style="info" %} Would you like to skip ahead and deploy a full ZenML cloud stack already, including an AzureML step operator? Check out the [in-browser stack deployment wizard](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/deploy-a-cloud-stack), the [stack registration wizard](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/register-a-cloud-stack), or [the ZenML Azure Terraform module](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/deploy-a-cloud-stack-with-terraform) for a shortcut on how to deploy & register this stack component. {% endhint %} * Create a `Machine learning` [workspace on Azure](https://docs.microsoft.com/en-us/azure/machine-learning/quickstart-create-resources). This should include an Azure container registry and an Azure storage account that will be used as part of your stack. * (Optional) Once your resource is created, you can head over to the `Azure Machine Learning Studio` and [create a compute instance or cluster](https://learn.microsoft.com/en-us/azure/machine-learning/how-to-create-compute-instance?view=azureml-api-2&tabs=python) to run your pipelines. If omitted, the AzureML step operator will use the serverless compute target or will provision a new compute target on the fly, depending on the settings used to configure the step operator. * (Optional) Create a [Service Principal](https://docs.microsoft.com/en-us/azure/developer/java/sdk/identity-service-principal-auth) for authentication. This is required if you intend to use a service connector to authenticate your step operator. ### How to use it To use the AzureML step operator, we need: * The ZenML `azure` integration installed. If you haven't done so, run ```shell zenml integration install azure ``` * [Docker](https://www.docker.com) installed and running. * An [Azure container registry](https://docs.zenml.io/stacks/container-registries/azure) as part of your stack. Take a look [here](https://docs.zenml.io/stacks/container-registries/azure#how-to-deploy-it) for a guide on how to set that up. * An [Azure artifact store](https://docs.zenml.io/stacks/artifact-stores/azure) as part of your stack. This is needed so that both your orchestration environment and AzureML can read and write step artifacts. Take a look [here](https://docs.zenml.io/stacks/container-registries/azure#how-to-deploy-it) for a guide on how to set that up. * An AzureML workspace and an optional compute cluster. Note that the AzureML workspace can share the Azure container registry and Azure storage account that are required above. See the [deployment section](azureml.md#how-to-deploy-it) for detailed instructions. There are two ways you can authenticate your step operator to be able to run steps on Azure: {% tabs %} {% tab title="Authentication via Service Connector" %} The recommended way to authenticate your AzureML step operator is by registering or using an existing [Azure Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/azure-service-connector) and connecting it to your AzureML step operator. The credentials configured for the connector must have permissions to create and manage AzureML jobs. Use these specific Azure-managed roles following the principle of least privilege: - **`AzureML Data Scientist`** - Allows creating and managing machine learning experiments and jobs - **`AzureML Compute Operator`** - Allows managing compute resources for running jobs These roles are scoped specifically to AzureML operations and provide the minimum permissions needed. Avoid using broader roles like `Contributor` or `Owner` at the resource group level. See [Azure ML role documentation](https://learn.microsoft.com/en-us/azure/machine-learning/how-to-assign-roles?view=azureml-api-2&tabs=team-lead) for more details. The AzureML step operator uses the `azure-generic` resource type, so make sure to configure the connector accordingly: ```shell zenml service-connector register --type azure -i zenml step-operator register \ --flavor=azureml \ --subscription_id= \ --resource_group= \ --workspace_name= \ # --compute_target_name= # optionally specify an existing compute target zenml step-operator connect --connector zenml stack register -s ... --set ``` {% endtab %} {% tab title="Implicit Authentication" %} If you don't connect your step operator to a service connector: * If using a [local orchestrator](https://docs.zenml.io/stacks/orchestrators/local): ZenML will try to implicitly authenticate to Azure via the local [Azure CLI configuration](https://learn.microsoft.com/en-us/cli/azure/authenticate-azure-cli-interactively). Make sure the Azure CLI account has the minimum required permissions: the `AzureML Data Scientist` and `AzureML Compute Operator` managed roles scoped to the specific AzureML workspace. * If using a remote orchestrator: the remote environment in which the orchestrator runs needs to be able to implicitly authenticate to Azure and have permissions to create and manage AzureML jobs. This is only possible if the orchestrator is also running in Azure and uses a form of implicit workload authentication like a service role. If this is not the case, you will need to use a service connector. ```shell zenml step-operator register \ --flavor=azureml \ --subscription_id= \ --resource_group= \ --workspace_name= \ # --compute_target_name= # optionally specify an existing compute target zenml stack register -s ... --set ``` {% endtab %} {% endtabs %} Once you added the step operator to your active stack, you can use it to execute individual steps of your pipeline by specifying it in the `@step` decorator as follows: ```python from zenml import step @step(step_operator=True) def trainer(...) -> ...: """Train a model.""" # This step will be executed in AzureML. ``` {% hint style="info" %} ZenML will build a Docker image called `/zenml:` which includes your code and use it to run your steps in AzureML. Check out [this page](https://docs.zenml.io/how-to/customize-docker-builds) if you want to learn more about how ZenML builds these images and how you can customize them. {% endhint %} #### Additional configuration The ZenML AzureML step operator comes with a dedicated class called `AzureMLStepOperatorSettings` for configuring its settings and it controls the compute resources used for step execution in AzureML. Currently, it supports three different modes of operation. 1. Serverless Compute (Default) - Set `mode` to `serverless`. - Other parameters are ignored. 2. Compute Instance - Set `mode` to `compute-instance`. - Requires a `compute_name`. - If a compute instance with the same name exists, it uses the existing compute instance and ignores other parameters. - If a compute instance with the same name doesn't exist, it creates a new compute instance with the `compute_name`. For this process, you can specify `compute_size` and `idle_type_before_shutdown_minutes`. 3. Compute Cluster - Set `mode` to `compute-cluster`. - Requires a `compute_name`. - If a compute cluster with the same name exists, it uses existing cluster, ignores other parameters. - If a compute cluster with the same name doesn't exist, it creates a new compute cluster. Additional parameters can be used for configuring this process. Here is an example how you can use the `AzureMLStepOperatorSettings` to define a compute instance: ```python from zenml import step from zenml.integrations.azure.flavors import AzureMLStepOperatorSettings azureml_settings = AzureMLStepOperatorSettings( mode="compute-instance", compute_name="MyComputeInstance", compute_size="Standard_NC6s_v3", ) @step( settings={ "step_operator": azureml_settings } ) def my_azureml_step(): # YOUR STEP CODE ... ``` You can check out the [AzureMLStepOperatorSettings SDK docs](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-azure.html#zenml.integrations.azure) for a full list of available attributes and [this docs page](https://docs.zenml.io/concepts/steps_and_pipelines/configuration) for more information on how to specify settings. #### Enabling CUDA for GPU-backed hardware Note that if you wish to use this step operator to run steps on a GPU, you will need to follow [the instructions on this page](https://docs.zenml.io/user-guides/tutorial/distributed-training) to ensure that it works. It requires adding some extra settings customization and is essential to enable CUDA for the GPU to give its full acceleration.
ZenML Scarf
--- ### Book/Component Guide/Step Operators/Baseten (docs/book/component-guide/step-operators/baseten.md) --- description: Executing individual steps as Baseten training jobs, including multi-node distributed training. --- # Baseten Step Operator [Baseten](https://www.baseten.co/) provides on-demand H100/H200 (and more) GPU capacity through its Training product. The ZenML Baseten step operator runs selected steps of your pipeline as Baseten training jobs, so the rest of the pipeline can run anywhere (locally, on Kubernetes, or any other orchestrator) while GPU-heavy steps execute on Baseten. It supports both: * **single-node execution** for a regular `@step` (full ZenML features: inputs, outputs, artifacts, logs), and * **multi-node distributed training** for a [`CommandStep`](https://docs.zenml.io/how-to/steps-pipelines/command_steps), where Baseten provisions `node_count` identical nodes and your command (e.g. `torchrun`) owns the distributed launch. ## When to use it Use the Baseten step operator if: * you need H100/H200 (and more) GPUs for specific steps but want to keep your existing orchestrator and [remote artifact store](https://docs.zenml.io/stacks/artifact-stores/), or * you want multi-node distributed training without managing a cluster — Baseten provisions and tears down the nodes per job. ## How to deploy it You need a [Baseten account](https://www.baseten.co/) and an API key (create one in your Baseten workspace settings). ### Baseten account requirements Two execution modes depend on **organization-level entitlements** that may need to be enabled by Baseten support for your account: | Mode | Requires | Symptom if not enabled | |---|---|---| | Single-node regular `@step` | **Custom base images** | Job creation fails with `Custom base images not supported for your organization` | | Multi-node (`node_count > 1`) | **Multi-node instance types** | Job creation fails with a `400 Bad Request` | This is because a regular `@step` runs the ZenML entrypoint, so its container must contain `zenml`, your code and dependencies — that is a *custom image*. A [`CommandStep`](https://docs.zenml.io/how-to/steps-pipelines/command_steps), by contrast, is an opaque command that can run on a **stock public image** (with `skip_build=True`), which any Baseten organization can pull — so the single-node `CommandStep` path works out of the box. If you plan to run regular `@step`s or multi-node jobs, ask Baseten to enable the corresponding entitlement first. These are Baseten account gates, not ZenML limitations — the operator builds, submits, polls, cancels and records metadata for every mode regardless. ## How to use it To use the Baseten step operator, you need: * the ZenML `baseten` integration installed: ```shell zenml integration install baseten ``` * a [remote artifact store](https://docs.zenml.io/stacks/artifact-stores/) as part of your stack (steps run remotely and write artifacts over the network), * a [remote container registry](https://docs.zenml.io/stacks/container-registries/) and an [image builder](https://docs.zenml.io/stacks/image-builders/) as part of your stack, so the step image can be built and pulled by Baseten. Register the step operator and add it to your stack: ```shell zenml step-operator register baseten_operator \ --flavor=baseten \ --api_key= \ --project=zenml-training zenml stack register baseten_stack \ -s baseten_operator \ -a \ -c \ -i \ --set ``` If your step image lives in a private registry, store the registry credentials (`username:password`) as a Baseten secret and reference it on the step operator with `--registry_auth_secret=`. {% hint style="info" %} Baseten only accepts references to named secrets that are pre-stored in its secrets backend — there is no way to pass credentials inline per-run. Create the secret once in the Baseten dashboard or with the Baseten CLI, then set `registry_auth_secret` to its name. {% endhint %} ### Single-node steps Point any step at the operator and request a GPU via `ResourceSettings`: ```python from zenml import step, pipeline from zenml.config import ResourceSettings from zenml.integrations.baseten.flavors import BasetenStepOperatorSettings @step( step_operator="baseten_operator", settings={ "step_operator": BasetenStepOperatorSettings(accelerator="H100"), "resources": ResourceSettings(gpu_count=1), }, ) def train() -> None: ... @pipeline def my_pipeline() -> None: train() ``` The `accelerator` type (`H100` or `H200`) is a step operator setting; the number of GPUs per node comes from `ResourceSettings.gpu_count`. ### Multi-node distributed training Multi-node runs the same container on every node, so a regular step would duplicate its artifacts, outputs and logs. Multi-node is therefore only allowed for a [`CommandStep`](https://docs.zenml.io/how-to/steps-pipelines/command_steps), which ZenML treats as an opaque command and never runs its machinery inside. Set `node_count > 1` and let your command own the distributed launch. Baseten injects these environment variables on every node, which you wire into `torchrun`: | Variable | Meaning | |---|---| | `BT_GROUP_SIZE` | number of nodes | | `BT_NODE_RANK` | rank of this node (0 = leader) | | `BT_LEADER_ADDR` | address of the leader node | | `BT_NUM_GPUS` | GPUs per node | ```python from zenml import CommandStep, pipeline from zenml.config import ResourceSettings from zenml.integrations.baseten.flavors import BasetenStepOperatorSettings train = CommandStep( command=[ "bash", "-lc", "torchrun --nnodes=$BT_GROUP_SIZE --node-rank=$BT_NODE_RANK " "--master-addr=$BT_LEADER_ADDR --master-port=29500 " "--nproc-per-node=$BT_NUM_GPUS train.py", ], step_operator="baseten_operator", settings={ "step_operator": BasetenStepOperatorSettings( accelerator="H200", node_count=4 ), "resources": ResourceSettings(gpu_count=8), }, ) @pipeline(dynamic=True) def training_pipeline() -> None: train() ``` The image must already contain your training code and dependencies. A regular step submitted with `node_count > 1` is rejected with a clear error. ### Passing secrets Sensitive environment variables are never inlined into the job config. Store your own secrets (API tokens, credentials) as Baseten secrets and map them with the `secrets` setting — the value is referenced rather than inlined: ```python BasetenStepOperatorSettings( secrets={"HF_TOKEN": "hf-access-token"}, ) ``` The ZenML store API token (which regular steps need to call back to the server) is handled automatically: the operator upserts it into a managed Baseten secret named `zenml-store-api-token-` on each run and references that, so the token never lands in the inlined job config. You can override this by mapping the token name explicitly in `secrets`. Command steps never talk to the ZenML server, so the token is dropped for them entirely. ### Caching and checkpointing Baseten can [persist a training cache](https://docs.baseten.co/training/loading) so datasets and model weights downloaded by a job survive across jobs (avoiding re-downloads), and it can manage checkpoint storage. Both are **disabled by default** and opt-in through settings: ```python BasetenStepOperatorSettings( accelerator="H100", enable_cache=True, # mount the persistent training cache (off by default) cache_enable_legacy_hf_mount=True, # also reuse the default Hugging Face cache path cache_require_affinity=False, # allow running across different GPU types enable_checkpointing=True, # persist checkpoints written to $BT_CHECKPOINT_DIR (off by default) ) ``` When `enable_cache` is on, write your downloads (e.g. `HF_HOME`, dataset staging) into the cache so subsequent runs reuse them. `cache_enable_legacy_hf_mount` additionally mounts the legacy Hugging Face cache location for code that downloads to the default HF path, and `cache_require_affinity` (default `True`) controls whether the job must land on nodes that already hold the cache — set it `False` to let the same project run across different GPU types. When `enable_checkpointing` is on, write checkpoints to the Baseten checkpoint directory exposed as `$BT_CHECKPOINT_DIR`. For more information and a full list of configurable attributes, check out the [SDK docs](https://sdkdocs.zenml.io/latest/).
ZenML Scarf
--- ### Book/Component Guide/Step Operators/Custom (docs/book/component-guide/step-operators/custom.md) --- description: Learning how to develop a custom step operator. --- # Develop a Custom Step Operator {% hint style="info" %} Before diving into the specifics of this component type, it is beneficial to familiarize yourself with our [general guide to writing custom component flavors in ZenML](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/implement-a-custom-stack-component). This guide provides an essential understanding of ZenML's component flavor concepts. {% endhint %} ### Base Abstraction The `BaseStepOperator` is the abstract base class that needs to be subclassed in order to run specific steps of your pipeline in a separate environment. As step operators can come in many shapes and forms, the base class exposes a deliberately basic and generic interface: ``` /* Detailed source-code truncated for AI context efficiency. */ ``` {% hint style="info" %} This is a slimmed-down version of the base implementation which aims to highlight the abstraction layer. In order to see the full implementation and get the complete docstrings, please check the [SDK docs](https://sdkdocs.zenml.io/latest/core_code_docs/core-step_operators.html#zenml.step_operators.base_step_operator) . {% endhint %} ### Build your own custom step operator If you want to create your own custom flavor for a step operator, you can follow the following steps: 1. Create a class that inherits from the `BaseStepOperator` class and implement the abstract `submit`, `get_status` and `cancel` methods: * The `submit()` method should prepare a suitable execution environment (e.g. a Docker image): The general environment is highly dependent on the concrete step operator implementation, but for ZenML to be able to run the step it requires you to install some `pip` dependencies. The list of requirements needed to successfully execute the step can be found via the Docker settings `info.pipeline.docker_settings` passed to the `submit()` method. Additionally, you'll have to make sure that all the source code of your ZenML step and pipeline are available within this execution environment. It should then run the entrypoint command: Actually running a single step of a pipeline requires knowledge of many ZenML internals and is implemented in the `zenml.step_operators.step_operator_entrypoint_configuration` module. As long as your environment was set up correctly (see the previous bullet point), you can run the step using the command provided via the `entrypoint_command` argument of the `submit()` method. * The `get_status()` method should get the infrastructure status of a specific step that was submitted to the step operator. * The `cancel()` method should cancel a specific step that was submitted to the step operator. 2. If your step operator allows the specification of per-step resources, make sure to handle the resources defined on the step (`info.config.resource_settings`) that was passed to the `submit()` method. 3. If you need to provide any configuration, create a class that inherits from the `BaseStepOperatorConfig` class adds your configuration parameters. 4. Bring both the implementation and the configuration together by inheriting from the `BaseStepOperatorFlavor` class. Make sure that you give a `name` to the flavor through its abstract property. Once you are done with the implementation, you can register it through the CLI. Please ensure you **point to the flavor class via dot notation**: ```shell zenml step-operator flavor register ``` For example, if your flavor class `MyStepOperatorFlavor` is defined in `flavors/my_flavor.py`, you'd register it by doing: ```shell zenml step-operator flavor register flavors.my_flavor.MyStepOperatorFlavor ``` {% hint style="warning" %} ZenML resolves the flavor class by taking the path where you initialized zenml (via `zenml init`) as the starting point of resolution. Therefore, please ensure you follow [the best practice](https://docs.zenml.io/user-guides/best-practices/iac) of initializing zenml at the root of your repository. If ZenML does not find an initialized ZenML repository in any parent directory, it will default to the current working directory, but usually, it's better to not have to rely on this mechanism and initialize zenml at the root. {% endhint %} Afterward, you should see the new flavor in the list of available flavors: ```shell zenml step-operator flavor list ``` {% hint style="warning" %} It is important to draw attention to when and how these base abstractions are coming into play in a ZenML workflow. * The **CustomStepOperatorFlavor** class is imported and utilized upon the creation of the custom flavor through the CLI. * The **CustomStepOperatorConfig** class is imported when someone tries to register/update a stack component with this custom flavor. Especially, during the registration process of the stack component, the config will be used to validate the values given by the user. As `Config` objects are inherently `pydantic` objects, you can also add your own custom validators here. * The **CustomStepOperator** only comes into play when the component is ultimately in use. The design behind this interaction lets us separate the configuration of the flavor from its implementation. This way we can register flavors and components even when the major dependencies behind their implementation are not installed in our local setting (assuming the `CustomStepOperatorFlavor` and the `CustomStepOperatorConfig` are implemented in a different module/path than the actual `CustomStepOperator`). {% endhint %} #### Enabling CUDA for GPU-backed hardware Note that if you wish to use your custom step operator to run steps on a GPU, you will need to follow [the instructions on this page](https://docs.zenml.io/user-guides/tutorial/distributed-training) to ensure that it works. It requires adding some extra settings customization and is essential to enable CUDA for the GPU to give its full acceleration.
ZenML Scarf
--- ### Book/Component Guide/Step Operators/Databricks (docs/book/component-guide/step-operators/databricks.md) --- description: Executing individual steps on Databricks. --- # Databricks [Databricks](https://www.databricks.com/) is a unified data and AI platform that is often used for large-scale data processing, feature engineering, and model training. ZenML's Databricks step operator allows you to execute individual steps on Databricks while keeping the rest of the pipeline on your active orchestrator. ### When to use it You should use the Databricks step operator if: * one or more steps in your pipeline should run on Databricks instead of the environment provided by your orchestrator. * you're already using Databricks for Spark-based data processing, distributed workloads, or training jobs. * you want to keep selective execution on Databricks without moving the entire pipeline to the [Databricks orchestrator](../orchestrators/databricks.md). {% hint style="info" %} Use the Databricks **step operator** if only selected steps should run on Databricks. Use the Databricks **orchestrator** if you want Databricks to orchestrate the full pipeline. {% endhint %} ### How it works The Databricks step operator uses the same wheel-based execution model as the Databricks orchestrator: 1. ZenML packages your project code into a Python wheel. 2. ZenML uploads that wheel to your Databricks workspace. 3. ZenML submits a one-time Databricks job run for the selected step. 4. Databricks installs the wheel and executes the ZenML step entrypoint remotely. This lets you offload a single step to Databricks without changing how the rest of your pipeline runs. {% hint style="info" %} The step operator uploads wheels under `/Workspace/Shared/.zenml` and removes the step-specific wheel directory after the Databricks run completes successfully. If a run fails or is canceled, the wheel directory is retained for debugging and can be removed manually from the same workspace path. {% endhint %} ### How to use it To use the Databricks step operator, first install the Databricks integration: ```shell zenml integration install databricks ``` You also need: * an active Databricks workspace * Databricks credentials with permission to submit jobs * a remote artifact store in your ZenML stack Then register the step operator: ```shell zenml step-operator register databricks_step_operator \ --flavor=databricks \ --host="https://xxxxx.x.azuredatabricks.net" \ --client_id={{databricks.client_id}} \ --client_secret={{databricks.client_secret}} ``` Add it to your stack: ```shell zenml stack register databricks_stack -s databricks_step_operator ... --set ``` Once the step operator is part of your active stack, you can use it on individual steps: ```python from zenml import step @step(step_operator="databricks_step_operator") def trainer(...) -> ...: ... ``` ### Additional configuration The Databricks step operator reuses the Databricks execution settings used by the Databricks orchestrator. You can configure Spark version, worker count, node types, autoscaling, Spark configuration, Spark environment variables, cluster policy, init scripts, and Docker image settings. Init scripts must use DBFS paths that start with `dbfs:/`. If you configure Docker registry authentication, provide both `docker_image_username` and `docker_image_password`. ```python from zenml.integrations.databricks.flavors.databricks_step_operator_flavor import ( DatabricksStepOperatorSettings, ) databricks_settings = DatabricksStepOperatorSettings( spark_version="15.3.x-scala2.12", num_workers=3, node_type_id="Standard_D4s_v5", policy_id=POLICY_ID, spark_conf={}, spark_env_vars={}, init_scripts=["dbfs:/scripts/install_dependencies.sh"], ) ``` Use `num_workers` for fixed-size clusters. For autoscaling clusters, omit `num_workers` and set `autoscale`, for example `autoscale=(2, 3)`. You can specify these settings on steps that should run on Databricks: ```python from zenml import step @step( step_operator="databricks_step_operator", settings={ "step_operator": databricks_settings, }, ) def databricks_step(...) -> ...: ... ``` {% hint style="info" %} The Databricks step operator submits one-time Databricks runs (`jobs.submit`) rather than persistent Databricks Jobs (`jobs.create`). Fields that only apply to persistent Jobs — `schedule_timezone`, `job_tags`, `max_concurrent_runs`, `max_retries`, `min_retry_interval_millis`, and `retry_on_timeout` — therefore live on `DatabricksOrchestratorSettings` and are not exposed on the step operator. {% endhint %} {% hint style="warning" %} Generic ZenML `resource_settings` are not translated to Databricks cluster sizing. Use the Databricks-specific step operator settings instead. {% endhint %} ### Stack requirements Unlike image-based step operators, the Databricks step operator does not require a container registry or image builder. It does require a remote artifact store so that both your orchestrator environment and Databricks can access artifacts. ### Databricks UI Submitted step runs are visible in Databricks. ZenML also stores Databricks run metadata on the step run so you can correlate ZenML execution with Databricks execution. #### Enabling CUDA for GPU-backed hardware If you plan to use GPU-enabled Databricks clusters, make sure your step environment and dependencies are configured accordingly. You can find general CUDA guidance in the [distributed training guide](https://docs.zenml.io/user-guides/tutorial/distributed-training/). Check out the [SDK docs](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-databricks.html#zenml.integrations.databricks) for the full Databricks integration API surface.
ZenML Scarf
--- ### Book/Component Guide/Step Operators/Kubernetes (docs/book/component-guide/step-operators/kubernetes.md) --- description: Executing individual steps in Kubernetes Pods. --- # Kubernetes ZenML's Kubernetes step operator allows you to submit individual steps to be run on Kubernetes pods. ### When to use it You should use the Kubernetes step operator if: * one or more steps of your pipeline require computing resources (CPU, GPU, memory) that are not provided by your orchestrator. * you have access to a Kubernetes cluster. ### How to deploy it The Kubernetes step operator requires a Kubernetes cluster in order to run. There are many ways to deploy a Kubernetes cluster using different cloud providers or on your custom infrastructure, and we can't possibly cover all of them, but you can check out our cloud guide. ### How to use it To use the Kubernetes step operator, we need: * The ZenML `kubernetes` integration installed. If you haven't done so, run ```shell zenml integration install kubernetes ``` * A Kubernetes cluster [deployed](kubernetes.md#how-to-deploy-it) * Either [Docker](https://www.docker.com) installed and running or a remote [image builder](https://docs.zenml.io/stacks/image-builders/) in your stack. * A [remote artifact store](https://docs.zenml.io/stacks/artifact-stores/) as part of your stack. This is needed so that both your orchestration environment and Kubernetes Pods can read and write step artifacts. Check out the documentation page of the artifact store you want to use for more information on how to set that up and configure authentication for it. {% hint style="info" %} It is recommended that you set up [a Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/service-connectors-guide) and use it to connect the Kubernetes step operator to the Kubernetes cluster, especially if you are using a Kubernetes cluster managed by a cloud provider like AWS, GCP or Azure. {% endhint %} We can then register the step operator and use it in our stacks. This can be done in two ways: 1. Using a Service Connector configured to access the remote Kubernetes cluster. Depending on your cloud provider, this should be either an [AWS](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/aws-service-connector), [Azure](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/azure-service-connector) or [GCP](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/gcp-service-connector) service connector. If you're using a Kubernetes cluster that is not provided by any of these, you can use the generic [Kubernetes](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/kubernetes-service-connector) service connector. You can then [connect the stack component to the Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/service-connectors-guide#connect-stack-components-to-resources): ``` /* Detailed source-code truncated for AI context efficiency. */ ``` 2. Using the local Kubernetes `kubectl` client. This client needs to be configured with a configuration context pointing to the remote cluster. The `kubernetes_context` configuration attribute must also be configured with the value of that context: ```shell zenml step-operator register \ --flavor=kubernetes \ --kubernetes_context= ``` We can then use the registered step operator in our active stack: ```shell # Add the step operator to the active stack zenml stack update -s ``` Once you added the step operator to your active stack, you can use it to execute individual steps of your pipeline by specifying it in the `@step` decorator as follows: ```python from zenml import step @step(step_operator=True) def trainer(...) -> ...: """Train a model.""" # This step will be executed in Kubernetes. ``` {% hint style="info" %} ZenML will build a Docker images which includes your code and use it to run your steps in Kubernetes. Check out [this page](https://docs.zenml.io/how-to/customize-docker-builds/) if you want to learn more about how ZenML builds these images and how you can customize them. {% endhint %} #### Interacting with pods via kubectl For debugging, it can sometimes be handy to interact with the Kubernetes pods directly via kubectl. To make this easier, we have added the following labels to all pods: * `run`: the name of the ZenML run. * `pipeline`: the name of the ZenML pipeline associated with this run. E.g., you can use these labels to manually delete all pods related to a specific pipeline: ```shell kubectl delete pod -n zenml -l pipeline=kubernetes_example_pipeline ``` #### Additional configuration Some configuration options for the Kubernetes step operator can only be set through the step operator config when you register it (and cannot be changed per-run or per-step through the settings): - **`kubernetes_namespace`** (default: "zenml"): The Kubernetes namespace to use for running the step pods. The namespace must already exist in the Kubernetes cluster. - **`incluster`** (default: False): If `True`, the step operator will run the step inside the same Kubernetes cluster as the orchestrator, ignoring the `kubernetes_context`. - **`kubernetes_context`**: The name of the Kubernetes context to use for running steps (ignored if using a service connector or `incluster`). The following configuration options can be set either through the step operator config or overridden using `KubernetesStepOperatorSettings`: - **`pod_settings`**: Node selectors, labels, affinity, tolerations, secrets, environment variables and image pull secrets to apply to the Kubernetes Pods. These can be either specified using the Kubernetes model objects or as dictionaries. - **`service_account_name`**: Name of the service account to use for the pod. - **`privileged`** (default: False): If the container should be run in privileged mode. - **`pod_startup_timeout`** (default: 600): The maximum time (in seconds) to wait for a pending step pod to start. - **`pod_failure_max_retries`** (default: 3): The maximum number of times to retry a step pod if it fails to start. - **`pod_failure_retry_delay`** (default: 10): The delay (in seconds) between pod failure retries and pod startup retries. - **`pod_failure_backoff`** (default: 1.0): The backoff factor for pod failure retries and pod startup retries. ``` /* Detailed source-code truncated for AI context efficiency. */ ``` Check out the [SDK docs](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-kubernetes.html#zenml.integrations.kubernetes) for a full list of available attributes and [this docs page](https://docs.zenml.io/concepts/steps_and_pipelines/configuration) for more information on how to specify settings. For more information and a full list of configurable attributes of the Kubernetes steop operator, check out the [SDK Docs](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-kubernetes.html#zenml.integrations.kubernetes) . #### Multi-pod command steps (distributed training) Command steps that own their own distributed launch (`torchrun`, prime-rl's entrypoints, Ray) can run across multiple pods by setting `pod_count`: ```python from zenml import CommandStep from zenml.integrations.kubernetes.flavors import KubernetesStepOperatorSettings train = CommandStep( command=[ "bash", "-lc", "torchrun --nnodes=$ZENML_KUBERNETES_POD_COUNT " "--node-rank=$JOB_COMPLETION_INDEX " "--rdzv-backend=c10d " "--rdzv-endpoint=$ZENML_KUBERNETES_MAIN_ADDRESS:$ZENML_KUBERNETES_MAIN_PORT " "train.py", ], name="train", step_operator="k8s", settings={"step_operator": KubernetesStepOperatorSettings(pod_count=4)}, ) ``` The step becomes an indexed Kubernetes Job (`completions = parallelism = pod_count`) plus a headless service for stable pod-to-pod DNS. Every pod runs the step command with `ZENML_KUBERNETES_POD_COUNT`, `ZENML_KUBERNETES_MAIN_ADDRESS` (the DNS name of the pod with rank 0) and `ZENML_KUBERNETES_MAIN_PORT` set, and Kubernetes injects each pod's rank as `JOB_COMPLETION_INDEX`. All pods must exit 0 for the step to succeed. The service is owned by the Job, so cancellation, TTL cleanup, or deletion reap everything without depending on step code running. Set `pod_count` in the step settings, not the pipeline settings. Regular (non-command) steps are refused at `pod_count > 1`: they would duplicate their artifacts, outputs, and logs on every pod. The pods of a multi-pod job can start before the headless service exists, so the launcher must retry its rendezvous rather than resolve `ZENML_KUBERNETES_MAIN_ADDRESS` exactly once at startup (`torchrun` does). #### Enabling CUDA for GPU-backed hardware Note that if you wish to use this step operator to run steps on a GPU, you will need to follow [the instructions on this page](https://docs.zenml.io/user-guides/tutorial/distributed-training/) to ensure that it works. It requires adding some extra settings customization and is essential to enable CUDA for the GPU to give its full acceleration.
ZenML Scarf
--- ### Book/Component Guide/Step Operators/Modal (docs/book/component-guide/step-operators/modal.md) --- description: Executing individual steps in Modal. --- # Modal Step Operator [Modal](https://modal.com) is a platform for running cloud infrastructure. It offers specialized compute instances to run your code and has a fast execution time, especially around building Docker images and provisioning hardware. ZenML's Modal step operator allows you to submit individual steps to be run on Modal compute instances. ### When to use it You should use the Modal step operator if: * You need fast execution time for steps that require computing resources (CPU, GPU, memory). * You want to easily specify the exact hardware requirements (e.g., GPU type, CPU count, memory) for each step. * You have access to Modal. ### How to deploy it To use the Modal step operator: * [Sign up for a Modal account](https://modal.com/signup) if you haven't already. * Install the Modal CLI by running `pip install modal` (or `zenml integration install modal`) and authenticate by running `modal setup` in your terminal. ### How to use it To use the Modal step operator, we need: * The ZenML `modal` integration installed. If you haven't done so, run ```shell zenml integration install modal ``` * Docker installed and running. * A cloud artifact store as part of your stack. This is needed so that both your orchestration environment and Modal can read and write step artifacts. Any cloud artifact store supported by ZenML will work with Modal. * A cloud container registry as part of your stack. Any cloud container registry supported by ZenML will work with Modal. * An Image Builder in your stack. ZenML uses it to build the Docker image that runs on Modal. The Modal step operator can use Modal authentication settings from the stack component configuration. If `token_id` and `token_secret` are configured on the step operator, ZenML creates an explicit Modal SDK client from those credentials and passes that client to Modal SDK calls. If these fields are not configured, ZenML passes no explicit client and Modal uses its normal authentication behavior, such as existing environment variables or `~/.modal.toml`. ZenML step runtime environment variables, including values needed by the step to connect back to the ZenML server, are passed to the Modal sandbox when the step starts. They are not added to the Modal image definition. Modal authentication settings used to submit the sandbox, the Modal environment used for app lookup, and container registry credentials used to pull the image are handled separately. We can then register the step operator: ```shell zenml step-operator register --flavor=modal \ --token_id= \ --token_secret= zenml stack update -s ... ``` Once you added the step operator to your active stack, you can use it to execute individual steps of your pipeline by specifying it in the `@step` decorator as follows: ```python from zenml import step @step(step_operator=True) def trainer(...) -> ...: """Train a model.""" # This step will be executed in Modal. ``` {% hint style="info" %} ZenML will build a Docker image which includes your code and use it to run your steps in Modal. Check out [this page](https://docs.zenml.io/how-to/customize-docker-builds) if you want to learn more about how ZenML builds these images and how you can customize them. {% endhint %} {% hint style="warning" %} Modal requires images imported from a registry to be built for `linux/amd64`. If you build locally on Apple Silicon, Docker may produce an `arm64` image by default, which Modal will reject during image import. Configure your Docker settings to build for `linux/amd64`, and prevent build reuse once while replacing any previously built `arm64` image: ```python from zenml.config import DockerSettings from zenml.config.docker_settings import DockerBuildConfig, DockerBuildOptions amd64_build_config = DockerBuildConfig( build_options=DockerBuildOptions(platform="linux/amd64") ) docker_settings = DockerSettings( # your existing Docker settings... parent_image_build_config=amd64_build_config, build_config=amd64_build_config, prevent_build_reuse=True, ) ``` If Docker still fails locally with a platform error while building a multi-stage image, make sure the local image builder uses the Docker CLI/BuildKit path instead of the Docker Python SDK path: ```shell export DOCKER_BUILDKIT=1 zenml image-builder update --use_subprocess_call=True ``` You can remove `prevent_build_reuse=True` again after ZenML has built and pushed a fresh `linux/amd64` image. {% endhint %} #### Additional configuration You can specify the hardware requirements for each step using the `ResourceSettings` class as described in our documentation on [resource settings](https://docs.zenml.io/user-guides/tutorial/distributed-training): ```python from zenml import step from zenml.config import ResourceSettings from zenml.integrations.modal.flavors import ModalStepOperatorSettings modal_settings = ModalStepOperatorSettings( gpu="A100", # GPU type (e.g., "T4", "A100") # region="us-east-1", # optional, enterprise/team only # cloud="aws", # optional, enterprise/team only # modal_environment="main", # optional Modal environment name # timeout=86400, # optional sandbox timeout in seconds ) resource_settings = ResourceSettings( cpu_count=2, memory="32GB", # gpu_count=1, # optional; if omitted and a GPU type is set, defaults to 1 GPU ) @step( step_operator=True, # or the specific name, e.g., step_operator="" settings={ "step_operator": modal_settings, "resources": resource_settings, }, ) def my_modal_step(): ... ``` Important: - If you request GPUs with `ResourceSettings.gpu_count > 0`, you must also specify a GPU type via `ModalStepOperatorSettings.gpu`; otherwise the run will fail with a validation error. - If a GPU type is set but `gpu_count == 0`, ZenML treats the step as CPU-only and logs a warning that the GPU type is ignored. - If `gpu_count` is omitted and a GPU type is set, Modal uses one GPU of that type. - `cpu_count` is passed through from `ResourceSettings` to Modal. `memory` must be a string such as `"32GB"`, `"32768MB"`, or `"32GiB"`. ZenML converts it to decimal megabytes (`MB`) and rounds fractional MB values up before passing it to Modal. - If the active container registry exposes credentials, ZenML passes them to Modal for image pulls. If no registry credentials are configured, Modal attempts to pull the image anonymously. {% hint style="info" %} Note that `cpu_count` specifies a soft minimum limit - Modal will guarantee at least this many physical cores, but the actual usage could be higher. The CPU cores/hour will also determine the minimum price paid for the compute resources. {% endhint %} This will run `my_modal_step` on a Modal instance with 1 A100 GPU, 2 CPUs, and 32GB of CPU memory. Check out the [Modal docs](https://modal.com/docs/guide/gpu) for the full list of supported GPU types and the [SDK docs](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-modal.html) for more details on the available settings. The settings allow you to specify the Modal environment, region, and cloud provider. `modal_environment` selects the Modal environment used for the app lookup; ZenML passes it as `environment_name` to `modal.App.lookup(...)`. The sandbox then belongs to that app, while ZenML step runtime environment variables are still passed separately to the sandbox runtime with `env=...`. Region and cloud provider settings are only available for Modal Enterprise and Team plan customers. Certain combinations of settings are not available. It is suggested to err on the side of looser settings rather than more restrictive ones to avoid pipeline execution failures. In the case of failures, however, Modal provides detailed error messages that can help identify what is incompatible. See more in the [Modal docs on region selection](https://modal.com/docs/guide/region-selection) for more details. --- ### Book/Component Guide/Step Operators/Runai (docs/book/component-guide/step-operators/runai.md) --- description: Executing individual steps on Run:AI clusters with fractional GPU support. --- # Run:AI Step Operator ZenML's Run:AI step operator allows you to submit individual steps to be run on [Run:AI](https://www.nvidia.com/en-us/software/run-ai/) clusters as training workloads with support for fractional GPU allocation. ### When to use it You should use the Run:AI step operator if: * one or more steps of your pipeline require GPU resources that are not provided by your orchestrator. * you need fractional GPU allocation (e.g., 0.5 GPU per step) to maximize resource utilization. * you're already using Run:AI for workload management. * you want to leverage Run:AI's scheduling and resource management features. ### Key Features * **Fractional GPU Support**: Allocate portions of a GPU (e.g., 0.25, 0.5, 0.75) to maximize resource utilization * **Training Workloads**: Submit individual ZenML steps as Run:AI training workloads * **Dynamic Pipeline Support**: Supports asynchronous step submission and monitoring in dynamic pipelines * **Project-based Resource Management**: Workloads are organized by Run:AI projects with quota policies * **SaaS and Self-hosted**: Works with both Run:AI SaaS and self-hosted deployments ### How to deploy it The Run:AI step operator requires access to a Run:AI cluster. You will need: * Access to a Run:AI cluster (SaaS or self-hosted) * A Run:AI project with sufficient resource quota * Run:AI API credentials (client ID and secret): * Navigate to Run:AI control plane → Access → Service accounts (called "Settings → Applications" on older versions) * Create a new [service account to obtain client credentials](https://run-ai-docs.nvidia.com/self-hosted/infrastructure-setup/authentication/service-accounts) * Assign it an access rule with an appropriate role and scope (see [Run:AI service account permissions](#runai-service-account-permissions) below) ### Run:AI service account permissions The `client_id` and `client_secret` you pass to the step operator authenticate as a Run:AI **service account** (formerly called "Application"). Run:AI uses RBAC of the form ` is a in a `, so giving the service account the right permissions means picking a role and a scope. The ZenML integration only ever exercises a small set of Run:AI API operations under the hood, so it does not need a broad role. The full surface area is: | Operation | Run:AI entity → action | |---|---| | Resolve project by name | Projects → View | | Resolve cluster | Clusters (or Clusters minimal) → View | | Submit training workload | Trainings → Create | | Poll workload status | Trainings → View | | Suspend on cancel/timeout | Trainings → Edit | | Delete workload (only if `delete_on_failure=True`) | Trainings → Delete | For basic usage, that's it. The integration constructs the full workload spec from your `RunAIStepOperatorSettings` and submits it directly, so it does **not** need access to Workspaces, Inferences, Environments, Compute resources, Policies, Users, Access rules, or Roles. Some advanced settings require additional read permissions because they reference existing Run:AI assets: | Advanced setting | Additional Run:AI entity → action | |---|---| | `workload_template_id` | Templates → View | | S3 mounts backed by Run:AI-managed credentials or data sources | Credentials/Data sources → View | PVC, ConfigMap, Secret, NFS, and HostPath mounts are passed as workload spec references. Run:AI and the underlying Kubernetes cluster validate whether the referenced objects are allowed for the project and namespace. #### Recommended role by Run:AI version **Run:AI self-hosted v2.24+ — use `AI practitioner` at project scope.** Run:AI 2.24 deprecated the legacy researcher/engineer roles in favor of `AI practitioner`, `Data and storage administrator`, and `Project administrator`. `AI practitioner` carries View/Edit/Create/Delete on Trainings and Workloads, View on Projects, and View on Clusters minimal and Node pools minimal — exactly matching what the integration needs. It is the smallest predefined role in 2.24 that fully covers the integration. **Run:AI v2.23 and earlier (including older self-hosted clusters and SaaS tenants not yet on 2.24) — use `L1 researcher` at project scope.** `L1 researcher` has VECD on Trainings, Workloads, and Workspaces, plus View on Projects, Clusters, and Node pools — also a complete superset of what's needed. Avoid `L2 researcher` (no dashboard view, no real security gain over L1 for an automated submitter) and avoid `ML engineer` (has VECD on Inferences, not Trainings — the wrong direction for ZenML training-style workloads). **Tightest least-privilege option (v2.24+) — build a custom role via the Roles API.** From v2.24 onward, administrators can compose custom roles from permission sets using `POST /api/v2/authorization/roles`. For the ZenML step operator the minimal custom role is: * Trainings — View, Create, Edit (add Delete only if you intend to set `delete_on_failure=True`) * Workloads — View (the status path returns the generic workload object too) * Projects — View * Clusters minimal — View * Node pools minimal — View (only needed if you pass `node_pools` in step settings) #### Scope: always use the Project scope Run:AI scopes are Projects, Departments, Clusters, or Account. Always assign the role at the specific **Project** scope that matches the `project_name` in your `RunAIStepOperatorConfig`. Assigning the role at Department, Cluster, or Account scope would let the same client ID submit workloads into projects ZenML was never configured to use, which defeats the point of a per-stack service account. The "Clusters minimal" / "Node pools minimal" permission sets exist so a project-scoped role can still resolve cluster and node-pool identifiers without leaking full cluster visibility. #### Quick setup 1. In the Run:AI control plane, go to **Access → Service accounts → + NEW SERVICE ACCOUNT**, name it (e.g., `zenml-step-operator`), and copy the client ID and client secret. The secret is only displayed once at creation time. 2. In **Access → Access rules → + ACCESS RULE**, set the subject to that service account, the role to `AI practitioner` (v2.24+) or `L1 researcher` (older), and the scope to the specific Run:AI project ZenML will submit into. 3. Confirm the project has sufficient GPU/CPU/memory quota on the relevant node pool — the workload will sit in `Pending` indefinitely (and eventually trip `pending_timeout`) if it doesn't. 4. If you use a private container registry, create a Docker-registry credential in the Run:AI project and pass its name as `image_pull_secret_name` when registering the step operator. ### How to use it To use the Run:AI step operator, we need: * The ZenML `runai` integration installed. If you haven't done so, run: ```shell zenml integration install runai ``` * A [remote artifact store](https://docs.zenml.io/stacks/artifact-stores/) as part of your stack. This is needed so that both your orchestration environment and Run:AI workloads can read and write step artifacts. * A [remote container registry](https://docs.zenml.io/stacks/container-registries/) to store the Docker images that will be used to run your steps. * An [image builder](https://docs.zenml.io/stacks/image-builders/) to build the Docker images. We can then register the step operator, using [ZenML secrets](https://docs.zenml.io/concepts/secrets#python-sdk-3) to store the Run:AI client ID and secret: ```shell zenml step-operator register \ --flavor=runai \ --client_id={{runai_secret.client_id}} \ --client_secret={{runai_secret.client_secret}} \ --runai_base_url= \ --project_name= ``` We can then add the step operator to our active stack: ```shell zenml stack update -s ``` Once you added the step operator to your active stack, you can use it to execute individual steps of your pipeline by specifying it in the `@step` decorator: ```python from zenml import step @step(step_operator="") def trainer(...) -> ...: """Train a model on Run:AI.""" ... ``` {% hint style="info" %} ZenML will build a Docker image which includes your code and use it to run your steps on Run:AI. Check out [this page](https://docs.zenml.io/how-to/customize-docker-builds/) if you want to learn more about how ZenML builds these images and how you can customize them. {% endhint %} You can use standard ZenML `DockerSettings` on the step or pipeline to control this image, for example to install additional Python requirements, set a parent image, or include extra files. These settings affect the image that Run:AI runs; Run:AI-specific settings such as storage mounts, security context, ports, and scheduling remain in `RunAIStepOperatorSettings`. The Run:AI step operator uses asynchronous workload submission under the hood. ZenML stores the Run:AI workload metadata for each step run and uses it for status monitoring and cancellation. ### Configuring GPU resources You can configure GPU allocation using step operator settings: ```python from zenml import step from zenml.integrations.runai.flavors import RunAIStepOperatorSettings # Request half a GPU runai_settings = RunAIStepOperatorSettings( gpu_devices_request=1, gpu_portion_request=0.5, # 0.5 = half GPU gpu_request_type="portion", cpu_core_request=2.0, cpu_memory_request="4G", ) @step( step_operator="runai", settings={"step_operator": runai_settings} ) def train_model(): # This step runs with half a GPU ... ``` #### CPU-only workload ```python settings = RunAIStepOperatorSettings( gpu_devices_request=0, # No GPU cpu_core_request=8.0, cpu_memory_request="32G", ) ``` ### Configuration options #### Step Operator Configuration (set during registration) | Option | Type | Required | Description | |--------|------|----------|-------------| | `client_id` | str | Yes | Run:AI client ID for API authentication | | `client_secret` | str | Yes | Run:AI client secret for API authentication | | `runai_base_url` | str | Yes | Run:AI control plane URL (e.g., `https://org.run.ai`) | | `project_name` | str | Yes | Run:AI project name for workload submission | | `cluster_name` | str | No | Run:AI cluster name (uses project's cluster if not specified) | | `image_pull_secret_name` | str | No | Name of Run:AI image pull secret for private registries | | `monitoring_interval` | float | No | Interval in seconds to poll workload status (default: 30) | | `workload_timeout` | int | No | Maximum time in seconds for workload completion | | `delete_on_failure` | bool | No | Delete failed workloads (default: False). Set to True to clean up failed runs | #### Step Settings (per-step configuration) | Option | Type | Default | Description | |--------|------|---------|-------------| | `gpu_devices_request` | int | 1 | Number of GPUs to request | | `gpu_portion_request` | float | 1.0 | Fractional GPU allocation (0.0-1.0) | | `gpu_request_type` | str | "portion" | GPU allocation method: "portion" or "memory" | | `gpu_memory_request` | str | None | GPU memory to request (e.g., "20Gi") when using memory type | | `gpu_portion_limit` | float | None | Maximum fractional GPU portion the workload can burst to when available | | `gpu_memory_limit` | str | None | Maximum GPU memory the workload can burst to when using memory requests | | `cpu_core_request` | float | 1.0 | Number of CPU cores to request | | `cpu_core_limit` | float | None | Maximum CPU cores the workload can burst to | | `cpu_memory_request` | str | "4G" | Memory to request (e.g., "4G", "8Gi") | | `cpu_memory_limit` | str | None | Maximum memory the workload can burst to | | `node_pools` | list | None | Ordered list of node pool names for scheduling | | `node_type` | str | None | Node type label for GPU selection | | `preemptibility` | str | None | "preemptible" or "non-preemptible" | | `priority_class` | str | None | Kubernetes PriorityClass name | | `tolerations` | list | None | Kubernetes tolerations (`RunAITolerationSettings`) for scheduling on tainted nodes | | `large_shm_request` | bool | False | Request large /dev/shm for PyTorch DataLoader | | `pvc_mounts` | list | None | PVC mounts (`RunAIPVCMountSettings`) | | `config_map_mounts` | list | None | ConfigMap mounts (`RunAIConfigMapMountSettings`) | | `secret_mounts` | list | None | Secret mounts (`RunAISecretMountSettings`) | | `nfs_mounts` | list | None | NFS mounts (`RunAINFSMountSettings`) | | `s3_mounts` | list | None | S3 mounts (`RunAIS3MountSettings`) | | `host_path_mounts` | list | None | HostPath mounts (`RunAIHostPathMountSettings`) | | `workload_template_id` | str | None | Existing Run:AI workload template ID | | `security_context` | object | None | Run:AI security context (`RunAISecurityContextSettings`) | | `ports` | list | None | Port declarations (`RunAIPortSettings`) | | `external_urls` | list | None | External URL exposure (`RunAIExternalURLSettings`) | | `parallelism` | int | None | Run:AI training workload parallelism | | `completions` | int | None | Run:AI training workload completions | A useful way to think about the timeout settings is: `pending_timeout` covers the queuing phase before Run:AI can start the workload, while `workload_timeout` covers the running phase after the workload has started. If a workload sits in `Pending` because no quota is available, `pending_timeout` is the timer that lets ZenML stop waiting instead of hanging indefinitely. Environment variables are configured through the standard ZenML `environment` settings on steps or pipelines; the Run:AI step operator does not introduce an additional environment-specific setting. ### Advanced training workload settings The Run:AI step operator can also pass through advanced fields for **standard training workloads**. These settings are additive and optional; if you do not set them, the existing default behavior is unchanged. Useful Run:AI references while configuring these settings: * [Run:AI standard training workloads](https://run-ai-docs.nvidia.com/self-hosted/2.22/workloads-in-nvidia-run-ai/using-training/standard-training/train-models) * [Run:AI data sources](https://run-ai-docs.nvidia.com/self-hosted/workloads-in-nvidia-run-ai/assets/datasources) * [Run:AI workload templates](https://run-ai-docs.nvidia.com/self-hosted/2.22/workloads-in-nvidia-run-ai/workload-templates) * [Run:AI CLI examples](https://run-ai-docs.nvidia.com/self-hosted/reference/cli/cli-examples) * [Run:AI REST API overview](https://run-ai-docs.nvidia.com/api/2.22) #### End-to-end advanced example The following example combines the most common advanced settings: an existing PVC, a ConfigMap, a template ID, explicit UID/GID settings, a debugging port, and conservative retry/timeout behavior. ``` /* Detailed source-code truncated for AI context efficiency. */ ``` #### Advanced settings reference These fields map to the Run:AI training workload request body. ZenML validates the local shape where possible, then sends the request to Run:AI; Run:AI and the underlying Kubernetes cluster still decide whether referenced assets, security settings, and ingress configuration are allowed. | ZenML setting | Run:AI concept | Notes | |---|---|---| | `pvc_mounts` | PVC data source / PVC volume | Existing or newly described PVC mount. `path` is the container mount path. | | `config_map_mounts` | ConfigMap data source / volume | `config_map` is the existing ConfigMap name; `mount_path` is the container path. | | `secret_mounts` | Secret data source / volume | Mounts a Secret as files. Use normal ZenML environment settings for non-secret environment variables. | | `nfs_mounts` | NFS data source / volume | Requires `server`, exported `path`, and container `mount_path`. | | `s3_mounts` | S3 data source | Use Run:AI/Kubernetes-managed credential references for private buckets. | | `host_path_mounts` | HostPath data source / volume | Depends heavily on cluster policy and should be used sparingly. | | `workload_template_id` | Workload template ID | Applies an existing Run:AI workload template. ZenML does not resolve template names. | | `tolerations` | Kubernetes tolerations | Allows scheduling on tainted nodes with typed key/operator/value/effect settings. | | `security_context` | Workload security block | UID/GID, non-root, seccomp, capabilities, and related pod security settings. | | `ports` | Port declarations | Declares container ports and optional service ports. | | `external_urls` | Exposed URLs | Requests Run:AI external URL exposure for long-running/debug endpoints. | | `parallelism` / `completions` | Training workload parallelism | Runs multiple pods for the same training workload; see caveat below. | #### Storage mounts Use the typed mount settings to attach existing Kubernetes/Run:AI storage references to the training workload. Run:AI documents these as [data sources](https://run-ai-docs.nvidia.com/self-hosted/workloads-in-nvidia-run-ai/assets/datasources), including remote locations such as NFS and S3 and local Kubernetes resources such as PVC, ConfigMap, HostPath, and Secret. Supported storage setting classes are `RunAIPVCMountSettings`, `RunAIConfigMapMountSettings`, `RunAISecretMountSettings`, `RunAINFSMountSettings`, `RunAIS3MountSettings`, and `RunAIHostPathMountSettings`. Mount paths must be absolute and unique within the workload. ConfigMap and Secret `default_mode` values must be four-character octal strings such as `"0644"` or `"0400"`. ZenML only passes references to Run:AI; it does not create PVCs, ConfigMaps, Secrets, buckets, or NFS exports. | Setting class | Important fields | |---|---| | `RunAIPVCMountSettings` | `claim_name`, `path`, `existing_pvc`, `read_only`, `ephemeral`, `claim_info`, `data_sharing` | | `RunAIConfigMapMountSettings` | `config_map`, `mount_path`, `sub_path`, `default_mode` | | `RunAISecretMountSettings` | `secret`, `mount_path`, `default_mode` | | `RunAINFSMountSettings` | `server`, `path`, `mount_path`, `read_only` | | `RunAIS3MountSettings` | `bucket`, `path`, `url`, `access_key_secret`, `secret_key_of_access_key_id`, `secret_key_of_secret_key` | | `RunAIHostPathMountSettings` | `path`, `mount_path`, `read_only`, `mount_propagation` | HostPath mounts and Secret mounts can expose sensitive host or credential data. Use them only when your Run:AI/Kubernetes policies allow them for the project. #### Workload templates Pass an existing Run:AI workload template ID with `workload_template_id`: ```python settings = RunAIStepOperatorSettings(workload_template_id="template-id") ``` Run:AI describes templates as reusable workload setups in the [workload templates documentation](https://run-ai-docs.nvidia.com/self-hosted/2.22/workloads-in-nvidia-run-ai/workload-templates). ZenML does not resolve template names or change Run:AI's template merge semantics. Run:AI workload requests use template IDs, so resolve template names before configuring ZenML. #### Scheduling tolerations Use `RunAITolerationSettings` to schedule workloads on Kubernetes nodes with matching taints: ```python from zenml.integrations.runai.flavors import ( RunAIStepOperatorSettings, RunAITolerationSettings, ) settings = RunAIStepOperatorSettings( tolerations=[ RunAITolerationSettings( key="nvidia.com/gpu", operator="Exists", effect="NoSchedule", ) ] ) ``` Supported fields are `key`, `operator` (`"Equal"` or `"Exists"`), `value`, and `effect` (`"NoSchedule"`, `"PreferNoSchedule"`, or `"NoExecute"`). Run:AI and Kubernetes still decide whether the project may use the targeted nodes. #### Security context Use `RunAISecurityContextSettings` for UID/GID and related security fields: ```python from zenml.integrations.runai.flavors import RunAISecurityContextSettings settings = RunAIStepOperatorSettings( security_context=RunAISecurityContextSettings( uid_gid_source="custom", run_as_uid=1000, run_as_gid=1000, run_as_non_root=True, read_only_root_filesystem=True, seccomp_profile_type="RuntimeDefault", supplemental_groups=[1000, 2000], ) ) ``` Run:AI's SDK uses `run_as_uid` and `run_as_gid` with `uid_gid_source="custom"` for explicit IDs. ZenML accepts `supplemental_groups` as a list of integers and serializes it to Run:AI's semicolon-separated format. Cluster admission policies still decide whether a security context is accepted. | Security field | Values / type | |---|---| | `uid_gid_source` | `"custom"`, `"fromTheImage"`, or `"fromIdpToken"` | | `run_as_uid`, `run_as_gid` | Non-negative integer UID/GID | | `run_as_non_root` | Boolean | | `supplemental_groups` | List of non-negative integer group IDs | | `seccomp_profile_type` | `"RuntimeDefault"`, `"Unconfined"`, or `"Localhost"` | | `allow_privilege_escalation`, `read_only_root_filesystem`, `host_ipc`, `host_network` | Boolean | | `capabilities` | List of Linux capabilities to add | #### Ports and external URLs Use `RunAIPortSettings` for port declarations and `RunAIExternalURLSettings` for Run:AI external URL exposure. Run:AI CLI examples show the same concept with flags such as `--external-url container=8888` in the [CLI examples](https://run-ai-docs.nvidia.com/self-hosted/reference/cli/cli-examples). ```python from zenml.integrations.runai.flavors import ( RunAIExternalURLSettings, RunAIPortSettings, ) settings = RunAIStepOperatorSettings( ports=[ RunAIPortSettings( container=8888, service_type="ClusterIP", external=30088, ) ], external_urls=[ RunAIExternalURLSettings( container=8888, authorization_type="authenticatedUsers", ) ], ) ``` The `container` value is the port inside the workload container. The optional `external` value is an integer service port exposed by Run:AI. The integration submits the exposure configuration but does not poll generated URLs or store them in ZenML metadata. | Setting class | Important fields | |---|---| | `RunAIPortSettings` | `container`, `service_type`, `external`, `tool_type`, `tool_name`, `name` | | `RunAIExternalURLSettings` | `container`, `url`, `authorization_type`, `authorized_users`, `authorized_groups`, `tool_type`, `tool_name`, `name` | External URLs are mainly useful for workloads that keep a service alive during step execution, for example a temporary debugger, profiler, or notebook endpoint. A normal ZenML training step exits after producing artifacts, so there may be nothing to reach by the time you open the URL. #### Parallelism and completions `parallelism` and `completions` are passed to the Run:AI training workload spec. Run:AI CLI examples show the equivalent concept with `runai training submit --parallelism 2 --completions 2` in the [CLI examples](https://run-ai-docs.nvidia.com/self-hosted/reference/cli/cli-examples). ```python settings = RunAIStepOperatorSettings(parallelism=2, completions=4) ``` Values greater than `1` can run the same ZenML step entrypoint multiple times for a single step run. This is useful only for explicitly idempotent workloads and is **not** distributed training support. Make sure artifact and metadata writes are safe before enabling it. #### Workload type scope Distributed training and inference workloads are separate Run:AI API resources (`DistributedCreationRequest` and `InferenceCreationRequest`) and are intentionally not modeled as extra fields on this training step operator. Distributed training needs a dedicated abstraction for worker roles, replica topology, rank/world-size coordination, and artifact writes. Inference workloads fit better in a model deployer or deployer-style abstraction because their lifecycle is service-oriented rather than step-run-oriented. ### Troubleshooting #### Common Issues **Issue: "Project not found in Run:AI"** - Ensure the project name exactly matches your Run:AI project - Verify the service account has at least View permission on Projects within the project's scope **Issue: "Failed to submit Run:AI workload" / 403 errors** - Check that your client ID and secret are correct - Verify your Run:AI base URL is accessible - Verify the service account has View/Edit/Create/Delete permissions (or at minimum View+Create+Edit) on Trainings within the project scope — see [Run:AI service account permissions](#runai-service-account-permissions) - Ensure your project has sufficient resource quota **Issue: Workload stays in `Pending` and eventually times out** - The Run:AI project most likely doesn't have enough quota on the requested node pool. Increase quota or pick a node pool with capacity via `node_pools`/`node_type` settings **Issue: "runapy package not found"** - Install the Run:AI integration: `zenml integration install runai` #### Viewing Logs Run:AI workload logs can be viewed in the Run:AI control plane UI: 1. Navigate to your Run:AI control plane 2. Go to Workloads → Training 3. Find your step workload and click to view logs
ZenML Scarf
--- ### Book/Component Guide/Step Operators/Sagemaker (docs/book/component-guide/step-operators/sagemaker.md) --- description: Executing individual steps in SageMaker. --- # Amazon SageMaker [SageMaker](https://aws.amazon.com/sagemaker/) offers specialized compute instances to run your training jobs and has a comprehensive UI to track and manage your models and logs. ZenML's SageMaker step operator allows you to submit individual steps to be run on Sagemaker compute instances. ### When to use it You should use the SageMaker step operator if: * one or more steps of your pipeline require computing resources (CPU, GPU, memory) that are not provided by your orchestrator. * you have access to SageMaker. If you're using a different cloud provider, take a look at the [Vertex](vertex.md) or [AzureML](azureml.md) step operators. ### How to deploy it Create a role in the IAM console that you want the jobs running in SageMaker to assume. This role should follow the principle of least privilege with specific permissions instead of broad managed policies. See the [Required IAM Permissions](#required-iam-permissions) section below for the minimal required permissions, or check [here](https://docs.aws.amazon.com/sagemaker/latest/dg/sagemaker-roles.html#sagemaker-roles-create-execution-role) for AWS's general guide on execution roles. ### How to use it To use the SageMaker step operator, we need: * The ZenML `aws` integration installed. If you haven't done so, run ```shell zenml integration install aws ``` * [Docker](https://www.docker.com) installed and running. * An IAM role with the correct permissions. See the [deployment section](sagemaker.md#how-to-deploy-it) for detailed instructions. * An [AWS container registry](https://docs.zenml.io/stacks/container-registries/aws) as part of our stack. Take a look [here](https://docs.zenml.io/stacks/container-registries/aws#how-to-deploy-it) for a guide on how to set that up. * A [remote artifact store](https://docs.zenml.io/stacks/artifact-stores/) as part of your stack. This is needed so that both your orchestration environment and SageMaker can read and write step artifacts. Check out the documentation page of the artifact store you want to use for more information on how to set that up and configure authentication for it. * An instance type that we want to execute our steps on. See [here](https://docs.aws.amazon.com/sagemaker/latest/dg/notebooks-available-instance-types.html) for a list of available instance types. * (Optional) An experiment that is used to group SageMaker runs. Check [this guide](https://docs.aws.amazon.com/sagemaker/latest/dg/experiments-create.html) to see how to create an experiment. There are two ways you can authenticate your orchestrator to AWS to be able to run steps on SageMaker: {% tabs %} {% tab title="Authentication via Service Connector" %} The recommended way to authenticate your SageMaker step operator is by registering or using an existing [AWS Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/aws-service-connector) and connecting it to your SageMaker step operator. The credentials configured for the connector must have the permissions described in the [Required IAM Permissions](#required-iam-permissions) section below. The SageMaker step operator uses these `aws-generic` resource type, so make sure to configure the connector accordingly: ```shell zenml service-connector register --type aws -i zenml step-operator register \ --flavor=sagemaker \ --role= \ --instance_type= \ # --experiment_name= # optionally specify an experiment to assign this run to zenml step-operator connect --connector zenml stack register -s ... --set ``` {% endtab %} {% tab title="Implicit Authentication" %} If you don't connect your step operator to a service connector: * If using a [local orchestrator](https://docs.zenml.io/stacks/orchestrators/local): ZenML will try to implicitly authenticate to AWS via the `default` profile in your local [AWS configuration file](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html). Make sure this profile has the permissions described in the [Required IAM Permissions](#required-iam-permissions) section below. * If using a remote orchestrator: the remote environment in which the orchestrator runs needs to be able to implicitly authenticate to AWS and assume the IAM role specified when registering the SageMaker step operator. This is only possible if the orchestrator is also running in AWS and uses a form of implicit workload authentication like the IAM role of an EC2 instance. If this is not the case, you will need to use a service connector. ```shell zenml step-operator register \ --flavor=sagemaker \ --role= \ --instance_type= \ # --experiment_name= # optionally specify an experiment to assign this run to zenml stack register -s ... --set python run.py # Authenticates with `default` profile in `~/.aws/config` ``` {% endtab %} {% endtabs %} Once you added the step operator to your active stack, you can use it to execute individual steps of your pipeline by specifying it in the `@step` decorator as follows: ```python from zenml import step @step(step_operator=True) def trainer(...) -> ...: """Train a model.""" # This step will be executed in SageMaker. ``` {% hint style="info" %} ZenML will build a Docker image called `/zenml:` which includes your code and use it to run your steps in SageMaker. Check out [this page](https://docs.zenml.io/how-to/customize-docker-builds/) if you want to learn more about how ZenML builds these images and how you can customize them. {% endhint %} #### Additional configuration For additional configuration of the SageMaker step operator, you can pass `SagemakerStepOperatorSettings` when defining or running your pipeline. Check out the [SDK docs](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-aws.html#zenml.integrations.aws) for a full list of available attributes and [this docs page](https://docs.zenml.io/concepts/steps_and_pipelines/configuration) for more information on how to specify settings. For more information and a full list of configurable attributes of the SageMaker step operator, check out the [SDK Docs](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-aws.html#zenml.integrations.aws) . ## Required IAM Permissions Instead of using the broad `AmazonS3FullAccess` and `AmazonSageMakerFullAccess` managed policies, follow the principle of least privilege by creating custom policies with only the required permissions: ### Execution Role Permissions (for SageMaker jobs) Create a custom policy for the execution role that SageMaker will assume when running your steps: ```json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "sagemaker:CreateProcessingJob", "sagemaker:DescribeProcessingJob", "sagemaker:StopProcessingJob", "sagemaker:CreateTrainingJob", "sagemaker:DescribeTrainingJob", "sagemaker:StopTrainingJob" ], "Resource": "*" }, { "Effect": "Allow", "Action": [ "s3:GetObject", "s3:PutObject", "s3:DeleteObject", "s3:ListBucket" ], "Resource": [ "arn:aws:s3:::your-artifact-store-bucket", "arn:aws:s3:::your-artifact-store-bucket/*" ] }, { "Effect": "Allow", "Action": [ "ecr:BatchCheckLayerAvailability", "ecr:GetDownloadUrlForLayer", "ecr:BatchGetImage" ], "Resource": "*" }, { "Effect": "Allow", "Action": [ "ecr:GetAuthorizationToken" ], "Resource": "*" }, { "Effect": "Allow", "Action": [ "logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents" ], "Resource": "*" } ] } ``` ### Client Permissions (for step operator usage) The client needs permissions to launch SageMaker jobs and pass the execution role: ```json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "sagemaker:CreateProcessingJob", "sagemaker:DescribeProcessingJob", "sagemaker:StopProcessingJob", "sagemaker:CreateTrainingJob", "sagemaker:DescribeTrainingJob", "sagemaker:StopTrainingJob" ], "Resource": "*" }, { "Effect": "Allow", "Action": "iam:PassRole", "Resource": "arn:aws:iam::ACCOUNT-ID:role/EXECUTION-ROLE-NAME", "Condition": { "StringEquals": { "iam:PassedToService": "sagemaker.amazonaws.com" } } } ] } ``` Replace `ACCOUNT-ID`, `EXECUTION-ROLE-NAME`, and `your-artifact-store-bucket` with your actual values. #### Enabling CUDA for GPU-backed hardware Note that if you wish to use this step operator to run steps on a GPU, you will need to follow [the instructions on this page](https://docs.zenml.io/user-guides/tutorial/distributed-training/) to ensure that it works. It requires adding some extra settings customization and is essential to enable CUDA for the GPU to give its full acceleration.
ZenML Scarf
--- ### Book/Component Guide/Step Operators/Spark Kubernetes (docs/book/component-guide/step-operators/spark-kubernetes.md) --- description: Executing individual steps on Spark --- # Spark The `spark` integration brings two different step operators: * **Step Operator**: The `SparkStepOperator` serves as the base class for all the Spark-related step operators. * **Step Operator**: The `KubernetesSparkStepOperator` is responsible for launching ZenML steps as Spark applications with Kubernetes as a cluster manager. ## Step Operators: `SparkStepOperator` A summarized version of the implementation can be summarized in two parts. First, the configuration: ```python from typing import Optional, Dict, Any from zenml.step_operators import BaseStepOperatorConfig class SparkStepOperatorConfig(BaseStepOperatorConfig): """Spark step operator config. Attributes: master: is the master URL for the cluster. You might see different schemes for different cluster managers which are supported by Spark like Mesos, YARN, or Kubernetes. Within the context of this PR, the implementation supports Kubernetes as a cluster manager. deploy_mode: can either be 'cluster' (default) or 'client' and it decides where the driver node of the application will run. submit_kwargs: is the JSON string of a dict, which will be used to define additional params if required (Spark has quite a lot of different parameters, so including them, all in the step operator was not implemented). """ master: str deploy_mode: str = "cluster" submit_kwargs: Optional[Dict[str, Any]] = None ``` and then the implementation: ```python from typing import List from pyspark.conf import SparkConf from zenml.step_operators import BaseStepOperator class SparkStepOperator(BaseStepOperator): """Base class for all Spark-related step operators.""" def _resource_configuration( self, spark_config: SparkConf, resource_configuration: "ResourceSettings", ) -> None: """Configures Spark to handle the resource configuration.""" def _backend_configuration( self, spark_config: SparkConf, step_config: "StepConfiguration", ) -> None: """Configures Spark to handle backends like YARN, Mesos or Kubernetes.""" def _io_configuration( self, spark_config: SparkConf ) -> None: """Configures Spark to handle different input/output sources.""" def _additional_configuration( self, spark_config: SparkConf ) -> None: """Appends the user-defined configuration parameters.""" def _launch_spark_job( self, spark_config: SparkConf, entrypoint_command: List[str] ) -> None: """Generates and executes a spark-submit command.""" def launch( self, info: "StepRunInfo", entrypoint_command: List[str], ) -> None: """Launches the step on Spark.""" ``` Under the base configuration, you will see the main configuration parameters: * `master` is the master URL for the cluster where Spark will run. You might see different schemes for this URL with varying cluster managers such as Mesos, YARN, or Kubernetes. * `deploy_mode` can either be 'cluster' (default) or 'client' and it decides where the driver node of the application will run. * `submit_args` is the JSON string of a dictionary, which will be used to define additional parameters if required ( Spark has a wide variety of parameters, thus including them all in a single class was deemed unnecessary.). In addition to this configuration, the `launch` method of the step operator gets additional configuration parameters from the `DockerSettings` and `ResourceSettings`. As a result, the overall configuration happens in 4 base methods: * `_resource_configuration` translates the ZenML `ResourceSettings` object to Spark's own resource configuration. * `_backend_configuration` is responsible for cluster-manager-specific configuration. * `_io_configuration` is a critical method. Even though we have materializers, Spark might require additional packages and configuration to work with a specific filesystem. This method is used as an interface to provide this configuration. * `_additional_configuration` takes the `submit_args`, converts, and appends them to the overall configuration. Once the configuration is completed, `_launch_spark_job` comes into play. This takes the completed configuration and runs a Spark job on the given `master` URL with the specified `deploy_mode`. By default, this is achieved by creating and executing a `spark-submit` command. ### Warning In its first iteration, the pre-configuration with `_io_configuration` method is only effective when it is paired with an `S3ArtifactStore` (which has an authentication secret). When used with other artifact store flavors, you might be required to provide additional configuration through the `submit_args`. ## Stack Component: `KubernetesSparkStepOperator` The `KubernetesSparkStepOperator` is implemented by subclassing the base `SparkStepOperator` and uses the `PipelineDockerImageBuilder` class to build and push the required Docker images. ```python from typing import Optional from zenml.integrations.spark.step_operators.spark_step_operator import ( SparkStepOperatorConfig ) class KubernetesSparkStepOperatorConfig(SparkStepOperatorConfig): """Config for the Kubernetes Spark step operator.""" namespace: Optional[str] = None service_account: Optional[str] = None ``` ```python from pyspark.conf import SparkConf from zenml.utils.pipeline_docker_image_builder import PipelineDockerImageBuilder from zenml.integrations.spark.step_operators.spark_step_operator import ( SparkStepOperator ) class KubernetesSparkStepOperator(SparkStepOperator): """Step operator which runs Steps with Spark on Kubernetes.""" def _backend_configuration( self, spark_config: SparkConf, step_config: "StepConfiguration", ) -> None: """Configures Spark to run on Kubernetes.""" # Build and push the image docker_image_builder = PipelineDockerImageBuilder() image_name = docker_image_builder.build_and_push_docker_image(...) # Adjust the spark configuration spark_config.set("spark.kubernetes.container.image", image_name) ... ``` For Kubernetes, there are also some additional important configuration parameters: * `namespace` is the namespace under which the driver and executor pods will run. * `service_account` is the service account that will be used by various Spark components (to create and watch the pods). Additionally, the `_backend_configuration` method is adjusted to handle the Kubernetes-specific configuration. ## When to use it You should use the Spark step operator: * when you are dealing with large amounts of data. * when you are designing a step that can benefit from distributed computing paradigms in terms of time and resources. ## How to deploy it To use the `KubernetesSparkStepOperator` you will need to setup a few things first: * **Remote ZenML server:** See the [deployment guide](https://docs.zenml.io/getting-started/deploying-zenml/) for more information. * **Kubernetes cluster:** There are many ways to deploy a Kubernetes cluster using different cloud providers or on your custom infrastructure. For AWS, you can follow the [Spark EKS Setup Guide](spark-kubernetes.md#spark-eks-setup-guide) below. ### Spark EKS Setup Guide The following guide will walk you through how to spin up and configure a [Amazon Elastic Kubernetes Service](https://aws.amazon.com/eks/) with Spark on it: #### EKS Kubernetes Cluster * Follow [this guide](https://docs.aws.amazon.com/eks/latest/userguide/service_IAM_role.html#create-service-role) to create an Amazon EKS cluster role. * Follow [this guide](https://docs.aws.amazon.com/eks/latest/userguide/create-node-role.html#create-worker-node-role) to create an Amazon EC2 node role. * Go to the [IAM website](https://console.aws.amazon.com/iam), and select `Roles` to edit both roles. * Instead of using broad managed policies, create custom policies with least privilege permissions: **For S3 Access (if needed for Spark jobs):** ```json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:GetObject", "s3:PutObject", "s3:DeleteObject", "s3:ListBucket" ], "Resource": [ "arn:aws:s3:::your-spark-bucket", "arn:aws:s3:::your-spark-bucket/*" ] } ] } ``` **For RDS Access (only if your Spark jobs access RDS):** ```json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "rds:DescribeDBInstances", "rds:DescribeDBClusters" ], "Resource": "*" } ] } ``` {% hint style="warning" %} **Security Best Practice:** Only attach the policies your Spark jobs actually need. The original `AmazonRDSFullAccess` and `AmazonS3FullAccess` policies grant excessive permissions that violate the principle of least privilege. Most Spark workloads only need specific S3 bucket access and rarely need RDS permissions. {% endhint %} * Go to the [EKS website](https://console.aws.amazon.com/eks). * Make sure the correct region is selected on the top right. * Click on `Add cluster` and select `Create`. * Enter a name and select the **cluster role** for `Cluster service role`. * Keep the default values for the networking and logging steps and create the cluster. * Note down the cluster name and the API server endpoint: ```bash EKS_CLUSTER_NAME= EKS_API_SERVER_ENDPOINT= ``` * After the cluster is created, select it and click on `Add node group` in the `Compute` tab. * Enter a name and select the **node role**. * For the instance type, we recommend `t3a.xlarge`, as it provides up to 4 vCPUs and 16 GB of memory. #### Docker image for the Spark drivers and executors When you want to run your steps on a Kubernetes cluster, Spark will require you to choose a base image for the driver and executor pods. Normally, for this purpose, you can either use one of the base images in [Spark’s dockerhub](https://hub.docker.com/r/apache/spark-py/tags) or create an image using the [docker-image-tool](https://spark.apache.org/docs/latest/running-on-kubernetes.html#docker-images) which will use your own Spark installation and build an image. When using Spark in EKS, you need to use the latter and utilize the `docker-image-tool`. However, before the build process, you also need to download the following packages * [`hadoop-aws` = 3.3.1](https://hadoop.apache.org/docs/r3.4.1/hadoop-aws/tools/hadoop-aws/index.html) * `aws-java-sdk-bundle` = 1.12.150 and put them in the `jars` folder within your Spark installation. Once that is set up, you can build the image as follows: ```bash cd $SPARK_HOME # If this empty for you then you need to set the SPARK_HOME variable which points to your Spark installation SPARK_IMAGE_TAG= ./bin/docker-image-tool.sh -t $SPARK_IMAGE_TAG -p kubernetes/dockerfiles/spark/bindings/python/Dockerfile -u 0 build BASE_IMAGE_NAME=spark-py:$SPARK_IMAGE_TAG ``` If you are working on an M1 Mac, you will need to build the image for the amd64 architecture, by using the prefix `-X` on the previous command. For example: ```bash ./bin/docker-image-tool.sh -X -t $SPARK_IMAGE_TAG -p kubernetes/dockerfiles/spark/bindings/python/Dockerfile -u 0 build ``` #### Configuring RBAC Additionally, you may need to create the several resources in Kubernetes in order to give Spark access to edit/manage your driver executor pods. To do so, create a file called `rbac.yaml` with the following content: ```yaml apiVersion: v1 kind: Namespace metadata: name: spark-namespace --- apiVersion: v1 kind: ServiceAccount metadata: name: spark-service-account namespace: spark-namespace --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: spark-role namespace: spark-namespace subjects: - kind: ServiceAccount name: spark-service-account namespace: spark-namespace roleRef: kind: ClusterRole name: edit apiGroup: rbac.authorization.k8s.io --- ``` And then execute the following command to create the resources: ```bash aws eks --region=$REGION update-kubeconfig --name=$EKS_CLUSTER_NAME kubectl create -f rbac.yaml ``` Lastly, note down the **namespace** and the name of the **service account** since you will need them when registering the stack component in the next step. ## How to use it To use the `KubernetesSparkStepOperator`, you need: * the ZenML `spark` integration. If you haven't installed it already, run ```shell zenml integration install spark ``` * [Docker](https://www.docker.com) installed and running. * A [remote artifact store](https://docs.zenml.io/stacks/artifact-stores/) as part of your stack. * A [remote container registry](https://docs.zenml.io/stacks/container-registries/) as part of your stack. * A Kubernetes cluster [deployed](spark-kubernetes.md#how-to-deploy-it). We can then register the step operator and use it in our active stack: ```bash zenml step-operator register spark_step_operator \ --flavor=spark-kubernetes \ --master=k8s://$EKS_API_SERVER_ENDPOINT \ --namespace= \ --service_account= ``` ```bash # Register the stack zenml stack register spark_stack \ -o default \ -s spark_step_operator \ -a spark_artifact_store \ -c spark_container_registry \ -i local_builder \ --set ``` Once you added the step operator to your active stack, you can use it to execute individual steps of your pipeline by specifying it in the `@step` decorator as follows: ```python from zenml import step @step(step_operator=True) def step_on_spark(...) -> ...: """Some step that should run with Spark on Kubernetes.""" ... ``` After successfully running any step with a `KubernetesSparkStepOperator`, you should be able to see that a Spark driver pod was created in your cluster for each pipeline step when running `kubectl get pods -n $KUBERNETES_NAMESPACE`. ### Additional configuration For additional configuration of the Spark step operator, you can pass `SparkStepOperatorSettings` when defining or running your pipeline. Check out the [SDK docs](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-spark.html#zenml.integrations.spark) for a full list of available attributes and [this docs page](https://docs.zenml.io/concepts/steps_and_pipelines/configuration) for more information on how to specify settings.
ZenML Scarf
--- ### Book/Component Guide/Step Operators/Ssh (docs/book/component-guide/step-operators/ssh.md) --- description: Executing individual steps on a remote host via SSH and Docker. --- # SSH Step Operator The SSH step operator allows you to execute individual ZenML steps on a remote Linux host via SSH. Steps run inside detached Docker containers on the remote machine, with optional GPU selection. This is ideal for teams that have dedicated GPU machines they want to use for training without needing a full cluster orchestration platform. {% hint style="info" %} If you want to run *entire* pipelines on a remote host (not just individual steps), see the sibling [SSH orchestrator](../orchestrators/ssh.md). The two components share the same SSH connection layer and can target the same machine. {% endhint %} {% hint style="warning" %} When a step operator step runs under the [SSH orchestrator](../orchestrators/ssh.md), it executes inside a container on the remote host rather than on your client. In that case configure the key via `ssh_private_key` instead of `ssh_key_path`, and set `verify_host_key=False` (or point `known_hosts_path` at a file baked into the image), since the container has no local key file or `known_hosts`. {% endhint %} ### When to use it You should use the SSH step operator if: * You have a **dedicated remote machine** (e.g., a GPU workstation or server) reachable via SSH. * You want to run compute-heavy steps (training, inference) on that machine while keeping your orchestrator lightweight. * You want to target **specific GPUs** on the host via `--gpus`. * You prefer a simple setup without Kubernetes or cloud-managed services. ### How to deploy it The SSH step operator connects to an existing remote host; it does not provision infrastructure. Your remote host must meet these requirements: * **Linux** operating system. * **Docker** installed and accessible to the SSH user (the user should be in the `docker` group). * **Network reachability** from the machine running your orchestrator to `hostname:port` over SSH. * If using GPUs: the **NVIDIA Container Toolkit** must be installed so Docker supports `--gpus`. ### How to use it To use the SSH step operator, you need: * The ZenML `ssh` integration installed. If you haven't done so, run: ```shell zenml integration install ssh ``` This installs [paramiko](https://www.paramiko.org/), the Python SSH library used under the hood. * A [remote artifact store](https://docs.zenml.io/stacks/artifact-stores/) as part of your stack (the remote container cannot access local files). * A [remote container registry](https://docs.zenml.io/stacks/container-registries/) to store the Docker images that will be pulled on the remote host. * An [image builder](https://docs.zenml.io/stacks/image-builders/) to build the Docker images for your steps. We can then register the step operator. SSH credentials are configured directly on the step operator: a `username` and **at least one of** `ssh_key_path` **or** `ssh_private_key` **must be provided** for authentication. **Using a key file:** ```shell zenml step-operator register \ --flavor=ssh \ --hostname= \ --username= \ --ssh_key_path=~/.ssh/id_ed25519 ``` **Using a key stored in a ZenML secret:** ```shell zenml secret create ssh_secret \ --private_key='' \ --passphrase='' zenml step-operator register \ --flavor=ssh \ --hostname= \ --username= \ --ssh_private_key={{ssh_secret.private_key}} \ --ssh_key_passphrase={{ssh_secret.passphrase}} ``` We can then add the step operator to our active stack: ```shell zenml stack update -s ``` Once you have added the step operator to your active stack, you can use it to execute individual steps of your pipeline by specifying it in the `@step` decorator: ```python from zenml import step @step(step_operator="") def trainer(...) -> ...: """Train a model on the remote host.""" # This step will be executed via SSH + Docker ``` {% hint style="info" %} ZenML will build a Docker image which includes your code and use it to run your steps on the remote host. Check out [this page](https://docs.zenml.io/how-to/customize-docker-builds/) if you want to learn more about how ZenML builds these images and how you can customize them. {% endhint %} #### What happens at runtime When a step runs via the SSH step operator: 1. ZenML builds and pushes the step image using your stack's image builder and container registry. 2. The step operator opens an SSH connection to the remote host. 3. A **preflight check** verifies that `docker` is available. 4. An **env-file** (with the step's environment variables) is uploaded to the remote working directory. 5. The Docker image is **pulled** on the remote host. 6. The container is started **detached** (`docker run -d`), and the env-file is deleted immediately afterwards so the step's secrets do not linger on the host. 7. ZenML polls the step's status with `docker inspect` until the container exits — the Docker daemon is the single source of truth — and reports success or failure. Step logs are captured by ZenML like any other step. ### GPU execution The SSH step operator supports targeting specific GPUs on the host. #### Selecting GPUs Use the `gpu_indices` setting to specify which GPU devices to expose to the container: ```python from zenml import step from zenml.integrations.ssh.flavors import SSHStepOperatorSettings ssh_settings = SSHStepOperatorSettings( gpu_indices=[0, 1], docker_run_args=["--shm-size=2g"], ) @step( step_operator="", settings={"step_operator": ssh_settings}, ) def train(): """Train with GPUs 0 and 1.""" ... ``` GPU indices are passed to Docker as `--gpus "device=0,1"`. They must be non-negative integers matching the device indices reported by `nvidia-smi` on the remote host. {% hint style="info" %} The step operator does not coordinate GPU access between concurrent steps. If you run multiple GPU steps on the same host at once, give them distinct `gpu_indices` so they don't oversubscribe the same device. {% endhint %} #### CPU-only execution If `gpu_indices` is not set (the default), the step runs without GPU access: ```python ssh_settings = SSHStepOperatorSettings() # CPU-only @step(step_operator="", settings={"step_operator": ssh_settings}) def preprocess(): ... ``` ### Configuration options #### Step Operator Configuration (set during registration) | Option | Type | Required | Default | Description | |--------|------|----------|---------|-------------| | `hostname` | str | Yes | - | Hostname or IP of the remote SSH server | | `username` | str | No* | - | SSH username (must have Docker permissions) | | `port` | int | No | `22` | SSH port on the remote host | | `ssh_key_path` | str | No* | - | Path to SSH private key file (RSA, Ed25519, ECDSA) | | `ssh_private_key` | str | No* | - | SSH private key content (supports `{{secret.key}}` references) | | `ssh_key_passphrase` | str | No | - | Passphrase for encrypted private key (supports `{{secret.key}}`) | | `verify_host_key` | bool | No | `True` | Verify remote host key against known_hosts | | `known_hosts_path` | str | No | - | Path to known_hosts file (defaults to system known_hosts) | | `connection_timeout` | float | No | `10.0` | SSH connection timeout in seconds | | `keepalive_interval` | int | No | `30` | Seconds between SSH keepalive packets (0 to disable) | | `remote_workdir` | str | No | `/tmp/zenml-ssh` | Directory for temporary files on the remote host | | `docker_binary` | str | No | `docker` | Path to Docker binary on the remote host | \* `username` and at least one of `ssh_key_path` or `ssh_private_key` must be provided. #### Step Settings (per-step configuration) | Option | Type | Default | Description | |--------|------|---------|-------------| | `gpu_indices` | list[int] | `None` | GPU device indices to expose via `--gpus` flag | | `docker_run_args` | list[str] | `None` | Additional `docker run` arguments (e.g., `["--shm-size=2g"]`) | {% hint style="warning" %} Do not include secrets in `docker_run_args` as they may appear in process listings on the remote host. {% endhint %} ### Security notes #### Host key verification By default, `verify_host_key` is `True`, which uses paramiko's **RejectPolicy** to reject connections to unknown hosts. This protects against man-in-the-middle attacks. If you haven't connected to the remote host before, add its host key first: ```shell ssh-keyscan -H >> ~/.ssh/known_hosts ``` For ephemeral or test hosts, you can set `verify_host_key=False` to auto-accept unknown host keys (less secure). #### Secrets handling * The env-file uploaded to the remote host is created with **`0600` permissions** (owner read/write only) and is **automatically deleted** after the step completes. * Use ZenML secrets with `{{secret.key}}` syntax for `ssh_private_key` and `ssh_key_passphrase` to avoid storing credentials in plaintext. Check out the [SDK docs](https://sdkdocs.zenml.io/latest/) for the full API reference.
ZenML Scarf
--- ### Book/Component Guide/Step Operators/Vertex (docs/book/component-guide/step-operators/vertex.md) --- description: Executing individual steps in Vertex AI. --- # Google Cloud VertexAI [Vertex AI](https://cloud.google.com/vertex-ai) offers specialized compute instances to run your training jobs and has a comprehensive UI to track and manage your models and logs. ZenML's Vertex AI step operator allows you to submit individual steps to be run on Vertex AI compute instances. ### When to use it You should use the Vertex step operator if: * one or more steps of your pipeline require computing resources (CPU, GPU, memory) that are not provided by your orchestrator. * you have access to Vertex AI. If you're using a different cloud provider, take a look at the [SageMaker](sagemaker.md) or [AzureML](azureml.md) step operators. ### How to deploy it * Enable Vertex AI [here](https://console.cloud.google.com/vertex-ai). * Create a [service account](https://cloud.google.com/iam/docs/service-accounts) with minimal required permissions instead of broad admin roles. See the [Required IAM Permissions](#required-iam-permissions) section below for specific permissions. ### How to use it To use the Vertex step operator, we need: * The ZenML `gcp` integration installed. If you haven't done so, run ```shell zenml integration install gcp ``` * [Docker](https://www.docker.com) installed and running. * Vertex AI enabled and a service account file. See the [deployment section](vertex.md#how-to-deploy-it) for detailed instructions. * A [GCR container registry](https://docs.zenml.io/stacks/container-registries/gcp) as part of our stack. * (Optional) A machine type that we want to execute our steps on (this defaults to `n1-standard-4`). See [here](https://cloud.google.com/vertex-ai/docs/training/configure-compute#machine-types) for a list of available machine types. * A [remote artifact store](https://docs.zenml.io/stacks/artifact-stores/) as part of your stack. This is needed so that both your orchestration environment and VertexAI can read and write step artifacts. Check out the documentation page of the artifact store you want to use for more information on how to set that up and configure authentication for it. You have three different options to provide GCP credentials to the step operator: * use the [`gcloud` CLI](https://cloud.google.com/sdk/gcloud) to authenticate locally with GCP. This only works in combination with the local orchestrator. ```shell gcloud auth login zenml step-operator register \ --flavor=vertex \ --project= \ --region= \ # --machine_type= # optionally specify the type of machine to run on ``` * configure the orchestrator to use a [service account key file](https://cloud.google.com/iam/docs/creating-managing-service-account-keys) to authenticate with GCP by setting the `service_account_path` parameter in the orchestrator configuration to point to a service account key file. This also works only in combination with the local orchestrator. ```shell zenml step-operator register \ --flavor=vertex \ --project= \ --region= \ --service_account_path= \ # --machine_type= # optionally specify the type of machine to run on ``` * (recommended) configure [a GCP Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/gcp-service-connector) with GCP credentials coming from a [service account key file](https://cloud.google.com/iam/docs/creating-managing-service-account-keys) or the local `gcloud` CLI set up with user account credentials and then link the Vertex AI Step Operator stack component to the Service Connector. This option works with any orchestrator. ```shell zenml service-connector register --type gcp --auth-method=service-account --project_id= --service_account_json=@ --resource-type gcp-generic # Or, as an alternative, you could use the GCP user account locally set up with gcloud # zenml service-connector register --type gcp --resource-type gcp-generic --auto-configure zenml step-operator register \ --flavor=vertex \ --region= \ # --machine_type= # optionally specify the type of machine to run on zenml step-operator connect --connector ``` We can then use the registered step operator in our active stack: ```shell # Add the step operator to the active stack zenml stack update -s ``` Once you added the step operator to your active stack, you can use it to execute individual steps of your pipeline by specifying it in the `@step` decorator as follows: ```python from zenml import step @step(step_operator=True) def trainer(...) -> ...: """Train a model.""" # This step will be executed in Vertex. ``` {% hint style="info" %} ZenML will build a Docker image called `/zenml:` which includes your code and use it to run your steps in Vertex AI. Check out [this page](https://docs.zenml.io/how-to/customize-docker-builds/) if you want to learn more about how ZenML builds these images and how you can customize them. {% endhint %} ## Required IAM Permissions Instead of using the broad `roles/aiplatform.admin` and `roles/storage.admin` roles, follow the principle of least privilege by creating a custom role with only the required permissions: ### Service Account Permissions Create a custom role with these specific permissions: **For Vertex AI Operations:** ``` aiplatform.customJobs.create aiplatform.customJobs.get aiplatform.customJobs.list aiplatform.customJobs.cancel aiplatform.jobs.get aiplatform.jobs.list ``` **For Container Registry Access:** ``` artifactregistry.repositories.uploadArtifacts artifactregistry.repositories.downloadArtifacts artifactregistry.repositories.get artifactregistry.repositories.list storage.objects.get storage.objects.create storage.buckets.get ``` **For Artifact Store Access (if using GCS):** ``` storage.objects.create storage.objects.delete storage.objects.get storage.objects.list storage.buckets.get ``` ### Alternative: Use Predefined Roles with Scope If you prefer using predefined roles, use these more specific alternatives: - **Instead of `roles/aiplatform.admin`**: Use `roles/aiplatform.user` - **Instead of `roles/storage.admin`**: Use `roles/storage.objectAdmin` scoped to specific buckets {% hint style="warning" %} **Security Best Practice:** The original `roles/aiplatform.admin` and `roles/storage.admin` roles grant excessive permissions that violate the principle of least privilege. The admin roles provide project-wide access that is unnecessary for step operator functionality. {% endhint %} #### Additional configuration You can specify the service account, network and reserved IP ranges to use for the VertexAI `CustomJob` by passing the `service_account`, `network` and `reserved_ip_ranges` parameters to the `step-operator register` command: ```shell zenml step-operator register \ --flavor=vertex \ --project= \ --region= \ --service_account= # optionally specify the service account to use for the VertexAI CustomJob --network= # optionally specify the network to use for the VertexAI CustomJob --reserved_ip_ranges= # optionally specify the reserved IP range to use for the VertexAI CustomJob ``` For additional configuration of the Vertex step operator, you can pass `VertexStepOperatorSettings` when defining or running your pipeline. ```python from zenml import step from zenml.integrations.gcp.flavors.vertex_step_operator_flavor import VertexStepOperatorSettings @step(step_operator=True, settings={"step_operator": VertexStepOperatorSettings( accelerator_type= "NVIDIA_TESLA_T4", # see https://cloud.google.com/vertex-ai/docs/reference/rest/v1/MachineSpec#AcceleratorType accelerator_count = 1, machine_type = "n1-standard-2", # see https://cloud.google.com/vertex-ai/docs/training/configure-compute#machine-types disk_type = "pd-ssd", # see https://cloud.google.com/vertex-ai/docs/training/configure-storage#disk-types disk_size_gb = 100, # see https://cloud.google.com/vertex-ai/docs/training/configure-storage#disk-size )}) def trainer(...) -> ...: """Train a model.""" # This step will be executed in Vertex. ``` Check out the [SDK docs](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-gcp.html#zenml.integrations.gcp) for a full list of available attributes and [this docs page](https://docs.zenml.io/concepts/steps_and_pipelines/configuration) for more information on how to specify settings. For more information and a full list of configurable attributes of the Vertex step operator, check out the [SDK Docs](https://sdkdocs.zenml.io/latest/integration_code_docs/integrations-gcp.html#zenml.integrations.gcp) . #### Enabling CUDA for GPU-backed hardware Note that if you wish to use this step operator to run steps on a GPU, you will need to follow [the instructions on this page](https://docs.zenml.io/user-guides/tutorial/distributed-training/) to ensure that it works. It requires adding some extra settings customization and is essential to enable CUDA for the GPU to give its full acceleration.
ZenML Scarf
#### Using Persistent Resources for Faster Development When developing ML pipelines that use Vertex AI, the startup time for each `CustomJob` can be significant since Vertex needs to provision new compute resources for each run. To speed up development iterations, you can use Vertex AI's [Persistent Resources](https://cloud.google.com/vertex-ai/docs/training/persistent-resource-overview) feature, which keeps compute resources warm between runs. To use persistent resources with the Vertex step operator, you need to do the following: **Step 1**: You create a persistent resource using the GCP Cloud UI, or by [following instructions in the GCP docs](https://cloud.google.com/vertex-ai/docs/training/persistent-resource-create). **Step 2**: Make sure your step operator is properly configured. For example, you need to have a service account specified in your step operator configuration. This service account needs to have permissions to access the persistent resource. ```bash # You can also use `zenml step-operator update` zenml step-operator register -f vertex --service_account= ``` {% hint style="warning" %} Please note that by default, ZenML step operators are registered with `boot_disk_type=pd-ssd` and persistent storages usually come with `boot_disk_type=pd-standard`. To avoid confusion, execute the following: ```shell zenml step-operator update --boot_disk_type=pd-standard ``` Or ensure that your worker pool configuration in your persistent storage matches that of your ZenML step operator. {% endhint %} **Step 3**: Configure your code to use the persistent resource: ```python from zenml.integrations.gcp.flavors.vertex_step_operator_flavor import VertexStepOperatorSettings @step(step_operator=True, settings={"step_operator": VertexStepOperatorSettings( persistent_resource_id="my-persistent-resource", # specify your persistent resource ID machine_type="n1-standard-4", accelerator_type="NVIDIA_TESLA_T4", accelerator_count=1, )}) def trainer(...) -> ...: """Train a model.""" # This step will use the persistent resource and start faster ``` Using a persistent resource is particularly useful when you're developing locally and want to iterate quickly on steps that need cloud resources. The startup time of the job can be extremely quick. {% hint style="warning" %} Remember that persistent resources continue to incur costs as long as they're running, even when idle. Make sure to monitor your usage and configure appropriate idle timeout periods. {% endhint %} --- ### Book/Getting Started/Core Concepts (docs/book/getting-started/core-concepts.md) --- description: Discovering the core concepts behind ZenML. icon: lightbulb --- # Core concepts **ZenML** is a unified, extensible, open-source MLOps framework for creating portable, production-ready **MLOps pipelines**. It's built for data scientists, ML Engineers, and MLOps Developers to collaborate as they develop to production. By extending the battle-tested principles you rely on for classical ML to the new world of AI agents, ZenML serves as one platform to develop, evaluate, and deploy your entire AI portfolio - from decision trees to complex multi-agent systems. In order to achieve this goal, ZenML introduces various concepts for different aspects of ML workflows and AI agent development, and we can categorize these concepts under three different threads:
1. DevelopmentAs a developer, how do I design my machine learning workflows?#1-developmentdevelopment.png
2. ExecutionWhile executing, how do my workflows utilize the large landscape of MLOps tooling/infrastructure?#2-executionexecution.png
3. ManagementHow do I establish and maintain a production-grade and efficient solution?#3-managementmanagement.png
{% embed url="https://www.youtube.com/embed/iCB4KNjl5vs" %} If you prefer visual learning, this short video demonstrates the key concepts covered below. {% endembed %} ## 1. Development First, let's look at the main concepts that play a role during the development stage of ML workflows and AI agent pipelines with ZenML. #### Step Steps are functions annotated with the `@step` decorator. The easiest one could look like this. ```python from zenml import step @step def step_1() -> str: """Returns a string.""" return "world" ``` These functions can also have inputs and outputs. For ZenML to work properly, these should preferably be typed. ```python from zenml import step @step(enable_cache=False) def step_2(input_one: str, input_two: str) -> str: """Combines the two strings passed in.""" combined_str = f"{input_one} {input_two}" return combined_str @step def evaluate_agent_response(prompt: str, test_query: str) -> dict: """Evaluates an AI agent's response to a test query.""" response = call_llm_agent(prompt, test_query) return {"query": test_query, "response": response, "quality_score": 0.95} ``` #### Pipelines At its core, ZenML follows a pipeline-based workflow for your projects. A **pipeline** consists of a series of **steps**, organized in any order that makes sense for your use case. As seen in the image, a step might use the outputs from a previous step and thus must wait until the previous step is completed before starting. This is something you can keep in mind when organizing your steps. Pipelines and steps are defined in code using Python _decorators_ or _classes_. This is where the core business logic and value of your work live, and you will spend most of your time defining these two things. Even though pipelines are simple Python functions, you are only allowed to call steps within this function. The inputs for steps called within a pipeline can either be the outputs of previous steps or alternatively, you can pass in values directly or map them onto pipeline parameters (as long as they're JSON-serializable). Similarly, you can return values from a pipeline that are step outputs as long as they are JSON-serializable. ```python from zenml import pipeline @pipeline def my_pipeline(): output_step_one = step_1() step_2(input_one="hello", input_two=output_step_one) @pipeline def agent_evaluation_pipeline(query: str = "What is machine learning?") -> str: """An AI agent evaluation pipeline.""" prompt = "You are a helpful assistant. Please answer: {query}" evaluation_result = evaluate_agent_response(prompt, query) return evaluation_result ``` {% hint style="info" %} If you use an AI coding agent, ZenML Agent Skills can help you scope a pipeline architecture (`zenml-scoping`) and then implement steps and pipelines correctly (`zenml-pipeline-authoring`). See [LLM tooling](../reference/llms-txt.md). {% endhint %} Executing the Pipeline is as easy as calling the function that you decorated with the `@pipeline` decorator. ```python if __name__ == "__main__": my_pipeline() agent_evaluation_pipeline(query="What is an LLM?") ``` #### Artifacts Artifacts represent the data that goes through your steps as inputs and outputs, and they are automatically tracked and stored by ZenML in the artifact store. They are produced by and circulated among steps whenever your step returns an object or a value. This means the data is not passed between steps in memory. Rather, when the execution of a step is completed, they are written to storage, and when a new step gets executed, they are loaded from storage. Artifacts can be traditional ML data (datasets, models, metrics) or AI agent components (prompt templates, agent configurations, evaluation results). The same artifact system seamlessly handles both use cases. The serialization and deserialization logic of artifacts is defined by [Materializers](../how-to/artifacts/materializers.md). #### Models Models are used to represent the outputs of a training process along with all metadata associated with that output. In other words: models in ZenML are more broadly defined as the weights as well as any associated information. This includes traditional ML models (scikit-learn, PyTorch, etc.) and AI agent configurations (prompt templates, tool definitions, multi-agent system architectures). Models are first-class citizens in ZenML and as such viewing and using them is unified and centralized in the ZenML API, client, as well as on the [ZenML Pro](https://zenml.io/pro) dashboard. #### Materializers Materializers define how artifacts live in between steps. More precisely, they define how data of a particular type can be serialized/deserialized, so that the steps are able to load the input data and store the output data. All materializers use the base abstraction called the `BaseMaterializer` class. While ZenML comes built-in with various implementations of materializers for different datatypes, if you are using a library or a tool that doesn't work with our built-in options, you can write [your own custom materializer](../how-to/artifacts/materializers.md) to ensure that your data can be passed from step to step. #### Parameters & Settings When we think about steps as functions, we know they receive input in the form of artifacts. We also know that they produce output (in the form of artifacts, stored in the artifact store). But steps also take parameters. The parameters that you pass into the steps are also (helpfully!) stored by ZenML. This helps freeze the iterations of your experimentation workflow in time, so you can return to them exactly as you run them. On top of the parameters that you provide for your steps, you can also use different `Setting`s to configure runtime configurations for your infrastructure and pipelines. #### Model and model versions ZenML exposes the concept of a `Model`, which consists of multiple different model versions. A model version represents a unified view of the ML models that are created, tracked, and managed as part of a ZenML project. Model versions link all other entities to a centralized view. ## 2. Execution Once you have implemented your workflow by using the concepts described above, you can focus your attention on the execution of the pipeline run. #### Stacks & Components When you want to execute a pipeline run with ZenML, **Stacks** come into play. A **Stack** is a collection of **stack components**, where each component represents the respective configuration regarding a particular function in your MLOps pipeline, such as pipeline orchestration or deployment systems, artifact repositories and container registries. Pipelines can be executed in two ways: in **batch mode** (traditional execution through an orchestrator) or in **online mode** (long-running HTTP servers that can be invoked via REST API calls). Deploying pipelines for online mode execution allows you to serve your ML workflows as real-time endpoints, making them accessible for live inference and interactive use cases. For instance, if you take a close look at the default local stack of ZenML, you will see two components that are **required** in every stack in ZenML, namely an _orchestrator_ and an _artifact store_. Additional components like _deployers_ can be added to enable specific functionality such as deploying pipelines as HTTP endpoints. {% hint style="info" %} Keep in mind that each one of these components is built on top of base abstractions and is completely extensible. {% endhint %} #### Orchestrator An **Orchestrator** is a workhorse that coordinates all the steps to run in a pipeline in batch mode. Since pipelines can be set up with complex combinations of steps with various asynchronous dependencies between them, the orchestrator acts as the component that decides what steps to run and when to run them. ZenML comes with a default _local orchestrator_ designed to run on your local machine. This is useful, especially during the exploration phase of your project. You don't have to rent a cloud instance just to try out basic things. #### Artifact Store An **Artifact Store** is a component that houses all data that passes through the pipeline as inputs and outputs. Each artifact that gets stored in the artifact store is tracked and versioned and this allows for extremely useful features like data caching, which speeds up your workflows. Similar to the orchestrator, ZenML comes with a default _local artifact store_ designed to run on your local machine. This is useful, especially during the exploration phase of your project. You don't have to set up a cloud storage system to try out basic things. #### Deployer A **Deployer** is a stack component that manages the deployment of pipelines as long-running HTTP servers useful for online mode execution. Unlike orchestrators that execute pipelines in batch mode, deployers can create and manage persistent services that wrap your pipeline in a web application, usually containerized, allowing it to be invoked through HTTP requests. ZenML comes with a _Docker deployer_ that can run deployments on your local machine as Docker containers, making it easy to test and develop real-time pipeline endpoints before moving to production infrastructure. #### Flavor ZenML provides a dedicated base abstraction for each stack component type. These abstractions are used to develop solutions, called **Flavors**, tailored to specific use cases/tools. With ZenML installed, you get access to a variety of built-in and integrated Flavors for each component type, but users can also leverage the base abstractions to create their own custom flavors. #### Stack Switching When it comes to production-grade solutions, it is rarely enough to just run your workflow locally without including any cloud infrastructure. Thanks to the separation between the pipeline code and the stack in ZenML, you can easily switch your stack independently from your code. For instance, all it would take you to switch from an experimental local stack running on your machine to a remote stack that employs a full-fledged cloud infrastructure is a single CLI command. #### Pipeline Snapshot A **Pipeline Snapshot** is an immutable snapshot of your pipeline that includes the pipeline DAG, code, configuration, and container images. Snapshots can be run from the server or dashboard, and can also be [deployed](#deployment). #### Pipeline Run A **Pipeline Run** is a record of a pipeline execution. When you run a pipeline using an orchestrator, a pipeline run is created tracking information about the execution such as the status, the artifacts and metadata produced by the pipeline and all its steps. When a pipeline is deployed for online mode execution, a pipeline run is similarly created for every HTTP request made to it. #### Deployment A **Deployment** is a running instance of a pipeline deployed as an HTTP endpoint. When you deploy a pipeline using a deployer, it becomes a long-running service that can be invoked through REST API calls. Each HTTP request to a deployment triggers a new pipeline run, creating the same artifacts and metadata tracking as traditional batch pipeline executions. This enables real-time inference, interactive ML workflows, and seamless integration with web applications and external services. ## 3. Management In order to benefit from the aforementioned core concepts to their fullest extent, it is essential to deploy and manage a production-grade environment that interacts with your ZenML installation. #### ZenML Server To use _stack components_ that are running remotely on a cloud infrastructure, you need to deploy a [**ZenML Server**](https://docs.zenml.io/user-guides/production-guide/deploying-zenml) so it can communicate with these stack components and run your pipelines. The server is also responsible for managing ZenML business entities like pipelines, steps, models, etc. #### Server Deployment In order to benefit from the advantages of using a deployed ZenML server, you can either choose to use the [**ZenML Pro SaaS offering**](https://docs.zenml.io/pro)**,** which provides a control plane for you to create managed instances of ZenML servers, or [deploy it in your self-hosted environment](deploying-zenml/). #### Metadata Tracking On top of the communication with the stack components, the **ZenML Server** also keeps track of all the bits of metadata around a pipeline run. With a ZenML server, you are able to access all of your previous experiments with the associated details. This is extremely helpful in troubleshooting. #### Secrets The **ZenML Server** also acts as a [centralized secrets store](deploying-zenml/secret-management.md) that safely and securely stores sensitive data, such as credentials used to access the services that are part of your stack. It can be configured to use a variety of different backends for this purpose, such as the AWS Secrets Manager, GCP Secret Manager, Azure Key Vault, and Hashicorp Vault. Secrets are sensitive data that you don't want to store in your code or configure alongside your stacks and pipelines. ZenML includes a [centralized secrets store](deploying-zenml/secret-management.md) that you can use to store and access your secrets securely. #### Collaboration Collaboration is a crucial aspect of any MLOps team as they often need to bring together individuals with diverse skills and expertise to create a cohesive and effective workflow for machine learning projects and AI agent development. A successful MLOps team requires seamless collaboration between data scientists, engineers, and DevOps professionals to develop, train, deploy, and maintain both traditional ML models and AI agent systems. With a deployed **ZenML Server**, users have the ability to create their own teams and project structures. They can easily share pipelines, runs, stacks, and other resources, streamlining the workflow and promoting teamwork across the entire AI development lifecycle. #### Dashboard The **ZenML Dashboard** also communicates with **the ZenML Server** to visualize your _pipelines_, _stacks_, and _stack components_. The dashboard serves as a visual interface to showcase collaboration with ZenML. You can invite _users_ and share your stacks with them. When you start working with ZenML, you'll start with a local ZenML setup, and when you want to transition, you will need to [deploy ZenML](deploying-zenml/). Don't worry though, there is a one-click way to do it, which we'll learn about later. #### VS Code Extension ZenML also provides a [VS Code extension](https://marketplace.visualstudio.com/items?itemName=ZenML.zenml-vscode) that allows you to interact with your ZenML stacks, runs, and server directly from your VS Code editor. If you're working on code in your editor, you can easily switch and inspect the stacks you're using, delete and inspect pipelines as well as even switch stacks.
ZenML Scarf
--- ### Book/Getting Started/Hello World (docs/book/getting-started/hello-world.md) --- description: >- Your first ML pipeline with ZenML - from local development to cloud deployment in minutes. icon: hand-wave --- # Hello World This guide will help you build and deploy your first ZenML pipeline, starting locally and then transitioning to the cloud without changing your code. The same principles you'll learn here apply whether you're building classical ML models or AI agents. {% stepper %} {% step %} #### Install ZenML Start by installing ZenML in a fresh Python environment: ```bash pip install 'zenml[server]' zenml login ``` This gives you access to both the ZenML Python SDK and CLI tools. It also surfaces the ZenML dashboard + connects it to your local client. {% endstep %} {% step %} #### Write your first pipeline Create a simple `run.py` file with a basic workflow:
from zenml import step, pipeline


@step
def basic_step() -> str:
    """A simple step that returns a greeting message."""
    return "Hello World!"


@pipeline
def basic_pipeline() -> str:
    """A simple pipeline with just one step."""
    greeting = basic_step()
    return greeting


if __name__ == "__main__":
    basic_pipeline()
Run this pipeline in batch mode locally: ```bash python run.py ``` You will see ZenML automatically tracks the execution and stores artifacts. View these on the CLI or on the dashboard. {% endstep %} {% step %} #### Create a Pipeline Snapshot (Optional but Recommended) Before deploying, you can create a **snapshot** - an immutable, reproducible version of your pipeline including code, configuration, and container images: ```bash # Create a snapshot of your pipeline zenml pipeline snapshot create run.basic_pipeline --name my_snapshot ``` Snapshots are powerful because they: - **Freeze your pipeline state** - Ensure the exact same pipeline always runs - **Enable parameterization** - Run the same snapshot with different inputs - **Support team collaboration** - Share ready-to-use pipeline configurations - **Integrate with automation** - Trigger from dashboards, APIs, or CI/CD systems [Learn more about Snapshots](../how-to/snapshots/snapshots.md) {% endstep %} {% step %} #### Deploy your pipeline as a real-time service ZenML can deploy your pipeline (or snapshot) as a persistent HTTP service for real-time inference: ```bash # Deploy your pipeline directly zenml pipeline deploy run.basic_pipeline --name my_deployment # OR deploy a snapshot (if you created one above) zenml pipeline snapshot deploy my_snapshot --deployment my_deployment ``` Your pipeline now runs as a production-ready service! This is perfect for serving predictions to web apps, powering AI agents, or handling real-time requests. **Key insight**: When you deploy a pipeline directly with `zenml pipeline deploy`, ZenML automatically creates an implicit snapshot behind the scenes, ensuring reproducibility. [Learn more about Pipeline Deployments](../how-to/deployment/deployment.md) {% endstep %} {% step %} #### Set up a ZenML Server (For Remote Infrastructure) To use remote infrastructure (cloud deployers, orchestrators, artifact stores), you need to deploy a ZenML server to manage your pipelines centrally. You can use [ZenML Pro](https://zenml.io/pro) (managed, 14-day free trial) or [deploy it yourself](../getting-started/deploying-zenml/README.md) (self-hosted, open-source). Connect your local environment: ```bash zenml login zenml project set ``` Once connected, you'll have a centralized dashboard to manage infrastructure, collaborate with team members, and schedule pipeline runs. {% endstep %} {% step %} #### Create your first remote stack (Optional) A "stack" in ZenML represents the infrastructure where your pipelines run. You can now scale from local development to cloud infrastructure without changing any code.
ZenML Stack Deployment Options

Stack deployment options

Remote stacks can include: - **[Remote Deployers](https://docs.zenml.io/stacks/stack-components/deployers)** ([AWS App Runner](https://docs.zenml.io/stacks/stack-components/deployers/aws-app-runner), [GCP Cloud Run](https://docs.zenml.io/stacks/stack-components/deployers/gcp-cloud-run), [Azure Container Instances](https://docs.zenml.io/stacks/stack-components/container-registries/azure)) - for deploying your pipelines as scalable HTTP services on the cloud - **[Remote Orchestrators](https://docs.zenml.io/stacks/stack-components/orchestrators)** ([Kubernetes](https://docs.zenml.io/stacks/stack-components/orchestrators/kubernetes), [GCP Vertex AI](https://docs.zenml.io/stacks/stack-components/orchestrators/vertex), [AWS SageMaker](https://docs.zenml.io/stacks/stack-components/orchestrators/sagemaker)) - for running batch pipelines at scale - **[Remote Artifact Stores](https://docs.zenml.io/stacks/stack-components/artifact-stores)** ([S3](https://docs.zenml.io/stacks/stack-components/artifact-stores/s3), [GCS](https://docs.zenml.io/stacks/stack-components/artifact-stores/gcp), [Azure Blob](https://docs.zenml.io/stacks/stack-components/artifact-stores/azure)) - for storing and versioning pipeline artifacts The fastest way to create a cloud stack is through the **Infrastructure-as-Code** option, which uses Terraform to deploy cloud resources and register them as a ZenML stack. You'll need: * [Terraform](https://developer.hashicorp.com/terraform/install) version 1.9+ installed locally * Authentication configured for your preferred cloud provider (AWS, GCP, or Azure) * Appropriate permissions to create resources in your cloud account ```bash # Create a remote stack using the deployment wizard zenml stack register \ --deployer \ --orchestrator \ --artifact-store ``` The wizard will guide you through each step. {% endstep %} {% step %} #### Deploy and run on remote infrastructure Once you have a remote stack, you can: 1. **Deploy your service to the cloud** - Your deployment runs on managed cloud infrastructure: ```bash zenml stack set zenml pipeline deploy run.basic_pipeline --name my_production_deployment ``` 2. **Run batch pipelines at scale** - Use the same code with a cloud orchestrator: ```bash zenml stack set python run.py # Automatically runs on cloud infrastructure ``` ZenML handles packaging code, building containers, orchestrating execution, and tracking artifacts automatically across all cloud providers.
Pipeline Run in ZenML Dashboard

Your pipeline in the ZenML Pro Dashboard

{% endstep %} {% step %} #### What's next? Congratulations! You've just experienced the core value proposition of ZenML: * **Write Once, Run Anywhere**: The same code runs locally during development and in the cloud for production * **Unified Framework**: Use the same MLOps principles for both classical ML models and AI agents * **Separation of Concerns**: Infrastructure configuration and ML code are completely decoupled, enabling independent evolution of each * **Full Tracking**: Every run, artifact, and model is automatically versioned and tracked - whether it's a scikit-learn model or a multi-agent system To continue your ZenML journey, explore these key topics: **For All AI Workloads:** * **AI-assisted pipeline building**: Use ZenML Agent Skills like `zenml-scoping` (architecture planning) and `zenml-pipeline-authoring` (implementation guidance) — see [LLM tooling](../reference/llms-txt.md) * **Pipeline Development**: Discover advanced features like [scheduling](../how-to/steps-pipelines/advanced_features.md#scheduling) and [caching](../how-to/steps-pipelines/advanced_features.md#caching) * **Artifact Management**: Learn how ZenML [stores, versions, and tracks your data](../how-to/artifacts/artifacts.md) automatically * **Organization**: Use [tags](../how-to/tags/tags.md) and [metadata](../how-to/metadata/metadata.md) to keep your AI projects structured **For LLMs and AI Agents:** * **LLMOps Guide**: Write your [first AI pipeline](your-first-ai-pipeline.md) for agent development patterns * **Deploying Agents**: To see an example of a deployed document extraction agent, see the [deploying agents](https://github.com/zenml-io/zenml/tree/main/examples/deploying_agent) example * **Agent Outer Loop**: See the [Agent Outer Loop](https://github.com/zenml-io/zenml/tree/main/examples/agent_outer_loop) example to learn about training classifiers and improving agents through feedback loops * **Agent Evaluation**: Learn to [systematically evaluate](https://github.com/zenml-io/zenml/tree/main/examples/agent_comparison) and compare different agent architectures * **Prompt Management**: Version and track prompts, tools, and agent configurations as [artifacts](../how-to/artifacts/artifacts.md) **Infrastructure & Deployment:** * **Containerization**: Understand how ZenML [handles containerization](../how-to/containerization/containerization.md) for reproducible execution * **Stacks & Infrastructure**: Explore the concepts behind [stacks](../how-to/stack-components/stack_components.md) and [service connectors](../how-to/stack-components/service_connectors.md) for authentication * **Secrets Management**: Learn how to [handle sensitive information](../how-to/secrets/secrets.md) securely * **Snapshots**: Create [reusable snapshots](../how-to/snapshots/snapshots.md) for standardized workflows {% endstep %} {% endstepper %}
ZenML Scarf
--- ### Book/Getting Started/Installation (docs/book/getting-started/installation.md) --- description: Installing ZenML and getting started. icon: cauldron --- # Installation {% stepper %} {% step %} #### Install ZenML ZenML currently supports **Python 3.10, 3.11, 3.12, 3.13, and 3.14**. Please make sure that you are using a supported Python version. {% tabs %} {% tab title="Base package" %} **ZenML** is a Python package that can be installed using `pip` or other Python package managers: ```shell pip install zenml ``` {% hint style="warning" %} Installing the base package only allows you to connect to a [deployed ZenML server](./deploying-zenml/). If you want to use ZenML purely locally, install it with the `local` extra: ```shell pip install 'zenml[local]' ``` {% endhint %} {% endtab %} {% tab title="Local Dashboard" %} If you want to use the [ZenML dashboard](https://github.com/zenml-io/zenml-dashboard) locally, you need to install ZenML with the `server` extra: ```shell pip install 'zenml[server]' ``` {% hint style="warning" %} If you want to run a local server while running on a Mac with Apple Silicon (M1, M2, M3, M4), you should set the following environment variable: ```bash export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES ``` You can read more about this [here](http://sealiesoftware.com/blog/archive/2017/6/5/Objective-C_and_fork_in_macOS_1013.html). {% endhint %} {% endtab %} {% tab title="Jupyter Notebooks" %} If you write your ZenML pipelines ins Jupyter notebooks, we recommend installing ZenML with the `jupyter` extra which includes improved CLI output and logs: ```shell pip install 'zenml[jupyter]' ``` {% endtab %} {% endtabs %} {% endstep %} {% step %} #### Verifying Installations Once the installation is completed, you can check whether the installation was successful either through Bash or Python: {% tabs %} {% tab title="Bash" %} ```bash zenml version ``` {% endtab %} {% tab title="Python" %} ```python import zenml print(zenml.__version__) ``` {% endtab %} {% endtabs %} If you would like to learn more about the current release, please visit our [PyPi package page.](https://pypi.org/project/zenml) {% endstep %} {% endstepper %} ## Running with Docker `zenml` is also available as a Docker image hosted publicly on [DockerHub](https://hub.docker.com/r/zenmldocker/zenml). Use the following command to get started in a bash environment with `zenml` available: ```shell docker run -it zenmldocker/zenml /bin/bash ``` If you would like to run the ZenML server with Docker: ```shell docker run -it -d -p 8080:8080 zenmldocker/zenml-server ``` ## Starting the local server By default, ZenML runs without a server connected to a local database on your machine. If you want to access the dashboard locally, you need to start a local server: ```shell # Make sure to have the `server` extra installed pip install "zenml[server]" zenml login --local # opens the dashboard locally ``` However, advanced ZenML features are dependent on a centrally deployed ZenML server accessible to other MLOps stack components. You can read more about it [here](deploying-zenml/). For the deployment of ZenML, you have the option to either [self-host](deploying-zenml/) it or register for a free [ZenML Pro](https://zenml.io/pro?utm_source=docs\&utm_medium=referral_link\&utm_campaign=cloud_promotion\&utm_content=signup_link) account.
ZenML Scarf
--- ### Book/Getting Started/System Architectures (docs/book/getting-started/system-architectures.md) --- description: Different variations of the ZenML architecture depending on your needs. icon: building-columns --- # System Architecture This guide walks through the various ways that ZenML can be deployed, from self-hosted OSS to\ SaaS to self-hosted ZenML Pro! ## ZenML OSS (Self-hosted) {% hint style="info" %} This page is intended as a high-level overview. To learn more about how to deploy ZenML OSS, read [this guide](deploying-zenml/). {% endhint %} A ZenML OSS deployment consists of the following moving pieces: * **ZenML OSS Server**: This is a FastAPI app that manages metadata of pipelines, artifacts, stacks, etc. Note: In ZenML Pro, the notion of a ZenML server is replaced with what is known as a "Workspace". For all intents and purposes, consider a ZenML Workspace to be a ZenML OSS server that comes with more functionality. * **OSS Metadata Store**: This is where all ZenML workspace metadata is stored, including ML metadata such as tracking and versioning information about pipelines and models. * **OSS Dashboard**: This is a ReactJS app that shows pipelines, runs, etc. * **Secrets Store**: All secrets and credentials required to access customer infrastructure services are stored in a secure secrets store. The ZenML Pro API has access to these secrets and uses them to access customer infrastructure services on behalf of the ZenML Pro. The secrets store can be hosted either by the ZenML Pro or by the customer. ZenML OSS is free with Apache 2.0 license. Learn how to deploy it [here](deploying-zenml/). {% hint style="info" %} To learn more about the core concepts for ZenML OSS, go [here](core-concepts.md). {% endhint %} ## ZenML Pro (SaaS or Self-hosted) {% hint style="info" %} If you're interested in assessing ZenML Pro SaaS, you can create a [free account](https://zenml.io/pro?utm_source=docs\&utm_medium=referral_link\&utm_campaign=cloud_promotion\&utm_content=signup_link). If you would like to self-host ZenML Pro, please [book a demo](https://zenml.io/book-a-demo). {% endhint %} The above deployment can be augmented with the ZenML Pro components: * **ZenML Pro Control Plane**: This is the central controlling entity of all workspaces. * **Pro Dashboard**: This is a dashboard that builds on top of the OSS dashboard and adds further functionality. * **Pro Metadata Store**: This is a PostgreSQL database where all ZenML Pro-related metadata is stored, such as roles, permissions, teams, and workspace management-related data. * **Pro Add-ons**: These are Python modules injected into the OSS Server for enhanced functionality. * **Identity Provider**: ZenML Pro offers flexible authentication options. In cloud-hosted deployments, it integrates with [Auth0](https://auth0.com/), allowing users to log in via social media or corporate credentials. For self-hosted deployments, customers can configure their own identity management solution, with ZenML Pro supporting custom OIDC provider integration. This allows organizations to leverage their existing identity infrastructure for authentication and authorization, whether using the cloud service or deploying on-premises. ZenML Pro offers many additional features to increase your team's productivity. No matter your specific needs, the hosting options for ZenML Pro range from easy SaaS integration to completely air-gapped deployments on your own infrastructure. You might have noticed that this architecture builds on top of the ZenML OSS system architecture. Therefore, if you already have ZenML OSS deployed, it is easy to enroll it as part of a ZenML Pro deployment! The above components interact with other MLOps stack components, secrets, and data in the following scenarios described below. {% hint style="info" %} To learn more about the core concepts for ZenML Pro, go [here](https://docs.zenml.io/pro/core-concepts) {% endhint %} ### ZenML Pro SaaS Architecture For the ZenML Pro SaaS deployment case, all ZenML services are hosted on infrastructure hosted by the ZenML Team. Customer secrets and credentials required to access customer infrastructure are stored and managed by the ZenML Pro Control Plane. On the ZenML Pro infrastructure, only ML _metadata_ (e.g. pipeline and model tracking and versioning information) is stored. All the actual ML data artifacts (e.g. data produced or consumed by pipeline steps, logs and visualizations, models) are stored on the customer cloud. This can be set up quite easily by configuring an [artifact store](https://docs.zenml.io/stacks/artifact-stores) with your MLOps stack. Your workspace only needs permissions to read from this data to display artifacts on the ZenML dashboard. The workspace also needs direct access to parts of the customer infrastructure services to support dashboard control plane features such as CI/CD, triggering and running pipelines, triggering model deployments and so on. The advantage of this setup is that it is a fully-managed service, and is very easy to get started with. However, for some clients, even some metadata can be sensitive; these clients should refer to the other architecture diagram. ### ZenML Pro Hybrid SaaS The partially self-hosted architecture offers a balanced approach that combines the benefits of cloud-hosted control with on-premises data sovereignty. In this configuration, while the ZenML Pro control plane remains hosted by ZenML (handling user management, authentication, RBAC and global workspace coordination), all other components - including services, data, and secrets - are deployed within your own cloud infrastructure. This hybrid model is particularly well-suited for organizations with: * A centralized MLOps or Platform team responsible for standardizing ML practices * Multiple business units or teams that require autonomy over their data and infrastructure * Strict security requirements where workspaces must operate behind VPN/corporate firewalls * Compliance requirements that mandate keeping sensitive data and ML artifact metadata within company infrastructure * Need for customization of workspace configurations while maintaining centralized governance The key advantages of this setup include: * Simplified user management through the ZenML-hosted control plane * Complete data sovereignty - sensitive data and ML artifacts remain within your infrastructure * Secure networking - workspaces communicate through outbound-only connections via VPN/private networks * Ability to customize and configure workspaces according to specific team needs * Reduced operational overhead compared to fully self-hosted deployments * Reduced maintenance burden - all control plane updates and maintenance are handled by ZenML This architecture strikes a balance between convenience and control, making it a popular choice for enterprises looking to standardize their MLOps practices while maintaining sovereignty. ### ZenML Pro Self-Hosted Architecture In the case of self-hosting ZenML Pro, all services, data, and secrets are deployed on the customer\ cloud. This is meant for customers who require completely air-gapped deployments, for the tightest security standards. [Reach out to us](mailto:cloud@zenml.io) if you want to set this up.
Detailed Architecture Diagram for self-hosted ZenML Pro deployment ZenML Pro self-hosted deployment details
Are you interested in ZenML Pro? [Sign up](https://zenml.io/pro/?utm_source=docs\&utm_medium=referral_link\&utm_campaign=cloud_promotion\&utm_content=signup_link) and get access with a free trial now!
ZenML Scarf
## Data Implications Across Deployment Scenarios | Deployment Scenario | Data Location | Data Movement | Data Access | Data Isolation | |---------------------|---------------|---------------|-------------|----------------| | **ZenML OSS (Self-hosted)** | All data remains on customer infrastructure: both ML metadata in OSS Metadata Store and actual ML data artifacts in customer Artifact Store | Data stays within customer boundary; moves between pipeline steps via the Orchestrator | Accessible only through customer infrastructure; no ZenML-managed components have access | Complete data isolation from ZenML-managed services | | **ZenML Pro SaaS** | ML metadata in ZenML-hosted DB; Actual ML data artifacts in customer Artifact Store; Secrets in ZenML-managed Secret Store | Metadata flows to ZenML Pro Control Plane; ML data artifacts stay on customer infrastructure; ZenML services access customer infrastructure using stored credentials | ZenML Pro has access to the customer secrets that are explicitly stored; Workspace optionally needs read access to artifact store for dashboard display; No actual ML data moves to ZenML infrastructure unless explicitly shared | Only metadata and credentials are stored on ZenML infrastructure; actual ML data remains isolated on customer infrastructure | | **ZenML Pro Hybrid SaaS** | Control Plane on ZenML infrastructure; Workspace, DB, Secret Store, Orchestrator, and Artifact Store on customer infrastructure | Only authentication/authorization data flows to ZenML; All ML data and metadata stays on customer infrastructure | ZenML Control Plane has limited access to user management data; No access to actual ML data or metadata; Customer maintains all data access controls | Strong data isolation with only authentication events crossing boundary. Allows securing access via VPN/private networks. | | **ZenML Pro Self-Hosted** | All components run on customer infrastructure | All data movement contained within customer infrastructure boundary | No external access to any data; completely air-gapped operation possible | Complete data isolation; ZenML has no access to any customer data |\n\n## Key Data Security Insights\n\n1. **Artifact Storage:** In all scenarios, the actual ML data artifacts (datasets, models, etc.) remain on customer infrastructure in the customer-controlled Artifact Store.\n\n2. **Metadata Storage:** The metadata (pipeline configurations, run information, metrics) is either stored in ZenML-managed databases (SaaS options) or customer-managed databases (self-hosted options).\n\n3. **Secret Management:** Credentials can be managed by ZenML (standard SaaS) or by the customer (hybrid SaaS or self-hosted), providing flexibility based on security requirements.\n\n4. **Data Access Patterns:**\n - The ZenML Workspace frontend requires read access to the Artifact Store to display artifacts in the dashboard\n - The Orchestrator requires read/write access to the Artifact Store to execute pipelines\n - Control Plane DB contains only users, roles, and workspace configuration information\n\n5. **Data Isolation:** Even in the fully-managed SaaS scenario, the actual ML data never leaves customer infrastructure unless explicitly configured to do so. Only metadata about runs, pipelines, etc. is stored in ZenML-managed systems.\n\nThis design ensures that even when using ZenML-managed services, customers maintain control over their sensitive ML data while benefiting from ZenML's orchestration and management capabilities. --- ### Book/Getting Started/Your First Ai Pipeline (docs/book/getting-started/your-first-ai-pipeline.md) --- description: Choose your path and build your first pipeline with ZenML in minutes. icon: rocket --- ## Your First AI Pipeline ZenML pipelines work the same for **classical ML**, **AI agents**, and **hybrid approaches**. Choose your path below to get started: {% hint style="info" %} Why ZenML pipelines? - **Reproducible & portable**: Run the same code locally or on the cloud by switching stacks. - **One approach for models and agents**: Steps, pipelines, and artifacts work for sklearn, classical ML, and LLMs alike. - **Observe by default**: Lineage and step metadata (e.g., latency, tokens, metrics) are tracked and visible in the dashboard. {% endhint %} {% hint style="info" %} **Plan and build pipelines with AI coding agents:** If you use Claude Code, Codex, Copilot, Cursor, or similar tools, ZenML provides Agent Skills that can scope your idea into a realistic multi-pipeline plan (`zenml-scoping`) and help you implement steps and pipelines (`zenml-pipeline-authoring`). See [LLM tooling](../reference/llms-txt.md) for installation instructions. {% endhint %} --- ## What do you want to build? Choose one of the paths below. The same ZenML pipeline pattern works for all of them—the difference is in your steps and how you orchestrate them. - **[Build AI Agents](#path-1-build-ai-agents)** - Use LLMs and tools to create autonomous agents - **[Build Classical ML Pipelines](#path-2-build-classical-ml-pipelines)** - Train and serve ML models with scikit-learn, TensorFlow, or PyTorch - **[Build Hybrid Systems](#path-3-build-hybrid-systems)** - Combine ML classifiers with agents --- ## Path 1: Build AI Agents Use large language models, prompts, and tools to build intelligent autonomous agents that can reason, take action, and interact with your systems. {% hint style="info" %} **Two ways to run agents.** This path runs your agent *inside a pipeline*, which is ideal for batch workloads and evaluation. If the agent itself runs in production, use [Kitaru](https://docs.zenml.io/kitaru): it records every model and tool call as a durable checkpoint, so you can replay a real run faithfully with one thing changed (a cheaper model, a different prompt), diff the two, and keep the version that wins. The two compose: a Kitaru flow is a dynamic ZenML pipeline, so agents and the pipelines that evaluate them share the same stacks and UI. {% endhint %} ### Architecture example ```mermaid --- config: layout: elk theme: mc --- flowchart TB U["CLI / curl / web UI"] --> D["ZenML Deployment
(doc_analyzer)"] subgraph PIPE["Pipeline: doc_analyzer"] I["ingest_document_step"] A["analyze_document_step"] R["render_analysis_report_step"] I --> A --> R end D --> PIPE subgraph STACK["Stack"] OR[("Deployer")] AR[("Artifact Store")] end PIPE --> AR D --> OR ```
View Quick Start & Examples ### Quick start ```bash git clone --depth 1 https://github.com/zenml-io/zenml.git cd zenml/examples/deploying_agent uv pip install -r requirements.txt ``` Then follow the guide in [`examples/deploying_agent`](https://github.com/zenml-io/zenml/tree/main/examples/deploying_agent): 1. **Define your steps**: Use LLM APIs (OpenAI, Claude, etc.) to build reasoning steps 2. **Deploy as HTTP service**: Turn your agent into a managed endpoint 3. **Invoke and monitor**: Use the CLI, curl, or the embedded web UI to interact with your agent 4. **Inspect traces**: View agent reasoning, tool calls, and metadata in the ZenML dashboard ### Example output - Automated document analysis (see `deploying_agent`) - Multi-turn chatbots with context - Autonomous workflows with tool integrations - Agentic RAG systems with retrieval steps ### Related examples - **[agent_outer_loop](https://github.com/zenml-io/zenml/tree/main/examples/agent_outer_loop)**: Combine ML classifiers with agents for hybrid intelligent systems - **[agentic_hitl_pipeline](https://github.com/zenml-io/zenml/tree/main/examples/agentic_hitl_pipeline)**: Add dynamic fan-out and human approval to an agent workflow - **[agent_comparison](https://github.com/zenml-io/zenml/tree/main/examples/agent_comparison)**: Compare different agent architectures and LLM providers - **[agent_framework_integrations](https://github.com/zenml-io/zenml/tree/main/examples/agent_framework_integrations)**: Integrate with popular agent frameworks - **[llm_finetuning](https://github.com/zenml-io/zenml/tree/main/examples/llm_finetuning)**: Fine-tune LLMs for specialized tasks
--- ## Path 2: Build Classical ML Pipelines Use scikit-learn, TensorFlow, PyTorch, or other ML frameworks to build data processing, feature engineering, training, and inference pipelines. ### Architecture example ```mermaid --- config: layout: elk theme: mc --- flowchart TB subgraph TRAIN["Training"] D["generate_churn_data"] T["train_churn_model"] D --> T end subgraph INFER["Inference"] P["predict_churn"] end U["Customer Features
(curl / SDK)"] --> INFER subgraph STACK["Stack"] OR[("Orchestrator")] AR[("Artifact Store")] DE[("Deployer")] end TRAIN --> AR TRAIN --> OR INFER --> DE INFER --> AR ```
View Quick Start & Examples ### Quick start ```bash git clone --depth 1 https://github.com/zenml-io/zenml.git cd zenml/examples/deploying_ml_model uv pip install -r requirements.txt ``` Then follow the guide in [`examples/deploying_ml_model`](https://github.com/zenml-io/zenml/tree/main/examples/deploying_ml_model): 1. **Build your pipeline**: Data loading → preprocessing → training → evaluation 2. **Deploy the model**: Serve your trained model as a real-time HTTP endpoint 3. **Monitor performance**: Track predictions, latency, and data drift in the dashboard 4. **Iterate**: Retrain and redeploy without code changes—just switch your orchestrator ### Example output - Predictive models (regression, classification) - Time series forecasting - NLP pipelines (sentiment analysis, text classification) - Computer vision workflows - Model scoring and ranking systems ### Related examples - **[e2e](https://github.com/zenml-io/zenml/tree/main/examples/e2e)**: End-to-end ML pipeline with data validation and model deployment - **[e2e_nlp](https://github.com/zenml-io/zenml/tree/main/examples/e2e_nlp)**: Domain-specific NLP pipeline example - **[mlops_starter](https://github.com/zenml-io/zenml/tree/main/examples/mlops_starter)**: Production-ready MLOps setup with monitoring and governance
--- ## Path 3: Build Hybrid Systems Combine classical ML models and AI agents in a single pipeline. For example, use a classifier to route requests to specialized agents, or use agents to augment ML predictions. ### Architecture example ```mermaid --- config: layout: elk theme: mc --- flowchart TB U["Customer Input
(curl / SDK)"] --> SA["Agent Service"] subgraph TRAIN["Training"] D["load_data"] T["train_classifier"] D --> T end subgraph SERVE["Serving"] C["classify_intent"] R["generate_response"] C --> R end SA --> SERVE subgraph STACK["Stack"] OR[("Orchestrator")] AR[("Artifact Store")] DE[("Deployer")] end TRAIN --> AR TRAIN --> OR SERVE --> AR SERVE --> DE ```
View Quick Start & Examples ### Quick start ```bash git clone --depth 1 https://github.com/zenml-io/zenml.git cd zenml/examples/agent_outer_loop uv pip install -r requirements.txt ``` Then follow the guide in [`examples/agent_outer_loop`](https://github.com/zenml-io/zenml/tree/main/examples/agent_outer_loop): 1. **Define both components**: Classical ML classifier + AI agent steps 2. **Wire them together**: Use the classifier output to influence agent behavior 3. **Deploy as one service**: The entire hybrid system becomes a single endpoint 4. **Monitor both**: Track ML metrics and agent traces in the same dashboard ### Example output - Intent classification with specialized agent handling - Upgrade paths: generic agent → train classifier → automatic routing - Ensemble systems combining multiple models and agents - Fact-checking pipelines with verification steps ### Related examples - **[agent_outer_loop](https://github.com/zenml-io/zenml/tree/main/examples/agent_outer_loop)**: Full hybrid example with automatic intent detection - **[deploying_agent](https://github.com/zenml-io/zenml/tree/main/examples/deploying_agent)**: Start here for the agent piece - **[deploying_ml_model](https://github.com/zenml-io/zenml/tree/main/examples/deploying_ml_model)**: Start here for the ML piece
--- ## Common Next Steps Once you've chosen your path and gotten your first pipeline running: ### Deploy remotely All three paths use the same deployment pattern. Configure a remote stack and deploy: ```bash # Create a remote stack (e.g., AWS) zenml stack register my-remote-stack \ --orchestrator aws-sagemaker \ --artifact-store s3-bucket \ --deployer aws # Set it and deploy—your code doesn't change zenml stack set my-remote-stack ``` Run in batch mode with: ```bash python run.py ``` Deploy as a real-time endpoint with: ```bash zenml pipeline deploy pipelines.my_pipeline.my_pipeline --config deploy_config.yaml ``` See [Deploying ZenML](deploying-zenml/README.md) for cloud setup details. ### View the dashboard Start the dashboard to explore your pipeline runs: ```bash zenml login ``` In the dashboard, you'll see: - **Pipeline DAGs**: Visual representation of your steps and data flow - **Artifacts**: Versioned outputs from each step (models, reports, traces) - **Metadata**: Latency, tokens, metrics, or custom metadata you track - **Timeline view**: Compare step durations and identify bottlenecks ## Core Concepts Recap Regardless of which path you choose: - **[Pipelines](../how-to/steps-pipelines/steps_and_pipelines.md)** - Orchestrate your workflow steps with automatic tracking - **[Steps](../how-to/steps-pipelines/steps_and_pipelines.md)** - Modular, reusable units (data loading, model training, LLM inference, etc.) - **[Artifacts](../how-to/artifacts/artifacts.md)** - Versioned outputs (models, predictions, traces, reports) with automatic logging - **[Stacks](../how-to/stack-components/stack_components.md)** - Switch execution environments (local, remote, cloud) without code changes - **[Deployments](../how-to/deployment/deployment.md)** - Turn pipelines into HTTP services with built-in UIs and monitoring For deeper dives, explore the [Concepts](../how-to/steps-pipelines/steps_and_pipelines.md) section in the docs.
ZenML Scarf
--- ### Book/Getting Started/Deploying Zenml/README (docs/book/getting-started/deploying-zenml/README.md) --- description: Why do we need to deploy ZenML? icon: rocket-launch --- # Deploy Moving your ZenML Server to a production environment offers several benefits over staying local: 1. **Scalability**: Production environments are designed to handle large-scale workloads, allowing your models to process more data and deliver faster results. 2. **Reliability**: Production-grade infrastructure ensures high availability and fault tolerance, minimizing downtime and ensuring consistent performance. 3. **Collaboration**: A shared production environment enables seamless collaboration between team members, making it easier to iterate on models and share insights. Despite these advantages, transitioning to production can be challenging due to the complexities involved in setting up the needed infrastructure. ## Components A ZenML deployment consists of multiple infrastructure components: * [FastAPI server](https://github.com/zenml-io/zenml/tree/main/src/zenml/zen_server) backed with a SQLite or MySQL database * [Python Client](https://github.com/zenml-io/zenml/tree/main/src/zenml) * An [open-source companion ReactJS](https://github.com/zenml-io/zenml-dashboard) dashboard * (Optional) [ZenML Pro API + Database + ZenML Pro dashboard](../system-architectures.md) You can read more in-depth about the system architecture of ZenML [here](../system-architectures.md).\ This documentation page will focus on the components required to deploy ZenML OSS.
Details on the ZenML Python Client The ZenML client is a Python package that you can install on your machine. It is used to interact with the ZenML server. You can install it using the `pip` command as outlined [here](../installation.md). This Python package gives you [the `zenml` command-line interface](https://sdkdocs.zenml.io/latest/cli.html) which you can use to interact with the ZenML server for common tasks like managing stacks, setting up secrets, and so on. It also gives you the general framework that lets you [author and deploy pipelines](https://docs.zenml.io/user-guides/starter-guide) and so forth. If you want to have more fine-grained control and access to the metadata that ZenML manages, you can use the Python SDK to access the API. This allows you to create your own custom automations and scripts and is the most common way teams access the metadata stored in the ZenML server. The full documentation for the Python SDK can be found [here](https://sdkdocs.zenml.io/latest/). The full HTTP [API documentation](https://docs.zenml.io/api-reference) can also be found by adding the`/doc` suffix to the URL when accessing your deployed ZenML server.
### Deployment scenarios When you first get started with ZenML, you have the following architecture on your machine. The SQLite database that you can see in this diagram is used to store information about pipelines, pipeline runs, stacks, and other configurations. This default setup allows you to get started and try out the core features, but you won't be able to use cloud-based components like serverless orchestrators and so on. Users can run the `zenml login --local` command to spin up a local ZenML OSS server to serve the dashboard. For the local OSS server option, the `zenml login --local` command implicitly connects the client to the server. The diagram for this looks as follows: In order to move into production, the ZenML server needs to be deployed somewhere centrally so that the different cloud stack components can read from and write to the server. Additionally, this also allows all your team members to connect to it and share stacks and pipelines. You connect to your deployed ZenML server using the `zenml login` command, and then you have the full benefits and power of ZenML. You can use all the cloud-based components, your metadata will be stored and synchronized across all the users of the server, and you can leverage features like centralized logs storage and pipeline artifact visualization. ## How to deploy ZenML Deploying the ZenML Server is a crucial step towards transitioning to a production-grade environment for your machine learning projects. By setting up a deployed ZenML Server instance, you gain access to powerful features, allowing you to use stacks with remote components, centrally track progress, collaborate effectively, and achieve reproducible results. Currently, there are two main options to access a deployed ZenML server: 1. **Managed deployment:** With [ZenML Pro](https://docs.zenml.io/pro) offering you can utilize a control plane to create ZenML servers, also known as [workspaces](https://docs.zenml.io/pro/core-concepts/workspaces). These workspaces are managed and maintained by ZenML's dedicated team, alleviating the burden of server management from your end. Importantly, your data remains securely within your stack, and ZenML's role is primarily to handle tracking of metadata and server maintenance. 2. **Self-hosted Deployment:** Alternatively, you have the ability to deploy ZenML on your own self-hosted environment. This can be achieved through various methods, including using [Docker](deploy-with-docker.md), [Helm](deploy-with-helm.md), or [HuggingFace Spaces](deploy-using-huggingface-spaces.md). We also offer our Pro version for self-hosted deployments, so you can use our full paid feature set while staying fully in control with an air-gapped solution on your infrastructure. Both options offer distinct advantages, allowing you to choose the deployment approach that best aligns with your organization's needs and infrastructure preferences. Whichever path you select, ZenML facilitates a seamless and efficient way to take advantage of the ZenML Server and enhance your machine learning workflows for production-level success. ### Options for deploying ZenML Documentation for the various deployment strategies can be found in the following pages below (in our 'how-to' guides):
Deploying ZenML using ZenML ProDeploying ZenML using ZenML Pro.zenml-pro.pnghttps://docs.zenml.io/pro/deployments/scenarios
Deploy with DockerDeploying ZenML in a Docker container.docker.pngdeploy-with-docker.md
Deploy with HelmDeploying ZenML in a Kubernetes cluster with Helm.helm.pngdeploy-with-helm.md
Deploy with HuggingFace SpacesDeploying ZenML to Hugging Face Spaces.hugging-face.pngdeploy-using-huggingface-spaces.md
ZenML Scarf
--- ### Book/Getting Started/Deploying Zenml/Custom Secret Stores (docs/book/getting-started/deploying-zenml/custom-secret-stores.md) --- description: Learning how to develop a custom secret store. --- # Custom secret stores The secrets store acts as the one-stop shop for all the secrets to which your pipeline or stack components might need access. It is responsible for storing, updating and deleting _only the secrets values_ for ZenML secrets, while the ZenML secret metadata is stored in the SQL database. The secrets store interface implemented by all available secrets store back-ends is defined in the `zenml.zen_stores.secrets_stores.secrets_store_interface` core module and looks more or less like this: ``` /* Detailed source-code truncated for AI context efficiency. */ ``` {% hint style="info" %} This is a slimmed-down version of the real interface which aims to highlight the abstraction layer. In order to see the full definition and get the complete docstrings, please check the [SDK docs](https://sdkdocs.zenml.io/latest/core_code_docs/core-zen_stores.html#zenml.zen_stores.secrets_stores) . {% endhint %} ## Build your own custom secrets store If you want to create your own custom secrets store implementation, you can follow the following steps: 1. Create a class that inherits from the `zenml.zen_stores.secrets_stores.base_secrets_store.BaseSecretsStore` base class and implements the `abstractmethod`s shown in the interface above. Use `SecretsStoreType.CUSTOM` as the `TYPE` value for your secrets store class. 2. If you need to provide any configuration, create a class that inherits from the `SecretsStoreConfiguration` class and add your configuration parameters there. Use that as the `CONFIG_TYPE` value for your secrets store class. 3. To configure the ZenML server to use your custom secrets store, make sure your code is available in the container image that is used to run the ZenML server. Then, use environment variables or helm chart values to configure the ZenML server to use your custom secrets store, as covered in the [deployment guide](./README.md).
ZenML Scarf
--- ### Book/Getting Started/Deploying Zenml/Deploy Using Huggingface Spaces (docs/book/getting-started/deploying-zenml/deploy-using-huggingface-spaces.md) --- description: Deploying ZenML to Huggingface Spaces. --- # Deploy using HuggingFace Spaces A quick way to deploy ZenML and get started is to use [HuggingFace Spaces](https://huggingface.co/spaces). HuggingFace Spaces is a platform for hosting and sharing ML projects and workflows, and it also works to deploy ZenML. You can be up and running in minutes (for free) with a hosted ZenML server, so it's a good option if you want to try out ZenML without any infrastructure overhead. {% hint style="info" %} If you are planning to use HuggingFace Spaces for production use, make sure you have [persistent storage turned on](https://huggingface.co/docs/hub/en/spaces-storage) so as to prevent loss of data. See our [other deployment options](./README.md) if you want alternative options. {% endhint %} In this diagram, you can see what the default deployment of ZenML on HuggingFace looks like. ## Deploying ZenML on HuggingFace Spaces You can deploy ZenML on HuggingFace Spaces with just a few clicks: [](https://huggingface.co/new-space?template=zenml/zenml) To set up your ZenML app, you need to specify three main components: the Owner (either your personal account or an organization), a Space name, and the Visibility (a bit lower down the page). Note that the space visibility needs to be set to 'Public' if you wish to connect to the ZenML server from your local machine. You have the option here to select a higher-tier machine to use for your server. The advantage of selecting a paid CPU instance is that it is not subject to auto-shutdown policies and thus will stay up as long as you leave it up. In order to make use of a persistent CPU, you'll likely want to create and set up a MySQL database to connect to (see below). To personalize your Space's appearance, such as the title, emojis, and colors, navigate to "Files and Versions" and modify the metadata in your README.md file. Full information on Spaces configuration parameters can be found on the HuggingFace [documentation reference guide](https://huggingface.co/docs/hub/spaces-config-reference). After creating your Space, you'll notice a 'Building' status along with logs displayed on the screen. When this switches to 'Running', your Space is ready for use. If the ZenML login UI isn't visible, try refreshing the page. In the upper-right hand corner of your space you'll see a button with three dots which, when you click on it, will offer you a menu option to "Embed this Space". (See [the HuggingFace documentation](https://huggingface.co/docs/hub/spaces-embed) for more details on this feature.) Copy the "Direct URL" shown in the box that you can now see on the screen. This should look something like this: `https://-.hf.space`. Open that URL and follow the instructions to initialize your ZenML server and set up an initial admin user account. ## Connecting to your ZenML Server from your local machine Once you have your ZenML server up and running, you can connect to it from your local machine. To do this, you'll need to get your Space's 'Direct URL' (see above). {% hint style="warning" %} Your Space's URL will only be available and usable for connecting from your local machine if the visibility of the space is set to 'Public'. {% endhint %} You can use the 'Direct URL' to connect to your ZenML server from your local machine with the following CLI command (after installing ZenML, and using your custom URL instead of the placeholder): ```shell zenml login '' ``` You can also use the Direct URL in your browser to use the ZenML dashboard as a fullscreen application (i.e. without the HuggingFace Spaces wrapper around it). ## Extra configuration options By default, the ZenML application will be configured to use an SQLite non-persistent database. If you want to use a persistent database, you can configure this by amending the `Dockerfile` to your Space's root directory. For full details on the various parameters you can change, see [our reference documentation](deploy-with-docker.md#advanced-server-configuration-options) on configuring ZenML when deployed with Docker. {% hint style="info" %} If you are using the space just for testing and experimentation, you don't need to make any changes to the configuration. Everything will work out of the box. {% endhint %} You can also use an external secrets backend together with your HuggingFace Spaces as described in [our documentation](deploy-with-docker.md#advanced-server-configuration-options). You should be sure to use HuggingFace's inbuilt ' Repository secrets' functionality to configure any secrets you need to use in your`Dockerfile` configuration. [See the documentation](https://huggingface.co/docs/hub/spaces-sdks-docker#secret-management) for more details on how to set this up. {% hint style="warning" %} If you wish to use a cloud secrets backend together with ZenML for secrets management, **you must update your password** on your ZenML Server on the Dashboard. This is because the default user created by the HuggingFace Spaces deployment process has no password assigned to it and as the Space is publicly accessible (since the Space is public) _potentially anyone could access your secrets without this extra step_. To change your password navigate to the Settings page by clicking the button in the upper right-hand corner of the Dashboard and then click 'Update Password'. {% endhint %} ## Troubleshooting If you are having trouble with your ZenML server on HuggingFace Spaces, you can view the logs by clicking on the "Open Logs" button at the top of the space. This will give you more context of what's happening with your server. If you have any other issues, please feel free to reach out to us on our [Slack channel](https://zenml.io/slack/) for more support. ## Upgrading your ZenML Server on HF Spaces The default space will use the latest version of ZenML automatically. If you want to update your version, you can simply select the 'Factory reboot' option within the 'Settings' tab of the space. Note that this will wipe any data contained within the space and so if you are not using a MySQL persistent database (as described above) you will lose any data contained within your ZenML deployment on the space. You can also configure the space to use an earlier version by updating the `Dockerfile`'s `FROM` import statement at the very top.
ZenML Scarf
--- ### Book/Getting Started/Deploying Zenml/Deploy With Custom Image (docs/book/getting-started/deploying-zenml/deploy-with-custom-image.md) --- description: Deploying ZenML with custom Docker images. --- # Deploy with custom images In most cases, deploying ZenML with the default `zenmlhub/zenml-server` Docker image should work just fine. However, there are some scenarios when you might need to deploy ZenML with a custom Docker image: * You have implemented a custom artifact store for which you want to enable [artifact visualizations](../../how-to/artifacts/visualizations.md) or [step logs](../../how-to/steps-pipelines/logging.md) in your dashboard. * You have forked the ZenML repository and want to deploy a ZenML server based on your own fork because you made changes to the server / database logic. {% hint style="warning" %} Deploying ZenML with custom Docker images is only possible for [Docker](deploy-with-docker.md) or [Helm](deploy-with-helm.md) deployments. {% endhint %} ### Build and Push Custom ZenML Server Docker Image Here is how you can build a custom ZenML server Docker image: 1. Set up a container registry of your choice. E.g., as an indivial developer you could create a free [Docker Hub](https://hub.docker.com/) account and then set up a free Docker Hub repository. 2. Clone ZenML (or your ZenML fork) and checkout the branch that you want to deploy, e.g., if you want to deploy ZenML version 0.41.0, run ```bash git checkout release/0.41.0 ``` 3. Copy the [ZenML base.Dockerfile](https://github.com/zenml-io/zenml/blob/main/docker/base.Dockerfile), e.g.: ```bash cp docker/base.Dockerfile docker/custom.Dockerfile ``` 4. Modify the copied Dockerfile: * Add additional dependencies: ```bash RUN pip install ``` * (Forks only) install local files instead of official ZenML: ```bash RUN pip install -e .[server,secrets-aws,secrets-gcp,secrets-azure,secrets-hashicorp,s3fs,gcsfs,adlfs,connectors-aws,connectors-gcp,connectors-azure] ``` 5. Build and push an image based on your Dockerfile: ```bash docker build -f docker/custom.Dockerfile . -t /: --platform linux/amd64 docker push /: ``` {% hint style="info" %} If you want to verify your custom image locally, you can follow the [Deploy a custom ZenML image via Docker](deploy-with-custom-image.md#deploy-a-custom-zenml-image-via-docker) section below to deploy the ZenML server locally first. {% endhint %} ### Deploy ZenML with your custom image Next, adjust your preferred deployment strategy to use the custom Docker image you just built. #### Deploy a custom ZenML image via Docker To deploy your custom image via Docker, first familiarize yourself with the general [ZenML Docker Deployment Guide](deploy-with-docker.md). To use your own image, follow the general guide step by step but replace all mentions of `zenmldocker/zenml-server` with your custom image reference `/:`. E.g.: * To run the ZenML server with Docker based on your custom image, do ```bash docker run -it -d -p 8080:8080 --name zenml /: ``` * To use `docker-compose`, adjust your `docker-compose.yml`: ```yaml services: zenml: image: /: ``` #### Deploy a custom ZenML image via Helm To deploy your custom image via Helm, first familiarize yourself with the general [ZenML Helm Deployment Guide](deploy-with-helm.md). To use your own image, the only thing you need to do differently is to modify the `image` section of your `values.yaml` file: ```yaml server: image: repository: / tag: ```
ZenML Scarf
--- ### Book/Getting Started/Deploying Zenml/Deploy With Docker (docs/book/getting-started/deploying-zenml/deploy-with-docker.md) --- description: Deploying ZenML in a Docker container. --- # Deploy with Docker The ZenML server container image is available at [`zenmldocker/zenml-server`](https://hub.docker.com/r/zenmldocker/zenml/) and can be used to deploy ZenML with a container management or orchestration tool like Docker and docker-compose, or a serverless platform like [Cloud Run](https://cloud.google.com/run), [Container Apps](https://learn.microsoft.com/en-us/azure/container-apps/overview), and more! This guide walks you through the various configuration options that the ZenML server container expects as well as a few deployment use cases. ## Try it out locally first If you're just looking for a quick way to deploy the ZenML server using a container, without going through the hassle of interacting with a container management tool like Docker and manually configuring your container, you can use the ZenML CLI to do so. You only need to have Docker installed and running on your machine: ```bash zenml login --local --docker ``` This command deploys a ZenML server locally in a Docker container, then connects your client to it. Similar to running plain `zenml login --local`, the server and the local ZenML client share the same SQLite database. The rest of this guide is addressed to advanced users who are looking to manually deploy and manage a containerized ZenML server. ## ZenML server configuration options If you're planning on deploying a custom containerized ZenML server yourself, you probably need to configure some settings for it like the **database** it should use, the **default user details,** and more. The ZenML server container image uses sensible defaults, so you can simply start a container without worrying too much about the configuration. However, if you're looking to connect the ZenML server to an external MySQL database or secrets management service, to persist the internal SQLite database, or simply want to control other settings like the default account, you can do so by customizing the container's environment variables. The following environment variables can be passed to the container: * **ZENML\_STORE\_URL**: This URL should point to an SQLite database file _mounted in the container_, or to a MySQL-compatible database service _reachable from the container_. It takes one of these forms: ``` sqlite:////path/to/zenml.db ``` or: ``` mysql://username:password@host:port/database ``` * **ZENML\_STORE\_SSL\_CA**: This can be set to a custom server CA certificate in use by the MySQL database service. Only valid when `ZENML_STORE_URL` points to a MySQL database that uses SSL-secured connections. The variable can be set either to the path where the certificate file is mounted inside the container or to the certificate contents themselves. * **ZENML\_STORE\_SSL\_CERT**: This can be set to a client SSL certificate required to connect to the MySQL database service. Only valid when `ZENML_STORE_URL` points to a MySQL database that uses SSL-secured connections and requires client SSL certificates. The variable can be set either to the path where the certificate file is mounted inside the container or to the certificate contents themselves. This variable also requires `ZENML_STORE_SSL_KEY` to be set. * **ZENML\_STORE\_SSL\_KEY**: This can be set to a client SSL private key required to connect to the MySQL database service. Only valid when `ZENML_STORE_URL` points to a MySQL database that uses SSL-secured connections and requires client SSL certificates. The variable can be set either to the path where the certificate file is mounted inside the container or to the certificate contents themselves. This variable also requires `ZENML_STORE_SSL_CERT` to be set. * **ZENML\_STORE\_SSL\_VERIFY\_SERVER\_CERT**: This boolean variable controls whether the SSL certificate in use by the MySQL server is verified. Only valid when `ZENML_STORE_URL` points to a MySQL database that uses SSL-secured connections. Defaults to `False`. * **ZENML\_LOGGING\_VERBOSITY**: Controls the verbosity of server logs inside the container. It can be set to `NOTSET`, `ERROR`, `WARN`, `INFO` (default), `DEBUG`, or `CRITICAL`. * **ZENML\_CONSOLE\_LOGGING\_FORMAT**: Controls the server container stdout/stderr log format. Set it to `console` (default) for structured text output, `json` for JSON formatted logs, or a valid Python `%`-style logging format string for custom console output. `ZENML_LOGGING_FORMAT` is still accepted as a deprecated alias. * **ZENML\_LOGGING\_COLORS\_DISABLED**: Set this to `true` to disable ANSI color output in console logs. Colors are enabled by default. * **ZENML\_SERVER\_OTEL\_EXPORTER\_OTLP\_ENDPOINT**: Set this to the base OTLP/HTTP endpoint of an OpenTelemetry-compatible backend to export ZenML server traces, metrics, and logs. The standard `OTEL_EXPORTER_OTLP_ENDPOINT` variable is also supported as a fallback. ZenML appends `/v1/traces`, `/v1/metrics`, and `/v1/logs` for each signal unless you set per-signal endpoint variables such as `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`, `OTEL_EXPORTER_OTLP_METRICS_ENDPOINT`, and `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT`, or their matching `ZENML_SERVER_OTEL_EXPORTER_OTLP__ENDPOINT` names. If no base or per-signal endpoint is set, server OpenTelemetry instrumentation is disabled. The server logs export to the OTel backend is independent of `ZENML_CONSOLE_LOGGING_FORMAT`: logs are exported as OTLP records with structured attributes derived from the underlying log record, not as console-formatted text or JSON. * **ZENML\_SERVER\_OTEL\_SERVICE\_NAME**: The service name reported in OpenTelemetry resource attributes for server traces, metrics, and logs. The standard `OTEL_SERVICE_NAME` variable is also supported as a fallback. Defaults to `zenml-server` for self-hosted deployments, or to the ZenML Pro workspace name for cloud deployments when not explicitly set. * **ZENML\_STORE\_BACKUP\_STRATEGY**: This variable controls the database backup strategy used by the ZenML server. See the [Database backup and recovery](deploy-with-docker.md#database-backup-and-recovery) section for more details about this feature and other related environment variables. Defaults to `in-memory`. * **ZENML\_SERVER\_RATE\_LIMIT\_ENABLED**: This variable controls the rate limiting for ZenML API (currently only for the `LOGIN` endpoint). It is disabled by default, so set it to `1` only if you need to enable rate limiting. To determine unique users a `X_FORWARDED_FOR` header or `request.client.host` is used, so before enabling this make sure that your network configuration is associating proper information with your clients in order to avoid disruptions for legitimate requests. * **ZENML\_SERVER\_LOGIN\_RATE\_LIMIT\_MINUTE**: If rate limiting is enabled, this variable controls how many requests will be allowed to query the login endpoint in a one minute interval. Set it to a desired integer value; defaults to `5`. * **ZENML\_SERVER\_LOGIN\_RATE\_LIMIT\_DAY**: If rate limiting is enabled, this variable controls how many requests will be allowed to query the login endpoint in an interval of day interval. Set it to a desired integer value; defaults to `1000`. If none of the `ZENML_STORE_*` variables are set, the container will default to creating and using an SQLite database file stored at `/zenml/.zenconfig/local_stores/default_zen_store/zenml.db` inside the container. The `/zenml/.zenconfig/local_stores` base path where the default SQLite database is located can optionally be overridden by setting the `ZENML_LOCAL_STORES_PATH` environment variable to point to a different path (e.g. a persistent volume or directory that is mounted from the host). ### Secret store environment variables Unless explicitly disabled or configured otherwise, the ZenML server will use the SQL database as [a secrets store backend](secret-management.md) where secret values are stored. If you want to use an external secrets management service like the AWS Secrets Manager, GCP Secrets Manager, Azure Key Vault, HashiCorp Vault or even your custom Secrets Store back-end implementation instead, you need to configure it explicitly using Docker environment variables. Depending on where you deploy your ZenML server and how your Kubernetes cluster is configured, you will also need to provide the credentials needed to access the secrets management service API. > **Important:** If you are updating the configuration of your ZenML Server container to use a different secrets store back-end or location, you should follow [the documented secrets migration strategy](secret-management.md#secrets-migration-strategy) to minimize downtime and to ensure that existing secrets are also properly migrated. {% tabs %} {% tab title="Default" %} The SQL database is used as the default secret store location. You only need to configure these options if you want to change the default behavior. It is particularly recommended to enable encryption at rest for the SQL database if you plan on using it as a secrets store backend. You'll have to configure the secret key used to encrypt the secret values. If not set, encryption will not be used and passwords will be stored unencrypted in the database. * **ZENML\_SECRETS\_STORE\_TYPE:** Set this to `sql` in order to explicitly set this type of secret store. * **ZENML\_SECRETS\_STORE\_ENCRYPTION\_KEY**: the secret key used to encrypt all secrets stored in the SQL secrets store. It is recommended to set this to a random string with a length of at least 32 characters, e.g.: ```python from secrets import token_hex token_hex(32) ``` or: ```shell openssl rand -hex 32 ``` > **Important:** If you configure encryption for your SQL database secrets store, you should keep the `ZENML_SECRETS_STORE_ENCRYPTION_KEY` value somewhere safe and secure, as it will always be required by the ZenML server to decrypt the secrets in the database. If you lose the encryption key, you will not be able to decrypt the secrets in the database and will have to reset them. {% endtab %} {% tab title="AWS" %} These configuration options are only relevant if you're using the AWS Secrets Manager as the secrets store backend. * **ZENML\_SECRETS\_STORE\_TYPE:** Set this to `aws` in order to set this type of secret store. The AWS Secrets Store uses the ZenML AWS Service Connector under the hood to authenticate with the AWS Secrets Manager API. This means that you can use any of the [authentication methods supported by the AWS Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/aws-service-connector#authentication-methods) to authenticate with the AWS Secrets Manager API. The minimum set of permissions that must be attached to the implicit or configured AWS credentials are: `secretsmanager:CreateSecret`, `secretsmanager:GetSecretValue`, `secretsmanager:DescribeSecret`, `secretsmanager:PutSecretValue`, `secretsmanager:TagResource` and `secretsmanager:DeleteSecret` and they must be associated with secrets that have a name starting with `zenml/` in the target region and account. The following IAM policy example can be used as a starting point: ``` { "Version": "2012-10-17", "Statement": [ { "Sid": "ZenMLSecretsStore", "Effect": "Allow", "Action": [ "secretsmanager:CreateSecret", "secretsmanager:GetSecretValue", "secretsmanager:DescribeSecret", "secretsmanager:PutSecretValue", "secretsmanager:TagResource", "secretsmanager:DeleteSecret" ], "Resource": "arn:aws:secretsmanager:::secret:zenml/*" } ] } ``` The following configuration options are supported: * **ZENML\_SECRETS\_STORE\_AUTH\_METHOD**: The AWS Service Connector authentication method to use (e.g. `secret-key` or `iam-role`). * **ZENML\_SECRETS\_STORE\_AUTH\_CONFIG**: The AWS Service Connector configuration, in JSON format (e.g. `{"aws_access_key_id":"","aws_secret_access_key":"","region":""}`). > **Note:** The remaining configuration options are deprecated and may be removed in a future release. Instead, you should set the `ZENML_SECRETS_STORE_AUTH_METHOD` and `ZENML_SECRETS_STORE_AUTH_CONFIG` variables to use the AWS Service Connector authentication method. * **ZENML\_SECRETS\_STORE\_REGION\_NAME**: The AWS region to use. This must be set to the region where the AWS Secrets Manager service that you want to use is located. * **ZENML\_SECRETS\_STORE\_AWS\_ACCESS\_KEY\_ID**: The AWS access key ID to use for authentication. This must be set to a valid AWS access key ID that has access to the AWS Secrets Manager service that you want to use. If you are using an IAM role attached to an EKS cluster to authenticate, you can omit this variable. * **ZENML\_SECRETS\_STORE\_AWS\_SECRET\_ACCESS\_KEY**: The AWS secret access key to use for authentication. This must be set to a valid AWS secret access key that has access to the AWS Secrets Manager service that you want to use. If you are using an IAM role attached to an EKS cluster to authenticate, you can omit this variable. {% endtab %} {% tab title="GCP" %} These configuration options are only relevant if you're using the GCP Secrets Manager as the secrets store backend. * **ZENML\_SECRETS\_STORE\_TYPE:** Set this to `gcp` in order to set this type of secret store. The GCP Secrets Store uses the ZenML GCP Service Connector under the hood to authenticate with the GCP Secrets Manager API. This means that you can use any of the [authentication methods supported by the GCP Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/gcp-service-connector#authentication-methods) to authenticate with the GCP Secrets Manager API. The minimum set of permissions that must be attached to the implicit or configured GCP credentials are as follows: * `secretmanager.secrets.create` for the target GCP project (i.e. no condition on the name prefix) * `secretmanager.secrets.get`, `secretmanager.secrets.update`, `secretmanager.versions.access`, `secretmanager.versions.add` and `secretmanager.secrets.delete` for the target GCP project and for secrets that have a name starting with `zenml-` This can be achieved by creating two custom IAM roles and attaching them to the principal (e.g. user or service account) that will be used to access the GCP Secrets Manager API with a condition configured when attaching the second role to limit access to secrets with a name prefix of `zenml-`. The following `gcloud` CLI command examples can be used as a starting point: ```bash gcloud iam roles create ZenMLServerSecretsStoreCreator \ --project \ --title "ZenML Server Secrets Store Creator" \ --description "Allow the ZenML Server to create new secrets" \ --stage GA \ --permissions "secretmanager.secrets.create" gcloud iam roles create ZenMLServerSecretsStoreEditor \ --project \ --title "ZenML Server Secrets Store Editor" \ --description "Allow the ZenML Server to manage its secrets" \ --stage GA \ --permissions "secretmanager.secrets.get,secretmanager.secrets.update,secretmanager.versions.access,secretmanager.versions.add,secretmanager.secrets.delete" gcloud projects add-iam-policy-binding \ --member serviceAccount: \ --role projects//roles/ZenMLServerSecretsStoreCreator \ --condition None # NOTE: use the GCP project NUMBER, not the project ID in the condition gcloud projects add-iam-policy-binding \ --member serviceAccount: \ --role projects//roles/ZenMLServerSecretsStoreEditor \ --condition 'title=limit_access_zenml,description="Limit access to secrets with prefix zenml-",expression=resource.name.startsWith("projects//secrets/zenml-")' ``` The following configuration options are supported: * **ZENML\_SECRETS\_STORE\_AUTH\_METHOD**: The GCP Service Connector authentication method to use (e.g. `service-account`). * **ZENML\_SECRETS\_STORE\_AUTH\_CONFIG**: The GCP Service Connector configuration, in JSON format (e.g. `{"project_id":"my-project","service_account_json":{ ... }}`). > **Note:** The remaining configuration options are deprecated and may be removed in a future release. Instead, you should set the `ZENML_SECRETS_STORE_AUTH_METHOD` and `ZENML_SECRETS_STORE_AUTH_CONFIG` variables to use the GCP Service Connector authentication method. * **ZENML\_SECRETS\_STORE\_PROJECT\_ID**: The GCP project ID to use. This must be set to the project ID where the GCP Secrets Manager service that you want to use is located. * **GOOGLE\_APPLICATION\_CREDENTIALS**: The path to the GCP service account credentials file to use for authentication. This must be set to a valid GCP service account credentials file that has access to the GCP Secrets Manager service that you want to use. If you are using a GCP service account attached to a GKE cluster to authenticate, you can omit this variable. NOTE: the path to the credentials file must be mounted into the container. {% endtab %} {% tab title="Azure" %} These configuration options are only relevant if you're using Azure Key Vault as the secrets store backend. * **ZENML\_SECRETS\_STORE\_TYPE:** Set this to `azure` in order to set this type of secret store. * **ZENML\_SECRETS\_STORE\_KEY\_VAULT\_NAME**: The name of the Azure Key Vault. This must be set to point to the Azure Key Vault instance that you want to use. The Azure Secrets Store uses the ZenML Azure Service Connector under the hood to authenticate with the Azure Key Vault API. This means that you can use any of the [authentication methods supported by the Azure Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/azure-service-connector#authentication-methods) to authenticate with the Azure Key Vault API. The following configuration options are supported: * **ZENML\_SECRETS\_STORE\_AUTH\_METHOD**: The Azure Service Connector authentication method to use (e.g. `service-account`). * **ZENML\_SECRETS\_STORE\_AUTH\_CONFIG**: The Azure Service Connector configuration, in JSON format (e.g. `{"tenant_id":"my-tenant-id","client_id":"my-client-id","client_secret": "my-client-secret"}`). > **Note:** The remaining configuration options are deprecated and may be removed in a future release. Instead, you should set the `ZENML_SECRETS_STORE_AUTH_METHOD` and `ZENML_SECRETS_STORE_AUTH_CONFIG` variables to use the Azure Service Connector authentication method. * **ZENML\_SECRETS\_STORE\_AZURE\_CLIENT\_ID**: The Azure application service principal client ID to use to authenticate with the Azure Key Vault API. If you are running the ZenML server hosted in Azure and are using a managed identity to access the Azure Key Vault service, you can omit this variable. * **ZENML\_SECRETS\_STORE\_AZURE\_CLIENT\_SECRET**: The Azure application service principal client secret to use to authenticate with the Azure Key Vault API. If you are running the ZenML server hosted in Azure and are using a managed identity to access the Azure Key Vault service, you can omit this variable. * **ZENML\_SECRETS\_STORE\_AZURE\_TENANT\_ID**: The Azure application service principal tenant ID to use to authenticate with the Azure Key Vault API. If you are running the ZenML server hosted in Azure and are using a managed identity to access the Azure Key Vault service, you can omit this variable. {% endtab %} {% tab title="Hashicorp" %} These configuration options are only relevant if you're using Hashicorp Vault as the secrets store backend. * **ZENML\_SECRETS\_STORE\_TYPE:** Set this to `hashicorp` in order to set this type of secret store. * **ZENML\_SECRETS\_STORE\_VAULT\_ADDR**: The URL of the HashiCorp Vault server to connect to. NOTE: this is the same as setting the `VAULT_ADDR` environment variable. * **ZENML\_SECRETS\_STORE\_VAULT\_NAMESPACE**: The Vault Enterprise namespace. Not required for Vault OSS. NOTE: this is the same as setting the `VAULT_NAMESPACE` environment variable. * **ZENML\_SECRETS\_STORE\_MOUNT\_POINT**: The mount point to use for the HashiCorp Vault secrets store. If not set, the default value of `secret` will be used. * **ZENML\_SECRETS\_STORE\_VAULT\_AUTH_METHOD**: The authentication method to use to authenticate with the HashiCorp Vault server. One of: `token`, `app_role`, `aws`. Defaults to `token` if not set. * **ZENML\_SECRETS\_STORE\_VAULT\_AUTH\_MOUNT\_POINT**: The mount point to use for the authentication method. If not set, the default value specific to the authentication method will be used. * **ZENML\_SECRETS\_STORE\_VAULT\_TOKEN**: The token to use to authenticate with the HashiCorp Vault server. Mandatory if the authentication method is `token`. NOTE: this is the same as setting the `VAULT_TOKEN` environment variable. * **ZENML\_SECRETS\_STORE\_VAULT\_APP\_ROLE\_ID**: The role ID to use for the app role authentication method. Mandatory if the authentication method is `app_role`. * **ZENML\_SECRETS\_STORE\_VAULT\_APP\_SECRET\_ID**: The secret ID to use for the app role authentication method. Mandatory if the authentication method is `app_role`. * **ZENML\_SECRETS\_STORE\_VAULT\_AWS\_ROLE**: The AWS role to use for the AWS authentication method. Only relevant if the authentication method is `aws`. * **ZENML\_SECRETS\_STORE\_VAULT\_AWS\_HEADER\_VALUE**: The AWS header value to use for the AWS authentication method. Only relevant if the authentication method is `aws`. * **ZENML\_SECRETS\_STORE\_MAX\_VERSIONS**: The maximum number of secret versions to keep for each Vault secret. If not set, the default value of 1 will be used (only the latest version will be kept). {% endtab %} {% tab title="Custom" %} These configuration options are only relevant if you're using a custom secrets store backend implementation. For this to work, you must have [a custom implementation of the secrets store API](custom-secret-stores.md) in the form of a class derived from `zenml.zen_stores.secrets_stores.base_secrets_store.BaseSecretsStore`. This class must be importable from within the ZenML server container, which means you most likely need to mount the directory containing the class into the container or build a custom container image that contains the class. The following configuration option is required: * **ZENML\_SECRETS\_STORE\_TYPE:** Set this to `custom` in order to set this type of secret store. * **ZENML\_SECRETS\_STORE\_CLASS\_PATH**: The fully qualified path to the class that implements the custom secrets store API (e.g. `my_package.my_module.MySecretsStore`). If your custom secrets store implementation requires additional configuration options, you can pass them as environment variables using the following naming convention: * `ZENML_SECRETS_STORE_`: The name of the option to pass to the custom secrets store class. The option name must be in uppercase and any hyphens (`-`) must be replaced with underscores (`_`). ZenML will automatically convert the environment variable name to the corresponding option name by removing the prefix and converting the remaining characters to lowercase. For example, the environment variable `ZENML_SECRETS_STORE_MY_OPTION` will be converted to the option name `my_option` and passed to the custom secrets store class configuration. {% endtab %} {% endtabs %} {% hint style="info" %} **ZENML\_SECRETS\_STORE\_TYPE**: Set this variable to `none`to disable the secrets store functionality altogether. {% endhint %} #### Backup secrets store [A backup secrets store](secret-management.md#backup-secrets-store) back-end may be configured for high-availability and backup purposes. or as an intermediate step in the process of [migrating secrets to a different external location or secrets manager provider](secret-management.md#secrets-migration-strategy). To configure a backup secrets store in the Docker container, use the same approach and instructions documented for the primary secrets store, but set the `**ZENML\_BACKUP\_SECRETS\_STORE\***` environment variables instead of `**ZENML\_SECRETS\_STORE\***`, e.g.: ```yaml ZENML_BACKUP_SECRETS_STORE_TYPE: aws ZENML_BACKUP_SECRETS_STORE_AUTH_METHOD: secret-key ZENML_BACKUP_SECRETS_STORE_AUTH_CONFIG: '{"aws_access_key_id":"", "aws_secret_access_key","","role_arn": ""}`' ``` ### Advanced server configuration options These configuration options are not required for most use cases, but can be useful in certain scenarios that require mirroring the same ZenML server configuration across multiple container instances (e.g. a Kubernetes deployment with multiple replicas): * **ZENML\_SERVER\_JWT\_SECRET\_KEY**: This is a secret key used to sign JWT tokens used for authentication. If not explicitly set, a random key is generated automatically by the server on startup and stored in the server's global configuration. This should be set to a random string with a recommended length of at least 32 characters, e.g.: ```python from secrets import token_hex token_hex(32) ``` or: ```shell openssl rand -hex 32 ``` The environment variables starting with _ZENML\_SERVER\_SECURE\_HEADERS\__\* can be used to enable, disable or set custom values for security headers in the ZenML server's HTTP responses. The following values can be set for any of the supported secure headers configuration options: * `enabled`, `on`, `true` or `yes` - enables the secure header with the default value. * `disabled`, `off`, `false`, `none` or `no` - disables the secure header entirely, so that it is not set in the ZenML server's HTTP responses. * any other value - sets the secure header to the specified value. The following secure headers environment variables are supported: * **ZENML\_SERVER\_SECURE\_HEADERS\_SERVER**: The `Server` HTTP header value used to identify the server. The default value is the ZenML server ID. * **ZENML\_SERVER\_SECURE\_HEADERS\_HSTS**: The `Strict-Transport-Security` HTTP header value. The default value is `max-age=63072000; includeSubDomains`. * **ZENML\_SERVER\_SECURE\_HEADERS\_XFO**: The `X-Frame-Options` HTTP header value. The default value is `SAMEORIGIN`. * **ZENML\_SERVER\_SECURE\_HEADERS\_CONTENT**: The `X-Content-Type-Options` HTTP header value. The default value is `nosniff`. * **ZENML\_SERVER\_SECURE\_HEADERS\_CSP**: The `Content-Security-Policy` HTTP header value. This is by default set to a strict CSP policy that only allows content from the origins required by the ZenML dashboard. NOTE: customizing this header is discouraged, as it may cause the ZenML dashboard to malfunction. * **ZENML\_SERVER\_SECURE\_HEADERS\_REFERRER**: The `Referrer-Policy` HTTP header value. The default value is `no-referrer-when-downgrade`. * **ZENML\_SERVER\_SECURE\_HEADERS\_CACHE**: The `Cache-Control` HTTP header value. The default value is `no-store, no-cache, must-revalidate`. * **ZENML\_SERVER\_SECURE\_HEADERS\_PERMISSIONS**: The `Permissions-Policy` HTTP header value. The default value is `accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()`. If you prefer to activate the server automatically during the initial deployment and also automate the creation of the initial admin user account, this legacy behavior can be brought back by setting the following environment variables: * **ZENML\_SERVER\_AUTO\_ACTIVATE**: Set this to `1` to automatically activate the server and create the initial admin user account when the server is first deployed. Defaults to `0`. * **ZENML\_DEFAULT\_USER\_NAME**: The name of the initial admin user account created by the server on the first deployment, during database initialization. Defaults to `default`. * **ZENML\_DEFAULT\_USER\_PASSWORD**: The password to use for the initial admin user account. Defaults to an empty password value, if not set. ## Run the ZenML server with Docker As previously mentioned, the ZenML server container image uses sensible defaults for most configuration options. This means that you can simply run the container with Docker without any additional configuration and it will work out of the box for most use cases: ```bash docker run -it -d -p 8080:8080 --name zenml zenmldocker/zenml-server ``` > **Note:** It is recommended to use a ZenML container image version that matches the version of your client, to avoid any potential API incompatibilities (e.g. `zenmldocker/zenml-server:0.21.1` instead of `zenmldocker/zenml-server`). The above command will start a containerized ZenML server running on your machine that uses a temporary SQLite database file stored in the container. Temporary means that the database and all its contents (stacks, pipelines, pipeline runs, etc.) will be lost when the container is removed with `docker rm`. You need to visit the ZenML dashboard at `http://localhost:8080` and activate the server by creating an initial admin user account. You can then connect your client to the server with the web login flow: ```shell $ zenml login http://localhost:8080 Connecting to: 'http://localhost:8080'... If your browser did not open automatically, please open the following URL into your browser to proceed with the authentication: http://localhost:8080/devices/verify?device_id=f7a7333a-3ef0-4f39-85a9-f190279456d3&user_code=9375f5cdfdaf36772ce981fe3ee6172c Successfully logged in. Creating default stack for user 'default'... Updated the global store configuration. ``` {% hint style="info" %} The `localhost` URL **will** work, even if you are using Docker-backed ZenML orchestrators in your stack, like [the local Docker orchestrator](https://docs.zenml.io/stacks/orchestrators/local-docker) or [a locally deployed Kubeflow orchestrator](https://docs.zenml.io/stacks/orchestrators/kubeflow). ZenML makes use of specialized DNS entries such as `host.docker.internal` and `host.k3d.internal` to make the ZenML server accessible from the pipeline steps running inside other Docker containers on the same machine. {% endhint %} You can manage the container with the usual Docker commands: * `docker logs zenml` to view the server logs * `docker stop zenml` to stop the server * `docker start zenml` to start the server again * `docker rm zenml` to remove the container If you are looking for a customized ZenML server Docker deployment, you can configure one or more of [the supported environment variables](deploy-with-docker.md#zenml-server-configuration-options) and then pass them to the container using the `docker run` `--env` or `--env-file` arguments (see the [Docker documentation](https://docs.docker.com/engine/reference/commandline/run/#set-environment-variables--e---env---env-file) for more details). For example: ```shell docker run -it -d -p 8080:8080 --name zenml \ --env ZENML_STORE_URL=mysql://username:password@host:port/database \ zenmldocker/zenml-server ``` If you're looking for a quick way to run both the ZenML server and a MySQL database with Docker, you can [deploy the ZenML server with Docker Compose](deploy-with-docker.md#zenml-server-with-docker-compose). The rest of this guide covers various advanced use cases for running the ZenML server with Docker. ### Persisting the SQLite database Depending on your use case, you may also want to mount a persistent volume or directory from the host into the container to store the ZenML SQLite database file. This can be done using the `--mount` flag (see the [Docker documentation](https://docs.docker.com/storage/volumes/) for more details). For example: ```shell mkdir zenml-server docker run -it -d -p 8080:8080 --name zenml \ --mount type=bind,source=$PWD/zenml-server,target=/zenml/.zenconfig/local_stores/default_zen_store \ zenmldocker/zenml-server ``` This deployment has the advantage that the SQLite database file is persisted even when the container is removed with `docker rm`. ### Docker MySQL database As a recommended alternative to the SQLite database, you can run a MySQL database service as another Docker container and connect the ZenML server container to it. A command like the following can be run to start the containerized MySQL database service: ```shell docker run --name mysql -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=password mysql:8.0 ``` If you also wish to persist the MySQL database data, you can mount a persistent volume or directory from the host into the container using the `--mount` flag, e.g.: ```shell mkdir mysql-data docker run --name mysql -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=password \ --mount type=bind,source=$PWD/mysql-data,target=/var/lib/mysql \ mysql:8.0 ``` Configuring the ZenML server container to connect to the MySQL database is just a matter of setting the `ZENML_STORE_URL` environment variable. We use the special `host.docker.internal` DNS name that is resolved from within the Docker containers to the gateway IP address used by the Docker network (see the [Docker documentation](https://docs.docker.com/desktop/networking/#use-cases-and-workarounds-for-all-platforms) for more details). On Linux, this needs to be explicitly enabled in the `docker run` command with the `--add-host` argument: ```shell docker run -it -d -p 8080:8080 --name zenml \ --add-host host.docker.internal:host-gateway \ --env ZENML_STORE_URL=mysql://root:password@host.docker.internal/zenml \ zenmldocker/zenml-server ``` You need to visit the ZenML dashboard at `http://localhost:8080` and activate the server by creating an initial admin user account. You can then connect your client to the server with the web login flow: ```shell zenml login http://localhost:8080 ``` ### Direct MySQL database connection This scenario is similar to the previous one, but instead of running a ZenML server, the client is configured to connect directly to a MySQL database running in a Docker container. As previously covered, the containerized MySQL database service can be started with a command like the following: ```shell docker run --name mysql -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=password mysql:8.0 ``` The ZenML client on the host machine can then be configured to connect directly to the database with a slightly different `zenml login` command: ```shell zenml login mysql://root:password@127.0.0.1/zenml ``` > **Note** The `localhost` hostname will not work with MySQL databases. You need to use the `127.0.0.1` IP address instead. ### ZenML server with `docker-compose` Docker compose offers a simpler way of managing multi-container setups on your local machine, which is the case for instance if you are looking to deploy the ZenML server container and connect it to a MySQL database service also running in a Docker container. To use Docker Compose, you need to [install the docker-compose plugin](https://docs.docker.com/compose/install/linux/) on your machine first. A `docker-compose.yml` file like the one below can be used to start and manage the ZenML server container and the MySQL database service all at once: ```yaml version: "3.9" services: mysql: image: mysql:8.0 ports: - 3306:3306 environment: - MYSQL_ROOT_PASSWORD=password zenml: image: zenmldocker/zenml-server ports: - "8080:8080" environment: - ZENML_STORE_URL=mysql://root:password@host.docker.internal/zenml links: - mysql depends_on: - mysql extra_hosts: - "host.docker.internal:host-gateway" restart: on-failure ``` Note the following: * `ZENML_STORE_URL` is set to the special Docker `host.docker.internal` hostname to instruct the server to connect to the database over the Docker network. * The `extra_hosts` section is needed on Linux to make the `host.docker.internal` hostname resolvable from the ZenML server container. To start the containers, run the following command from the directory where the `docker-compose.yml` file is located: ```shell docker compose -p zenml up -d ``` or, if you need to use a different filename or path: ```shell docker compose -f /path/to/docker-compose.yml -p zenml up -d ``` You need to visit the ZenML dashboard at `http://localhost:8080` to activate the server by creating an initial admin account. You can then connect your client to the server with the web login flow: ```shell zenml login http://localhost:8080 ``` Tearing down the installation is as simple as running: ```shell docker compose -p zenml down ``` ## Database backup and recovery An automated database backup and recovery feature is enabled by default for all Docker deployments. The ZenML server will automatically back up the database in-memory before every database schema migration and restore it if the migration fails. {% hint style="info" %} The database backup automatically created by the ZenML server is only temporary and only used as an immediate recovery in case of database migration failures. It is not meant to be used as a long-term backup solution. If you need to back up your database for long-term storage, you should use a dedicated backup solution. {% endhint %} Several database backup strategies are supported, depending on where and how the backup is stored. The strategy can be configured by means of the `ZENML_STORE_BACKUP_STRATEGY` environment variable: * `disabled` - no backup is performed * `in-memory` - the database schema and data are stored in memory. This is the fastest backup strategy, but the backup is not persisted across container restarts, so no manual intervention is possible in case the automatic DB recovery fails after a failed DB migration. Adequate memory resources should be allocated to the ZenML server container when using this backup strategy with larger databases. This is the default backup strategy. * `database` - the database is copied to a backup database in the same database server. This requires the `ZENML_STORE_BACKUP_DATABASE` environment variable to be set to the name of the backup database. This backup strategy is only supported for MySQL compatible databases and the user specified in the database URL must have permissions to manage (create, drop, and modify) the backup database in addition to the main database. * `dump-file` - the database schema and data are dumped to a filesystem location inside the ZenML server container. This location can be customized by means of the `ZENML_STORE_BACKUP_DIRECTORY` environment variable. When this strategy is configured, users should mount a host directory in the container and point the `ZENML_STORE_BACKUP_DIRECTORY` variable to where it's mounted inside the container. If a host directory is not mounted, the dump file will be stored in the container's filesystem and will be lost when the container is removed. * `mydumper` - the database is backed up using mydumper/myloader. This requires the `mydumper` and `myloader` utilities to be installed in the ZenML server container. The `ZENML_STORE_MYDUMPER_THREADS`, `ZENML_STORE_MYDUMPER_COMPRESS`, `ZENML_STORE_MYDUMPER_EXTRA_ARGS`, `ZENML_STORE_MYLOADER_THREADS`, and `ZENML_STORE_MYLOADER_EXTRA_ARGS` environment variables can be used to configure the backup and restore processes. * `custom` - use a custom backup engine. This requires the `ZENML_STORE_CUSTOM_BACKUP_ENGINE` environment variable to be set to the class path of the custom backup engine. The class should extend from the `zenml.zen_stores.migrations.backup.base_backup_engine.BaseBackupEngine` base class and be importable from the container image that you are using for the ZenML server. Arguments for the custom backup engine can be passed using the `ZENML_STORE_CUSTOM_BACKUP_ENGINE_CONFIG` environment variable. The following additional rules are applied concerning the creation and lifetime of the backup: * a backup is not attempted if the database doesn't need to undergo a migration (e.g. when the ZenML server is upgraded to a new version that doesn't require a database schema change or if the ZenML version doesn't change at all). * a backup file or database is created before every database migration attempt (i.e. when the container starts). If a backup already exists (i.e. persisted in a mounted host directory or backup database), it is NOT overwritten. Instead, the existing backup is used to rollback the database to the previous state in case the migration fails again. * the persistent backup file or database is cleaned up after the migration is completed successfully or if the database doesn't need to undergo a migration. This includes backups created by previous failed migration attempts. * the persistent backup file or database is NOT cleaned up after a failed migration. This allows the user to manually inspect and/or apply the backup if the automatic recovery fails. {% hint style="warning" %} When running in production where database sizes are large, you should use the `mydumper` backup strategy or write your own custom backup engine. The other backup strategies are not recommended because they are inefficient and will take a long time and consume a lot of resources to handle large databases. {% endhint %} The following example shows how to deploy the ZenML server to use a mounted host directory to persist the database backup file during a database migration: ```shell mkdir mysql-data docker run --name mysql -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=password \ --mount type=bind,source=$PWD/mysql-data,target=/var/lib/mysql \ mysql:8.0 docker run -it -d -p 8080:8080 --name zenml \ --add-host host.docker.internal:host-gateway \ --mount type=bind,source=$PWD/mysql-data,target=/db-dump \ --env ZENML_STORE_URL=mysql://root:password@host.docker.internal/zenml \ --env ZENML_STORE_BACKUP_STRATEGY=dump-file \ --env ZENML_STORE_BACKUP_DIRECTORY=/db-dump \ zenmldocker/zenml-server ``` ## Troubleshooting You can check the logs of the container to verify if the server is up and, depending on where you have deployed it, you can also access the dashboard at a `localhost` port (if running locally) or through some other service that exposes your container to the internet. ### CLI Docker deployments If you used the `zenml login --local --docker` CLI command to deploy the Docker ZenML server, you can check the logs with the command: ```shell zenml logs -f ``` ### Manual Docker deployments If you used the `docker run` command to manually deploy the Docker ZenML server, you can check the logs with the command: ```shell docker logs zenml -f ``` If you used the `docker compose` command to manually deploy the Docker ZenML server, you can check the logs with the command: ```shell docker compose -p zenml logs -f ```
ZenML Scarf
--- ### Book/Getting Started/Deploying Zenml/Deploy With Helm (docs/book/getting-started/deploying-zenml/deploy-with-helm.md) --- description: Deploying ZenML in a Kubernetes cluster with Helm. --- # Deploy with Helm If you wish to manually deploy and manage ZenML in a Kubernetes cluster of your choice, ZenML also includes a Helm chart among its available deployment options. The chart uses standard Kubernetes APIs and works on managed cloud clusters (EKS, GKE, AKS), enterprise platforms like Red Hat OpenShift and SUSE Rancher, and self-managed clusters on-premises or in your own cloud. You can find the chart on this [ArtifactHub repository](https://artifacthub.io/packages/helm/zenml/zenml), along with the templates, default values and instructions on how to install it. Read on to find detailed explanations on prerequisites, configuration, and deployment scenarios. ## Prerequisites You'll need the following: * A Kubernetes cluster (version 1.21 or higher recommended) * Optional, but recommended: a MySQL-compatible database reachable from the Kubernetes cluster (e.g. one of the managed databases offered by Google Cloud, AWS, or Azure). A MySQL server version of 8.0 or higher is required * the [Kubernetes client](https://kubernetes.io/docs/tasks/tools/#kubectl) already installed on your machine and configured to access your cluster * [Helm](https://helm.sh/docs/intro/install/) installed on your machine * Optional: an external Secrets Manager service (e.g. one of the managed secrets management services offered by Google Cloud, AWS, Azure, or HashiCorp Vault). By default, ZenML stores secrets inside the SQL database that it's connected to, but you also have the option of using an external cloud Secrets Manager service if you already happen to use one of those cloud or service providers ## ZenML Helm Configuration You can start by taking a look at the [`values.yaml` file](https://artifacthub.io/packages/helm/zenml/zenml?modal=values) and familiarize yourself with some of the configuration settings that you can customize for your ZenML deployment. In addition to tools and infrastructure, you will also need to collect and [prepare information related to your database](deploy-with-helm.md#collect-information-from-your-sql-database-service) and [information related to your external secrets management service](deploy-with-helm.md#collect-information-from-your-secrets-management-service) to be used for the Helm chart configuration and you may also want to install additional [optional services in your cluster](deploy-with-helm.md#optional-cluster-services). When you are ready, you can proceed to the [installation](deploy-with-helm.md#zenml-helm-installation) section. {% hint style="info" %} **Values key rename:** The top-level `zenml` key in the Helm chart values has been renamed to `server`. Existing values files that use the old `zenml` key continue to work — the chart automatically merges both keys. We recommend using only one key; if both are present, `zenml` values take precedence for overlapping keys. The `zenml` key is deprecated and will be removed in a future release. {% endhint %} ### Collect information from your SQL database service Using an external MySQL-compatible database service is optional, but is recommended for production deployments. If omitted, ZenML will default to using an embedded SQLite database, which has the following limitations: * the SQLite database is not persisted, meaning that it will be lost if the ZenML server pod is restarted or deleted * the SQLite database does not scale horizontally, meaning that you will not be able to use more than one replica at a time for the ZenML server pod If you decide to use an external MySQL-compatible database service, you will need to collect and prepare the following information for the Helm chart configuration: * the hostname and port where the SQL database is reachable from the Kubernetes cluster * the username and password that will be used to connect to the database. It is recommended that you create a dedicated database user for the ZenML server and that you restrict its privileges to only access the database that will be used by ZenML. Enforcing secure SSL connections for the user/database is also recommended. See the [MySQL documentation](https://dev.mysql.com/doc/refman/5.7/en/access-control.html) for more information on how to set up users and privileges. * the name of the database that will be used by ZenML. The database does not have to exist prior to the deployment ( ZenML will create it on the first start). However, you need to create the database if you follow the best practice of restricting database user privileges to only access it. * if you plan on using SSL to secure the client database connection, you may also need to prepare additional SSL certificates and keys: * the TLS CA certificate that was used to sign the server TLS certificate, if you're using a self-signed certificate or signed by a custom certificate authority that is not already trusted by default by most operating systems. * the TLS client certificate and key. This is only needed if you decide to use client certificates for your DB connection (some managed DB services support this, CloudSQL is an example). ### Collect information from your secrets management service Using an externally managed secrets management service like those offered by Google Cloud, AWS, Azure or HashiCorp Vault is optional, but is recommended if you are already using those cloud service providers. If omitted, ZenML will default to using the SQL database to store secrets. If you decide to use an external secrets management service, you will need to collect and prepare the following information for the Helm chart configuration (for supported back-ends only): For the AWS secrets manager: * the AWS region that you want to use to store your secrets * an AWS access key ID and secret access key that provides full access to the AWS secrets manager service. You can create a dedicated IAM user for this purpose, or use an existing user with the necessary permissions. If you deploy the ZenML server in an EKS Kubernetes cluster that is already configured to use implicit authorization with an IAM role for service accounts, you can omit this step. For the Google Cloud secrets manager: * the Google Cloud project ID that you want to use to store your secrets * a Google Cloud service account that has access to the secrets manager service. You can create a dedicated service account for this purpose, or use an existing service account with the necessary permissions. For the Azure Key Vault: * the name of the Azure Key Vault that you want to use to store your secrets * the Azure tenant ID, client ID, and client secret associated with the Azure service principal that will be used to access the Azure Key Vault. You can create a dedicated application service principal for this purpose, or use an existing service principal with the necessary permissions. If you deploy the ZenML server in an AKS Kubernetes cluster that is already configured to use implicit authorization through the Azure-managed identity service, you can omit this step. For the HashiCorp Vault: * the URL of the HashiCorp Vault server * the token that will be used to access the HashiCorp Vault server. ### Optional cluster services It is common practice to install additional infrastructure-related services in a Kubernetes cluster to support the deployment and long-term management of applications. For example: * an Ingress service like [nginx-ingress](https://kubernetes.github.io/ingress-nginx/deploy/) is recommended if you want to expose HTTP services to the internet. An Ingress is required if you want to use secure HTTPS for your ZenML deployment. The alternative is to use a LoadBalancer service to expose the ZenML service using plain HTTP, but this is not recommended for production. * a [cert-manager](https://cert-manager.io/docs/installation/) is recommended if you want to generate and manage TLS certificates for your ZenML deployment. It can be used to automatically provision TLS certificates from a certificate authority (CA) of your choice, such as [Let's Encrypt](https://letsencrypt.org/). As an alternative, the ZenML Helm chart can be configured to auto-generate self-signed or you can generate the certificates yourself and provide them to the Helm chart, but this makes it more difficult to manage the certificates and you need to manually renew them when they expire. ## ZenML Helm Installation ### Configure the Helm chart To use the Helm chart with custom values that includes path to files like the database SSL certificates, you need to pull the chart to your local directory first. You can do this with the following command: ```bash helm pull oci://public.ecr.aws/zenml/zenml --version --untar ``` Next, to customize the Helm chart for your deployment, you should create a copy of the `values.yaml` file that you can find at `./zenml/values.yaml` (let’s call this `custom-values.yaml`). You’ll use this as a template to customize your configuration. Any values that you don’t override you should simply remove from your `custom-values.yaml` file to keep it clean and compatible with future Helm chart releases. In most cases, you’ll need to change the following configuration values in `custom-values.yaml`: * the database configuration, if you mean to use an external database: * the database URL, formatted as `mysql://:@:/` * CA and/or client TLS certificates, if you’re using SSL to secure the connection to the database can be provided in the `database.sslCa`, `database.sslCert` and `database.sslKey` fields as either an inline value or a secret reference (in the latter case, the secret(s) must be created in the same namespace as the ZenML server before the deployment). * the Ingress configuration, if enabled: * enabling TLS * enabling self-signed certificates * configuring the hostname that will be used to access the ZenML server, if different from the IP address or hostname associated with the Ingress service installed in your cluster ### Install the Helm chart Once everything is configured, you can run the following command in the `./zenml` folder to install the Helm chart. ``` helm -n install zenml-server . --create-namespace --values custom-values.yaml ``` ### Connect to the deployed ZenML server Immediately after deployment, the ZenML server needs to be activated before it can be used. The activation process includes creating an initial admin user account and configuring some server settings. You can do this only by visiting the ZenML server URL in your browser and following the on-screen instructions. Connecting your local ZenML client to the server is not possible until the server is properly initialized. The Helm chart should print out a message with the URL of the deployed ZenML server. You can use the URL to open the ZenML UI in your browser. To connect your local client to the ZenML server, you can run: ```bash zenml login https://zenml.example.com:8080 --no-verify-ssl ``` To disconnect from the current ZenML server and revert to using the local default database, use the following command: ```bash zenml logout ``` ## ZenML Helm Deployment Scenarios This section covers some common Helm deployment scenarios for ZenML. ### Minimal deployment The example below is a minimal configuration for a ZenML server deployment that uses a temporary SQLite database and a ClusterIP service that is not exposed to the internet: ```yaml server: ingress: enabled: false ``` Once deployed, you have to use port-forwarding to access the ZenML server and to connect to it from your local machine: ```bash kubectl -n zenml-server port-forward svc/zenml-server 8080:8080 zenml login http://localhost:8080 ``` This is just a simple example only fit for testing and evaluation purposes. For production deployments, you should use an external database and an Ingress service with TLS certificates to secure and expose the ZenML server to the internet. ### Deployment with persistent local database When using a local SQLite database with persistence enabled, you need to configure proper volume permissions. The ZenML Helm chart sets `podSecurityContext.fsGroup: 1000` by default to ensure the ZenML container (running as UID 1000) can write to the persistent volume. Example configuration: ```yaml server: database: persistence: enabled: true size: "10Gi" # storageClassName: "" # Optional: use default storage class if not specified # podSecurityContext.fsGroup is set to 1000 by default # This ensures the container can write to the persistent volume ``` {% hint style="warning" %} If you override `podSecurityContext` in your custom values, ensure that `fsGroup: 1000` is included. Without this setting, the persistent volume will be mounted with root:root ownership, causing permission errors and preventing the ZenML server from starting. {% endhint %} This configuration is also required when using persistent volumes for database backup dumps. See the [Database backup and recovery](deploy-with-helm.md#database-backup-and-recovery) section for more details. ### Basic deployment with local database This deployment use-case still uses a local database, but it exposes the ZenML server to the internet using an Ingress service with TLS certificates generated by the cert-manager and signed by Let's Encrypt. First, you need to install cert-manager and nginx-ingress in your Kubernetes cluster. You can use the following commands to install them with their default configuration: ```bash helm repo add jetstack https://charts.jetstack.io helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx helm repo update helm install cert-manager jetstack/cert-manager --namespace cert-manager --create-namespace --set installCRDs=true helm install nginx-ingress ingress-nginx/ingress-nginx --namespace nginx-ingress --create-namespace ``` Next, you need to create a ClusterIssuer resource that will be used by cert-manager to generate TLS certificates with Let's Encrypt: ```bash cat < privateKeySecretRef: name: letsencrypt-staging solvers: - http01: ingress: class: nginx EOF ``` Finally, you can deploy the ZenML server with the following Helm values: ```yaml server: ingress: enabled: true annotations: cert-manager.io/cluster-issuer: "letsencrypt-staging" tls: enabled: true generateCerts: false ``` > **Note** This use-case exposes ZenML at the root URL path of the IP address or hostname of the Ingress service. You cannot share the same Ingress hostname and URL path for multiple applications. See the next section for a solution to this problem. ### Shared Ingress controller If the root URL path of your Ingress controller is already in use by another application, you cannot use it for ZenML. This section presents three possible solutions to this problem. #### Use a dedicated Ingress hostname for ZenML If you know the IP address of the load balancer in use by your Ingress controller, you can use a service like https://nip.io/ to create a new DNS name associated with it and expose ZenML at this new root URL path. For example, if your Ingress controller has the IP address `192.168.10.20`, you can use a DNS name like `zenml.192.168.10.20.nip.io` to expose ZenML at the root URL path `https://zenml.192.168.10.20.nip.io`. To find the IP address of your Ingress controller, you can use a command like the following: ```bash kubectl -n nginx-ingress get svc nginx-ingress-ingress-nginx-controller -o jsonpath='{.status.loadBalancer.ingress[0].ip}' ``` You can deploy the ZenML server with the following Helm values: ```yaml server: ingress: enabled: true annotations: cert-manager.io/cluster-issuer: "letsencrypt-staging" host: zenml..nip.io tls: enabled: true generateCerts: false ``` > **Note** This method does not work if your Ingress controller is behind a load balancer that uses a hostname mapped to several IP addresses instead of an IP address. #### Use a dedicated Ingress URL path for ZenML If you cannot use a dedicated Ingress hostname for ZenML, you can use a dedicated Ingress URL path instead. For example, you can expose ZenML at the URL path `https:///zenml`. To deploy the ZenML server with a dedicated Ingress URL path, you can use the following Helm values: ```yaml server: ingress: enabled: true annotations: cert-manager.io/cluster-issuer: "letsencrypt-staging" nginx.ingress.kubernetes.io/rewrite-target: /$1 path: /zenml/?(.*) tls: enabled: true generateCerts: false ``` > **Note** This method has one current limitation: the ZenML UI does not support URL rewriting and will not work properly if you use a dedicated Ingress URL path. You can still connect your client to the ZenML server and use it to run pipelines as usual, but you will not be able to use the ZenML UI. #### Use a DNS service to map a different hostname to the Ingress controller This method requires you to configure a DNS service like AWS Route 53 or Google Cloud DNS to map a different hostname to the Ingress controller. For example, you can map the hostname `zenml.` to the Ingress controller's IP address or hostname. Then, simply use the new hostname to expose ZenML at the root URL path. ### Gateway API configuration ZenML also supports Kubernetes Gateway API through `HTTPRoute` resources. This is useful when your platform team manages shared Gateway infrastructure and your application chart only needs to attach routes. Use the following values pattern: ```yaml server: ingress: enabled: false gateway: enabled: true annotations: {} gatewayRef: name: zenml-gateway namespace: gateway-infra sectionName: https-backend host: zenml.example.com path: / ``` Both `server.ingress.enabled` and `server.gateway.enabled` can be set to `true` simultaneously. This is useful during migration periods: the chart renders both an Ingress and an HTTPRoute, allowing traffic to flow through whichever controller DNS points to. Kubernetes controllers only act on resources they own — an ingress controller ignores HTTPRoutes and a Gateway controller ignores Ingresses — so enabling both creates no conflict. For a full migration flow (prerequisites, rollout strategy, TLS options, DNS cutover, and rollback), see [Migrate to Gateway API](migrate-to-gateway-api.md). ### Secret Store configuration Unless explicitly disabled or configured otherwise, the ZenML server will use the SQL database as [a secrets store backend](secret-management.md) where secret values are stored. If you want to use an external secrets management service like the AWS Secrets Manager, GCP Secrets Manager, Azure Key Vault, HashiCorp Vault or even your custom Secrets Store back-end implementation instead, you need to configure it in the Helm values. Depending on where you deploy your ZenML server and how your Kubernetes cluster is configured, you will also need to provide the credentials needed to access the secrets management service API. > **Important:** If you are updating the configuration of your ZenML Server deployment to use a different secrets store back-end or location, you should follow [the documented secrets migration strategy](secret-management.md#secrets-migration-strategy) to minimize downtime and to ensure that existing secrets are also properly migrated. {% tabs %} {% tab title="AWS" %} **Using the SQL database as a secrets store backend (default)** The SQL database is used as the default location where the ZenML secrets store keeps the secret values. You only need to configure these options if you want to change the default behavior. It is particularly recommended to enable encryption at rest for the SQL database if you plan on using it as a secrets store backend. You'll have to configure the secret key used to encrypt the secret values. If not set, encryption will not be used and passwords will be stored unencrypted in the database. This value should be set to a random string with a recommended length of at least 32 characters, e.g.: * generate a random string with Python: ```python from secrets import token_hex token_hex(32) ``` * or with OpenSSL: ```shell openssl rand -hex 32 ``` * then configure it in the Helm values: ```yaml server: # ... # Secrets store settings. This is used to store centralized secrets. secretsStore: # The type of the secrets store type: sql # Configuration for the SQL secrets store sql: encryptionKey: 0f00e4282a3181be32c108819e8a860a429b613e470ad58531f0730afff64545 ``` > **Important:** If you configure encryption for your SQL database secrets store, you should keep the `encryptionKey` value somewhere safe and secure, as it will always be required by the ZenML Server to decrypt the secrets in the database. If you lose the encryption key, you will not be able to decrypt the secrets anymore and will have to reset them. {% endtab %} {% tab title="AWS" %} **Using the AWS Secrets Manager as a secrets store backend** The AWS Secrets Store uses the ZenML AWS Service Connector under the hood to authenticate with the AWS Secrets Manager API. This means that you can use any of the [authentication methods supported by the AWS Service Connector](https://docs.zenml.io/stacks/service-connectors/connector-types/aws-service-connector#authentication-methods) to authenticate with the AWS Secrets Manager API. The minimum set of permissions that must be attached to the implicit or configured AWS credentials are: `secretsmanager:CreateSecret`, `secretsmanager:GetSecretValue`, `secretsmanager:DescribeSecret`, `secretsmanager:PutSecretValue`, `secretsmanager:TagResource` and `secretsmanager:DeleteSecret` and they must be associated with secrets that have a name starting with `zenml/` in the target region and account. The following IAM policy example can be used as a starting point: ``` { "Version": "2012-10-17", "Statement": [ { "Sid": "ZenMLSecretsStore", "Effect": "Allow", "Action": [ "secretsmanager:CreateSecret", "secretsmanager:GetSecretValue", "secretsmanager:DescribeSecret", "secretsmanager:PutSecretValue", "secretsmanager:TagResource", "secretsmanager:DeleteSecret" ], "Resource": "arn:aws:secretsmanager:::secret:zenml/*" } ] } ``` Example configuration for the AWS Secrets Store: ```yaml server: # ... # Secrets store settings. This is used to store centralized secrets. secretsStore: # Set to false to disable the secrets store. enabled: true # The type of the secrets store type: aws # Configuration for the AWS Secrets Manager secrets store aws: # The AWS Service Connector authentication method to use. authMethod: secret-key # The AWS Service Connector configuration. authConfig: # The AWS region to use. This must be set to the region where the AWS # Secrets Manager service that you want to use is located. region: us-east-1 # The AWS credentials to use to authenticate with the AWS Secrets aws_access_key_id: aws_secret_access_key: ``` {% endtab %} {% tab title="GCP" %} **Using the GCP Secrets Manager as a secrets store backend** The GCP Secrets Store uses the ZenML GCP Service Connector under the hood to authenticate with the GCP Secrets Manager API. This means that you can use any of the [authentication methods supported by the GCP Service Connector](https://docs.zenml.io/stacks/service-connectors/connector-types/gcp-service-connector#authentication-methods) to authenticate with the GCP Secrets Manager API. The minimum set of permissions that must be attached to the implicit or configured GCP credentials are as follows: * `secretmanager.secrets.create` for the target GCP project (i.e. no condition on the name prefix) * `secretmanager.secrets.get`, `secretmanager.secrets.update`, `secretmanager.versions.access`, `secretmanager.versions.add` and `secretmanager.secrets.delete` for the target GCP project and for secrets that have a name starting with `zenml-` This can be achieved by creating two custom IAM roles and attaching them to the principal (e.g. user or service account) that will be used to access the GCP Secrets Manager API with a condition configured when attaching the second role to limit access to secrets with a name prefix of `zenml-`. The following `gcloud` CLI command examples can be used as a starting point: ```bash gcloud iam roles create ZenMLServerSecretsStoreCreator \ --project \ --title "ZenML Server Secrets Store Creator" \ --description "Allow the ZenML Server to create new secrets" \ --stage GA \ --permissions "secretmanager.secrets.create" gcloud iam roles create ZenMLServerSecretsStoreEditor \ --project \ --title "ZenML Server Secrets Store Editor" \ --description "Allow the ZenML Server to manage its secrets" \ --stage GA \ --permissions "secretmanager.secrets.get,secretmanager.secrets.update,secretmanager.versions.access,secretmanager.versions.add,secretmanager.secrets.delete" gcloud projects add-iam-policy-binding \ --member serviceAccount: \ --role projects//roles/ZenMLServerSecretsStoreCreator \ --condition None # NOTE: use the GCP project NUMBER, not the project ID in the condition gcloud projects add-iam-policy-binding \ --member serviceAccount: \ --role projects//roles/ZenMLServerSecretsStoreEditor \ --condition 'title=limit_access_zenml,description="Limit access to secrets with prefix zenml-",expression=resource.name.startsWith("projects//secrets/zenml-")' ``` Example configuration for the GCP Secrets Store: ``` /* Detailed source-code truncated for AI context efficiency. */ ``` {% endtab %} {% tab title="Azure" %} **Using the Azure Key Vault as a secrets store backend** The Azure Secrets Store uses the ZenML Azure Service Connector under the hood to authenticate with the Azure Key Vault API. This means that you can use any of the [authentication methods supported by the Azure Service Connector](https://docs.zenml.io/stacks/service-connectors/connector-types/azure-service-connector#authentication-methods) to authenticate with the Azure Key Vault API. Example configuration for the Azure Key Vault Secrets Store: ```yaml server: # ... # Secrets store settings. This is used to store centralized secrets. secretsStore: # Set to false to disable the secrets store. enabled: true # The type of the secrets store type: azure # Configuration for the Azure Key Vault secrets store azure: # The name of the Azure Key Vault. This must be set to point to the Azure # Key Vault instance that you want to use. key_vault_name: # The Azure Service Connector authentication method to use. authMethod: service-principal # The Azure Service Connector configuration. authConfig: # The Azure application service principal credentials to use to # authenticate with the Azure Key Vault API. client_id: client_secret: tenant_id: ``` {% endtab %} {% tab title="Hashicorp" %} **Using the HashiCorp Vault as a secrets store backend** To use the HashiCorp Vault service as a Secrets Store back-end, it must be configured in the Helm values: ```yaml server: # ... # Secrets store settings. This is used to store centralized secrets. secretsStore: # Set to false to disable the secrets store. enabled: true # The type of the secrets store type: hashicorp # Configuration for the HashiCorp Vault secrets store hashicorp: # The url of the HashiCorp Vault server to use vault_addr: https://vault.example.com # The token used to authenticate with the Vault server vault_token: # The Vault Enterprise namespace. Not required for Vault OSS. vault_namespace: # The mount point to use for the HashiCorp Vault secrets store. If not set, the default value of `secret` will be used. mount_point: ``` {% endtab %} {% tab title="Custom" %} **Using a custom secrets store backend implementation** You have the option of using [a custom implementation of the secrets store API](secret-management.md) as your secrets store back-end. This must come in the form of a class derived from `zenml.zen_stores.secrets_stores.base_secrets_store.BaseSecretsStore`. This class must be importable from within the ZenML server container, which means you most likely need to build a custom container image that contains the class. Then, you can configure the Helm values to use your custom secrets store as follows: ``` /* Detailed source-code truncated for AI context efficiency. */ ``` {% endtab %} {% endtabs %} #### Backup secrets store [A backup secrets store](secret-management.md#backup-secrets-store) back-end may be configured for high-availability and backup purposes. or as an intermediate step in the process of [migrating secrets to a different external location or secrets manager provider](secret-management.md#secrets-migration-strategy). To configure a backup secrets store in the Helm chart, use the same approach and instructions documented for the primary secrets store, but using the `backupSecretsStore` configuration section instead of `secretsStore`, e.g.: ```yaml server: # ... # Backup secrets store settings. This is used as a backup for the primary # secrets store. backupSecretsStore: # Set to true to enable the backup secrets store. enabled: true # The type of the backup secrets store type: aws # Configuration for the AWS Secrets Manager backup secrets store aws: # The AWS Service Connector authentication method to use. authMethod: secret-key # The AWS Service Connector configuration. authConfig: # The AWS region to use. This must be set to the region where the AWS # Secrets Manager service that you want to use is located. region: us-east-1 # The AWS credentials to use to authenticate with the AWS Secrets aws_access_key_id: aws_secret_access_key: ``` ### Database backup and recovery An automated database backup and recovery feature is enabled by default for all Helm deployments. During Helm updates, the ZenML server will automatically back up the database before upgrading it and restore it if the upgrade fails. {% hint style="info" %} The database backup automatically created by the ZenML server is only temporary and only used as an immediate recovery in case of database migration failures. It is not meant to be used as a long-term backup solution. If you need to back up your database for long-term storage, you should use a dedicated backup solution. {% endhint %} Several database backup strategies are supported, depending on where and how the backup is stored. The strategy can be configured by means of the `server.database.backupStrategy` Helm value: * `disabled` - no backup is performed * `in-memory` - the database schema and data are stored in memory. This is the fastest backup strategy, but the backup is not persisted across pod restarts, so no manual intervention is possible in case the automatic DB recovery fails after a failed DB migration. Adequate memory resources should be allocated to the ZenML server pod when using this backup strategy with larger databases. This is the default backup strategy. * `database` - the database is copied to a backup database in the same database server. This requires the `backupDatabase` option to be set to the name of the backup database. This backup strategy is only supported for MySQL compatible databases and the user specified in the database URL must have permissions to manage (create, drop, and modify) the backup database in addition to the main database. * `dump-file` - the database schema and data are dumped to a file local to the database initialization and upgrade job. Users may optionally configure a persistent volume where the dump file will be stored by setting the `backupPVStorageSize` and optionally the `backupPVStorageClass` options. If a persistent volume is not configured, the dump file will be stored in an emptyDir volume, which is not persisted. If configured, the user is responsible for deleting the resulting PVC when uninstalling the Helm release. * `mydumper` - the database is backed up using mydumper/myloader. This requires the `mydumper` and `myloader` utilities to be installed in the ZenML server container. The `mydumperThreads`, `mydumperCompress`, `mydumperExtraArgs`, `myloaderThreads`, and `myloaderExtraArgs` options can be used to configure the backup and restore processes. * `custom` - use a custom backup engine. This requires the `customBackupEngine` option to be set to the class path of the custom backup engine. The class should extend from the `zenml.zen_stores.migrations.backup.base_backup_engine.BaseBackupEngine` base class and be importable from the container image that you are using for the ZenML server. Arguments for the custom backup engine can be passed using the `customBackupEngineConfig` option. > **NOTE:** You should also set the `podSecurityContext.fsGroup` option if you are using a persistent volume to store the dump file. {% hint style="warning" %} When running in production where database sizes are large, you should use the `mydumper` backup strategy or write your own custom backup engine. The other backup strategies are not recommended because they are inefficient and will take a long time and consume a lot of resources to handle large databases. {% endhint %} The following additional rules are applied concerning the creation and lifetime of the backup: * a backup is not attempted if the database doesn't need to undergo a migration (e.g. when the ZenML server is upgraded to a new version that doesn't require a database schema change or if the ZenML version doesn't change at all). * a backup file or database is created before every database migration attempt (i.e. during every Helm upgrade). If a backup already exists (i.e. persisted in a persistent volume or backup database), it is NOT overwritten. Instead, the existing backup is used to rollback the database to the previous state in case the migration fails again. * the persistent backup file or database is cleaned up after the migration is completed successfully or if the database doesn't need to undergo a migration. This includes backups created by previous failed migration attempts. * the persistent backup file or database is NOT cleaned up after a failed migration. This allows the user to manually inspect and/or apply the backup if the automatic recovery fails. The following example shows how to configure the ZenML server to use a persistent volume to store the database dump file: ```yaml server: # ... database: url: "mysql://admin:password@my.database.org:3306/zenml" # Configure the database backup strategy backupStrategy: dump-file backupPVStorageSize: 1Gi podSecurityContext: fsGroup: 1000 # if you're using a PVC for backup, this should necessarily be set. ``` ### Observability and OpenTelemetry You can configure server log output and OpenTelemetry export through dedicated Helm values. For example: ```yaml server: logging: verbosity: # default is info format: # default is console colorsDisabled: # default is false openTelemetry: endpoint: http://otel-collector:4318 serviceName: zenml-server ``` `server.logging.verbosity` sets the ZenML server log level. The legacy `server.debug` option is still supported for compatibility and forces the server log level to `debug` when set to `true`, but new deployments should use `server.logging.verbosity` instead. `server.logging.format` can be set to `console` (default), `json`, or a valid Python `%`-style logging format string. This controls the server container stdout/stderr output, i.e. the logs that Kubernetes pod log collectors scrape. The older `ZENML_LOGGING_FORMAT` environment variable is still supported through `server.environment` as a deprecated alias but will be removed in a future version. Setting `server.openTelemetry.endpoint` enables server OpenTelemetry instrumentation and exports traces, metrics, and logs using OTLP/HTTP. The standard `OTEL_EXPORTER_OTLP_ENDPOINT` environment variable is also supported as a fallback through `server.environment`. Configure the base collector endpoint and ZenML appends `/v1/traces`, `/v1/metrics`, and `/v1/logs` for each signal. You can override individual signal endpoints with the standard `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`, `OTEL_EXPORTER_OTLP_METRICS_ENDPOINT`, and `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT` variables, or with the matching `ZENML_SERVER_OTEL_EXPORTER_OTLP__ENDPOINT` names. If no base or per-signal endpoint is set, server OpenTelemetry instrumentation is disabled. Standard OTLP headers, timeout, and compression variables are handled by the OpenTelemetry Python exporters. OTLP/gRPC protocol variables are not supported because the server configures OTLP/HTTP exporters directly. You can read more about the OpenTelemetry environment variables and SDK configuration [here](https://opentelemetry.io/docs/languages/sdk-configuration/). The server logs export to the OpenTelemetry collector is independent of `ZENML_CONSOLE_LOGGING_FORMAT`: logs are exported as OTLP records with structured attributes derived from the underlying log record, not as console-formatted text or JSON. ### Custom CA Certificates If you need to connect to services using HTTPS with certificates signed by custom Certificate Authorities (e.g., self-signed certificates), you can configure custom CA certificates. There are two ways to provide custom CA certificates: 1. Direct injection in values.yaml: ```yaml server: certificates: customCAs: - name: "my-custom-ca" certificate: | -----BEGIN CERTIFICATE----- MIIDXTCCAkWgAwIBAgIJAJC1HiIAZAiIMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV ... -----END CERTIFICATE----- ``` 2. Reference existing Kubernetes secrets: ```yaml server: certificates: secretRefs: - name: "my-secret" key: "ca.crt" ``` The certificates will be installed in the server container, allowing it to securely connect to services using these custom CA certificates. ### HTTP Proxy Configuration If your environment requires a proxy for external connections, you can configure it using: ```yaml server: proxy: enabled: true httpProxy: "http://proxy.example.com:8080" httpsProxy: "http://proxy.example.com:8080" # Additional hostnames/domains/IPs/CIDRs to exclude from proxying additionalNoProxy: - "internal.example.com" - "10.0.0.0/8" ``` By default, the following hostnames/domains are excluded from proxying: - `localhost`, `127.0.0.1`, `::1` (IPv4 and IPv6 localhost) - `fe80::/10` (IPv6 link-local addresses) - `.svc` and `.svc.cluster.local` (Kubernetes service DNS domains) - The hostname from `server.serverURL` if configured - The ingress hostname (`server.ingress.host`) if configured - Internal service names used for communication between components You can add additional exclusions using the `additionalNoProxy` list. The NO_PROXY environment variable accepts: - Hostnames (e.g., "zenml.example.com") - Domain names with leading dot for wildcards (e.g., ".example.com") - IPv4 addresses (e.g., "10.0.0.1") - IPv4 ranges in CIDR notation (e.g., "10.0.0.0/8") - IPv6 addresses (e.g., "::1") - IPv6 ranges in CIDR notation (e.g., "fe80::/10")
ZenML Scarf
--- ### Book/Getting Started/Deploying Zenml/Live Event Streaming (docs/book/getting-started/deploying-zenml/live-event-streaming.md) --- description: Enable live event streaming on the ZenML server and consume the HTTP/SSE feed. --- # Live event streaming ZenML can send events published from inside a pipeline run to any HTTP client subscribed to that run. Use it for LLM token streaming, progress updates, live dashboards, and similar cases where you need to surface intermediate output from a running step. This page covers operating streaming on the server: how to turn it on, which broker to pick, how to consume the SSE feed, and the wire contract clients code against. For the producer-side Python API (calling `zenml.streaming.publish()` from a step), see [Streaming Events](../../how-to/steps-pipelines/streaming_events.md). {% hint style="warning" %} Streaming is best-effort, not persistent storage. Events are size-capped, can be dropped under load, and disappear when the broker's retention window elapses. **Once an event is lost it is gone — there is no secondary store, no replay endpoint, and no fallback.** If you need to keep something, write it as run metadata or an artifact from the step. {% endhint %} ## Enable streaming Streaming is off by default. The single setting that enables it is `stream_broker_implementation_source` on the server config (or `streaming.streamBrokerImplementationSource` on the Helm chart). Until that field is set, the streaming endpoints return `501 Not Implemented`, producer-side `publish()` calls are dropped without sending HTTP, and the server does not open a broker connection. ### Pick a broker The Redis Streams broker (`zenml.zen_server.streaming.brokers.redis_streams.RedisStreamsBroker`) ships with ZenML. It requires Redis 5+ and the `redis` Python extra (`pip install 'zenml[server-streaming]'`). It namespaces stream keys by deployment ID so multiple ZenML servers can share a Redis cluster without colliding. ### Configure with Helm ```yaml server: streaming: streamBrokerImplementationSource: zenml.zen_server.streaming.brokers.redis_streams.RedisStreamsBroker environment: ZENML_REDIS_BROKER_URL: redis://my-redis.svc.cluster.local:6379/0 ``` The chart installs an SSE-only Gateway API `HTTPRoute` rule that disables Envoy's default 15-second request timeout for clients that send `Accept: text/event-stream` against the `/api/v1/runs/` tree. Browsers' `EventSource` and the ZenML server's own emitted frames match this condition. Custom clients that send a quality-list `Accept` header fall through to the default rule and are cut off at 15 seconds. ### Configure with environment variables If you deploy without the chart, set the same field via env var: ``` ZENML_SERVER_STREAM_BROKER_IMPLEMENTATION_SOURCE=zenml.zen_server.streaming.brokers.redis_streams.RedisStreamsBroker ZENML_REDIS_BROKER_URL=redis://... ``` Behind your own ingress, disable request timeouts and any response buffering for the SSE path. The server emits `X-Accel-Buffering: no` and `Cache-Control: no-cache, no-store, no-transform` to cover common intermediaries, but you may need to set the same on your proxy. ### Server config reference | Field (`ServerConfiguration`) | Helm key (`server.streaming.*`) | Default | Notes | |---|---|---|---| | `stream_broker_implementation_source` | `streamBrokerImplementationSource` | unset | Setting this enables streaming. | | `streaming_heartbeat_seconds` | `heartbeatSeconds` | `30.0` | SSE heartbeat interval. | | `streaming_max_subscribers_per_stream` | `maxSubscribersPerStream` | `100` | Maximum simultaneous subscribers per run. The 101st subscriber receives `503`. | | `streaming_broadcaster_idle_grace_seconds` | `broadcasterIdleGraceSeconds` | `30.0` | How long the server keeps a stream's broker reader running after the last subscriber disconnects, so a quick reconnect does not have to re-establish it. | ### Redis settings Connection settings are read from the shared `ZENML_REDIS_` prefix, so the same Redis instance can be used by the streaming broker and by other ZenML components that talk to Redis. Settings specific to the streaming broker are read from `ZENML_REDIS_STREAMS_BROKER_` and override the shared values when set. | Variable | Default | Notes | |---|---|---| | `ZENML_REDIS_BROKER_URL` | — | `redis://...` or `rediss://...`. Required. | | `ZENML_REDIS_MAX_CONNECTIONS` | `10` | Connection pool size. Increase if you expect many concurrent runs. Override per component with `ZENML_REDIS_STREAMS_BROKER_MAX_CONNECTIONS`. | | `ZENML_REDIS_SOCKET_TIMEOUT` | `2.0` | Per-call timeout in seconds. | | `ZENML_REDIS_STREAMS_BROKER_MAX_STREAM_LENGTH` | `10000` | Maximum entries retained per run (`XADD MAXLEN ~`). | | `ZENML_REDIS_STREAMS_BROKER_STREAM_TTL_SECONDS` | `3600` | TTL on each run's stream, refreshed on every publish. | At startup, the server runs a single connectivity check against the broker. If the configured Redis URL is wrong or the host is unreachable, the server fails to boot and reports the error, instead of returning `503` on every later request. ## Consume the stream Streams are exposed as [Server-Sent Events (SSE)](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events) on: ``` GET /api/v1/runs/{pipeline_run_id}/events/stream Accept: text/event-stream Authorization: Bearer ``` Consuming requires `READ` permission on the run (the same permission as viewing it in the dashboard). Publishing — covered on the [client SDK page](../../how-to/steps-pipelines/streaming_events.md) — requires `UPDATE`. ### Browser ```javascript const es = new EventSource( `/api/v1/runs/${runId}/events/stream`, { withCredentials: true } ); es.addEventListener("event", (e) => console.log(JSON.parse(e.data))); es.addEventListener("end", () => es.close()); ``` `EventSource` automatically reconnects with the standard `Last-Event-ID` header, so transient drops resume after the last received event (see [Resuming after a disconnect](#resuming-after-a-disconnect)). ### Command line ```bash curl -N -H "Accept: text/event-stream" \ -H "Authorization: Bearer $ZENML_TOKEN" \ "$ZENML_URL/api/v1/runs/$RUN_ID/events/stream" ``` `-N` disables curl's output buffering so frames arrive as the server writes them. ## SSE wire format Each frame the server emits has the form: ``` id: event: data: ``` Reserved event names: | `event:` | Meaning | |----------|---------| | `event` (default) or any custom `kind` | A producer-published payload. `data` is the JSON-serialized `StreamEvent`. | | `end` | The run has reached a terminal state. The server will close the connection. | | `gap` | The subscriber may have missed events between the last `id` and now. Reasons: `outage` (broker reachability/reader error), `overflow` (per-subscriber queue full), `shutdown` (server is shutting down). | | `error` | A transient server-side error. The client should reconnect with `Last-Event-ID`. | | `cursor` | A frame the server emits for filtered-out events and for forward-compatible unknown frame types. Carries an `id:` so `Last-Event-ID` advances. `data` is `{}` for filtered events and `{"unknown_type": ""}` for frames the server didn't recognize (useful for spotting producer-vs-server version mismatches). Clients can ignore both. | Heartbeats arrive as comment frames (`: ping\n\n`) every `streaming_heartbeat_seconds` (default 30 s) and require no client handling. Comments do not dispatch events (they will not trigger any `addEventListener` callback), which is why filtered or unknown frames use `event: cursor` instead. ### Filtering The SSE endpoint accepts three multi-value query parameters that restrict which events are delivered. Each parameter accepts repeated values; within a parameter the values are ORed, and the parameters combine with AND. Filtered-out events still advance the server cursor via `cursor` frames — clients can reconnect with `Last-Event-ID` and will not see them replayed. | Parameter | Matches | Example | |---|---|---| | `kinds` | `StreamEvent.kind` | `?kinds=token&kinds=progress` | | `step_names` | `StreamEvent.step_name` (the invocation id of the step) | `?step_names=summarize` | | `correlation_ids` | `StreamEvent.correlation_id` (producer-set sub-flow tag) | `?correlation_ids=gen-42` | Combined: ``` GET /api/v1/runs/{run}/events/stream?kinds=token&step_names=summarize ``` returns only `token`-kind events from the `summarize` step. ### Resuming after a disconnect The server honors the standard SSE `Last-Event-ID` request header on reconnect. Browsers' `EventSource` sends it automatically. Other clients should track the last `id:` they received and send it back to resume: ``` GET /api/v1/runs/{run}/events/stream Last-Event-ID: ``` Clients that cannot set request headers (some embedded environments) can use the `?since=` query parameter as an equivalent — both specify the starting cursor. If both are sent, the header wins. If the cursor is older than the broker's retention window, the missing events are not redelivered and the server does not signal that loss happened. The next read returns whatever is still retained. Subscribers can also attach to a run that has already terminated. The server replays the broker's retained event history (up to the retention TTL) and then closes with an `end` event. Once the TTL elapses the history is gone and the subscribe returns just `end`. **Lost events are unrecoverable.** Streaming is best-effort: events never leave the broker for any durable store, and ZenML keeps no secondary copy. Artifacts and run metadata persist the run's *outcomes*, not the intermediate stream. Plan accordingly: - If you need replay, write the relevant state as an artifact or metadata entry from the step. - If your consumer maintains UI state derived from the stream (running aggregates, scrollback), design it to tolerate gaps — drop accumulated state and re-derive from new events going forward, rather than expecting to "fetch what you missed". ## Delivery semantics | Property | What you get | |----------|--------------| | Ordering | Per-run, monotonic by broker id. | | Duplicates | Within a single connection, each event id is delivered at most once. On reconnect with `Last-Event-ID`, the server resumes strictly after the last seen id, so events are not re-delivered. There is no producer-side retry on publish failure, so producers cannot introduce duplicates. Subscribers should still dedupe on event `id` defensively. | | Loss | Events can be lost via producer-side queue overflow (4 096 per process), server-side publish failure (logged, not retried), broker-side `MAXLEN` truncation, retention TTL, or per-subscriber queue overflow. Per-subscriber overflow emits a `gap: overflow` frame; the other loss modes are silent. **Lost events are not recoverable from any other source** — ZenML keeps no durable copy of the stream. | | Retention | `ZENML_REDIS_STREAMS_BROKER_STREAM_TTL_SECONDS` (default 1 h after the last publish). | | Multi-replica | The broker delivers events across replicas, keyed by deployment id. | | Persistence | None. Use run metadata or artifacts if you need durable storage. | ## Limits - Per-event payload is limited to **64 KiB** on the wire envelope. - The producer-side queue holds up to **4 096 events per process**. When full, the oldest queued event is dropped to make room. - The broker stream is limited per run (default 10 000 entries). Subscribers that fall too far behind will silently miss the trimmed events — there is no wire-level signal for retention loss, and trimmed events are not stored anywhere recoverable. - The per-run subscriber limit is `streaming_max_subscribers_per_stream` (default 100). The 101st connection receives `503 Service Unavailable` with `Retry-After: 5`. ## Troubleshooting **SSE connections drop after 15 seconds behind an ingress.** Your proxy is enforcing a request timeout. The bundled Helm chart configures the Gateway API `HTTPRoute` to disable it for SSE; if you run a custom ingress, do the same for `/api/v1/runs/.../events/stream` (or any path where the request carries `Accept: text/event-stream`). **Subscribers report missing events on reconnect.** The subscriber is falling behind the broker's retention window. The missed events are gone — they are not stored anywhere durable. Either reduce the producer rate, raise `ZENML_REDIS_STREAMS_BROKER_MAX_STREAM_LENGTH`, or have the subscriber drop accumulated stream-derived state on every `gap` and re-derive from new events going forward. **No events arrive.** Confirm streaming is enabled (the streaming endpoints return something other than `501`), check the consumer has `READ` on the run, and verify the producer is calling `zenml.streaming.publish()` from inside a step or pipeline context. Calls made outside such a context are dropped. **`501 Not Implemented` on the streaming endpoints.** `stream_broker_implementation_source` is unset. Once it is configured, both the publish endpoint and the SSE endpoint become available. They return `501` together when streaming is disabled. **Server boot fails with "Stream broker startup probe failed".** The configured broker cannot reach its backing store. For Redis, check `ZENML_REDIS_BROKER_URL`, TLS settings, and network reachability from the server pod. --- ### Book/Getting Started/Deploying Zenml/Migrate To Gateway Api (docs/book/getting-started/deploying-zenml/migrate-to-gateway-api.md) --- description: Migrate ZenML Helm deployments from Ingress to Kubernetes Gateway API. --- # Migrate to Gateway API Gateway API is the Kubernetes networking successor to the legacy Ingress model. If you currently expose ZenML through `server.ingress`, this guide shows how to migrate to `server.gateway` with minimal downtime. ## Why migrate - Gateway API is the long-term Kubernetes direction for north-south traffic. - It provides clearer separation between shared infrastructure ownership (`Gateway`) and app routing ownership (`HTTPRoute`). - It maps well to multi-tenant environments where platform teams manage shared gateways and application teams manage routes. ## Prerequisites - Kubernetes 1.26+ - A Gateway API implementation with `v1` CRDs available (for example, `gateway.networking.k8s.io/v1` for `Gateway` and `HTTPRoute`; Envoy Gateway, Istio, NGINX Gateway Fabric, GKE Gateway) - A `Gateway` resource already provisioned and reachable using the installed Gateway API `v1` CRDs - DNS access for cutover planning > ZenML renders `HTTPRoute` resources with `apiVersion: gateway.networking.k8s.io/v1`. Clusters that only have older Gateway API CRDs installed (for example, `v1beta1`) must upgrade those CRDs before enabling `server.gateway`. ## Migration overview 1. Install or verify your Gateway API implementation. 2. Create a shared `Gateway` (platform-managed). 3. Enable `server.gateway` in ZenML Helm values (alongside existing `server.ingress` for zero-downtime migration). 4. Deploy and verify `HTTPRoute` status and application health. 5. Perform DNS cutover (if using a new load balancer). 6. Disable `server.ingress` after stabilization. ## Helm values migration ### Step 1: Enable both (parallel period) Both `server.ingress.enabled` and `server.gateway.enabled` can be `true` simultaneously. The chart renders both an Ingress and an HTTPRoute. Kubernetes controllers only act on resources they own — an ingress controller ignores HTTPRoutes and a Gateway controller ignores Ingresses — so enabling both creates no conflict. ```yaml server: ingress: enabled: true # keep Ingress active during migration host: zenml.example.com gateway: enabled: true # also create HTTPRoute gatewayRef: name: zenml-gateway namespace: gateway-infra sectionName: https-backend host: zenml.example.com path: / ``` At this point, traffic still flows through the ingress controller (DNS points to its load balancer). The HTTPRoute is ready on the Gateway side but idle. ### Step 2: DNS cutover Update DNS to point to the Gateway's load balancer. Both the Ingress and HTTPRoute exist, so the transition is seamless — the ingress controller handles traffic until DNS propagates, then the Gateway takes over. ### Step 3: Disable Ingress (cleanup) After DNS is stable and traffic is flowing through the Gateway, disable Ingress: ```yaml server: ingress: enabled: false gateway: enabled: true gatewayRef: name: zenml-gateway namespace: gateway-infra sectionName: https-backend host: zenml.example.com path: / ``` ## Gateway implementation examples ### Envoy Gateway Use a shared `GatewayClass` and `Gateway`, then attach ZenML `HTTPRoute` resources via `parentRefs`. ### Istio Use Istio's Gateway API support (managed Gateway + HTTPRoute) and keep ZenML route ownership in tenant namespaces. ### NGINX Gateway Fabric Deploy NGINX Gateway Fabric and configure a shared `Gateway` listener; point ZenML `gatewayRef` to that shared gateway. ### GKE Gateway Create a GKE-managed `Gateway` and map your external DNS hostnames to the provisioned load balancer. ## Custom annotations You can add custom annotations to the `HTTPRoute` resource: ```yaml server: gateway: enabled: true annotations: my-annotation: my-value gatewayRef: name: zenml-gateway namespace: gateway-infra ``` This is useful for attaching Envoy-specific policies, adding metadata for service mesh integration, or custom routing labels. ## TLS options You can terminate TLS in one of two common ways: - **Gateway-managed TLS** (e.g. cert-manager certificate references in gateway listeners) - **Cloud load balancer TLS termination** (e.g. ACM on AWS NLB) with HTTP forwarded to Gateway listeners Choose the model that matches your platform standards. ZenML only needs the correct external hostname and reachable `Gateway` listener. ## Validation checklist - `kubectl get gateway -A` shows the target gateway as accepted/programmed - `kubectl get httproute -A` shows ZenML routes attached/accepted - `curl https:///health` returns success - Optional: test large uploads and UI/API login flows ## DNS cutover If migration introduces a new load balancer: 1. Lower DNS TTL (for example 60s) before cutover. 2. Update CNAME/A records to the new load balancer. 3. Monitor health, latency, and error rate. 4. Restore normal TTL after stabilization. ## Rollback Since both Ingress and Gateway can be enabled simultaneously, rollback during the parallel period is simply a DNS change — point DNS back to the ingress controller's load balancer. No Helm changes needed. If you've already disabled Ingress (Step 3), re-enable it: ```yaml server: ingress: enabled: true host: zenml.example.com gateway: enabled: false ``` Re-deploy, repoint DNS to the ingress controller's load balancer, and verify `/health` and login. Keep both infrastructure paths available during stabilization to ensure a low-risk rollback. --- ### Book/Getting Started/Deploying Zenml/Secret Management (docs/book/getting-started/deploying-zenml/secret-management.md) --- description: Configuring the secrets store. --- # Secret management ## Centralized secrets store ZenML provides a centralized secrets management system that allows you to register and manage secrets in a secure way. The metadata of the ZenML secrets (e.g. name, ID, owner, scope etc.) is always stored in the ZenML server database, while the actual secret values are stored and managed separately, through the ZenML Secrets Store. This allows for a flexible deployment strategy that meets the security and compliance requirements of your organization. In a local ZenML deployment, secret values are also stored in the local SQLite database. When connected to a remote ZenML server, the secret values are stored in the secrets management back-end that the server's Secrets Store is configured to use, while all access to the secrets is done through the ZenML server API.

Basic Secrets Store Architecture

Currently, the ZenML server can be configured to use one of the following supported secrets store back-ends: * the same SQL database that the ZenML server is using to store secrets metadata as well as other managed objects such as pipelines, stacks, etc. This is the default option. * the AWS Secrets Manager * the GCP Secret Manager * the Azure Key Vault * the HashiCorp Vault * a custom secrets store back-end implementation is also supported ## Secret value size limits A ZenML **secret** is a name plus a map of string keys to string values. You can reason about its size as the sum of the UTF-8 size of every key name and every value in the map, not a per-key limit. Non-Latin text usually needs more bytes per character than English in that total. When the server uses the default SQL (server database) secrets store, ZenML enforces a 64 KiB (65,536 byte) maximum on that total, and the server rejects larger payloads. For AWS, GCP, Azure, HashiCorp Vault, a custom store, or any other non-default back end, the effective limit is whatever that system allows for the stored payload; use the table and official links below. | **Secrets store** | **Approx. limit** | **More information** | | --- | --- | --- | | **Default (server database)** | **64 KiB** | — | | **AWS Secrets Manager** | **64 KiB** | [Secrets Manager quotas](https://docs.aws.amazon.com/secretsmanager/latest/userguide/reference_limits.html) | | **Google Cloud Secret Manager** | **64 KiB** | [Quotas and limits](https://cloud.google.com/secret-manager/quotas) | | **Azure Key Vault** | **25 KB** | [About secrets (size and attributes)](https://learn.microsoft.com/en-us/azure/key-vault/secrets/about-secrets)| | **HashiCorp Vault (KV v2)** | **~512 KiB to ~1 MiB** | [Vault KV v2 size limits](https://developer.hashicorp.com/vault/docs/internals/limits#versioned-key-value-store-kv-v2-secret-engine) (depends on storage back-end) | | **Custom** | **Varies** | | If the content is still too big, register another ZenML secret (a new name), split keys across secrets, or keep the file in an artifact or object store and only put a reference in a value. ## Configuration and deployment Configuring the specific secrets store back-end that the ZenML server uses is done at deployment time. This involves deciding on one of the supported back-ends and authentication mechanisms and configuring the ZenML server with the necessary credentials to authenticate with the back-end. The ZenML secrets store reuses the [ZenML Service Connector](https://docs.zenml.io/stacks/service-connectors/auth-management) authentication mechanisms to authenticate with the secrets store back-end. This means that the same authentication methods and configuration parameters that are supported by the available Service Connectors are also reflected in the ZenML secrets store configuration. It is recommended to practice the principle of least privilege when configuring the ZenML secrets store and to use credentials with the documented minimum required permissions to access the secrets store back-end. The ZenML secrets store configured for the ZenML Server can be updated at any time by updating the ZenML Server configuration and redeploying the server. This allows you to easily switch between different secrets store back-ends and authentication mechanisms. However, it is recommended to follow [the documented secret store migration strategy](secret-management.md#secrets-migration-strategy) to minimize downtime and to ensure that existing secrets are also properly migrated, in case the location where secrets are stored in the back-end changes. For more information on how to deploy a ZenML server and configure the secrets store back-end, refer to your deployment strategy inside the deployment guide. ## Backup secrets store The ZenML Server deployment may be configured to optionally connect to _a second Secrets Store_ to provide additional features such as high-availability, backup and disaster recovery as well as an intermediate step in the process of migrating [secrets from one secrets store location to another](secret-management.md#secrets-migration-strategy). For example, the primary Secrets Store may be configured to use the internal database, while the backup Secrets Store may be configured to use the AWS Secrets Manager. Or two different AWS Secrets Manager accounts or regions may be used. {% hint style="warning" %} Always make sure that the backup Secrets Store is configured to use a different location than the primary Secrets Store. The location can be different in terms of the Secrets Store back-end type (e.g. internal database vs. AWS Secrets Manager) or the actual location of the Secrets Store back-end (e.g. different AWS Secrets Manager account or region, GCP Secret Manager project or Azure Key Vault's vault). Using the same location for both the primary and backup Secrets Store will not provide any additional benefits and may even result in unexpected behavior. {% endhint %} When a backup secrets store is in use, the ZenML Server will always attempt to read and write secret values from/to the primary Secrets Store first while ensuring to keep the backup Secrets Store in sync. If the primary Secrets Store is unreachable, if the secret values are not found there, or any otherwise unexpected error occurs, the ZenML Server falls back to reading and writing from/to the backup Secrets Store. Only if the backup Secrets Store is also unavailable, the ZenML Server will return an error. In addition to the hidden backup operations, users can also explicitly trigger a backup operation by using the `zenml secret backup` CLI command. This command will attempt to read all secrets from the primary Secrets Store and write them to the backup Secrets Store. Similarly, the `zenml secret restore` CLI command can be used to restore secrets from the backup Secrets Store to the primary Secrets Store. These CLI commands are useful for migrating secrets from one Secrets Store to another. ## Secrets migration strategy Sometimes you may need to change the external provider or location where secrets values are stored by the Secrets Store. The immediate implication of this is that the ZenML server will no longer be able to access existing secrets with the new configuration until they are also manually copied to the new location. Some examples of such changes include: * switching Secrets Store back-end types (e.g. from internal SQL database to AWS Secrets Manager or Azure Key Vault) * switching back-end locations (e.g. changing the AWS Secrets Manager account or region, GCP Secret Manager project or Azure Key Vault's vault). In such cases, it is not sufficient to simply reconfigure and redeploy the ZenML server with the new Secrets Store configuration. This is because the ZenML server will not automatically migrate existing secrets to the new location. Instead, you should follow a specific migration strategy to ensure that existing secrets are also properly migrated to the new location with minimal, even zero downtime. The secrets migration process makes use of the fact that [a secondary Secrets Store](secret-management.md#backup-secrets-store) can be configured for the ZenML server for backup purposes. This secondary Secrets Store is used as an intermediate step in the migration process. The migration process is as follows (we'll refer to the Secrets Store that is currently in use as _Secrets Store A_ and the Secrets Store that will be used after the migration as _Secrets Store B_): 1. Re-configure the ZenML server to use _Secrets Store B_ as the secondary Secrets Store. 2. Re-deploy the ZenML server. 3. Use the `zenml secret backup` CLI command to back up all secrets from _Secrets Store A_ to _Secrets Store B_. You don't have to worry about secrets that are created or updated by users during or after this process, as they will be automatically backed up to _Secrets Store B_. If you also wish to delete secrets from _Secrets Store A_ after they are successfully backed up to _Secrets Store B_, you should run `zenml secret backup --delete-secrets` instead. 4. Re-configure the ZenML server to use _Secrets Store B_ as the primary Secrets Store and remove _Secrets Store A_ as the secondary Secrets Store. 5. Re-deploy the ZenML server. This migration strategy is not necessary if the actual location of the secrets values in the Secrets Store back-end does not change. For example: * updating the credentials used to authenticate with the Secrets Store back-end before or after they expire * switching to a different authentication method to authenticate with the same Secrets Store back-end (e.g. switching from an IAM account secret key to an IAM role in the AWS Secrets Manager) If you are a [ZenML Pro](https://zenml.io/pro) user, you can configure your cloud backend based on your [deployment scenario](../system-architectures.md).
ZenML Scarf
--- ### Book/Getting Started/Zenml Pro/README (docs/book/getting-started/zenml-pro/README.md) --- description: Learn about the ZenML Pro features and deployment scenarios. cover: .gitbook/assets/procover.png coverY: 0 layout: width: default cover: visible: true size: hero title: visible: true description: visible: true tableOfContents: visible: true outline: visible: true pagination: visible: true metadata: visible: true --- # Introduction ## What is ZenML Pro? The [Pro version of ZenML](https://zenml.io/pro) extends the Open Source product with advanced features for enterprise-grade MLOps. It provides multi-user collaboration, role-based access control, flexible deployment options, and professional support to help teams scale their ML operations. {% hint style="info" %} To get access to ZenML Pro, [book a call](https://www.zenml.io/book-your-demo). {% endhint %} ## ZenML OSS vs Pro Feature Comparison | Feature Category | ZenML OSS | ZenML Pro | |-----------------|-----------|-----------| | **User Management** | Single-user mode | Multi-user support with SSO, [organizations](organization.md), and [teams](teams.md) | | **Access Control** | ❌ No RBAC | Full [role-based access control](roles.md) with customizable permissions | | **Multi-tenancy** | ❌ No workspaces/projects | [Workspaces](workspaces.md) and [projects](projects.md) for team and resource isolation | | **ZenML Web UI** | Basic pipeline and run visualization | Pro UI with [Model Control Plane](https://docs.zenml.io/concepts/models), [Artifact Control Plane](https://docs.zenml.io/concepts/dashboard-features), and comparison views | | **Pipeline Execution** | Run pipelines via SDK/CLI | Run pipelines from the UI, manage schedules through the UI, [triggers](https://docs.zenml.io/concepts/snapshots) | | **Stack Configuration** | User-managed stacks | Advanced stack configurations with workspace/project-level restrictions for platform teams | | **Security** | Community updates | Prioritized security patches, SOC 2 and ISO 27001 certification | | **Deployment** | Self-hosted only | [SaaS](saas-deployment.md), [Hybrid](hybrid-deployment.md), or [Self-hosted](self-hosted-deployment.md) options | | **Support** | Community support | Professional support included (SaaS deployments) | | **Reporting** | Basic run tracking | Advanced usage reports and analytics | | **Core Features** | ✅ Run pipelines on stacks
✅ Full observability over runs
✅ Artifact tracking
| ✅ All OSS features
✅ [Run Snapshots](https://docs.zenml.io/concepts/snapshots)
✅ Model Control Plane
✅ Artifact Control Plane
✅ Enhanced filtering and search | ## Security & Compliance All ZenML Pro deployments include: - ✅ **SOC 2 Type II** certification - ✅ **ISO 27001** certification - ✅ **Vulnerability Assessment Reports** available on request - ✅ **Software Bill of Materials (SBOM)** available on request ## Documentation Guide This documentation is organized to help you understand, deploy, and manage ZenML Pro: | Section | Description | |---------|-------------| | [**System Architecture**](system-architecture.md) | How ZenML Pro services (Control Plane, Workspace Server, Workload Manager) communicate and interact | | [**Scenarios**](scenarios.md) | Decision guide to help you choose between SaaS, Hybrid, and Self-hosted deployments | | [**Deployment Details**](deploy-details.md) | Reference for configurable options, environment variables, and permissions for each component | | [**Upgrades and Updates**](upgrades-updates.md) | How to upgrade each ZenML Pro component | | [**Core Concepts**](hierarchy.md) | Organizations, Workspaces, Projects, Teams, and Hierarchy | | [**Access Management**](roles.md) | Roles, Permissions, Service Accounts, and Secrets | ## Pro Feature Details
WorkspacesIsolated environments for teams and projectspro-workspaces.pngworkspaces.md
OrganizationsTop-level entity for managing users and teamspro-organizations.pngorganization.md
TeamsGroup users for simplified access managementpro-teams.pngteams.md
RolesCustomizable role-based access controlpro-roles.pngroles.md
ProjectsOrganize work within workspacespro-projects.pngprojects.md
SnapshotsTrigger pipelines from dashboard, SDK, CLI, or REST APIpro-workload-managers.pngsnapshots.md
Deployment OptionsSaaS, Hybrid, or Full On-Prem deploymentspro-self-host.pngscenarios.md
ZenML Scarf
--- ### Book/Getting Started/Zenml Pro/Deploy Control Plane K8s (docs/book/getting-started/zenml-pro/deploy-control-plane-k8s.md) --- description: Deploy ZenML Pro Self-hosted on Kubernetes with Helm - complete self-hosted setup with no external dependencies. layout: title: visible: true description: visible: true tableOfContents: visible: true outline: visible: true pagination: visible: true --- # Self-hosted Deployment on Kubernetes with Helm This guide provides step-by-step instructions for deploying ZenML Pro on Kubernetes using Helm charts. All components run within your infrastructure with zero external dependencies. ZenML Pro deployments use standard Kubernetes APIs and work on managed cloud clusters (EKS, GKE, AKS), enterprise platforms like Red Hat OpenShift and SUSE Rancher, and self-managed clusters on-premises or in your own cloud. ## Deployment Architecture All components run entirely within your Kubernetes cluster and infrastructure: ### Architecture Components Client access includes browser-based access to the ZenML UI dashboard and connections from developer laptops or CI systems to workspaces. The Kubernetes cluster provides the compute and services layer across several namespaces. The `zenml-controlplane-namespace` contains the UI Pod (hosting the ZenML Pro UI, connecting to the control plane and all workspaces) and the Control Plane Pod (API Server and User Management/RBAC). The `zenml-workspace-namespace` contains the Workspace Server Pod with the ZenML API Server, and Workload Manager that manages pipelines, stacks, and snapshots. The `zenml-runners-namespace` contains Runner Pods created on-demand for snapshots, and the `orchestrator-namespace` contains Orchestrator Pods for pipeline execution when using the Kubernetes orchestrator. The data and storage layer includes a MySQL database for workspace and control plane metadata (TCP 3306), an optional secrets backend such as AWS Secrets Manager or Vault, an artifact store (S3, GCS, or Azure Blob) for models, datasets, and artifacts, and a container registry (AWS ECR, Google Artifact Registry, or Azure) for pipeline images. ## Prerequisites Before starting, make sure you go through the [general prerequisites for self-hosted deployments](deploy-prerequisites.md) and have collected the necessary artifacts and information. Particular requirements for Kubernetes with Helm deployments are listed below. **Infrastructure:** - Kubernetes cluster (1.24+) **Network:** - Load balancer, network gateway or Ingress controllers etc. - Internal DNS resolution - TLS certificates signed by your internal CA (or self-signed) - Network connectivity between cluster components **Tools (on a machine with internet access for initial setup):** - Helm (3.0+) ## Install the ZenML Pro Control Plane ### Step 1: Create Kubernetes Secrets If you are using an internal container registry, you may need to create a secret to allow the ZenML Pro control plane and workspace servers to pull the images. The following is an example of how to do this: ```bash # Create namespace for ZenML Pro kubectl create namespace zenml-pro # Create secret for internal registry credentials (if needed) kubectl -n zenml-pro create secret docker-registry image-pull-secret \ --docker-server=internal-registry.mycompany.com \ --docker-username= \ --docker-password= ``` You'll use this secret in the next step when configuring the Helm values for the ZenML Pro control plane. ### Step 2: Configure Helm Values for the Control Plane The example below is a basic configuration for the ZenML Pro control plane Helm chart. For a full list of configurable values and documentation, see the [ZenML Pro Helm chart reference](https://artifacthub.io/packages/helm/zenml-pro/zenml-pro). ``` /* Detailed source-code truncated for AI context efficiency. */ ``` Minimum required settings: * the database credentials (`zenml.database.external`) * the URL (`zenml.serverURL`) where the ZenML Pro Control Plane API and UI will be reachable In addition to the above, the following might also be relevant for you: * configure container registry credentials (`imagePullSecrets`) * injecting custom CA certificates (`zenml.certificates`), especially important if the TLS certificates used by the ZenML Pro services are signed by a custom Certificate Authority * configure HTTP proxy settings (`zenml.proxy`) * custom container image repository locations (`zenml.image.api` and `zenml.image.dashboard`) * the username and password used for the default admin account (`zenml.auth.password`) * SSO authentication settings (`zenml.auth.sso`) — see the [Single Sign-On (SSO)](sso.md) guide for prerequisites and migration from password authentication * additional Ingress settings (`zenml.ingress`) * Kubernetes resources allocated to the pods (`resources`) * If you set up a common DNS prefix that you plan on using for all the ZenML Pro services, you may configure the domain of the HTTP cookies used by the ZenML Pro dashboard to match it by setting `zenml.auth.authCookieDomain` to the DNS prefix (e.g. `.my.domain` instead of `zenml-pro.my-domain`) ### Step 3: Deploy the ZenML Pro Control Plane with Helm Using the remote Helm chart, if you have access to the internet: ```bash helm install zenml-pro oci://public.ecr.aws/zenml/zenml-pro \ --namespace zenml-pro \ --create-namespace \ --values zenml-pro-values.yaml \ --version ``` Using the local Helm chart, if you have downloaded the chart previously: ```bash helm install zenml-pro ./zenml-pro-.tgz \ --namespace zenml-pro \ --create-namespace \ --values zenml-pro-values.yaml ``` Verify deployment: ```bash kubectl -n zenml-pro get pods kubectl -n zenml-pro get svc kubectl -n zenml-pro get ingress ``` Wait for all pods to be running and healthy. ### Step 4: Onboard the ZenML Pro Control Plane The next step involves creating your first super-user user account. {% hint style="info" %} Super-user accounts are special user accounts that are granted additional privileges and are able to bypass RBAC restrictions and create and manage other user accounts, organizations and workspace resources for the entire ZenML Pro instance. Super-user privileges can later on be granted to other users or revoked if no longer needed. {% endhint %} You will use this account to log in to the ZenML Pro UI to perform the following tasks that are part of onboarding your ZenML Pro instance: 1. Create one or more [organizations](organization.md). After SSO is configured, organization [trusted domains](trusted-domains.md) (via the API) can change user discovery and whether some invitations require an explicit accept step. 2. Create user accounts and/or invite other users to join the organization(s). 3. Assign [roles and permissions](roles.md) to users or group them into [teams](teams.md) 4. [Enroll workspaces](enroll-workspace.md) in the organization(s) Creating the initial super-user account is different depending on whether you are using SSO authentication or password authentication. #### Option 1: Using SSO Authentication If you are using SSO authentication, you can simply access the ZenML Pro UI and use the SSO login flow to authenticate with your identity provider. {% hint style="info" %} The first account that logs in with SSO authentication will be automatically granted super-user privileges. {% endhint %} #### Option 2: Using Password Authentication If you only enabled password authentication, a reserved admin account is available that can be used to create the first local super-user account. Instructions on how to access the admin account are provided in the Helm chart output after deployment: ``` You may access the ZenML Pro server at: https://zenml-pro.my.domain Use the following credentials: Username: admin Password: fetch the password by running: kubectl get secret --namespace zenml-pro zenml-pro -o jsonpath="{.data.ZENML_CLOUD_ADMIN_PASSWORD}" | base64 --decode; echo ``` {% hint style="warning" %} The ZenML Pro admin user should only be used for the initial onboarding and emergency administrative operations related to super-user account management: creating the first super-user account and granting super-user privileges to other users when needed. Use a regular user account for all other operations. {% endhint %} Creating and managing local user accounts is currently only supported through the ZenML Pro OpenAPI interface or programmatically accessing the ZenML Pro API. There is no support for this in the ZenML Pro UI yet. {% tabs %} {% tab title="OpenAPI Interface" %} To access the ZenML Pro OpenAPI interface, append the `/api/v1` path to the ZenML Pro server URL in your browser. For example: https://zenml-pro.my.domain/api/v1. Then use the OpenAPI UI to authenticate with the admin username and password. Using the OpenAPI interface, you can manage local user accounts by making requests to the `/api/v1/users` endpoint. For example, to create a new super-user account: {% endtab %} {% tab title="curl" %} First, obtain a bearer token by authenticating with the admin credentials: ```bash # Fetch a bearer token using admin credentials curl -X POST https://zenml-pro.my.domain/api/v1/auth/login \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "username=admin&password=" ``` The response will contain an `access_token` field. Use this token to create a new super-user account: ```bash # Create a new super-user account curl -X POST "https://zenml-pro.my.domain/api/v1/users?username=superuser&password=password&is_superuser=true" \ -H "Authorization: Bearer " ``` {% endtab %} {% endtabs %} ### Step 5: Deploy and Enroll Workspaces Now that the ZenML Pro control plane is onboarded, you can deploy and enroll one or more workspaces. To deploy and enroll a workspace, you can use the process covered in the [Deploying Workspaces](deploy-workspace-k8s.md) sections. Make sure to use the ZenML Pro control plane URLs you used for the self-hosted deployment: * ZenML Pro control plane UI: https://zenml-pro.internal.mycompany.com * ZenML Pro control plane API: https://zenml-pro.internal.mycompany.com/api/v1s ## Access the Workspaces from ZenML CLI To login to a workspace with the ZenML CLI, you need to pass the custom ZenML Pro API URL: ```bash zenml login --pro-api-url https://zenml-pro.internal.mycompany.com/api/v1 ``` Alternatively, you can set the `ZENML_PRO_API_URL` environment variable: ```bash export ZENML_PRO_API_URL=https://zenml-pro.internal.mycompany.com/api/v1 zenml login ``` ## Day 2 Operations For information on upgrading ZenML Pro components, see the [Upgrades & Updates](upgrades-updates.md) guide. ## Related Resources - [Self-hosted Deployment Overview](self-hosted-deployment.md) - [Kubernetes Documentation](https://kubernetes.io/docs/) - [MySQL Documentation](https://dev.mysql.com/doc/) - [Helm Documentation](https://helm.sh/docs/) ## Support For self-hosted deployments, contact ZenML Support: - Email: [cloud@zenml.io](mailto:cloud@zenml.io) - Provide: Deployment status, configuration details and any error logs Request from ZenML Support: - Pre-deployment architecture consultation - Offline support packages - Update bundles and release notes - Security documentation (SBOM, vulnerability reports)
ZenML Scarf
--- ### Book/Getting Started/Zenml Pro/Deploy Details (docs/book/getting-started/zenml-pro/deploy-details.md) --- description: Reference documentation for deploying ZenML Pro components. icon: sliders --- # Deployment Details This section provides reference documentation for configuring each ZenML Pro component. Use these guides to understand all available configuration options, environment variables, permissions, and network requirements. ## When to Use These Guides - **During initial deployment**: Deploy components according to your infrastructure - **Post-deployment tuning**: Adjust deployment settings based on usage patterns - **Troubleshooting**: Verify deployments when issues arise ## Related Documentation - [System Architecture](system-architecture.md) - Understand how components interact - [Scenarios](scenarios.md) - Choose the right deployment option - [Upgrades and Updates](upgrades-updates.md) - How to upgrade components
ZenML Scarf
--- ### Book/Getting Started/Zenml Pro/Deploy Prerequisites (docs/book/getting-started/zenml-pro/deploy-prerequisites.md) --- description: Prepare for deploying the ZenML Pro control plane and/or workspace servers in a self-hosted environment. layout: title: visible: true description: visible: true tableOfContents: visible: true outline: visible: true pagination: visible: true --- # Self-hosted Deployment Prerequisites and Preparation This page outlines the general infrastructure requirements and preparation steps for deploying ZenML Pro components (control plane or workspace servers) in a self-hosted environment. These prerequisites apply regardless of the specific deployment method you choose. After reviewing these requirements and collecting the necessary artifacts and information, proceed to the deployment guide for your chosen component and infrastructure type. ## ZenML Pro Software Artifacts ZenML Pro consists of two distinct software component groups, each with its own release cycle and versioning: ### Control Plane Components {% hint style="info" %} You only need to deploy and manage the Control Plane components if you are setting up [the fully self-hosted scenario](self-hosted-deployment.md). If you are using the [hybrid scenario](hybrid-deployment.md) or the [SaaS scenario](saas-deployment.md), the Control Plane is fully managed by ZenML. {% endhint %} The **Control Plane API** and **Control Plane Web UI** are developed and released together as part of the ZenML Pro product. These components: - Share a unified version number (e.g., `0.13.0`) - Have container images and Helm chart releases aligned to the same version - Container images are privately hosted in the ZenML GCP Artifact Registry and ZenML AWS ECR repositories. Access to these registries is [granted upon request](#authenticating-to-zenml-pro-container-registries): - **images hosted in the ZenML GCP Artifact Registry**: - `europe-west3-docker.pkg.dev/zenml-cloud/zenml-pro/zenml-pro-api:` - `europe-west3-docker.pkg.dev/zenml-cloud/zenml-pro/zenml-pro-dashboard:` - **images hosted in the ZenML AWS ECR repositories**: - `715803424590.dkr.ecr.eu-west-1.amazonaws.com/zenml-pro-api:` - `715803424590.dkr.ecr.eu-west-1.amazonaws.com/zenml-pro-dashboard:` - The `zenml-pro` Helm chart is publicly hosted in [ZenML Pro ArtifactHub repository](https://artifacthub.io/packages/helm/zenml-pro/zenml-pro) **Release notes:** [ZenML Pro Control Plane Changelog](https://docs.zenml.io/changelog/pro-control-plane) ### ZenML Pro Workspace Server Component {% hint style="info" %} You need to deploy and manage the Workspace Server components if you are setting up either the [hybrid scenario](hybrid-deployment.md) or [the self-hosted scenario](self-hosted-deployment.md). If you are using the [SaaS scenario](saas-deployment.md), the Workspace Servers are fully managed by ZenML. {% endhint %} The **Workspace Server** is built on top of the open-source ZenML server. This component: - Follows the same versioning as ZenML OSS releases (e.g., `0.93.2`) - Is released simultaneously with ZenML OSS - Uses the same OSS `zenml` Helm chart for deployment - Receives the same features and fixes as the OSS server, with additional Pro-specific integrations - Container images are privately hosted in the ZenML GCP Artifact Registry and ZenML AWS ECR repositories. Access to these registries is [granted upon request](#authenticating-to-zenml-pro-container-registries): - **images hosted in the ZenML GCP Artifact Registry**: - `europe-west3-docker.pkg.dev/zenml-cloud/zenml-pro/zenml-pro-server:` - **images hosted in the ZenML AWS ECR repositories**: - `715803424590.dkr.ecr.eu-central-1.amazonaws.com/zenml-pro-server:` - The `zenml` Helm chart is publicly hosted in [ZenML OSS ArtifactHub repository](https://artifacthub.io/packages/helm/zenml/zenml) **Release notes:** [ZenML Server & SDK Changelog](https://docs.zenml.io/changelog/server-sdk) {% hint style="info" %} The Control Plane and Workspace Server versions are independent. For example, you might run Control Plane version `0.13.0` with Workspace Servers running version `0.93.2`. Compatibility between versions is guaranteed as long as the Control Plane version is at least as new as the Workspace Server version. {% endhint %} ### Authenticating to ZenML Pro Container Registries {% hint style="info" %} To request access to the ZenML Pro container registries, contact [cloud@zenml.io](mailto:cloud@zenml.io) with details about your infrastructure and preferred authentication method. {% endhint %} Access to the ZenML Pro container registries is granted upon request. The authentication method depends on your infrastructure: **Option 1: AWS IAM Role (recommended for AWS workloads)** If you run your infrastructure on AWS (EKS, ECS, EC2, Fargate, etc.), you can provide ZenML with the IAM role ARN associated with your workload. ZenML will grant this role permission to pull images from the AWS ECR repositories. | Scenario | What to provide | |----------|-----------------| | EKS with IRSA | The IAM role ARN used by your Kubernetes service account | | ECS tasks | The task execution role ARN | | EC2 instances | The instance profile role ARN | This approach is preferred because it uses native AWS IAM authentication without managing long-lived credentials. **Option 2: GCP Service Account (recommended for GCP workloads)** If you run your infrastructure on GCP (GKE, Cloud Run, Compute Engine, etc.), you can provide ZenML with your service account principal. ZenML will grant this principal permission to pull images from the GCP Artifact Registry. | Scenario | What to provide | |----------|-----------------| | GKE with Workload Identity | The Kubernetes service account email (e.g., `sa-name@project-id.iam.gserviceaccount.com`) | | Cloud Run | The service account email used by your Cloud Run service | | Compute Engine | The service account email attached to your VM instances | This approach uses native GCP IAM authentication without managing long-lived credentials. **Option 3: Docker credentials (for other environments)** If you run on infrastructure outside of AWS or GCP, or if you prefer to use explicit credentials, ZenML can provide you with GCP authentication credentials that work with Docker directly. Use these credentials to authenticate with Docker before pulling the images hosted in the ZenML GCP Artifact Registry: ```bash cat your-zenml-issued-key.json | docker login -u _json_key --password-stdin https://europe-west3-docker.pkg.dev ``` ### Air-gapped Deployment Process If you need to deploy ZenML Pro fully self-hosted (control plane and workspace servers) in an air-gapped environment (a network with no direct internet access), you'll need to transfer all required artifacts to your internal infrastructure. Here's a step-by-step process performed on a machine with internet access that builds a bundle of all required artifacts that can then be transferred to your air-gapped environment. #### 1. Pull Container Images {% hint style="info" %} Access to the ZenML Pro container registries is restricted and granted upon request. See the [Authentication to ZenML Pro Container Registries](#authenticating-to-zenml-pro-container-registries) section for more details. {% endhint %} On a machine with internet access and access to the ZenML Pro container registries, after having authenticated your docker client, pull all required images. Example commands: ```bash docker pull europe-west3-docker.pkg.dev/zenml-cloud/zenml-pro/zenml-pro-api: docker pull europe-west3-docker.pkg.dev/zenml-cloud/zenml-pro/zenml-pro-dashboard: docker pull europe-west3-docker.pkg.dev/zenml-cloud/zenml-pro/zenml-pro-server: docker pull zenmldocker/zenml: ``` Save images to tar files for transfer: ```bash docker save europe-west3-docker.pkg.dev/zenml-cloud/zenml-pro/zenml-pro-api: > zenml-pro-api.tar docker save europe-west3-docker.pkg.dev/zenml-cloud/zenml-pro/zenml-pro-dashboard: > zenml-pro-dashboard.tar docker save europe-west3-docker.pkg.dev/zenml-cloud/zenml-pro/zenml-pro-server: > zenml-pro-server.tar docker save zenmldocker/zenml: > zenml-client.tar ``` #### 2. Download Helm Charts This step is only required if you are using Helm to deploy ZenML Pro. On the same machine with internet access: 1. Pull the Helm charts: - ZenML Pro Control Plane: `oci://public.ecr.aws/zenml/zenml-pro` - ZenML Workspace Server: `oci://public.ecr.aws/zenml/zenml` 2. Save charts as `.tgz` files for transfer {% hint style="info" %} **Version Synchronization**: The container image tags and the Helm chart versions are synchronized: - **ZenML Pro Control Plane**: Image tags match the ZenML Pro Helm chart version. Check the [ZenML Pro ArtifactHub repository](https://artifacthub.io/packages/helm/zenml-pro/zenml-pro) for available versions. - **ZenML Workspace Server**: Image tags match the ZenML OSS Helm chart version. Check the [ZenML OSS ArtifactHub repository](https://artifacthub.io/packages/helm/zenml/zenml) or the [ZenML GitHub releases page](https://github.com/zenml-io/zenml/releases). When copying images to your internal registry, maintain the same version tags to ensure compatibility between components. {% endhint %} #### 3. Create Offline Bundle Create a bundle containing all artifacts: ``` zenml-air-gapped-bundle/ ├── images/ │ ├── zenml-pro-api.tar │ ├── zenml-pro-dashboard.tar │ ├── zenml-pro-server.tar │ └── zenml-client.tar ├── charts/ │ ├── zenml-pro-.tgz │ └── zenml-.tgz └── manifest.txt ``` The manifest should document: - All image names and versions - Helm chart versions - Date of bundle creation - Required internal registry URLs #### 4. Transfer to Air-gapped Environment Transfer the bundle to your air-gapped environment using approved methods: - Physical media (USB drive, external drive) - Approved secure file transfer system - Air-gap transfer appliances - Any method compliant with your security policies #### 5. Load Images into Internal Registry In your air-gapped environment, load the images: 1. Extract all tar files: ```bash cd images/ for file in *.tar; do docker load < "$file"; done ``` 2. Tag images for your internal registry: ```bash docker tag europe-west3-docker.pkg.dev/zenml-cloud/zenml-pro/zenml-pro-api: internal-registry.mycompany.com/zenml/zenml-pro-api: docker tag europe-west3-docker.pkg.dev/zenml-cloud/zenml-pro/zenml-pro-dashboard: internal-registry.mycompany.com/zenml/zenml-pro-dashboard: docker tag europe-west3-docker.pkg.dev/zenml-cloud/zenml-pro/zenml-pro-server: internal-registry.mycompany.com/zenml/zenml-pro-server: docker tag zenmldocker/zenml: internal-registry.mycompany.com/zenml/zenml: ``` 3. Push images to your internal registry: ``` docker push internal-registry.mycompany.com/zenml/zenml-pro-api:version docker push internal-registry.mycompany.com/zenml/zenml-pro-dashboard:version docker push internal-registry.mycompany.com/zenml/zenml-pro-server:version docker push internal-registry.mycompany.com/zenml/zenml:version ``` ## Infrastructure Prerequisites ZenML Pro requires several infrastructure components to operate. The specific implementation of these components may vary depending on your environment, cloud provider, and organizational policies. ### Containerized Workload Infrastructure ZenML Pro runs as a set of containerized services that require an orchestration platform capable of running and managing containers. This infrastructure hosts the following components: - The **Control Plane API** server for organization and workspace management - The **Control Plane Web UI** for the central web interface - **Workspace Servers**: One or more ZenML server instances that handle ML pipeline metadata {% hint style="info" %} The Control Plane and Workspace Server components are completely independent from an infrastructure perspective. Different types of containerized infrastructure can be used for the Control Plane and the Workspace Server. For example, you can use ECS for the Control Plane and Kubernetes for the Workspace Server, or vice versa. {% endhint %} **Supported options:** | Infrastructure Type | Support Level | |---------------------|---------------| | Kubernetes (1.24+) | ✅ Officially supported and documented | | Container-as-a-Service (ECS, Cloud Run, etc.) | ⚠️ Possible but not documented | | Serverless containers (Fargate, Lambda, etc.) | ⚠️ Possible but not documented | | Virtual machines with Docker (VMware, VirtualBox, etc.) | ⚠️ Possible but not documented | | Docker-compose | ✅ Can be provided upon request for local testing purposes | These Helm-based deployments use standard Kubernetes APIs and work on managed cloud clusters (EKS, GKE, AKS), enterprise platforms like Red Hat OpenShift and SUSE Rancher, and self-managed clusters on-premises or in your own cloud. {% hint style="warning" %} While ZenML Pro can theoretically run on any container orchestration platform, only Kubernetes deployments are officially fully supported and documented. If you require a different deployment target, please contact [cloud@zenml.io](mailto:cloud@zenml.io) to discuss your requirements. {% endhint %} ### Database ZenML Pro requires relational database(s) for storing metadata: | Component | MySQL 8.0+ | PostgreSQL | |-----------|------------|------------| | Control Plane | ✅ Supported | ✅ Supported | | Workspace Servers | ✅ Supported | ❌ Not supported | **Database options:** - **Managed database services**: AWS RDS, Google Cloud SQL, Azure Database for MySQL/PostgreSQL, etc. - **Self-hosted databases**: MySQL or PostgreSQL running on VMs, Kubernetes, or bare metal **Database naming:** Each ZenML Pro component requires its own dedicated database (not a separate database instance — just a unique database name within the instance): - One database for the **control plane** (e.g., `zenml_pro`) - only applicable if you are setting up [the fully self-hosted scenario](self-hosted-deployment.md) - One database for **each workspace server** (e.g., `zenml_workspace_1`, `zenml_workspace_2`, etc.) - applicable for both the [hybrid scenario](hybrid-deployment.md) and [the fully self-hosted scenario](self-hosted-deployment.md) {% hint style="info" %} The databases do not need to be created in advance. ZenML Pro will automatically create them on first startup, provided the database user has the necessary permissions (`CREATE DATABASE`). If your database user lacks these permissions, create the databases manually before deployment. {% endhint %} **Sizing considerations:** - A single MySQL database instance can host all ZenML Pro databases for small-to-medium deployments. For increased reliability and data isolation, consider using separate database instances for the control plane and workspace servers. - For high availability, configure database replication and automated backups - Contact [cloud@zenml.io](mailto:cloud@zenml.io) for sizing guidance based on your expected workload ### Container Registry A container registry is required for most ZenML Pro deployments. While ZenML Pro container images can be pulled directly from ZenML's authenticated registries (AWS ECR or GCP Artifact Registry), you will almost certainly need your own container registry for running ML pipelines. **Reasons why you might need a container registry:** - **ML pipeline workloads**: When running ZenML pipelines with containerized orchestrators (Kubernetes, Docker, Kubeflow, Sagemaker, Vertex AI, etc.), ZenML builds and pushes container images containing your pipeline dependencies. These images must be stored in a registry accessible by your orchestration infrastructure. - **Air-gapped deployments**: ZenML Pro images must be stored in an internal registry since external registries are not reachable. - **Image customization**: When you need to modify ZenML Pro images to install CA certificates, configure HTTP proxies, or add other customizations. - **Compliance requirements**: When organizational policies require all container images to be sourced from internal registries. {% hint style="info" %} Even if you use ZenML's hosted registries for the ZenML Pro control plane and workspace server images, you will still need a separate container registry for your ML pipeline images if you plan to use any containerized orchestrator. {% endhint %} **Supported container registries:** | Registry Type | Examples | |---------------|----------| | Cloud-managed | AWS ECR, Google Artifact Registry, Azure Container Registry | | Self-hosted | Harbor, Quay, JFrog Artifactory, Docker Registry | | Local | Docker is enough for local testing purposes with docker-compose | ### SSO Identity Provider (Optional) {% hint style="info" %} SSO configuration is optional and only applicable if you are setting up [the fully self-hosted scenario](self-hosted-deployment.md). You can start with local accounts and enable SSO later without losing existing data. [The hybrid scenario](hybrid-deployment.md) already uses the ZenML Pro SaaS SSO for authentication. {% endhint %} By default, the ZenML Pro control plane uses local accounts with password authentication. Single Sign-On (SSO) can be enabled to authenticate users through an external identity provider instead. For detailed information about SSO prerequisites, identity provider configuration, and migrating from password authentication to SSO, see the [Single Sign-On (SSO)](sso.md) guide. ### Network Services ZenML Pro requires several network services to expose its APIs and handle secure communications: #### Load Balancer / Ingress A load balancer or ingress mechanism is needed to: - Route external traffic to the control plane API and dashboard - Route traffic to workspace server APIs - Distribute traffic across multiple replicas for high availability **Options:** - **Cloud provider load balancers**: AWS ALB/NLB, GCP Load Balancer, Azure Load Balancer, etc. - **Kubernetes Ingress controllers**: NGINX Ingress Controller, Traefik, HAProxy, Contour, etc. - **Kubernetes Gateway API**: The newer, more expressive routing standard using `Gateway` and `HTTPRoute` resources, supported by implementations like Envoy Gateway, Istio, NGINX Gateway Fabric, GKE Gateway Controller, and others - **Reverse proxies**: NGINX, Envoy, HAProxy, etc. (for non-Kubernetes deployments) #### DNS / Hostnames ZenML Pro requires DNS hostnames for the control plane API, the web UI, and each workspace server. Planning your DNS structure in advance simplifies TLS certificate management and service integration. **Recommended approach — common subdomain:** Using a common parent subdomain for all ZenML Pro components is recommended: | Component | Example Hostname | |-----------|------------------| | Web UI | `zenml.ml.example.com` | | Control Plane API | `zenml-api.ml.example.com`; can also share the same hostname as the web UI if HTTP path routing is used (e.g. `https://zenml.ml.example.com/api/v1`) | | Workspace Server | `zenml-workspace-1.ml.example.com` | **Benefits of a common subdomain:** - **Simplified TLS certificates**: A single wildcard certificate (`*.zenml.example.com`) can secure all components - **Secure cookie sharing**: Authentication cookies can be shared across subdomains, enabling seamless integration between the dashboard and workspace servers - **Easier DNS management**: All records can be managed under a single DNS zone **Alternative — separate domains:** {% hint style="info" %} The Control Plane and Workspace Server components can run on different domains. The authentication mechanisms will still work effectively across domains. {% endhint %} Using completely separate domains or subdomains is also supported but requires additional configuration: | Component | Example Hostname | |-----------|------------------| | Control Plane API | `zenml-api.site-one.com` | | Web UI | `zenml.site-one.com` | | Workspace Server | `ml-workspace.site-two.com` | With separate domains, you will need individual TLS certificates for each endpoint and may need to configure CORS and cookie settings explicitly. {% hint style="warning" %} Always use fully qualified domain names (FQDNs) for your endpoints (e.g., `https://zenml.ml.example.com`). Avoid simple DNS prefixes without a proper domain structure (e.g., `https://zenml.cluster`) or localhost names (e.g., `https://localhost`, `https://zenml.localhost`), as some browsers and TLS implementations may not accept certificates for such names. {% endhint %} #### TLS/HTTPS Termination ZenML Pro containers do **not** handle TLS termination internally. HTTPS traffic must be terminated externally before reaching the containers. **Options for TLS termination:** - **Cloud provider load balancers**: With SSL/TLS certificates managed by the cloud provider or uploaded manually - **Kubernetes Ingress controllers**: With cert-manager for automatic certificate management or manually provisioned certificates - **Kubernetes Gateway API**: Supports TLS termination via `Gateway` listener configuration with certificates stored in Kubernetes Secrets; also integrates with cert-manager - **Reverse proxies**: With TLS configuration (for non-Kubernetes deployments) **Certificate requirements:** - Valid TLS certificates for all exposed endpoints (control plane API, dashboard, workspace servers) - Certificates can be issued by a public CA, a private/internal CA, or be self-signed - If using self-signed certificates or a private CA, the CA certificate must be installed on all client machines and in container images used for ZenML Pro containers and ML pipelines {% hint style="info" %} If you are using self-signed certificates, it is highly recommended to at least use a common self-signed CA certificate for all the ZenML Pro services (control plane and workspace servers). This simplifies certificate management - you only need to install one CA certificate system-wide on all servers and client machines, then use it to sign all the TLS certificates for the ZenML Pro services. {% endhint %} #### Internal Connectivity The following internal network connectivity is required: | Source | Destination | Port | Purpose | |--------|-------------|------|---------| | ZenML containers | Database server | 3306 (MySQL) or 5432 (PostgreSQL) | Metadata storage | | Container runtime (e.g. Kubernetes nodes, Docker hosts, etc.) | Container registry | 443 | Image pulls (if using private registry) | | Workspace servers | Control plane | 443 | Enrollment, status updates, RBAC permissions (if using [the fully self-hosted scenario](self-hosted-deployment.md)) | | Web UI | Control plane | 443 | Authentication, API requests, UI data requests (if using [the fully self-hosted scenario](self-hosted-deployment.md)) | #### External Connectivity Depending on your deployment model: | Connectivity | Required For | |--------------|--------------| | Inbound HTTPS (443) | Client access to dashboard and APIs | | Outbound HTTPS (443) | Control plane server connecting to the identity provider for SSO authentication (if enabled and using [the fully self-hosted scenario](self-hosted-deployment.md)) | | Outbound HTTPS (443) | Pulling images from ZenML registries (if not air-gapped) | | Outbound HTTPS (443) | Workspace servers connecting to the control plane for enrollment, status updates, RBAC permissions (if using [the hybrid scenario](hybrid-deployment.md)) | ## Information to Collect Before Deployment Before proceeding with the deployment, gather all the information listed below. Having these details ready will streamline the deployment process and reduce configuration errors. {% hint style="info" %} Use the tables below as a checklist. Fill in the values for your environment and keep them handy during deployment. {% endhint %} ### Database Configuration You need connection details for each database. A single database instance can host multiple databases (one per component), or you can use separate instances for isolation. | Parameter | Control Plane | Workspace Server(s) | |-----------|---------------|---------------------| | Database type | MySQL or PostgreSQL | MySQL only | | Hostname / endpoint | `_______________` | `_______________` | | Port | `_______________` (default: 3306/5432) | `_______________` (default: 3306) | | Database name | `_______________` (e.g., `zenml_pro`) | `_______________` (e.g., `zenml_workspace_1`) | | Username | `_______________` | `_______________` | | Password | `_______________` | `_______________` | | SSL/TLS required | Yes / No | Yes / No | | CA certificate path (if applicable) | `_______________` | `_______________` | {% hint style="warning" %} Each workspace server requires its own dedicated database. If you plan to deploy multiple workspaces, prepare a separate database name for each. {% endhint %} ### DNS and Hostnames | Component | Hostname | Notes | |-----------|----------|-------| | Web UI | `_______________` | e.g., `zenml.ml.example.com` | | Control Plane API | `_______________` | Can share hostname with UI using path routing, e.g., `zenml.ml.example.com/api/v1` | | Workspace Server 1 | `_______________` | e.g., `workspace-1.ml.example.com` | | Workspace Server 2 (if applicable) | `_______________` | | | Additional workspaces... | `_______________` | | ### TLS Certificates | Parameter | Value | |-----------|-------| | Certificate type | ☐ Public CA    ☐ Private/Internal CA    ☐ Self-signed | | Wildcard certificate | ☐ Yes (`*._______________`)    ☐ No (individual certs) | | Certificate file(s) path | `_______________` | | Private key file path | `_______________` | | CA certificate path (if private CA) | `_______________` | | Certificate management | ☐ Manual    ☐ cert-manager    ☐ Cloud provider managed | ### Container Registry for ZenML Pro Images Choose where ZenML Pro container images will be pulled from: | Parameter | Value | |-----------|-------| | Image source | ☐ ZenML AWS ECR    ☐ ZenML GCP Artifact Registry    ☐ Private registry | | Registry URL (if private) | `_______________` | | Authentication method | ☐ AWS IAM Role    ☐ GCP Service Account    ☐ Docker credentials | | IAM Role ARN / Service Account (if applicable) | `_______________` | | Registry username (if Docker credentials) | `_______________` | | Registry password/token (if Docker credentials) | `_______________` | **Image URIs:** | Image | URI | |-------|-----| | Control Plane API | `_______________` (e.g., `europe-west3-docker.pkg.dev/zenml-cloud/zenml-pro/zenml-pro-api:`) | | Web UI (Dashboard) | `_______________` (e.g., `europe-west3-docker.pkg.dev/zenml-cloud/zenml-pro/zenml-pro-dashboard:`) | | Workspace Server | `_______________` (e.g., `europe-west3-docker.pkg.dev/zenml-cloud/zenml-pro/zenml-pro-server:`) | ### SSO Configuration (if enabled) Skip this section if using local accounts only. For the full list of SSO-related prerequisites and information to collect, see the [Single Sign-On (SSO)](sso.md) guide. ## Next Steps Once you have collected all the required information, proceed to the deployment guide for your chosen component and infrastructure type: - [Control Plane Kubernetes Deployment](deploy-control-plane-k8s.md) — Deploy ZenML Pro control plane on Kubernetes using Helm charts - [Workspace Server Kubernetes Deployment](deploy-workspace-k8s.md) — Deploy ZenML Pro workspace servers on Kubernetes using Helm charts - [Workspace Server AWS ECS Deployment](deploy-workspace-ecs.md) — Deploy ZenML Pro workspace servers on AWS ECS
ZenML Scarf
--- ### Book/Getting Started/Zenml Pro/Deploy Workspace Ecs (docs/book/getting-started/zenml-pro/deploy-workspace-ecs.md) --- description: Deploy ZenML Pro Hybrid on AWS ECS with a managed control plane. layout: title: visible: true description: visible: true tableOfContents: visible: true outline: visible: true pagination: visible: true --- # Hybrid Deployment on AWS ECS This guide provides high-level instructions for deploying ZenML Pro in a Hybrid setup on AWS ECS (Elastic Container Service). ## Architecture Overview In this setup: - **ZenML workspace** runs in ECS tasks within your VPC - **Load balancer** handles HTTPS traffic and routes to ECS tasks - **Database** stores workspace metadata in AWS RDS - **Secrets manager** stores Pro credentials securely - **NAT gateway** enables outbound access to ZenML Cloud control plane ## Prerequisites Before starting, make sure you go through the [general prerequisites for hybrid deployments](deploy-prerequisites.md) and have collected the necessary artifacts and information. Particular requirements for AWS ECS deployments are listed below. - AWS Account with appropriate IAM permissions - Basic familiarity with AWS ECS, VPC, and RDS ## Install the ZenML Pro Workspace Server ### Step 1: Enroll the Workspace in the ZenML Pro Control Plane Make sure to enroll the workspace in the ZenML Pro control plane by following the [Enroll a Workspace in the ZenML Pro Control Plane](enroll-workspace.md) guide and collect the necessary enrollment credentials. ### Step 2: Set Up AWS Infrastructure #### VPC and Subnets Create a VPC with: - **Public subnets** (at least 2 across different availability zones) - for the Application Load Balancer - **Private subnets** (at least 2 across different availability zones) - for ECS tasks and RDS #### Security Groups Create three security groups: 1. **ALB Security Group** - Inbound: HTTPS (443) and HTTP (80) from `0.0.0.0/0` - Outbound: HTTP (8000) to the ECS security group 2. **ECS Security Group** - Inbound: HTTP (8000) from the ALB security group - Outbound: HTTPS (443) to `0.0.0.0/0` (for ZenML Cloud access) - Outbound: TCP (3306 for MySQL) to the RDS security group 3. **RDS Security Group** - Inbound: TCP (3306 for MySQL) from the ECS security group - Outbound: Not restricted #### NAT Gateway To enable ECS tasks to reach ZenML Cloud: 1. Create an Elastic IP in your AWS region 2. Create a NAT Gateway in one of your public subnets 3. Wait for the NAT Gateway to be available #### Route Tables For your private subnets (where ECS tasks run): 1. Create a route table 2. Add a default route (`0.0.0.0/0`) pointing to the NAT Gateway 3. Associate this route table with your private subnets ### Step 3: Set Up RDS Database Create an RDS database instance. **Important**: Workspace servers only support MySQL, not PostgreSQL. **Configuration:** - **DB Engine**: MySQL 8.0+ (PostgreSQL is not supported for workspace servers) - **Instance Class**: `db.t3.micro` or larger depending on expected load - **Storage**: 100 GB initial (with automatic scaling enabled) - **Multi-AZ**: Enable for production deployments - **VPC**: Your ZenML VPC - **Subnet Group**: Create a DB subnet group with your private subnets - **Security Group**: RDS security group created above - **Backups**: 30 days retention minimum - **Logs**: Enable error, general, and slowquery logs to CloudWatch **After creation:** 1. Note the database endpoint (hostname) 2. Create the initial database: `zenml_hybrid` 3. Create a database user with full permissions on the database ### Step 4: Store Secrets in AWS Secrets Manager Store your Pro credentials securely: 1. **OAuth2 Client Secret** - Secret name: `zenml/pro/oauth2-client-secret` - Value: Your workspace enrollment key 2. (Optional) **Database Password** - Secret name: `zenml/rds/password` - Value: Your RDS database password Note the ARN of your OAuth2 secret - you'll reference it in the task definition. ### Step 5: Create ECS IAM Roles Create two IAM roles: #### Task Execution Role This role allows ECS to pull images and manage logs: - Attach: `AmazonECSTaskExecutionRolePolicy` - Add inline policy for Secrets Manager access: - Action: `secretsmanager:GetSecretValue` - Resource: Your OAuth2 secret ARN - Action: `logs:CreateLogGroup`, `logs:CreateLogStream`, `logs:PutLogEvents` - Resource: Your CloudWatch log group #### Task Role This role is for application-level permissions (optional for basic setup): - Leave empty for now, or add policies if your tasks need to access other AWS services ### Step 6: Create ECS Task Definition In the AWS Console or using AWS CLI/Terraform, create a task definition with: **Task Configuration:** - **Compatibility**: FARGATE - **CPU**: 512 (0.5 vCPU) - **Memory**: 1024 MB - **Network Mode**: awsvpc - **Execution Role**: Task execution role created above - **Task Role**: Task role created above **Container Configuration:** - **Image**: `715803424590.dkr.ecr.eu-central-1.amazonaws.com/zenml-pro-server:` - **Port Mapping**: Container port 8000 to port 8000 - **Essential**: Yes **Environment Variables:** Set these in the task definition: | Variable | Value | |----------|-------| | `ZENML_SERVER_DEPLOYMENT_TYPE` | `cloud` | | `ZENML_SERVER_PRO_API_URL` | `https://cloudapi.zenml.io` | | `ZENML_SERVER_PRO_DASHBOARD_URL` | `https://cloud.zenml.io` | | `ZENML_SERVER_PRO_ORGANIZATION_ID` | Your organization ID from enrollment | | `ZENML_SERVER_PRO_ORGANIZATION_NAME` | Your organization name from enrollment | | `ZENML_SERVER_PRO_WORKSPACE_ID` | Your workspace ID from enrollment | | `ZENML_SERVER_PRO_WORKSPACE_NAME` | Your workspace name from enrollment | | `ZENML_SERVER_PRO_OAUTH2_AUDIENCE` | `https://cloudapi.zenml.io` | | `ZENML_SERVER_SERVER_URL` | `https://zenml.mycompany.com` | | `ZENML_DATABASE_URL` | `mysql://user:password@hostname:3306/zenml_hybrid` (MySQL only - PostgreSQL not supported) | | `ZENML_SERVER_HOSTNAME` | `0.0.0.0` | | `ZENML_SERVER_PORT` | `8000` | | `ZENML_LOGGING_LEVEL` | `INFO` | **Secrets:** Reference your secret from Secrets Manager: | Variable | Secret | |----------|--------| | `ZENML_SERVER_PRO_OAUTH2_CLIENT_SECRET` | `arn:aws:secretsmanager:region:account:secret:zenml/pro/oauth2-client-secret` | **Logging:** Configure CloudWatch logs: - **Log Group**: `/ecs/zenml-hybrid` - **Log Stream Prefix**: `ecs` - **Region**: Your AWS region ### Step 7: Create ECS Cluster and Service Create an ECS cluster named `zenml-hybrid`. Then create an ECS service within this cluster: **Service Configuration:** - **Cluster**: zenml-hybrid - **Task Definition**: zenml-hybrid (latest version) - **Launch Type**: FARGATE - **Desired Count**: 1 (or more for high availability) - **Platform Version**: LATEST **Network Configuration:** - **VPC**: Your ZenML VPC - **Subnets**: Your private subnets - **Security Group**: ECS security group - **Public IP**: Disabled (tasks don't need public IPs) **Load Balancing:** - **Load Balancer Type**: Application Load Balancer - **Container**: zenml-server - **Container Port**: 8000 - (Leave the target group selection for the next step) ### Step 8: Set Up Application Load Balancer Create an Application Load Balancer (ALB): **Configuration:** - **Subnets**: Your public subnets - **Security Group**: ALB security group #### Target Group Create a target group for your ECS service: **Health Check Configuration:** - **Protocol**: HTTP - **Path**: `/health` - **Port**: 8000 - **Interval**: 30 seconds - **Timeout**: 5 seconds - **Healthy Threshold**: 2 - **Unhealthy Threshold**: 3 #### Listeners Create two listeners on your ALB: 1. **HTTPS Listener (Port 443)** - **Certificate**: Your TLS certificate from ACM or imported - **Default Action**: Forward to your target group 2. **HTTP Listener (Port 80)** - **Default Action**: Redirect to HTTPS (port 443) ### Step 9: Configure DNS In your DNS provider (Route 53 or external): 1. Create an A record (or CNAME) pointing to your ALB's DNS name - **Name**: `zenml.mycompany.com` - **Target**: Your ALB's DNS name or IP - **Type**: A record (use Alias if in Route 53) 2. Allow time for DNS propagation (typically 5-15 minutes) ### Step 10: Verify the Deployment 1. **Check ECS Service Status** - Go to ECS console → Clusters → zenml-hybrid → Services - Verify the service shows "Active" - Check that desired and running task counts match 2. **Check Task Logs** - Go to CloudWatch → Log Groups → `/ecs/zenml-hybrid` - View log stream to look for startup messages - Verify no critical errors appear 3. **Test HTTPS Access** - Visit `https://zenml.mycompany.com` in your browser - You should see ZenML Pro login redirecting to cloud.zenml.io 4. **Verify Control Plane Connection** - In CloudWatch logs, look for messages indicating successful connection to ZenML Cloud - Check for any authentication or SSL errors ## Network & Firewall Requirements ### Outbound Access to ZenML Cloud Your ECS tasks need HTTPS (port 443) outbound access to: - `cloudapi.zenml.io` - For control plane authentication This is enabled by the NAT Gateway and ECS security group configuration. ### Inbound Access from Clients Clients need HTTPS (port 443) inbound access to: - `zenml.mycompany.com` - Your ALB endpoint This is enabled by the ALB and ALB security group configuration. ### Database Access ECS tasks need TCP access to: - Your RDS instance on port 3306 (MySQL) This is enabled by the ECS security group egress rule and RDS security group ingress rule. ## Scaling & High Availability ### Multiple Tasks For high availability: 1. Update the ECS service's desired count to 2 or more 2. ECS will distribute tasks across availability zones 3. The ALB automatically distributes traffic to all healthy tasks ### Auto Scaling (Optional) To automatically scale based on CPU or memory usage: 1. Register a scalable target (your ECS service) 2. Create a target tracking scaling policy 3. Set target CPU utilization (e.g., 70%) ## Monitoring & Logging ### CloudWatch Logs Monitor your deployment: 1. Go to CloudWatch → Log Groups → `/ecs/zenml-hybrid` 2. Set up log filters to find errors: filter for `ERROR` or `CRITICAL` 3. Create metric filters if needed ### CloudWatch Alarms Create alarms for: - **High CPU Utilization**: Alert when average CPU > 80% - **Failed Tasks**: Alert when tasks exit unexpectedly - **Unhealthy Targets**: Alert when ALB marks tasks as unhealthy ### Application Logs For production deployments: 1. Forward CloudWatch logs to your centralized logging system (ELK, Datadog, etc.) 2. Set up alerts for authentication failures to ZenML Cloud 3. Monitor database connection errors ## Database Maintenance ### Backups Automated backups are configured, but: 1. Verify backup retention is set to at least 30 days 2. Test backup restoration periodically 3. Store backups in a different region for disaster recovery ### Monitoring Monitor database health: 1. Check RDS Performance Insights for slow queries 2. Review CloudWatch metrics for connection count and CPU 3. Monitor free storage space and create alerts ## Troubleshooting ### Task Won't Start Check ECS task logs in CloudWatch: 1. Go to `/ecs/zenml-hybrid` log group 2. Look for error messages about image pull failures or environment variable issues 3. Verify IAM execution role has correct permissions ### Database Connection Failed 1. Verify database is running and accessible 2. Check ECS security group allows outbound to RDS security group 3. Verify `ZENML_DATABASE_URL` has correct hostname, port, and credentials 4. Test connectivity from an ECS task using a MySQL client ### Can't Reach Server via HTTPS 1. Verify ALB is in "Active" state 2. Check ALB target group - tasks should show "Healthy" 3. Verify TLS certificate is valid for your domain 4. Check DNS resolution: `nslookup zenml.mycompany.com` ### Control Plane Connection Issues Check CloudWatch logs for: 1. OAuth2 authentication errors - verify `ZENML_SERVER_PRO_OAUTH2_CLIENT_SECRET` is correct 2. Network connectivity errors - verify NAT Gateway is operational 3. Certificate validation errors - verify outbound HTTPS to cloudapi.zenml.io works ## Updating the Deployment ### Update Configuration 1. Modify environment variables in the task definition 2. Create a new task definition revision 3. Update the ECS service to use the new task definition 4. ECS will gradually replace old tasks with new ones ### Upgrade ZenML Version 1. Update the container image in the task definition 2. Create a new task definition revision 3. Update the ECS service 4. Monitor CloudWatch logs during the update ## Cleanup To remove the deployment: 1. **Delete ECS Service** - Go to ECS → Clusters → zenml-hybrid → Services - Delete the zenml-server service - Set desired count to 0 first 2. **Delete ECS Cluster** - Delete the cluster once service is removed 3. **Delete ALB** - Go to EC2 → Load Balancers - Delete the ALB and associated target groups 4. **Delete RDS Instance** - Go to RDS → Databases - Delete the zenml-hybrid-db instance - Skip final snapshot if you don't need a backup 5. **Delete VPC and Related Resources** - Delete NAT Gateway (releases Elastic IP) - Delete subnets, route tables, security groups - Delete VPC 6. **Clean Up Secrets** - Go to Secrets Manager - Delete zenml/pro/oauth2-client-secret ## Next Steps - [Configure your organization in ZenML Cloud](https://cloud.zenml.io) - [Set up users and teams](organization.md) - [Configure stacks and service connectors](https://docs.zenml.io/concepts/stack_components) - [Run your first pipeline](https://github.com/zenml-io/zenml/tree/main/examples/quickstart) ## Related Documentation - [Hybrid Deployment Overview](hybrid-deployment.md) - [Self-hosted Deployment Overview](self-hosted-deployment.md) - [AWS ECS Documentation](https://docs.aws.amazon.com/ecs/) - [AWS RDS Documentation](https://docs.aws.amazon.com/rds/)
ZenML Scarf
--- ### Book/Getting Started/Zenml Pro/Deploy Workspace Event Triggers And Schedules (docs/book/getting-started/zenml-pro/deploy-workspace-event-triggers-and-schedules.md) --- description: >- Enable ZenML Pro event triggers and schedules (scheduler and executor microservices) for self-hosted workspace servers on Kubernetes. layout: title: visible: true description: visible: true tableOfContents: visible: true outline: visible: true pagination: visible: true --- # Enable Event Triggers and Schedules for the Workspace Server ZenML Pro [schedule triggers](triggers.md#schedule-triggers) run pipelines on a cron or interval. [Platform event triggers](triggers.md#platform-event-triggers) run pipelines when lifecycle events occur in the ZenML platform (for example after another pipeline completes). On self-hosted workspaces, both are part of the same opt-in capability and use the same background infrastructure: two additional microservices—the **scheduler** and the **executor**—plus a **Redis** broker that connects them. {% hint style="warning" %} **Commercial add-on:** Event triggers and schedules (schedule triggers and platform event triggers) are not included in the base ZenML Pro plan. Your organization must purchase and enable them explicitly. See the [pricing page](https://www.zenml.io/pricing) for plans and contact ZenML if you need entitlements enabled for your license. {% endhint %} {% hint style="warning" %} Deploy these microservices only for workspace servers installed with the ZenML Helm chart on Kubernetes. Other platforms (for example AWS ECS) are not covered here. {% endhint %} {% hint style="info" %} **Prerequisite:** Event triggers and schedules dispatch work against [pipeline snapshots](snapshots.md). You must enable the workload manager (snapshot support) on the workspace server before or together with the scheduler and executor. Follow [Enable Snapshot Support](deploy-workspace-snapshots.md) first if you have not configured it yet. {% endhint %} ## Prerequisites - **[Snapshot support (workload manager)](deploy-workspace-snapshots.md) configured** so triggered runs can execute pipeline snapshots in Kubernetes. - A **Redis** instance reachable from the workspace namespace. The scheduler and executor use Redis Streams as a message broker. Use a URL such as `redis://:6379/0`, or `rediss://:/0` when Redis requires TLS. - Enough cluster resources for the two microservices below (see the example `resources`). ## What to configure in Helm The ZenML Helm chart deploys optional background processes as additional microservices, each declared under the `workerDeployments` key in your workspace `values.yaml`. Each map entry becomes its own Kubernetes Deployment. Add a `workerDeployments` block next to your existing `server:` configuration. Each microservice uses the same container image as the ZenML Pro server by default and overrides the entrypoint to run the `plugins` helper with the subcommands below. The example enables both the **scheduler** and **executor** microservices: they use the `plugins` command with `start-scheduler` and `start-executor`, share `ZENML_REDIS_BROKER_URL`, and set SQLAlchemy pool sizes appropriate for dedicated pods. Adjust `resources`, probes, and pool sizes to match your cluster and load. {% hint style="warning" %} The **scheduler** microservice must always run as a **single replica** with a **`Recreate`** rollout strategy. Do not scale it horizontally or switch to `RollingUpdate`; multiple scheduler pods or overlapping rollouts can break schedule and event dispatch. {% endhint %} ``` /* Detailed source-code truncated for AI context efficiency. */ ``` For platform event triggers, the workspace **API server** must use the **same** Redis broker URL so it can publish pipeline lifecycle events (for example run completion) to Redis Streams for the executor to consume. Define this as a Kubernetes Secret in `server.secretEnvironment` and merge with any keys you already set: ```yaml server: secretEnvironment: ZENML_REDIS_BROKER_URL: redis://zenml-redis:6379/0 ``` ### Environment variables (reference) | Variable | Where | Purpose | |----------|--------|---------| | `ZENML_REDIS_BROKER_URL` | scheduler, executor, API server | Redis connection URL for the broker | | `ZENML_STORE_POOL_SIZE` | scheduler, executor | SQLAlchemy pool size (defaults apply if unset) | | `ZENML_STORE_MAX_OVERFLOW` | scheduler, executor | SQLAlchemy max overflow for the store connection pool | | `ZENML_CONSUMER_WORKER_POOL_SIZE` | executor | Async pool size for dispatch processing in the executor microservice | ## Apply the change After updating your values file, upgrade the release (adjust release name and namespace as you use them): ```bash helm upgrade zenml oci://public.ecr.aws/zenml/zenml \ --namespace zenml-workspace \ --values zenml-workspace-values.yaml ``` ## Related behavior - **Triggers and snapshots:** Both schedule and platform event triggers attach to [pipeline snapshots](snapshots.md). Snapshot support is therefore a **prerequisite**: without the [workload manager](deploy-workspace-snapshots.md), triggered runs cannot execute as described in [Triggers](triggers.md). - **Concepts:** See [Schedule Triggers](triggers.md#schedule-triggers) and [Platform Event Triggers](triggers.md#platform-event-triggers) for how these triggers are modeled in ZenML Pro. ## Related resources - [Self-hosted Deployment on Kubernetes with Helm](deploy-workspace-k8s.md) - [Enable Snapshot (Workload Manager) Support](deploy-workspace-snapshots.md) - [Triggers](triggers.md) - [Helm chart on Artifact Hub](https://artifacthub.io/packages/helm/zenml/zenml) --- ### Book/Getting Started/Zenml Pro/Deploy Workspace K8s (docs/book/getting-started/zenml-pro/deploy-workspace-k8s.md) --- description: Deploy ZenML Pro workspaces on Kubernetes with Helm and enroll them in the ZenML Pro control plane layout: title: visible: true description: visible: true tableOfContents: visible: true outline: visible: true pagination: visible: true --- # Self-hosted Deployment on Kubernetes with Helm This guide provides step-by-step instructions for deploying ZenML Pro workspaces on Kubernetes using Helm and enrolling them in the ZenML Pro control plane. ZenML Pro workspace deployments use standard Kubernetes APIs and work on managed cloud clusters (EKS, GKE, AKS), enterprise platforms like Red Hat OpenShift and SUSE Rancher, and self-managed clusters on-premises or in your own cloud. ## Prerequisites Before starting, make sure you go through the [general prerequisites for hybrid deployments](deploy-prerequisites.md) and have collected the necessary artifacts and information. Particular requirements for Kubernetes with Helm deployments are listed below. **Infrastructure:** - Kubernetes cluster (1.24+) **Network:** - Load balancer, network gateway or Ingress controllers etc. - Internal DNS resolution - TLS certificates signed by your internal CA (or self-signed) - Network connectivity between cluster components **Tools (on a machine with internet access for initial setup):** - Helm (3.0+) ## Install the ZenML Pro Workspace Server ### Step 1: Enroll the Workspace in the ZenML Pro Control Plane Make sure to enroll the workspace in the ZenML Pro control plane by following the [Enroll a Workspace in the ZenML Pro Control Plane](enroll-workspace.md) guide and collect the necessary enrollment credentials. ### Step 2: Create Kubernetes Secrets If you are using an internal container registry, you may need to create a secret to allow the ZenML Pro workspace servers to pull the images. The following is an example of how to do this: ```bash # Create namespace for ZenML Pro kubectl create namespace zenml-pro-workspace # Create secret for internal registry credentials (if needed) kubectl -n zenml-pro-workspace create secret docker-registry image-pull-secret \ --docker-server=internal-registry.mycompany.com \ --docker-username= \ --docker-password= ``` You'll use this secret in the next step when configuring the Helm values for the ZenML Pro workspace server. ### Step 3: Configure Helm Values for Workspace Server {% hint style="info" %} The ZenML Pro workspace server is developed on top of the open-source ZenML server and inherits all its features and deployment options. This deployment also uses the open-source ZenML Helm chart, with the only notable differences being that the ZenML Pro workspace server is configured to connect to the ZenML Pro control plane and uses a different container image that is released separately from the open-source ZenML server. {% endhint %} The example below is a basic configuration for the ZenML Pro control plane Helm chart. For a full list of configurable values and documentation, also see the [OSS ZenML Helm chart on ArtifactHub](https://artifacthub.io/packages/helm/zenml/zenml). For advanced deployment configurations, you can also consult the [Deploy with Helm](https://docs.zenml.io/deploying-zenml/deploying-zenml/deploy-with-helm) documentation, which covers topics such as: * database configuration options * external secrets store backends (AWS Secrets Manager, GCP Secret Manager, Azure Key Vault, HashiCorp Vault) * database backup strategies ``` /* Detailed source-code truncated for AI context efficiency. */ ``` **Minimum required settings:** * the database credentials (`server.database.url`) * the URL (`server.serverURL`) and Ingress hostname (`server.ingress.host`) where the ZenML Pro workspace server will be reachable * the Pro configuration (`server.pro.*`) with your organization and workspace details **Additional relevant settings:** * configure container registry credentials (`imagePullSecrets`) if your cluster needs to authenticate to the container registry * injecting custom CA certificates (`server.certificates`), especially important if the TLS certificates used by the ZenML Pro services are signed by a custom Certificate Authority * configure HTTP proxy settings (`server.proxy`) * custom container image repository location (`server.image.repository`) * additional Ingress settings (`server.ingress`) * Kubernetes resources allocated to the pods (`resources`) ### Step 4: Deploy the ZenML Pro Workspace Server with Helm Using the remote Helm chart, if you have access to the internet: ```bash helm install zenml oci://public.ecr.aws/zenml/zenml \ --version \ --namespace zenml-workspace \ --create-namespace \ --values zenml-workspace-values.yaml ``` Using the local Helm chart, if you have downloaded the chart previously: ```bash # Deploy workspace helm install zenml ./zenml-.tgz \ --namespace zenml-workspace \ --create-namespace \ --values zenml-workspace-values.yaml ``` Verify deployment: ```bash kubectl -n zenml-workspace get pods kubectl -n zenml-workspace get svc kubectl -n zenml-workspace get ingress ``` ### Step 5: Install Internal CA Certificates If the TLS certificates used by the ZenML Pro workspace server are signed by a custom Certificate Authority, you need to install the CA certificates on every machine that needs to access the ZenML workspace server. #### System-wide Installation On all client machines that will access the ZenML workspace server: 1. Obtain your internal CA certificate 2. Install it in the system certificate store: - **Linux**: Copy to `/usr/local/share/ca-certificates/` and run `update-ca-certificates` - **macOS**: Use `sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain ` - **Windows**: Use `certutil -addstore "Root" cert.pem` 3. For some browsers (e.g., Chrome), updating the system's CA certificates is not enough. You will also need to import the CA certificates into the browser. 4. For Python/ZenML client: ```bash export REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt ``` #### For Containerized Pipelines When running containerized pipelines with ZenML, you'll need to install the CA certificates into the container images built by ZenML. Customize the build process via [DockerSettings](https://docs.zenml.io/how-to/customize-docker-builds): 1. Create a custom Dockerfile: ```dockerfile # Use the original ZenML client image as a base image FROM zenmldocker/zenml: # Install certificates COPY my-custom-ca.crt /usr/local/share/ca-certificates/ RUN update-ca-certificates ENV REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt ``` 2. Build and push the image to your internal registry: ```bash docker build -t internal-registry.mycompany.com/zenml/zenml: . docker push internal-registry.mycompany.com/zenml/zenml: ``` 3. Update your ZenML pipeline code to use the custom image: ```python from zenml.config import DockerSettings from zenml import __version__ # Define the custom base image CUSTOM_BASE_IMAGE = f"internal-registry.mycompany.com/zenml/zenml:{__version__}" docker_settings = DockerSettings( parent_image=CUSTOM_BASE_IMAGE, ) @pipeline(settings={"docker": docker_settings}) def my_pipeline() -> None: ... ``` ### Access the Workspace UI 1. Open the ZenML Pro control plane UI in your browser 2. Sign in with your organization credentials 3. You should see your workspace running and ready to use in the organization it was enrolled in ### Access the Workspaces from ZenML CLI To login to a workspace with the ZenML CLI: ```bash zenml login ``` ### (Optional) Opt-in Workspace Server Features Several ZenML Pro capabilities are not turned on by the default Helm installation. They need extra infrastructure, environment variables, or additional microservices beyond the main workspace server. Many are paid add-ons on top of the base plan—see the [pricing page](https://www.zenml.io/pricing)—and must be licensed and enabled for your organization before they work end-to-end. **What it enables** points to ZenML Pro documentation for the capability. **What it deploys** summarizes the extra components at a high level. In the ZenML Helm chart, each optional background process is modeled as an additional microservice (its own Kubernetes Deployment) next to the API server. You declare those microservices in `values.yaml` under the `workerDeployments` map; each key under that map configures one microservice. The per-feature guides show the exact YAML. | Guide | What it enables | What it deploys | Minimum workspace server version | |-------|-----------------|-----------------|----------------------------------| | [Enable Snapshot Support](deploy-workspace-snapshots.md) | [Snapshots](snapshots.md) | Workload manager: server env vars and RBAC so the workspace can create "runner Jobs" in Kubernetes | 0.90.0 | | [Enable Event Triggers and Schedules](deploy-workspace-event-triggers-and-schedules.md) | [Schedule triggers](triggers.md#schedule-triggers), [platform event triggers](triggers.md#platform-event-triggers) | **Scheduler** and **executor** microservices, **Redis** (broker URL via **`secretEnvironment`**); **`server.secretEnvironment`** **`ZENML_REDIS_BROKER_URL`** for platform events; requires [snapshot support](deploy-workspace-snapshots.md) to run attached snapshots | 0.94.3 | | [Enable Resource Pools](deploy-workspace-resource-pools.md) | [Resource pools](resource-pools.md) | **Resource pool reconciler** microservice | 0.94.3 | Deploy [snapshot support](deploy-workspace-snapshots.md) before you rely on [event triggers and schedules](deploy-workspace-event-triggers-and-schedules.md) end-to-end: triggers run against pipeline snapshots, which need the workload manager to execute on the cluster. **Platform event triggers** also require **`ZENML_REDIS_BROKER_URL`** on the main workspace server, set through **`server.secretEnvironment`** (or **`server.environmentSecretKeyRefs`**) rather than plain `server.environment`, because broker URLs may contain credentials. Schedule-only triggers do not need Redis on the server. See [Enable Event Triggers and Schedules](deploy-workspace-event-triggers-and-schedules.md). ## Day 2 Operations For information on upgrading ZenML Pro components, see the [Upgrades & Updates](upgrades-updates.md) guide. ## Related Resources - [Self-hosted Deployment Overview](self-hosted-deployment.md) - [Kubernetes Documentation](https://kubernetes.io/docs/) - [MySQL Documentation](https://dev.mysql.com/doc/) - [Helm Documentation](https://helm.sh/docs/) ## Support For self-hosted and hybrid SaaS/self-hosted deployments, contact ZenML Support: - Email: [cloud@zenml.io](mailto:cloud@zenml.io) - Provide: Deployment status, configuration details and any error logs Request from ZenML Support: - Pre-deployment architecture consultation - Offline support packages - Update bundles and release notes - Security documentation (SBOM, vulnerability reports)
ZenML Scarf
--- ### Book/Getting Started/Zenml Pro/Deploy Workspace Resource Pools (docs/book/getting-started/zenml-pro/deploy-workspace-resource-pools.md) --- description: >- Enable the ZenML Pro resource pool reconciler microservice for self-hosted workspace servers on Kubernetes. layout: title: visible: true description: visible: true tableOfContents: visible: true outline: visible: true pagination: visible: true --- # Enable Resource Pools for the Workspace Server [Resource pools](resource-pools.md) let you model shared capacity (GPUs, custom keys, and related limits) for dynamic pipelines. Keeping pool state consistent uses a background **reconciler** process in ZenML Pro. On **Kubernetes** self-hosted deployments, you enable that process by adding a microservice (the resource pool reconciler) that runs `plugins start-resource-pool-reconciler` (same image as the workspace server). {% hint style="warning" %} **Commercial add-on:** Resource pools are not included in the base ZenML Pro plan. Your organization must purchase and enable them explicitly. See the [pricing page](https://www.zenml.io/pricing) for plans and contact ZenML if you need entitlements enabled for your license. {% endhint %} {% hint style="warning" %} Deploy this microservice only for workspace servers installed with the ZenML Helm chart on Kubernetes. Other platforms (for example AWS ECS) are not covered here. {% endhint %} ## Prerequisites - Resource pools apply to [dynamic pipelines](https://docs.zenml.io/how-to/steps-pipelines/dynamic_pipelines). Ensure your teams understand that contract before enabling the reconciler. - Enough cluster resources for one extra microservice (see the example `resources` below). ## What to configure in Helm The ZenML Helm chart deploys optional background processes as additional microservices, each declared under the `workerDeployments` key in your workspace `values.yaml`. Each map entry becomes its own Kubernetes Deployment. Add the **resource pool reconciler** under `workerDeployments` next to your existing `server:` configuration. That microservice uses the same container image as the ZenML Pro server by default and overrides the entrypoint to run the reconciler. Set SQLAlchemy pool sizes appropriate for a dedicated pod. The example below is a reasonable starting point; adjust `resources` and probes for your environment. {% hint style="warning" %} The **resource pool reconciler** microservice must always run as a **single replica** with a **`Recreate`** rollout strategy. Do not scale it horizontally or switch to `RollingUpdate`; multiple reconciler pods or overlapping rollouts can corrupt or confuse pool reconciliation. {% endhint %} ```yaml workerDeployments: resource-pool-reconciler: enabled: true replicaCount: 1 command: ["plugins"] args: ["start-resource-pool-reconciler"] strategy: type: Recreate resources: requests: cpu: 100m memory: 256Mi limits: cpu: 500m memory: 512Mi environment: ZENML_STORE_POOL_SIZE: "1" ZENML_STORE_MAX_OVERFLOW: "1" livenessProbe: httpGet: path: /health port: 8000 initialDelaySeconds: 30 periodSeconds: 60 timeoutSeconds: 2 failureThreshold: 3 readinessProbe: httpGet: path: /health port: 8000 initialDelaySeconds: 10 periodSeconds: 30 timeoutSeconds: 2 failureThreshold: 3 ``` ### Environment variables (reference) | Variable | Purpose | |----------|---------| | `ZENML_STORE_POOL_SIZE` | SQLAlchemy pool size for store access in this microservice | | `ZENML_STORE_MAX_OVERFLOW` | SQLAlchemy max overflow for the store connection pool | ## Apply the change After updating your values file, upgrade the release (adjust release name and namespace as you use them): ```bash helm upgrade zenml oci://public.ecr.aws/zenml/zenml \ --namespace zenml-workspace \ --values zenml-workspace-values.yaml ``` ## Related resources - [Resource pools](resource-pools.md) — product overview and concepts - [Resource pools — Core concepts](resource-pools-core-concepts.md) - [Self-hosted Deployment on Kubernetes with Helm](deploy-workspace-k8s.md) - [Helm chart on Artifact Hub](https://artifacthub.io/packages/helm/zenml/zenml) --- ### Book/Getting Started/Zenml Pro/Deploy Workspace Snapshots (docs/book/getting-started/zenml-pro/deploy-workspace-snapshots.md) --- description: Enable snapshot support for self-hosted ZenML Pro workspaces layout: title: visible: true description: visible: true tableOfContents: visible: true outline: visible: true pagination: visible: true --- # Enable Snapshot (Workload Manager) Support for the Workspace Server The Workspace Server includes a Workload Manager feature that allows running pipelines directly from the ZenML Pro UI. This feature requires access to a Kubernetes cluster where ad-hoc pipeline runner pods can be created. {% hint style="warning" %} Snapshots are only available from ZenML Pro Workspace Server version 0.90.0 onwards. {% endhint %} {% hint style="warning" %} Snapshot support is only available for workspace servers that are deployed on Kubernetes. Workspace servers that are deployed on AWS ECS or other platforms are currently not supported. {% endhint %} ## Prerequisites Basic requirements: - Kubernetes cluster (1.24+) accessible from the workspace server - Dedicated namespace for runner pods - Service accounts with RBAC permissions to create/manage pods - Image pull secrets for the service accounts ### Understanding Workload Manager Sub-features Running pipelines from the UI relies on running Kubernetes jobs (aka "runner" jobs) that are responsible for launching the pipelines in the same manner as when running them from the CLI or SDK. These jobs need to use container images with the correct Python package dependencies to be able to launch the pipelines. There are several ways to achieve this and you'll need to choose the one that best fits your needs: 1. **Reuse snapshot container images**: the same pipeline container images that are built for the snapshot being run can also be used for the "runner" jobs. For this to work, you have to grant the "runner" jobs pull access to all container registries where these images are stored (i.e. the Container Registries used in your ZenML Stacks). This option allows running only snapshots associated with stacks that include the same container registry. 2. **Build "runner" container images on-demand**: in this variant, the Workspace Server will launch additional Kubernetes jobs to build the "runner" images when needed and push them to a configured container registry. It requires these "builder" Kubernetes jobs to have push permissions to a private container registry and the "runner" jobs to have pull access to the same container registry. This option is the most flexible and allows running snapshots associated with any stack and any integration. 3. **Use pre-built "runner" image**: you can provide a single pre-built "runner" image (stored by you in a container registry) for all runs. This is the simplest and fastest option, but you have to ensure that the image has all the correct Python package dependencies to be able to launch the pipelines. This option is the most limited and requires you to pre-build a container image that contains all the dependencies for all possible stacks and integrations used by your pipelines. **Store logs externally**: By default, logs shown in the ZenML Pro UI are extracted from the "runner" job pods. Since pods may disappear, you can configure external log storage where these logs will be stored. Currently, this is only supported with the AWS implementation. If you enable this, you need to configure the S3 bucket and region where the logs will be stored and grant the ZenML Pro Workspace Server pods write access to this bucket. There are three available Workload Manager implementations: * **Kubernetes**: Runs pipelines in the same Kubernetes cluster as the ZenML Pro workspace server. * **AWS**: Extends the Kubernetes implementation to build/push images to AWS ECR and store logs in AWS S3. ### 1. Create Kubernetes Resources for the Workload Manager Create a dedicated namespace and service account where the runner jobs will be launched. ```bash # Create namespace kubectl create namespace zenml-workload-manager # Create service account kubectl -n zenml-workload-manager create serviceaccount zenml-workload-manager ``` ### 2. Choose Implementation Your choice of implementation will determine the additional environment variables you need to configure in the ZenML Workspace Server Helm deployment: **Option A: Kubernetes Implementation (Basic)** Provides generic Kubernetes functionality to run snapshots. ```yaml server: environment: ZENML_SERVER_WORKLOAD_MANAGER_IMPLEMENTATION_SOURCE: zenml_cloud_plugins.kubernetes_workload_manager.KubernetesWorkloadManager ZENML_KUBERNETES_WORKLOAD_MANAGER_NAMESPACE: zenml-workload-manager ZENML_KUBERNETES_WORKLOAD_MANAGER_SERVICE_ACCOUNT: zenml-workload-manager ``` **Option B: AWS Implementation** Provides AWS-specific features including external S3 logs and ECR integration. ```yaml server: environment: ZENML_SERVER_WORKLOAD_MANAGER_IMPLEMENTATION_SOURCE: zenml_cloud_plugins.aws_kubernetes_workload_manager.AWSKubernetesWorkloadManager ZENML_KUBERNETES_WORKLOAD_MANAGER_NAMESPACE: zenml-workload-manager ZENML_KUBERNETES_WORKLOAD_MANAGER_SERVICE_ACCOUNT: zenml-workload-manager ZENML_AWS_KUBERNETES_WORKLOAD_MANAGER_REGION: eu-central-1 # To enable storing logs externally in S3, also set the following environment variables: ZENML_KUBERNETES_WORKLOAD_MANAGER_ENABLE_EXTERNAL_LOGS: "true" ZENML_AWS_KUBERNETES_WORKLOAD_MANAGER_BUCKET: s3://my-bucket/run-template-logs ``` ### 3. Configure the Runner Image Source Choose how runner images are managed. Your choice of implementation will determine the additional environment variables you need to configure in the ZenML Workspace Server Helm deployment: **Option 1: Reuse snapshot container images** Reuse the container images built for the snapshot being run. ```yaml server: environment: ZENML_KUBERNETES_WORKLOAD_MANAGER_BUILD_RUNNER_IMAGE: "false" # Keep this empty or skip setting it to reuse the snapshot container images # ZENML_KUBERNETES_WORKLOAD_MANAGER_RUNNER_IMAGE: ``` **Option 2: Have ZenML Build Runner Images** Build the runner images on-demand. ```yaml server: environment: ZENML_KUBERNETES_WORKLOAD_MANAGER_BUILD_RUNNER_IMAGE: "true" ZENML_KUBERNETES_WORKLOAD_MANAGER_DOCKER_REGISTRY: internal-registry.mycompany.com/zenml ``` **Option 3: Use a Pre-built Runner Image** Use a pre-built runner image for all runs. ```yaml server: environment: ZENML_KUBERNETES_WORKLOAD_MANAGER_BUILD_RUNNER_IMAGE: "false" ZENML_KUBERNETES_WORKLOAD_MANAGER_RUNNER_IMAGE: internal-registry.mycompany.com/zenml/zenml: ``` ### 4. Configure Permissions The Kubernetes service account running the ZenML Workspace Server needs additional permissions: * permissions to create and manage jobs in the workload manager Kubernetes namespace set up in step 1. * if the AWS implementation is used and external S3 logs are enabled, permissions to write to the configured S3 bucket. The workload manager Kubernetes service account set up in step 1 also needs the following container registry permissions: * permissions to pull images from the container registry where the runner images are stored. * if the option to build runner images on-demand is chosen, permissions to push images to the container registry where the runner images will be pushed. Granting these permissions can be achieved in several ways: * grant the entire cluster access to the container registry * use implicit workload identity access to the container registry - available in most cloud providers by granting the Kubernetes service account access to the container registry * configure a service account with implicit access to the container registry - associating some cloud service identity (e.g. a GCP service account, an AWS IAM role, etc.) with the Kubernetes service account * configure an image pull secret for the service account - similar to the previous option, but using a Kubernetes secret instead of a cloud service identity #### Recommended least-privilege split Use separate identities for: 1. the service account running the Workspace Server pod, 2. the service account configured in `ZENML_KUBERNETES_WORKLOAD_MANAGER_SERVICE_ACCOUNT` for runner/builder jobs. **Workspace Server service account RBAC (in workload manager namespace)** The Workspace Server creates, monitors, and cleans up workload manager jobs and fetches pod logs. Grant: - `batch/jobs`: `create`, `get`, `deletecollection` - `core/pods`: `list` - `core/pods/log`: `get` **Workload manager runner/builder service account** The runner/builder jobs launched by the workload manager do not need Kubernetes API permissions for the workload manager control loop itself. This account mainly needs: - container registry pull permissions for runner images, - container registry push permissions if build-on-demand is enabled, - optional cloud permissions required by your workload implementation (for example S3 write access when AWS external logs are enabled). ### 4. Environment Variable Reference All supported environment variables for workload manager configuration: | Variable | Required | Description | |----------|----------|-------------| | `ZENML_SERVER_WORKLOAD_MANAGER_IMPLEMENTATION_SOURCE` | Yes | Implementation class (see options above) | | `ZENML_KUBERNETES_WORKLOAD_MANAGER_NAMESPACE` | Yes | Kubernetes namespace for runner jobs | | `ZENML_KUBERNETES_WORKLOAD_MANAGER_SERVICE_ACCOUNT` | Yes | Kubernetes service account for runner jobs | | `ZENML_KUBERNETES_WORKLOAD_MANAGER_BUILD_RUNNER_IMAGE` | No | Whether to build runner images (default: `false`) | | `ZENML_KUBERNETES_WORKLOAD_MANAGER_DOCKER_REGISTRY` | Conditional | Registry for runner images (required if building images) | | `ZENML_KUBERNETES_WORKLOAD_MANAGER_RUNNER_IMAGE` | No | Pre-built runner image (used if not building) | | `ZENML_KUBERNETES_WORKLOAD_MANAGER_ENABLE_EXTERNAL_LOGS` | No | Store logs externally (default: `false`, AWS only) | | `ZENML_KUBERNETES_WORKLOAD_MANAGER_POD_RESOURCES` | No | Pod resources in JSON format | | `ZENML_KUBERNETES_WORKLOAD_MANAGER_TTL_SECONDS_AFTER_FINISHED` | No | Cleanup time for finished jobs (default: 2 days) | | `ZENML_KUBERNETES_WORKLOAD_MANAGER_NODE_SELECTOR` | No | Node selector in JSON format | | `ZENML_KUBERNETES_WORKLOAD_MANAGER_TOLERATIONS` | No | Tolerations in JSON format | | `ZENML_KUBERNETES_WORKLOAD_MANAGER_JOB_BACKOFF_LIMIT` | No | Backoff limit for builder/runner jobs | | `ZENML_KUBERNETES_WORKLOAD_MANAGER_POD_FAILURE_POLICY` | No | Pod failure policy for builder/runner jobs | | `ZENML_SERVER_MAX_CONCURRENT_TEMPLATE_RUNS` | No | Max concurrent snapshot runs per pod (default: 2) | **AWS-specific variables:** | Variable | Required | Description | |----------|----------|-------------| | `ZENML_AWS_KUBERNETES_WORKLOAD_MANAGER_BUCKET` | Conditional | S3 bucket for logs (required if external logs enabled) | | `ZENML_AWS_KUBERNETES_WORKLOAD_MANAGER_REGION` | Conditional | AWS region (required if building images) | ### 5. Complete Configuration Examples **Minimal Kubernetes Configuration:** ```yaml server: environment: ZENML_SERVER_WORKLOAD_MANAGER_IMPLEMENTATION_SOURCE: zenml_cloud_plugins.kubernetes_workload_manager.KubernetesWorkloadManager ZENML_KUBERNETES_WORKLOAD_MANAGER_NAMESPACE: zenml-workspace-namespace ZENML_KUBERNETES_WORKLOAD_MANAGER_SERVICE_ACCOUNT: zenml-workspace-service-account ``` **Full AWS Configuration:** ```yaml server: environment: ZENML_SERVER_WORKLOAD_MANAGER_IMPLEMENTATION_SOURCE: zenml_cloud_plugins.aws_kubernetes_workload_manager.AWSKubernetesWorkloadManager ZENML_KUBERNETES_WORKLOAD_MANAGER_NAMESPACE: zenml-workspace-namespace ZENML_KUBERNETES_WORKLOAD_MANAGER_SERVICE_ACCOUNT: zenml-workspace-service-account ZENML_KUBERNETES_WORKLOAD_MANAGER_BUILD_RUNNER_IMAGE: "true" ZENML_KUBERNETES_WORKLOAD_MANAGER_DOCKER_REGISTRY: 339712793861.dkr.ecr.eu-central-1.amazonaws.com ZENML_KUBERNETES_WORKLOAD_MANAGER_ENABLE_EXTERNAL_LOGS: "true" ZENML_KUBERNETES_WORKLOAD_MANAGER_POD_RESOURCES: '{"requests": {"cpu": "100m", "memory": "400Mi"}, "limits": {"memory": "700Mi"}}' ZENML_AWS_KUBERNETES_WORKLOAD_MANAGER_BUCKET: s3://my-bucket/run-template-logs ZENML_AWS_KUBERNETES_WORKLOAD_MANAGER_REGION: eu-central-1 ZENML_KUBERNETES_WORKLOAD_MANAGER_NODE_SELECTOR: '{"node-pool": "zenml-pool"}' ZENML_KUBERNETES_WORKLOAD_MANAGER_TOLERATIONS: '[{"key": "node-pool", "operator": "Equal", "value": "zenml-pool", "effect": "NoSchedule"}]' ZENML_SERVER_MAX_CONCURRENT_TEMPLATE_RUNS: 10 ``` **Configuration with a Pre-built Runner Image:** ```yaml server: environment: ZENML_SERVER_WORKLOAD_MANAGER_IMPLEMENTATION_SOURCE: zenml_cloud_plugins.kubernetes_workload_manager.KubernetesWorkloadManager ZENML_KUBERNETES_WORKLOAD_MANAGER_NAMESPACE: zenml-workspace-namespace ZENML_KUBERNETES_WORKLOAD_MANAGER_SERVICE_ACCOUNT: zenml-workspace-service-account ZENML_KUBERNETES_WORKLOAD_MANAGER_BUILD_RUNNER_IMAGE: "false" ZENML_KUBERNETES_WORKLOAD_MANAGER_RUNNER_IMAGE: internal-registry.mycompany.com/zenml/zenml: ZENML_KUBERNETES_WORKLOAD_MANAGER_POD_RESOURCES: '{"requests": {"cpu": "100m", "memory": "400Mi"}, "limits": {"memory": "700Mi"}}' ZENML_KUBERNETES_WORKLOAD_MANAGER_TTL_SECONDS_AFTER_FINISHED: 86400 ZENML_SERVER_MAX_CONCURRENT_TEMPLATE_RUNS: 2 ``` ### 6. Update Workspace Deployment Update your workspace server Helm values with workload manager configuration and redeploy: ```bash helm upgrade zenml ./zenml-.tgz \ --namespace zenml-workspace \ --values zenml-workspace-values.yaml ``` ## Related Resources - [Self-hosted Deployment Overview](self-hosted-deployment.md) - [Kubernetes Documentation](https://kubernetes.io/docs/) - [Helm Documentation](https://helm.sh/docs/)
ZenML Scarf
--- ### Book/Getting Started/Zenml Pro/Enroll Workspace (docs/book/getting-started/zenml-pro/enroll-workspace.md) --- description: Enroll a ZenML Pro workspace in the ZenML Pro control plane layout: title: visible: true description: visible: true tableOfContents: visible: true outline: visible: true pagination: visible: true --- # Enroll a Workspace in the ZenML Pro Control Plane Before deploying a self-hosted workspace, or when migrating an existing ZenML OSS server to ZenML Pro, you need to enroll the workspace in a ZenML Pro organization. The enrollment procedure creates a workspace placeholder in the organization and generate the necessary enrollment credentials. You will use these credentials (e.g. workspace ID, enrollment key) to configure the workspace server deployment. To enroll a workspace, you simply use the "New Workspace" button in the ZenML Pro UI under your organization and toggle the "Enroll Workspace" switch. {% hint style="info" %} Enrolling workspaces through the UI was not supported in ZenML Pro control plane versions 0.13.1 and earlier. If you're running an older version, you can still use the ZenML Pro OpenAPI interface or programmatically access the ZenML Pro API, as described below. {% endhint %} {% tabs %} {% tab title="OpenAPI Interface" %} First, log in to the ZenML Pro UI as usual. Then, to access the ZenML Pro OpenAPI interface, append the `/api/v1` path to the ZenML Pro server URL in your browser. For example: https://zenml-pro.my.domain/api/v1s Using the OpenAPI interface, you can manage local user accounts by making requests to the `/api/v1/workspaces` endpoint. For example, to create a new super-user account: {% endtab %} {% tab title="curl" %} First, [create a personal access token (PAT)](personal-access-tokens.md) using the ZenML Pro UI. Then, use this PAT to enroll the workspace via curl: ```bash # Create a new super-user account curl -X POST "https://zenml-pro.my.domain/api/v1/workspaces?name=my-workspace&enroll=true" \ -H "Authorization: Bearer " ``` {% endtab %} {% endtabs %} The workspace enrollment response will contain all the necessary enrollment credentials for the workspace that you will need to configure the workspace server during deployment: * the workspace ID * the enrollment key * the organization ID * the organization name * the workspace name These details will need to be passed to the workspace server container in the form of environment variables: | Variable | Value | |----------|-------| | `ZENML_SERVER_DEPLOYMENT_TYPE` | `cloud` | | `ZENML_SERVER_PRO_API_URL` | The URL where the ZenML Pro control plane API is reachable. | | `ZENML_SERVER_PRO_DASHBOARD_URL` | The URL where the ZenML Pro control plane UI is reachable. | | `ZENML_SERVER_PRO_ORGANIZATION_ID` | The ID of the organization the workspace belongs to. | | `ZENML_SERVER_PRO_ORGANIZATION_NAME` | The name of the organization the workspace belongs to. | | `ZENML_SERVER_PRO_WORKSPACE_ID` | The ID of the workspace. | | `ZENML_SERVER_PRO_WORKSPACE_NAME` | The name of the workspace. | | `ZENML_SERVER_PRO_OAUTH2_CLIENT_SECRET` | The enrollment key for the workspace. | NOTE: in the case of Helm, these values are configurable as Helm values when deploying the workspace server: ```yaml server: ... pro: enabled: true apiURL: https://zenml-pro.my.domain/api/v1 dashboardURL: https://zenml-pro.my.domain enrollmentKey: organizationID: organizationName: workspaceID: workspaceName: ``` Alternatively, use `enrollmentKeySecretRef` to source the enrollment key from an existing Kubernetes Secret: ```yaml server: pro: enrollmentKeySecretRef: name: key: ``` Configure only one of `enrollmentKey` and `enrollmentKeySecretRef`.
ZenML Scarf
--- ### Book/Getting Started/Zenml Pro/Hierarchy (docs/book/getting-started/zenml-pro/hierarchy.md) --- description: Understanding ZenML's hierarchical structure icon: ball-pile --- # Hierarchy In ZenML Pro, there is a slightly different entity hierarchy as compared to the open-source ZenML\ framework. This document walks you through the key differences and new concepts that are only available for Pro users. {% hint style="info" %} s**Note**: Workspaces were previously called "Tenants" in earlier versions of ZenML Pro. We've updated the terminology to better reflect their role in organizing MLOps resources. {% endhint %} The image above shows the hierarchy of concepts in ZenML Pro. * At the top level is your [**Organization**](organization.md). An organization is a collection of users, teams, and workspaces. * Each [**Workspace**](workspaces.md) (formerly `tenant`) is an isolated deployment of a ZenML server (with some pro features). It contains multiple projects and their resources. * Each [**Project**](projects.md) is a logical subdivision within a workspace that provides isolation for MLOps resources like pipelines, artifacts, and models. Projects have their own roles and access controls. * [**Teams**](teams.md) are groups of users within an organization. They help in organizing users and managing access to resources at organization, workspace, and project levels. * **Users** are single individual accounts on a ZenML Pro instance. * [**Roles**](roles.md) exist at organization, workspace, and project levels to control what actions users can perform. More details about each of these concepts are available in their linked pages below:
OrganizationsLearn about managing organizations in ZenML Pro.organization.mdpro-organizations.png
WorkspacesUnderstand how to work with workspaces in ZenML Pro.workspaces.mdpro-workspaces.png
ProjectsLearn about managing projects and their resources.projects.mdpro-projects.png
TeamsExplore team management in ZenML Pro.teams.mdpro-teams.png
Roles & PermissionsLearn about role-based access control in ZenML Pro.roles.mdpro-roles.png
ZenML Scarf
--- ### Book/Getting Started/Zenml Pro/Hybrid Deployment (docs/book/getting-started/zenml-pro/hybrid-deployment.md) --- description: >- Learn about ZenML Pro Hybrid SaaS deployment - balancing control with convenience for enterprise MLOps. icon: building-shield --- # Hybrid SaaS Deployment ZenML Pro Hybrid SaaS offers the perfect balance between control and convenience. While ZenML manages user authentication and RBAC through a cloud-hosted control plane, all your data, metadata, and workspaces run securely within your own infrastructure. {% hint style="info" %} To learn more about Hybrid SaaS deployment, [book a call](https://www.zenml.io/book-your-demo). {% endhint %} ## Overview The Hybrid deployment model is designed for organizations that need to keep sensitive data and metadata within their infrastructure boundaries while still benefiting from centralized user management and simplified operations. ## Architecture ### What Runs Where | Component | Location | Purpose | |-----------|----------|---------| | Pro Control Plane | ZenML Infrastructure | Manages authentication, RBAC, and global workspace coordination | | ZenML Pro Server(s) | Your Infrastructure | Handles pipeline orchestration and execution | | Metadata Store | Your Infrastructure | Stores all pipeline runs, model metadata, and tracking information | | Secrets Store | Your Infrastructure | Stores all credentials and sensitive configuration | | Compute Resources | Your infrastructure through [stacks](https://docs.zenml.io/concepts/stack_components) | Executes pipeline steps and training jobs | | Data & Artifacts | Your infrastructure through [stacks](https://docs.zenml.io/concepts/stack_components) | Stores datasets, models, and pipeline artifacts | {% hint style="success" %} All metadata, secrets, and ML artifacts remain within your infrastructure. Only authentication and authorization data flows to the ZenML control plane. {% endhint %} ## Key Benefits ### Enhanced Security & Compliance All metadata stays within your infrastructure, ensuring complete data sovereignty. Credentials never leave your environment, and workspaces operate behind your security perimeter, making the deployment compatible with VPN and firewall policies. ### Centralized Governance The hybrid model provides unified user management through a single control plane for all workspaces. Permissions are centrally managed across teams with consistent RBAC, and you only need to configure SSO integration once. Platform teams gain global visibility across all workspaces while enforcing standardized organizational policies. ### Balanced Control You maintain full control over workspace configuration and resources while benefiting from reduced operational overhead compared to a fully self-hosted deployment. Workspace resources can be configured to specific team needs, and workspaces can be fully isolated per team, department, or entity. ### Production Ready The control plane and UI are automatically updated and maintained by ZenML, and you get direct access to ZenML experts through professional support. ## Ideal Use Cases Hybrid SaaS works well for regulated industries (finance, healthcare, government) with strict data residency requirements, and for organizations with centralized MLOps teams managing multiple business units. It's also a good fit for companies with existing VPN or firewall policies that restrict inbound connections, enterprises requiring audit trails of all data access within their infrastructure, teams needing customization while maintaining centralized user management, and organizations with compliance requirements mandating on-premises metadata storage. ## Architecture Details ### Network Security Workspaces initiate outbound-only connections to the control plane, meaning no inbound connections are required to your infrastructure. This makes the deployment compatible with strict firewall policies. Each workspace can be deployed in separate VPCs or networks, isolated per team, department, or customer. Different workspaces can be configured with different security policies and managed independently by different teams. #### Private connectivity for Google Cloud workspaces For organizations that deploy Hybrid workspaces in Google Cloud, it is now possible to connect the Google Cloud workspace VPC privately to the ZenML control plane infrastructure in AWS without sending traffic over the public internet. This can be implemented with [AWS Interconnect - multicloud](https://aws.amazon.com/about-aws/whats-new/2026/04/aws-announces-ga-AWS-interconnect-multicloud/) together with [Google Cloud Cross-Cloud Interconnect](https://docs.cloud.google.com/network-connectivity/docs/interconnect/how-to/cci/aws/connectivity-overview), giving the workspace a dedicated, private network path between Google Cloud and AWS. This option is useful when your security policy requires the workspace server and metadata store to stay in Google Cloud while control-plane communication must use private cloud networking rather than public internet routing or VPN. The exact topology depends on your Google Cloud VPC, AWS networking setup, region pairing, routing, BGP configuration, and redundancy requirements. Contact [cloud@zenml.io](mailto:cloud@zenml.io) or your ZenML representative to validate the design before implementation. ### Data Residency | Data Type | Storage Location | Purpose | |-----------|------------------|---------| | Account metadata | Control Plane | Authentication only | | RBAC policies | Control Plane | Authorization decisions | | Pipeline metadata | Your Infrastructure | Run history, metrics, parameters | | Model metadata | Your Infrastructure | Model versions, stages, annotations | | Artifacts | Your Infrastructure | Datasets, models, visualizations | | Secrets | Your Infrastructure | Cloud credentials, API keys | | Logs | Your Infrastructure | Step outputs, debug information | ## Setup Process ### 1. Initial Configuration [Book a demo](https://www.zenml.io/book-your-demo) to get started. The ZenML team will set up your organization in the control plane, establish secure communication channels, and optionally configure SSO integration. ### 2. Workspace Deployment Deploy ZenML workspaces in your infrastructure using one of the supported deployment backends: Kubernetes (recommended, including EKS, GKE, AKS, or self-managed clusters), AWS ECS, or other container orchestration platforms. Your infrastructure needs to provide a MySQL database, egress access to `cloud.zenml.io` for control plane communication, and compute resources for the ZenML server container. For Kubernetes environments, we provide officially [supported Helm charts](https://artifacthub.io/packages/helm/zenml/zenml) to simplify deployment. For non-Kubernetes environments, we recommend managing the ZenML server lifecycle using infrastructure-as-code tools such as Terraform, Pulumi, or AWS CloudFormation. ## Security Documentation For software deployed on your infrastructure, ZenML provides vulnerability assessment reports with comprehensive security analysis, a software bill of materials (SBOM) with complete dependency inventory for compliance, compliance documentation to support your security audits and certifications, and architecture review through security team consultation for deployment planning. Contact [cloud@zenml.io](mailto:cloud@zenml.io) to request security documentation. ## Monitoring & Maintenance ### Control Plane (ZenML Managed) ZenML handles automatic updates, security patches, uptime monitoring, and backup and recovery for the control plane. ### Workspaces (Your Responsibility) You are responsible for database maintenance and backups, workspace version updates (with ZenML guidance), infrastructure scaling, and resource monitoring. ### Support Included Your subscription includes professional support with SLA, architecture consultation, migration assistance, and security advisory updates. ## Comparison with Other Deployments | Feature | SaaS | Hybrid SaaS | Self-hosted | |---------|------|-------------|-------------| | Setup Time | Minutes | Hours to Days | Days to Weeks | | Metadata Location | ZenML Infra | Your Infra | Your Infra | | Secret Management | ZenML or Yours | Your Infra | Your Infra | | User Management | ZenML Managed | ZenML Managed | Self-Managed | | Maintenance | Zero | Workspace Only | Full Stack | | Control | Minimal | Moderate | Complete | | Best For | Fast start | Security + Convenience | Strictest compliance | [Compare all deployment options →](scenarios.md) ## Migration Paths ### From ZenML OSS You can migrate from ZenML OSS by deploying a ZenML Pro-compatible workspace in your own infrastructure, starting from your existing ZenML OSS workspace deployment if you have one. The process involves updating your Docker image to the latest Pro Hybrid image provided by ZenML, setting required environment variables according to the ZenML Pro documentation and restarting your deployment to apply these changes. After that, migrate your users and teams, then run `zenml login` to authenticate via [cloud.zenml.io](https://cloud.zenml.io) and connect your SDK clients to the new workspace. ### From SaaS to Hybrid If you're interested in migrating from ZenML Pro SaaS to a Hybrid SaaS setup, we're here to help guide you through every step of the process. Because migration paths can vary depending on your organization's size, data residency requirements, and current ZenML setup, we recommend discussing your plans with a ZenML solutions architect. [Book a migration consultation](https://www.zenml.io/book-your-demo) or email us at [cloud@zenml.io](mailto:cloud@zenml.io). Your ZenML representative will provide you with a tailored migration checklist, technical documentation, and direct support to ensure a smooth transition with minimal downtime. ### Between Workspaces A workspace deep copy feature for migrating pipelines and artifacts between workspaces is coming soon. ## Related Resources - [System Architecture](system-architecture.md) - [Scenarios](scenarios.md) - [SaaS Deployment](saas-deployment.md) - [Self-hosted Deployment](self-hosted-deployment.md) - [Deployment Details](deploy-details.md) - [Upgrades and Updates](upgrades-updates.md) - [Workspaces](workspaces.md) - [Organizations](organization.md) ## Get Started Ready to deploy ZenML Pro in Hybrid mode? [Book a Demo](https://www.zenml.io/book-your-demo) or [contact us](mailto:cloud@zenml.io) with questions.
ZenML Scarf
--- ### Book/Getting Started/Zenml Pro/Organization (docs/book/getting-started/zenml-pro/organization.md) --- description: Manage organizations in ZenML icon: building --- # Organizations ZenML Pro arranges various aspects of your work experience around the concept of an **Organization**. This is the top-most level structure within the ZenML Cloud environment. Generally, an organization contains a group of users and one or more [workspaces](workspaces.md). ## Inviting Team Members to Your Organization Inviting users to your organization to work on the organization's workspaces is easy. Simply click `Add Member` in the Organization settings, and give them an initial Role. The user will be sent an invitation email. If a user is part of an organization, they can utilize their login on all workspaces they have authority to access. ## Trusted domains ZenML Pro can optionally associate trusted email domains with your organization. That setting affects user discovery when inviting people and how some organization invitations behave with SSO. See [Trusted domains](trusted-domains.md). If your organization runs on ZenML-managed ZenML Pro, trusted domains are maintained by ZenML operators. To request or change trusted domains, [contact ZenML support](https://zenml.io/slack) or your account contact. If you self-host the ZenML Pro control plane, super-user administrators configure trusted domains through the API as described in [Trusted domains](trusted-domains.md). ## Manage Organization settings like billing and roles The billing information for your workspaces is managed on the organization level, among other settings like the members in your organization and the roles they have. You can access the organization settings by clicking on your profile picture in the top right corner and selecting "Settings". ## Other operations involving organizations There are a lot of other operations involving Organizations that you can perform directly through the API. You can find more information about the API by visiting [https://cloudapi.zenml.io/](https://cloudapi.zenml.io/).
ZenML Scarf
--- ### Book/Getting Started/Zenml Pro/Personal Access Tokens (docs/book/getting-started/zenml-pro/personal-access-tokens.md) --- description: Learn how to manage and use Personal Access Tokens. icon: user-lock --- # Personal Access Tokens Personal Access Tokens (PATs) in ZenML Pro provide a secure way to authenticate your user account programmatically with the ZenML Pro API and workspaces. PATs are associated with your personal user account and inherit your full permissions within all organizations you are a member of. {% hint style="warning" %} **Security Consideration** Personal Access Tokens inherit your complete user permissions and should be used with care. For automation tasks like CI/CD pipelines, we strongly recommend using [service accounts](service-accounts.md) instead, following the principle of least privilege. Service accounts allow you to grant only the specific permissions needed for automated workflows. {% endhint %} {% hint style="info" %} **Account-Level Management** Personal Access Tokens in ZenML Pro are tied to your user account and are not scoped to a specific organization. This means that you can use the same PAT to access all organizations your user account is a member of. {% endhint %} ## Accessing Personal Access Token Management To manage Personal Access Tokens for your user account in ZenML Pro, navigate to your ZenML Pro dashboard, click on your profile picture in the top right corner, then select **"Settings"** and select **"Access Tokens"** from the settings sidebar. This is the main interface where you can perform all Personal Access Token operations. ## Using Personal Access Tokens Once you have created a Personal Access Token, you can use it to authenticate to the ZenML Pro API and programmatically manage your organization. You can also use the PAT to access all the workspaces in your organization to e.g. run pipelines from the ZenML Python client. ### ZenML Pro API programmatic access The PAT can be used to authenticate to the ZenML Pro management REST API programmatically. There are two methods to do this - one is simpler but less secure, the other is secure and recommended but more complex: {% tabs %} {% tab title="Direct PAT authentication" %} {% hint style="warning" %} This approach, albeit simple, is not recommended because the long-lived PAT is exposed with every API request, which makes it easier to be compromised. Use it only in low-risk circumstances. {% endhint %} To authenticate to the REST API, simply pass the PAT directly in the `Authorization` header used with your API calls: * using curl: ```bash curl -H "Authorization: Bearer YOUR_PAT" https://cloudapi.zenml.io/users/me ``` * using wget: ```bash wget -qO- --header="Authorization: Bearer YOUR_PAT" https://cloudapi.zenml.io/users/me ``` * using python: ```python import requests response = requests.get( "https://cloudapi.zenml.io/users/me", headers={"Authorization": f"Bearer YOUR_PAT"} ) print(response.json()) ``` {% endtab %} {% tab title="Token exchange authentication" %} Reduce the risk of PAT exposure by periodically exchanging the PAT for a short-lived API token: 1. To obtain a short-lived API token using your PAT, send a POST request to the `/auth/login` endpoint. Here are examples using common HTTP clients: * using curl: ```bash curl -X POST -d "password=" https://cloudapi.zenml.io/auth/login ``` * using wget: ```bash wget -qO- --post-data="password=" \ --header="Content-Type: application/x-www-form-urlencoded" \ https://cloudapi.zenml.io/auth/login ``` * using python: ```python import requests import json response = requests.post( "https://cloudapi.zenml.io/auth/login", data={"password": ""}, headers={"Content-Type": "application/x-www-form-urlencoded"} ) print(response.json()) ``` This will return a response like this (the short-lived API token is the `access_token` field): ```json { "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3MGJjZTg5NC1hN2VjLTRkOTYtYjE1Ny1kOTZkYWY5ZWM2M2IiLCJpc3MiOiJmMGQ5NjI1Ni04YmQyLTQxZDctOWVjZi0xMmYwM2JmYTVlMTYiLCJhdWQiOiJmMGQ5NjI1Ni04YmQyLTQxZDctOWVjZi0xMmYwM2JmYTVlMTYiLCJleHAiOjE3MTk0MDk0NjAsImFwaV9rZXlfaWQiOiIzNDkyM2U0NS0zMGFlLTRkMjctODZiZS0wZGRhNTdkMjA5MDcifQ.ByB1ngCPtBenGE6UugsWC6Blga3qPqkAiPJUSFDR-u4", "token_type": "bearer", "expires_in": 3600, "device_id": null, "device_metadata": null } ``` 2. Once you have obtained a short-lived API token, you can use it to authenticate your API requests by including it in the `Authorization` header. When the short-lived API token expires, simply repeat the steps above to obtain a new short-lived API token. For example, you can use the following command to check your current user: * using curl: ```bash curl -H "Authorization: Bearer YOUR_API_TOKEN" https://cloudapi.zenml.io/users/me ``` * using wget: ```bash wget -qO- --header="Authorization: Bearer YOUR_API_TOKEN" https://cloudapi.zenml.io/users/me ``` * using python: ```python import requests response = requests.get( "https://cloudapi.zenml.io/users/me", headers={"Authorization": f"Bearer {YOUR_API_TOKEN}"} ) print(response.json()) ``` {% endtab %} {% endtabs %} See the [API documentation](https://docs.zenml.io/api-reference/pro-api/getting-started) for detailed information on programmatic access patterns. ### Workspace access You can also use your Personal Access Token to access all the workspaces in your organization: * with environment variables: ```bash # set this to the ZenML Pro workspace URL export ZENML_STORE_URL=https://your-org.zenml.io export ZENML_STORE_API_KEY= # optional, for self-hosted ZenML Pro API servers, set this to the ZenML Pro # API URL, if different from the default https://cloudapi.zenml.io export ZENML_PRO_API_URL=https://... ``` * with the CLI: ```bash zenml login --api-key # You will be prompted to enter your PAT ``` #### ZenML Pro Workspace API programmatic access Similar to the ZenML Pro API programmatic access, the PAT can be used to authenticate to the ZenML Pro workspace REST API programmatically. This is no different from [using the OSS API key to authenticate to the OSS workspace REST API programmatically](https://docs.zenml.io/api-reference/oss-api/getting-started#using-a-service-account-and-an-api-key). There are two methods to do this - one is simpler but less secure, the other is secure and recommended but more complex: {% tabs %} {% tab title="Direct PAT authentication" %} {% hint style="warning" %} This approach, albeit simple, is not recommended because the long-lived PAT is exposed with every API request, which makes it easier to be compromised. Use it only in low-risk circumstances. {% endhint %} Use the PAT directly to authenticate your API requests by including it in the `Authorization` header. For example, you can use the following command to check your current workspace user: * using curl: ```bash curl -H "Authorization: Bearer YOUR_PAT" https://your-workspace-url/api/v1/current-user ``` * using wget: ```bash wget -qO- --header="Authorization: Bearer YOUR_PAT" https://your-workspace-url/api/v1/current-user ``` * using python: ```python import requests response = requests.get( "https://your-workspace-url/api/v1/current-user", headers={"Authorization": f"Bearer {YOUR_PAT}"} ) print(response.json()) ``` {% endtab %} {% tab title="Token exchange authentication" %} Reduce the risk of PAT exposure by periodically exchanging the PAT for a short-lived workspace API token. 1. To obtain a short-lived workspace API token using your PAT, send a POST request to the `/api/v1/login` endpoint. Here are examples using common HTTP clients: * using curl: ```bash curl -X POST -d "password=" https://your-workspace-url/api/v1/login ``` * using wget: ```bash wget -qO- --post-data="password=" \ --header="Content-Type: application/x-www-form-urlencoded" \ https://your-workspace-url/api/v1/login ``` * using python: ```python import requests import json response = requests.post( "https://your-workspace-url/api/v1/login", data={"password": ""}, headers={"Content-Type": "application/x-www-form-urlencoded"} ) print(response.json()) ``` This will return a response like this (the short-lived workspace API token is the `access_token` field): ```json { "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3MGJjZTg5NC1hN2VjLTRkOTYtYjE1Ny1kOTZkYWY5ZWM2M2IiLCJpc3MiOiJmMGQ5NjI1Ni04YmQyLTQxZDctOWVjZi0xMmYwM2JmYTVlMTYiLCJhdWQiOiJmMGQ5NjI1Ni04YmQyLTQxZDctOWVjZi0xMmYwM2JmYTVlMTYiLCJleHAiOjE3MTk0MDk0NjAsImFwaV9rZXlfaWQiOiIzNDkyM2U0NS0zMGFlLTRkMjctODZiZS0wZGRhNTdkMjA5MDcifQ.ByB1ngCPtBenGE6UugsWC6Blga3qPqkAiPJUSFDR-u4", "token_type": "bearer", "expires_in": 3600, "refresh_token": null, "scope": null } ``` 2. Once you have obtained a short-lived workspace API token, you can use it to authenticate your API requests by including it in the `Authorization` header. When the short-lived workspace API token expires, simply repeat the steps above to obtain a new one. For example, you can use the following command to check your current workspace user: * using curl: ```bash curl -H "Authorization: Bearer YOUR_API_TOKEN" https://your-workspace-url/api/v1/current-user ``` * using wget: ```bash wget -qO- --header="Authorization: Bearer YOUR_API_TOKEN" https://your-workspace-url/api/v1/current-user ``` * using python: ```python import requests response = requests.get( "https://your-workspace-url/api/v1/current-user", headers={"Authorization": f"Bearer {YOUR_API_TOKEN}"} ) print(response.json()) ``` {% endtab %} {% endtabs %} ## Personal Access Token Operations Personal Access Tokens are the credentials used to authenticate your user account programmatically. You can have multiple PATs, allowing for different access patterns for various tools and applications. ### Creating a Personal Access Token {% hint style="danger" %} **One-Time Display** The Personal Access Token value is only shown once during creation and cannot be retrieved later. If you lose a PAT, you must create a new one or rotate the existing PAT. {% endhint %} ### Activating and Deactivating Personal Access Tokens Individual Personal Access Tokens can be activated or deactivated as needed. {% hint style="warning" %} **Delayed workspace-level effect** Short-lived API token associated with the deactivated PAT issued for workspaces in your organization may still be valid for up to one hour after the PAT is deactivated. {% endhint %} ### Rotating Personal Access Tokens PAT rotation creates a new token value while optionally preserving the old token for a transition period. This is essential for maintaining security without service interruption. {% hint style="info" %} **Zero-Downtime Rotation** By setting a retention period, you can update your applications to use the new PAT while the old token remains functional. This enables zero-downtime token rotation for production systems. {% endhint %} ### Deleting Personal Access Tokens {% hint style="warning" %} **Delayed workspace-level effect** Short-lived API token associated with the deleted PAT issued for workspaces in your organization may still be valid for up to one hour after the PAT is deleted. {% endhint %} ## Security Best Practices ### Token Management * **Regular Rotation**: Rotate PATs regularly (recommended: every 90 days) * **Set the Expiration Date**: Set an expiration date for PATs to automatically revoke them after a certain period of time, especially if you are only planning on using them for a short period of time. * **Use Service Accounts for CI/CD**: For automated workflows and CI/CD pipelines, use [service accounts](service-accounts.md) instead of PATs. This follows the principle of least privilege by granting only necessary permissions rather than your full user permissions. * **Secure Storage**: Store PATs in secure credential management systems, never in code repositories * **Monitor Usage**: Regularly review the "last used" timestamps to identify unused tokens ### Access Control * **Descriptive Naming**: Use clear, descriptive names for PATs to track their purposes (e.g., "work-laptop", "home-jupyter") * **Documentation**: Maintain documentation of which systems and tools use which tokens * **Regular Audits**: Periodically review and clean up unused PATs ### Operational Security * **Immediate Deactivation**: Deactivate PATs immediately when they're no longer needed or if a device is lost or compromised * **Incident Response**: Have procedures in place to quickly rotate or deactivate compromised tokens * **Minimize Token Scope**: Only create PATs when necessary for programmatic access; use regular login for interactive sessions ## Troubleshooting ### Common Issues **Personal Access Token Not Working** * Verify the PAT is active * Check that the PAT hasn't expired (if using rotation with retention) * Ensure the PAT is correctly formatted in your environment variables * Verify your user account has the necessary permissions **Personal Access Token Creation Failed** * Ensure you have permission to create PATs in the organization * Verify the PAT name doesn't conflict with existing tokens * Check with your organization administrator if PAT creation is restricted {% hint style="info" %} **Need Help?** If you encounter issues with Personal Access Tokens, check the ZenML Pro documentation or contact your organization administrator for assistance with permissions and access control. {% endhint %}
ZenML Scarf
--- ### Book/Getting Started/Zenml Pro/Projects (docs/book/getting-started/zenml-pro/projects.md) --- description: Managing projects in ZenML icon: clipboard-list --- # Projects Projects in ZenML Pro provide a logical subdivision within workspaces, allowing you to organize and manage your MLOps resources more effectively. Each project acts as an isolated environment within a workspace, with its own set of pipelines, artifacts, models, and access controls. This isolation is particularly valuable when working with both traditional ML models and AI agent systems, allowing teams to separate different types of experiments and workflows. ## Understanding Projects Projects help you organize your ML work and resources. You can use projects to separate different initiatives, teams, or experiments while sharing common resources across your workspace. This includes separating traditional ML experiments from AI agent development work. Projects offer several key benefits: 1. **Resource Isolation**: Keep pipelines, artifacts, and models organized and separated by project 2. **Granular Access Control**: Define specific roles and permissions at the project level 3. **Team Organization**: Align projects with specific teams or initiatives within your organization 4. **Resource Management**: Track and manage resources specific to each project independently 5. **Experiment Separation**: Isolate different types of AI development work (ML vs agents vs multi-modal systems) ## Using Projects with the CLI Before you can work with projects, you need to be logged into your workspace. If you haven't done this yet, see the [Workspaces](workspaces.md#using-the-cli) documentation for instructions on logging in. ### Creating a project To create a new project using the CLI, run the following command: ```bash zenml project register ``` ### Setting an active project After initializing your ZenML repository (`zenml init`), you should set an active project. This is similar to how you set an active stack: ```bash zenml project set default ``` This command sets the "default" project as your active project. All subsequent ZenML operations will be executed in the context of this project. {% hint style="warning" %} Best practice is to set your active project right after running `zenml init`, just like you would set an active stack. This ensures all your resources are properly organized within the project. {% endhint %} You can also set the project to be used by your client via an environment variable: ```bash export ZENML_ACTIVE_PROJECT_ID= ``` ### Setting a default project The default project is something that each user can configure. This project will be automatically set as the active project when you connect your local Python client to a ZenML Pro workspace. You can set your default project either when creating a new project or when activating it: ```bash # Set default project during registration zenml project register --set-default # Set default project during activation zenml project set --default ``` ## Creating and Managing Projects To create a new project: {% stepper %} {% step %} #### Navigate to Projects From your workspace dashboard, click on the **Projects** tab. {% endstep %} {% step %} #### Click "Add a New Project" In the project creation form, you'll need to provide: * **Project Name**: A descriptive name for your project * **Project ID**: A unique identifier that enables you to access your project through both the API and CLI. Use only letters, numbers, and hyphens or underscores (no spaces). * **Description** (optional): A brief explanation of what your project is about {% endstep %} {% step %} #### Configure Project Settings After creating the project, you can configure additional settings such as: * Adding team members and assigning roles * Setting up project-specific configurations * Configuring integrations {% endstep %} {% endstepper %} ## Managing Project Resources Projects provide isolation for various MLOps resources: ### Pipelines * Pipelines created within a project are only visible to project members * Pipeline runs and their artifacts are scoped to the project * Pipeline configurations and snapshots are project-specific ### Artifacts and Models * Artifacts and models are isolated within their respective projects * Version control and lineage tracking is project-specific * Sharing artifacts between projects requires explicit permissions ## Best Practices 1. **Project Structure** * Create projects based on logical boundaries (e.g., use cases, teams, or products) * Use clear naming conventions for projects * Document project purposes and ownership * Separate traditional ML and agent development where needed 2. **Access Control** * Start with default roles before creating custom ones * Regularly audit project access and permissions * Use teams for easier member management * Implement stricter controls for production agent systems 3. **Resource Management** * Monitor resource usage within projects * Set up appropriate quotas and limits * Clean up unused resources regularly * Track LLM API costs per project for agent development 4. **Documentation** * Maintain project-specific documentation * Document custom roles and their purposes * Keep track of project dependencies and integrations ## Project Hierarchy Projects exist within the following hierarchy in ZenML Pro: 1. Organization (top level) 2. Workspaces (contain multiple projects) 3. Projects (contain resources) 4. Resources (pipelines, artifacts, models, etc.) This hierarchy ensures clear organization and access control at each level while maintaining flexibility in resource management.
ZenML Scarf
--- ### Book/Getting Started/Zenml Pro/Resource Pools Core Concepts (docs/book/getting-started/zenml-pro/resource-pools-core-concepts.md) --- description: >- Precise definitions for ZenML Pro resource pools, subject policies, and resource requests. --- # Core concepts This page defines **pools**, **policies**, and **requests** for ZenML Pro. ## Pools A **resource pool** is a named shared bucket. For each resource key (for example `gpu`), you set how many units exist in the pool. Policies on that pool further split that capacity among orchestrators and step operators. Steps and pools use the same keys and integer amounts. Typical keys are: | Key | Meaning | | --- | --- | | `gpu` | GPU count (requested by steps through `ResourceSettings.gpu_count`) | | `mcpu` | Milli-CPU (requested by steps through `ResourceSettings.cpu_count * 1000`, rounded up) | | `memory_mb` | Memory in megabytes (requested by steps through `ResourceSettings.memory`) | | `step_run` | One concurrent step run (added automatically by the server for each step) | Custom keys (for example `tpu`) can be set with `pool_resources` on the step. ### CLI: pools ```bash # Create a pool (capacity as JSON or YAML) zenml resource-pool create training-gpus \ --capacity '{"gpu": 8, "step_run": 32}' \ --description "Shared training GPUs for the workspace" # List pools with occupied vs total capacity zenml resource-pool list # Inspect one pool (name, ID prefix, or full ID) zenml resource-pool describe training-gpus # Shrink or grow capacity (0 removes a key from the pool) zenml resource-pool update training-gpus --capacity '{"gpu": 4}' # Remove a pool (use -y to skip confirmation) zenml resource-pool delete training-gpus --yes ``` ## Policies A **policy** connects one stack component—the orchestrator or step operator that acts as the *resource requester* for a step—to one pool. Think of three knobs per resource key: * **Reserved** — How much of the pool you *label* as belonging to this component for accounting. Usage up to that amount counts as *in share*; anything above it (while the pool still has free units) is *borrowed* idle capacity. Reserved is not a separate pile of hardware: it is the share used to decide who is “in their rights” versus who is on spare capacity. Across all policies on the same pool, reserved totals per key cannot exceed the pool capacity. Reserved must also be ≤ that policy’s limit for the same key. * **Limit** — The hard ceiling on how much this component may hold from the pool at once for that key. Grants never go above the limit, even if the pool is empty. For **preemptible** workloads, the space between reserved and limit is where borrowing can happen (subject to pool free capacity). Non-preemptible work does not use that band: each requested amount per key must be **≤ reserved**, and a higher **limit** does not raise that ceiling (limit still caps preemptible burst and total use). * **Priority** — A number; higher means that component’s requests are preferred in the queue. When the reconciler must **preempt** someone, it looks at **lower** priority preemptible runs first as victims (see below). {% hint style="warning" %} A single orchestrator or step operator may have **several policies** attached, each pointing at a **different pool**. The server still treats each step as one **resource request**. Eligibility and allocation are evaluated **per pool** against the **full** set of requested keys: the step may be queued on more than one pool, but at most **one** pool ends up owning the active allocation. ZenML does **not** split a request across pools (for example GPUs from one pool and `mcpu` from another). Every key in the request must be satisfiable from the **same** pool and policy that wins. See [Examples — Multiple pools and multi-key requests](resource-pools-examples.md#multiple-pools-and-multi-key-requests). {% endhint %} ### CLI: policies ```bash # Attach an orchestrator to a pool (default component type is orchestrator) zenml resource-pool attach-policy training-gpus my-k8s-orch \ --priority 10 \ --reserved '{"gpu": 2}' \ --limit '{"gpu": 4}' # Same for a step operator stack component zenml resource-pool attach-policy training-gpus my-remote-operator \ --component-type step_operator \ --priority 5 \ --reserved '{"gpu": 2}' \ --limit '{"gpu": 4}' # List every policy on a pool zenml resource-pool list-policies training-gpus # List all pools a given orchestrator is attached to zenml resource-pool list-policies --component my-k8s-orch # Remove that component’s policy from the pool zenml resource-pool detach-policy training-gpus my-k8s-orch ``` ## Resource requests For eligible runs, the server builds a **resource request** from the step’s `ResourceSettings`, records whether the step is preemptible, and tracks status: queued, allocated, rejected, preempted, or cancelled. {% hint style="info" %} Only dynamic pipelines participate in resource queuing and allocation waiting: the server creates resource requests and the client blocks until allocation when the snapshot is dynamic. Static pipelines do not use this path today. {% endhint %} What users set in `ResourceSettings` becomes a server-side resource request (for example `gpu_count` → `gpu`, `cpu_count` → `mcpu`, `memory` → `memory_mb`, plus an implicit concurrent `step_run` slot). The pool must define capacity for each key requested by the step, except for three built-in types: if the pool has no row for `mcpu`, `memory_mb`, or the implicit `step_run` key, ZenML Pro treats that dimension as **effectively unbounded** at the pool layer, so missing rows there do not by themselves cause rejection. For every other key (including `gpu` and custom keys from `pool_resources`), a missing pool row means zero capacity: a positive request is rejected and the step run fails to start. {% hint style="warning" %} If the pool does define a key but the subject policy omits that key, limits fall back to the pool total and reserved defaults to zero for bounded keys. Non-preemptible steps must stay within their reserved capacity, so a positive request for a key not defined in the policy is rejected. {% endhint %} ### Step decorators: `ResourceSettings` Declare demand on the step; the server turns it into the resource request when the pipeline is **dynamic** and pooling applies to the stack. **Typical GPU / CPU / memory (preemptible by default):** ```python from zenml import step from zenml.config import ResourceSettings @step( settings={ "resources": ResourceSettings( gpu_count=2, cpu_count=4, memory="16GiB", ) } ) def train() -> None: ... ``` **Non-preemptible (must stay within policy reserved per key):** ```python @step( settings={ "resources": ResourceSettings( gpu_count=1, preemptible=False, ) } ) def production_train() -> None: ... ``` **Custom pool keys** (must exist on the pool and policy when non-preemptible): ```python @step( settings={ "resources": ResourceSettings( gpu_count=1, pool_resources={"tensorrt_sessions": 1}, ) } ) def infer() -> None: ... ``` Typed fields override the same keys if both appear in `pool_resources`. See [step configuration](https://docs.zenml.io/how-to/steps-pipelines/configuration) in the OSS docs for the full `ResourceSettings` model. ### CLI: resource requests Requests are created when dynamic steps run; you **inspect** or **clean them up** from the CLI (IDs come from list output or the dashboard). ```bash # All resource requests visible to your user (see --help for filters) zenml resource-request list # Full detail for one request (status, resources, step run link) zenml resource-request describe 01234567-89ab-cdef-0123-456789abcdef # Drop a stuck or abandoned request (use with care) zenml resource-request delete 01234567-89ab-cdef-0123-456789abcdef # Requests tied to a specific pool: queued only, active only, or both zenml resource-pool requests training-gpus --view queued zenml resource-pool requests training-gpus --view active zenml resource-pool requests training-gpus --view all ``` ## See also * [Resource pools](resource-pools.md) — overview and UX story * [Examples](resource-pools-examples.md) — scenarios and outcomes * [How preemption works](resource-pools-reconciliation.md#how-preemption-works) — behavior and ordering * [Resource pool reconciliation](resource-pools-reconciliation.md) — runtime flow --- ### Book/Getting Started/Zenml Pro/Resource Pools Examples (docs/book/getting-started/zenml-pro/resource-pools-examples.md) --- description: >- Step-by-step ZenML Pro resource pool examples: pool JSON, policy JSON, ResourceSettings, and outcomes for new users. --- # Resource pool examples (workbook) Read this page like a short course: each section is one self-contained scenario. You will always see three things—the pool (shared capacity), the policy (how one orchestrator or step operator may use that pool), and the step (`ResourceSettings`). Then we spell out what the server does. **Assumptions unless stated otherwise:** * Steps are preemptible by default if you omit `preemptible=False`. * One step run at a time when we say “no other work is running,” so you can focus on a single decision. * Every key in a policy’s `reserved` and `limit` must exist on the pool’s capacity. You cannot meter a resource in policy that the pool does not define. * If one orchestrator or step operator has policies to several pools, the step still receives at most one allocation from one pool. The whole request must be eligible on that pool; resources are not split across pools for a single step. For definitions of reserved, limit, and priority, see [Core concepts](resource-pools-core-concepts.md). For preemption ordering, see [How preemption works](resource-pools-reconciliation.md#how-preemption-works). ## Primer: from `ResourceSettings` to the resource request Say a step declares: ```python from zenml.config import ResourceSettings ResourceSettings( gpu_count=2, cpu_count=4, memory="16GiB", pool_resources={"tensorrt_sessions": 1}, preemptible=True, ) ``` ZenML turns that into one resource request. Roughly: | Source | Request key | How the amount is derived | Example value | | --- | --- | --- | --- | | `gpu_count` | `gpu` | Same as `gpu_count` | 2 | | `cpu_count` | `mcpu` | `ceil(cpu_count * 1000)` | 4000 | | `memory` | `memory_mb` | Converted to megabytes | 17180 (for `"16GiB"`) | | `pool_resources` | (your names) | Copied as-is, merged with typed fields | 1 | | Server | `step_run` | Always 1 per step | 1 | The pool must define capacity for bounded keys such as `gpu` and `tensorrt_sessions`. If the pool has no row for `mcpu`, `memory_mb`, or `step_run`, that dimension is unbounded at the pool layer (see the examples below). For everything else, a missing pool row means zero capacity. If you want a policy to set `reserved` / `limit` on a key, that key must appear on the pool first—policy keys are always a subset of pool keys. --- ## Warm-up: one pool, one policy, only GPUs ### Preemptible step borrows past reserved **Story:** The team has four GPUs “labeled” for them, but the pool is empty. They ask for six GPUs and allow preemption. They may borrow two idle GPUs. **Pool** ```json { "name": "datacenter-gpus", "capacity": { "gpu": 8 } } ``` **Policy** (orchestrator `team-ml-orch` attached to this pool) ```json { "pool": "datacenter-gpus", "component": "team-ml-orch", "component_type": "orchestrator", "priority": 10, "reserved": { "gpu": 4 }, "limit": { "gpu": 8 } } ``` **Step** ```python from zenml import step from zenml.config import ResourceSettings @step( settings={ "resources": ResourceSettings( gpu_count=6, preemptible=True, ) } ) def train() -> None: ... ``` **Outcome:** Allocated immediately (no queue). Four GPUs count against the policy reserved share; two are borrowed from free pool capacity (between reserved and limit, and pool must still have free units). --- ### Non-preemptible step stays inside reserved **Story:** Same pool and policy. Production wants two GPUs and opts out of preemption. Two is within the four-GPU reservation. **Pool** ```json { "name": "datacenter-gpus", "capacity": { "gpu": 8 } } ``` **Policy** ```json { "pool": "datacenter-gpus", "component": "team-ml-orch", "component_type": "orchestrator", "priority": 10, "reserved": { "gpu": 4 }, "limit": { "gpu": 8 } } ``` **Step** ```python @step( settings={ "resources": ResourceSettings( gpu_count=2, preemptible=False, ) } ) def production_train() -> None: ... ``` **Outcome:** Allocated (assuming no other contention). Non-preemptible work must satisfy requested ≤ reserved per key; `2 ≤ 4` passes. --- ### Non-preemptible step beyond reserved **Story:** Same pool and policy. Production asks for six GPUs but refuses preemption. Non-preemptible work cannot use the “borrow” band above reserved. **Pool** ```json { "name": "datacenter-gpus", "capacity": { "gpu": 8 } } ``` **Policy** ```json { "pool": "datacenter-gpus", "component": "team-ml-orch", "component_type": "orchestrator", "priority": 10, "reserved": { "gpu": 4 }, "limit": { "gpu": 8 } } ``` **Step** ```python @step( settings={ "resources": ResourceSettings( gpu_count=6, preemptible=False, ) } ) def too_large_production_train() -> None: ... ``` **Outcome:** Rejected immediately (dynamic run fails fast). Six exceeds reserved (4) for `gpu`; non-preemptible requests cannot borrow up to limit. --- ## CPU, memory, and step slots: unbounded vs metered ### GPU-only pool—CPU and memory not quota’d **Story:** You only modeled GPUs on the pool and policy. The step still sends `mcpu` and `memory_mb` on the request, but those keys are unbounded at the pool layer when omitted, and this policy omits them too—so they do not block non-preemptible work. **Pool** ```json { "name": "training", "capacity": { "gpu": 4 } } ``` **Policy** ```json { "pool": "training", "component": "k8s-orch", "component_type": "orchestrator", "priority": 10, "reserved": { "gpu": 2 }, "limit": { "gpu": 4 } } ``` **Step** ```python @step( settings={ "resources": ResourceSettings( gpu_count=1, cpu_count=32, memory="64GiB", preemptible=False, ) } ) def hungry_but_ok_on_gpu() -> None: ... ``` **Outcome:** Allocated if nothing else is wrong. Only `gpu` is gated here; `mcpu` / `memory_mb` / `step_run` are not limited by pool or policy in this pattern. CPU and memory remain informational unless you add rows later. --- ### Non-preemptible CPU inside policy reserved **Story:** You cap milli-CPU on the pool, then split it with reserved / limit on the policy. Non-preemptible CPU demand must fit reserved per key. **Pool** ```json { "name": "training", "capacity": { "gpu": 4, "mcpu": 32000 } } ``` **Policy** ```json { "pool": "training", "component": "k8s-orch", "component_type": "orchestrator", "priority": 10, "reserved": { "gpu": 2, "mcpu": 4000 }, "limit": { "gpu": 4, "mcpu": 32000 } } ``` **Step** ```python @step( settings={ "resources": ResourceSettings( gpu_count=1, cpu_count=2, preemptible=False, ) } ) def fits_reserved_cpu() -> None: ... ``` **Outcome:** Allocated. `cpu_count=2` → `mcpu` 2000 ≤ reserved 4000, and `gpu` is valid. --- ### Non-preemptible CPU over policy reserved **Pool** ```json { "name": "training", "capacity": { "gpu": 4, "mcpu": 32000 } } ``` **Policy** ```json { "pool": "training", "component": "k8s-orch", "component_type": "orchestrator", "priority": 10, "reserved": { "gpu": 2, "mcpu": 4000 }, "limit": { "gpu": 4, "mcpu": 32000 } } ``` **Step** ```python @step( settings={ "resources": ResourceSettings( gpu_count=1, cpu_count=8, preemptible=False, ) } ) def exceeds_reserved_cpu() -> None: ... ``` **Outcome:** Rejected. `cpu_count=8` → `mcpu` 8000 > reserved 4000; non-preemptible work cannot borrow toward limit on `mcpu`. --- ### Preemptible CPU burst with policy `mcpu` rows **Pool** and **Policy:** same as *Non-preemptible CPU inside policy reserved* (pool includes `gpu` and `mcpu`; policy sets both keys). **Step** ```python @step( settings={ "resources": ResourceSettings( gpu_count=1, cpu_count=8, preemptible=True, ) } ) def preemptible_cpu_burst() -> None: ... ``` **Outcome:** May allocate using headroom up to limit on `mcpu` (and pool free capacity), analogous to GPU borrowing. --- ### Preemptible when pool lists `mcpu` but policy omits it **Story:** The pool caps total milli-CPU. With no `mcpu` on the policy, reserved defaults to 0 and limit falls back to the pool total. **Pool** ```json { "name": "training", "capacity": { "gpu": 4, "mcpu": 8000 } } ``` **Policy** (only `gpu`) ```json { "pool": "training", "component": "k8s-orch", "component_type": "orchestrator", "priority": 10, "reserved": { "gpu": 2 }, "limit": { "gpu": 4 } } ``` **Step** ```python @step( settings={ "resources": ResourceSettings( gpu_count=1, cpu_count=4, preemptible=True, ) } ) def preemptible_with_pool_mcpu() -> None: ... ``` **Outcome:** Allocated or queued then allocated when possible. `mcpu` 4000 ≤ effective limit 8000 (pool total). --- ### Non-preemptible when pool lists `mcpu` but policy omits it **Pool** and **Policy:** same as the previous example. **Step** ```python @step( settings={ "resources": ResourceSettings( gpu_count=1, cpu_count=1, preemptible=False, ) } ) def non_preemptible_positive_mcpu_zero_reserved() -> None: ... ``` **Outcome:** Rejected. Any positive `mcpu` with reserved 0 fails for non-preemptible work. Fix: add `mcpu` to the policy with enough reserved, or remove `mcpu` from the pool if you wanted fully unbounded CPU at the pool layer. --- ### Capping concurrent steps with `step_run` **Story:** You want both GPUs and a ceiling on how many steps from this orchestrator run at once. Each step always requests one `step_run`. **Pool** ```json { "name": "training", "capacity": { "gpu": 16, "step_run": 10 } } ``` **Policy** ```json { "pool": "training", "component": "k8s-orch", "component_type": "orchestrator", "priority": 10, "reserved": { "gpu": 8, "step_run": 4 }, "limit": { "gpu": 16, "step_run": 4 } } ``` **Step** ```python @step( settings={ "resources": ResourceSettings( gpu_count=2, preemptible=True, ) } ) def train() -> None: ... ``` **Outcome:** The server grants only when both `gpu` and `step_run` have enough free units. If GPUs are free but all `step_run` slots are taken, the request waits in the queue. --- ## Custom keys from `pool_resources` ### Custom key fully configured **Story:** You track a scarce license or device class with `pool_resources`. **Pool** ```json { "name": "inference", "capacity": { "gpu": 8, "tensorrt_sessions": 4 } } ``` **Policy** ```json { "pool": "inference", "component": "gpu-step-op", "component_type": "step_operator", "priority": 10, "reserved": { "gpu": 2, "tensorrt_sessions": 2 }, "limit": { "gpu": 8, "tensorrt_sessions": 4 } } ``` **Step** ```python @step( settings={ "resources": ResourceSettings( gpu_count=1, pool_resources={"tensorrt_sessions": 1}, preemptible=False, ) } ) def infer() -> None: ... ``` **Outcome:** Allocated when `1 ≤ reserved` for both `gpu` and `tensorrt_sessions`. Unbounded defaults do not apply to custom keys—the pool must list them. --- ### Custom key on pool but missing from policy **Story:** Same pool capacity; policy only defines `gpu`. **Pool** ```json { "name": "inference", "capacity": { "gpu": 8, "tensorrt_sessions": 2 } } ``` **Policy** ```json { "pool": "inference", "component": "gpu-step-op", "component_type": "step_operator", "priority": 10, "reserved": { "gpu": 2 }, "limit": { "gpu": 8 } } ``` **Step** ```python @step( settings={ "resources": ResourceSettings( gpu_count=1, pool_resources={"tensorrt_sessions": 1}, preemptible=False, ) } ) def infer() -> None: ... ``` **Outcome:** Rejected. Missing policy row → reserved 0 for `tensorrt_sessions`; non-preemptible cannot ask for a positive amount. Fix: add `tensorrt_sessions` to the policy, or mark the step preemptible if borrowing is acceptable. --- ## Hard rejections (no queue) ### Request exceeds pool capacity **Pool** ```json { "name": "small", "capacity": { "gpu": 8 } } ``` **Policy** ```json { "pool": "small", "component": "k8s-orch", "component_type": "orchestrator", "priority": 10, "reserved": { "gpu": 8 }, "limit": { "gpu": 8 } } ``` **Step** ```python @step( settings={ "resources": ResourceSettings( gpu_count=10, preemptible=True, ) } ) def too_big_for_planet() -> None: ... ``` **Outcome:** Rejected immediately. Ten exceeds the pool total for `gpu`; the request does not join a queue. --- ### Request exceeds policy limit (pool could fit) **Pool** ```json { "name": "shared", "capacity": { "gpu": 8 } } ``` **Policy** ```json { "pool": "shared", "component": "team-a-orch", "component_type": "orchestrator", "priority": 10, "reserved": { "gpu": 2 }, "limit": { "gpu": 4 } } ``` **Step** ```python @step( settings={ "resources": ResourceSettings( gpu_count=6, preemptible=True, ) } ) def over_team_limit() -> None: ... ``` **Outcome:** Rejected. Six exceeds this component’s limit (4) for `gpu`, even if eight GPUs exist in the pool. --- ## Contention: queues and priorities ### Two teams, same priority, not enough GPUs **Story:** Red and Blue orchestrators share one pool. Policies use the same priority. Many preemptible steps each want 2 GPUs; the pool cannot satisfy everyone at once. **Pool** ```json { "name": "shared", "capacity": { "gpu": 8 } } ``` **Policies** ```json [ { "pool": "shared", "component": "red-orch", "component_type": "orchestrator", "priority": 10, "reserved": { "gpu": 4 }, "limit": { "gpu": 8 } }, { "pool": "shared", "component": "blue-orch", "component_type": "orchestrator", "priority": 10, "reserved": { "gpu": 4 }, "limit": { "gpu": 8 } } ] ``` **Step** (typical for either team) ```python @step( settings={ "resources": ResourceSettings( gpu_count=2, preemptible=True, ) } ) def train() -> None: ... ``` **Outcome:** Requests wait in the pool queue until GPUs free up. Among the same policy priority, ordering tends to favor older waiters (FIFO-style). The allocator also prefers a request that still fits entirely in its unused reserved slice over one that must borrow when both are waiting—so a team with reservation headroom is not stuck behind another team that is already bursting, if the next grant can be served from that reserved slice. No preemption until a higher-priority waiter or reclaim logic forces it. --- ### Higher priority wins; lower may be preempted **Story:** Sandbox bursts with preemptible work. Production has higher policy priority and needs GPUs when the pool is full. **Pool** ```json { "name": "shared", "capacity": { "gpu": 8 } } ``` **Policies** ```json [ { "pool": "shared", "component": "sandbox-orch", "component_type": "orchestrator", "priority": 10, "reserved": { "gpu": 4 }, "limit": { "gpu": 8 } }, { "pool": "shared", "component": "prod-orch", "component_type": "orchestrator", "priority": 100, "reserved": { "gpu": 2 }, "limit": { "gpu": 8 } } ] ``` **Sandbox** (already holding six GPUs, preemptible) ```python @step( settings={ "resources": ResourceSettings( gpu_count=6, preemptible=True, ) } ) def sandbox_experiment() -> None: ... ``` **Prod** (new, preemptible, needs four) ```python @step( settings={ "resources": ResourceSettings( gpu_count=4, preemptible=True, ) } ) def prod_train() -> None: ... ``` **Outcome:** If four GPUs cannot be granted without reclaiming space, the reconciler may preempt Sandbox’s preemptible runs (lower policy priority) so Prod can proceed. See [How preemption works](resource-pools-reconciliation.md#how-preemption-works) for victim ordering. --- ### Production non-preemptible waits on reserved only **Story:** Prod uses `preemptible=False` and asks only for what is reserved. If another non-preemptible job on the same stack component already holds the reserved GPUs, this step does not borrow from Sandbox’s burst. **Pool** ```json { "name": "shared", "capacity": { "gpu": 8 } } ``` **Policies** (same as previous example: Sandbox priority 10, Prod priority 100) **Prod step** ```python @step( settings={ "resources": ResourceSettings( gpu_count=2, preemptible=False, ) } ) def prod_sla_job() -> None: ... ``` **Outcome:** Waits in the queue if Prod’s reserved `gpu` (2) is already used by other non-preemptible work on `prod-orch`. It will not take Sandbox’s borrowed GPUs. Ways out: raise reserved for Prod, wait for the other job to finish, or use preemptible Prod work if policy allows. --- ## Multiple pools and multi-key requests {% hint style="warning" %} Several policies on the same stack component mean several pools may try to satisfy the same resource request, but only one pool can win. Every key in the request must pass that pool’s checks; ZenML does not take `gpu` from one pool and `mcpu` from another for one step. {% endhint %} ### Two pools on one orchestrator—primary pool wins **Story:** You attach two policies to the same orchestrator pointing at different pools. The step still produces one resource request, enqueued in every eligible pool; only one pool may win. **Pools** ```json [ { "name": "eu-west-gpu", "capacity": { "gpu": 16 } }, { "name": "eu-north-gpu", "capacity": { "gpu": 16 } } ] ``` **Policies** ```json [ { "pool": "eu-west-gpu", "component": "regional-orch", "component_type": "orchestrator", "priority": 20, "reserved": { "gpu": 8 }, "limit": { "gpu": 16 } }, { "pool": "eu-north-gpu", "component": "regional-orch", "component_type": "orchestrator", "priority": 10, "reserved": { "gpu": 8 }, "limit": { "gpu": 16 } } ] ``` **Step** ```python @step( settings={ "resources": ResourceSettings( gpu_count=1, preemptible=True, ) } ) def train() -> None: ... ``` **Outcome:** The server tries higher policy priority first—eu-west before eu-north. Whichever pool grants first owns the allocation; the other queue entry is dropped as stale. Use this for primary/fallback or regional capacity, not for splitting one step across unrelated quotas. --- ### One step must satisfy every key in each pool **Story:** Eligibility is checked per pool against all keys on the request. If a pool lacks a key the step needs, that pool treats it as zero capacity—the request is not eligible there. **Pool A** (GPUs only) ```json { "name": "gpu-only", "capacity": { "gpu": 8 } } ``` **Pool B** (full bundle) ```json { "name": "gpu-and-trt", "capacity": { "gpu": 8, "tensorrt_sessions": 4 } } ``` **Policy** (example: only Pool B is attached, or imagine Pool A attached alone) **Step** ```python @step( settings={ "resources": ResourceSettings( gpu_count=1, pool_resources={"tensorrt_sessions": 1}, preemptible=True, ) } ) def infer() -> None: ... ``` **Outcome:** A pool with only `gpu` cannot satisfy `tensorrt_sessions`—that dimension is zero there, so the request does not enqueue on that pool. **Lesson:** Model every scarce bounded dimension you care about on one pool (or ensure every candidate pool defines the same key set for those keys). The next section shows how `mcpu` and `memory_mb` differ: omitting them on one pool keeps that path eligible even when another pool meters them strictly. --- ### Two pools: higher-priority path meters CPU/RAM; GPU-only path still wins **Story:** One orchestrator has two policies (same pattern as *Two pools on one orchestrator—primary pool wins*). Pool B’s capacity and policy include `mcpu` and `memory_mb`, with reserved amounts sized for small non-preemptible jobs. Pool A only defines `gpu`; it does not list `mcpu` or `memory_mb`, so those dimensions are unbounded at the pool layer and its policy does not reserve them. A non-preemptible step asks for one GPU but more CPU and RAM than Pool B’s policy allows. The higher-priority policy (Pool B) cannot grant that request; the lower-priority policy (Pool A) can, because the request’s CPU and memory demand is not quota’d on that path. The allocation is owned by Pool A. **Pool A** (GPUs only—no `mcpu` or `memory_mb` on the pool) ```json { "name": "gpu-only-fallback", "capacity": { "gpu": 8 } } ``` **Pool B** (GPUs plus metered CPU and memory) ```json { "name": "metered-cpu-mem", "capacity": { "gpu": 8, "mcpu": 128000, "memory_mb": 524288 } } ``` **Policies** (same component, different priorities—B is preferred when both can grant) ```json [ { "pool": "metered-cpu-mem", "component": "k8s-orch", "component_type": "orchestrator", "priority": 100, "reserved": { "gpu": 4, "mcpu": 4000, "memory_mb": 8192 }, "limit": { "gpu": 8, "mcpu": 64000, "memory_mb": 131072 } }, { "pool": "gpu-only-fallback", "component": "k8s-orch", "component_type": "orchestrator", "priority": 50, "reserved": { "gpu": 4 }, "limit": { "gpu": 8 } } ] ``` **Step** ```python @step( settings={ "resources": ResourceSettings( gpu_count=1, cpu_count=8, memory="32GiB", preemptible=False, ) } ) def train() -> None: ... ``` **Outcome:** The request maps to roughly `mcpu` 8000 and tens of thousands of `memory_mb` for `32GiB`. For non-preemptible work, each key must be ≤ policy reserved on the path you use. Pool B’s policy reserves only `mcpu` 4000 and `memory_mb` 8192, so that path cannot satisfy the step. Pool A’s policy has no `mcpu` or `memory_mb` rows; with those keys absent from the pool, they are not treated as zero capacity, so the step remains eligible there on `gpu` alone. The reconciler allocates from Pool A and drops the competing queue row for Pool B. If you want large non-preemptible jobs to stay on the metered pool, raise reserved (and capacity) on Pool B for `mcpu` and `memory_mb`, or reduce demand in `ResourceSettings`—otherwise the GPU-only policy acts as an escape hatch for heavy CPU/RAM asks. --- ## See also * [Resource pools](resource-pools.md) — overview * [Core concepts](resource-pools-core-concepts.md) — pools, policies, requests * [How preemption works](resource-pools-reconciliation.md#how-preemption-works) — preemption ordering * [Step configuration](https://docs.zenml.io/how-to/steps-pipelines/configuration) — full `ResourceSettings` reference --- ### Book/Getting Started/Zenml Pro/Resource Pools Reconciliation (docs/book/getting-started/zenml-pro/resource-pools-reconciliation.md) --- description: >- How the resource pool reconciliation process works in ZenML Pro. --- # Resource Pool Reconciliation ## Runtime flow (orchestration) 1. Request creation: For eligible runs, the server derives requested resources from the step’s `ResourceSettings` (see below), adds `step_run: 1`, and stores `preemptible` from the same settings. The resource requester is the stack’s step operator if the step uses one, otherwise the orchestrator. 2. Queuing: If capacity is not available immediately, the step can remain queued until the reconciler allocates it. 3. Client wait: The step launcher polls the resource request until it is allocated (with backoff). If the request is not allocated, it is rejected, preempted, or cancelled, the client surfaces an error. When allocation succeeds, the step is published as running and execution proceeds. 4. Preemption: If the job at the front of the queue still cannot be granted, the reconciler may stop other *preemptible* runs in that pool to free units (see [How preemption works](#how-preemption-works)). Non-preemptible runs are never stopped this way. They are also constrained so each request’s per-key demand is **≤ policy reserved** for that key—even when **limit** is higher—so they never rely on borrowed capacity that could clash with other non-preemptible use on the same component. 5. Post-preemption retry: after preemption, if the step configuration allows retries, the step goes back to the queue and is retried again. If the number of retries is exhausted, the step fails. See [Automatic Step Retries](https://docs.zenml.io/how-to/steps-pipelines/advanced_features#automatic-step-retries) for more information. 6. Deallocation: When the step run completes, the resources are released back to the pool. If the step crashes unexpectedly, the resources are eventually released back to the pool. {% hint style="warning" %} If the resource requester (orchestrator or step operator) has more than one policy attached to more than one pool, the same logical request may appear in several pool queues, but reconciliation still grants at most one active allocation. Eligibility is computed separately per pool against the entire resource request; the system never assigns part of a step’s demand to one pool and the rest to another. {% endhint %} ## How preemption works **When.** Preemption runs only when the next queued request for a pool cannot be allocated—there is not enough free capacity, or a policy rule blocks the grant. The reconciler may then mark selected *already running* requests as preempted, which cancels those step runs and returns their units to the pool. **Who can be stopped.** Only steps with `preemptible=True` (the default in `ResourceSettings`) are candidates. `preemptible=False` means “never pick this run as the one to kill.” **Who gets stopped first (simple picture).** 1. Among preemptible runs in the same pool, **lower policy priority** is considered before higher priority. If the waiting job’s priority is *strictly higher* than a victim’s, that victim can be preempted to make room, as long as freeing it actually fixes the shortage. 2. **Reserved** adds a second idea: *reclaim*. If the waiting component still has unused **reserved** headroom on this pool (reserved minus what it is already using here), the system may preempt preemptible runs that are using **borrowed** capacity—even when those runs have the same or higher priority than the waiter. Intuition: your reserved share is “yours to fill”; if someone else is on the spare capacity you could have used under your reservation, they can be moved out of the way. 3. **Limit** does not pick victims. It only caps how much a component may hold; if the waiting request itself is over its own limit, killing other jobs will not fix that—you need a higher limit or a smaller request. Victims are ordered by ascending policy priority, then by allocation time as a tie-break. ### Step-level: `preemptible` | Setting | Effect | | --- | --- | | `preemptible=True` (default) | This run may be preempted to help another request. | | `preemptible=False` | This run is never preempted. Each requested amount per pool key must be ≤ that key’s **reserved** on the policy; **limit** above reserved does not increase what a non-preemptible step may request. | Policies do not override `preemptible`; they only affect ordering and reclaim among runs that are allowed to be preempted. ### After preemption Preempted step runs are stopped and the resources are released back to the pool. The steps are put back into the queue and are retried again. If the number of retries is exhausted or the step is not configured to allow retries, the step fails. See [Automatic Step Retries](https://docs.zenml.io/how-to/steps-pipelines/advanced_features#automatic-step-retries) for more information. ## Policy scenarios (how reserved, limit, and preemptible interact) For the problems these patterns solve in everyday terms, see [Resource pools](resource-pools.md). * Fair share plus burst: set **reserved** to the slice you want to account as “yours” and **limit** to the most that stack may ever hold. **Preemptible** steps can **borrow** idle capacity between reserved and limit (and up to the pool) when the pool has room. **Non-preemptible** steps only use up to **reserved** per requested key, regardless of a higher limit. * Production vs experiments: higher **priority** on production policies; experimental steps stay **preemptible** so production can take capacity or reclaim borrowed slack when it needs its reservation. * Non-preemptible training: set `preemptible=False` and size **reserved** so each step’s per-key request (for example `gpu_count`) is ≤ reserved for that key. **Limit** can be higher for preemptible burst on the same policy, but it does not raise the ceiling for non-preemptible requests; raise **reserved** if those jobs need more per step. The reconciler also blocks non-preemptible grants that would sit on borrowed capacity in ways that conflict with other non-preemptible use on the component. * Several pools for one component: multiple policies with different **priority** values; higher priority is preferred when queuing and allocating, subject to each pool’s **limit**. For preemption rules (priority vs reclaim), see [How preemption works](#how-preemption-works). --- ### Book/Getting Started/Zenml Pro/Resource Pools (docs/book/getting-started/zenml-pro/resource-pools.md) --- description: >- Fair GPU and compute sharing for AI/ML teams: dependable production capacity, shared pools, idle reuse, and workspace-level quotas. icon: layer-group --- # Resource pools {% hint style="info" %} Resource pools are part of ZenML's paid features. For availability and plans, see the [pricing page](https://www.zenml.io/pricing). {% endhint %} {% hint style="info" %} Resource pools are only available for [dynamic pipelines](https://docs.zenml.io/how-to/steps-pipelines/dynamic_pipelines). {% endhint %} If you run AI or ML work in a shared environment, you have probably seen the same problems: jobs fighting over GPUs, surprise slowdowns when another team launches a big run, or expensive hardware sitting idle while someone waits in a queue. Resource pools are ZenML Pro’s answer. They are aimed at *platform and team leaders* who need clear rules, and at *practitioners* who just want their steps to get the right compute without babysitting the cluster. Here are typical situations this feature is built for: **“Our production jobs must finish—no surprises.”** You have training, fine-tuning, or inference that cannot vanish because someone else submitted a heavier workload. You want a clear agreement: this stack or this team gets a dependable slice of capacity, and critical steps are not stopped to make room for ad hoc work. **“We share one pool of GPUs across many teams.”** You need one place that describes how much capacity exists, who may use it, and what happens when everyone wants it at once—without maintaining a separate spreadsheet or manual booking process for every pipeline. **“We paid for the hardware—we should use it when it’s free.”** When one group is quiet, you want other teams to use spare capacity so machines do not sit empty. You also want the original team to get their capacity back when they return, without a long negotiation or a cluster reconfiguration every time. **“Engineers describe needs; ops maps them to reality.”** Pipeline authors say what each step requires (GPUs, memory, and so on). Platform or DevOps ties those stacks to the right shared capacity. The same pipeline code can run in different environment or stages without hard-coding cluster details. **“We need to ration more than just GPUs.”** Alongside standard compute, you may need to track things like licenses, special hardware, or how many pipeline steps may run at once. Pools let you treat those as countable resources under the same workspace-level model, as long as your organization agrees on names and units. None of this replaces your orchestrator or cloud provider—it coordinates demand so teams see fair queuing, optional sharing of idle capacity, and explicit rules for critical versus best-effort work. When you are ready for how ZenML models that behavior, continue with [Introduction to capacity management](#introduction-to-capacity-management) and [Core concepts](#core-concepts). ## Introduction to capacity management ZenML Pro resource pooling separates three concerns: what capacity exists, what each pipeline step asks for, and which stack components may draw from which pools under which rules. The subsections below describe the strategy; [Core concepts](#core-concepts) then defines terms and mechanics precisely. ### Supply: resource pools On one side, **resource pools** describe resources that are available and consumable when pipeline steps run. They often include standard compute—GPUs, CPUs, and RAM—that orchestrators understand and that map to real infrastructure, but pools are not limited to that. Teams can define **custom resource keys** as a convention between whoever operates the platform and whoever writes pipelines: capabilities, access to external services or tools, licenses, or any other scarce thing you want to schedule in integer units. The same resource key names must appear in pool capacity and in step requests for the convention to work. This is the contract between the platform and the users. The number of **parallel step runs** is also modeled as a resource today so concurrency can be capped alongside GPU or CPU demand. A pool can hold **multiple resource types at once** (for example GPUs and a `step_run` slot). **Who defines pools:** the role that owns infrastructure access—IT ops, platform engineering, DevOps, or similar — defines and maintains resource pools at workspace scope so every team in that workspace shares the same catalog of named capacity. ### Demand: `ResourceSettings` on steps On the other side, **pipeline steps** declare what they need through `ResourceSettings`. That is typically the ML engineer, AI engineer, or data engineer annotating each step with GPUs, CPUs, memory, and optionally the same custom keys the admins put in pools. Authors can align requests with published pool capacity and follow whatever naming convention the platform agreed on for non-standard resources. Steps also declare whether or not they are **preemptible**. Preemption is the ability to interrupt a running step run before it completes, to free up resources for other steps demanding them with a higher priority. Steps that are preemptible are not guaranteed to complete if they go outside of their reserved capacity. They may be interrupted, re-added to the queue and restarted again later, or even cancelled if they are not configured to allow retries. That is the product-level tradeoff: **preemptible** steps may access more of the pool — including capacity that others are not using right now — but another workload can force them off the machine if priorities and policies require it, so they can fail early when contention is high. **Non-preemptible** steps opt into a smaller, dependable slice: they only consume what resources are reserved for them, so they are not evicted for pool reasons, at the cost of not using the “burst” capacity above that reservation. ### The link: subject policies **Subject policies** connect pools to execution. A policy binds a workload bearing **stack component** — today an orchestrator or step operator — to a pool and states what subset of that pool’s resources that component may use. Steps stay decoupled from a specific pool name: resolution uses the component in the active stack, so the same step definition can behave differently in different environments without code changes. Policies are usually owned by the same platform or admin function, but ML teams can share ownership where it makes sense. Each policy carries **priority**, **reserved** amounts per key, and optionally **limits**: * **Reservation** answers “what pool share is accounted exclusively to this component” and is the only capacity that **non-preemptible** steps may rely on: they cannot use the gap between reserved and limit. * **Limits** (and the pool’s own maximum) bound how many resources **preemptible** work can take when spare capacity exists; without a limit, the effective ceiling is what the pool still has free. A higher limit does not raise the ceiling for non-preemptible requests; you need to raise **reserved** if those jobs need more per step. When thinking about subject policies, it's helpful to understand that multiple steps in the same run or even in different runs will share the resources defined in the same subject policy if they run on the same stack component. So they will compete for the same reserved resources. If they go outside of their reserved capacity, they will also compete with other stack components that access the same pool. Together, pools plus policies plus step annotations implement a **shared, prioritized, optionally elastic** scheduling story: strict guarantees where needed, elastic sharing where teams accept preemption risk. ### What this looks like: three surfaces **1 — Pool (supply).** Platform ops create a workspace pool, name it whatever helps the org (say **datacenter-one**), and record how much of each scarce thing exists there: **10 GPUs**, **200 CPUs** and **500 GB of memory**. That number is the shared ceiling everyone draws from. ```shell zenml resource-pool create datacenter-one \ --capacity '{"gpu": 10, "mcpu": 200000, "memory_mb": 5120000}' ``` **2 — Policy (wiring a stack to a pool).** They attach a **subject policy** so a specific stack component knows which pool to use and what slice it may claim. If your pipeline runs on a stack called **prod-stack**, its orchestrator (or step operator) is the component named in the policy: “prod-stack’s orchestrator may pull from **datacenter-one**, with *this much* reserved and *this much* limit,” and a priority versus other stacks. ```shell zenml resource-pool attach-policy datacenter-one prod-stack \ --priority 10 \ --reserved '{"gpu": 4, "mcpu": 8000, "memory_mb": 20480}' \ --limit '{"gpu": 6, "mcpu": 16000, "memory_mb": 81920}' ``` **3 — Step request (what the run asks for).** The data scientist opens a step and says, in effect, **“this step needs three GPUs, 1 CPU and 2 GB of memory”** and can be preempted. ```python from zenml import step, pipeline from zenml.config import ResourceSettings resource_settings=ResourceSettings( gpu_count=3, cpu_count=1, memory="2GiB", preemptible=True, ) @step(settings={"resources": resource_settings}) def my_step(input: str) -> None: print(input) @pipeline(dynamic=True) def my_pipeline(input: str) -> None: my_step(input) if __name__ == "__main__": my_pipeline(input="Hello, World!") ``` When they launch a **dynamic** run on **prod-stack**, ZenML turns that into a **resource request**: three GPUs, matched against **datacenter-one** through prod-stack’s policy. If three are free, the step proceeds; if not, it **waits** in line; if the ask breaks the rules (too many GPUs, non-preemptible without reservation), it **fails fast** with a clear status. Run and step views show **queued / allocated / rejected** so operators can compare ZenML to the real cluster. The subsections explain how queues, borrowing, and preemption implement that model in ZenML Pro. ## See also * [Workspaces](./workspaces.md) — pools are scoped to the workspace. * [Teams](./teams.md) — organizational context for who owns which stacks and policies. * ZenML OSS: [step and pipeline configuration](https://docs.zenml.io/how-to/steps-pipelines/configuration). --- ### Book/Getting Started/Zenml Pro/Roles (docs/book/getting-started/zenml-pro/roles.md) --- description: >- Learn about the different roles and permissions you can assign to your team members in ZenML Pro. icon: lock --- # Roles & Permissions ZenML Pro offers a robust role-based access control (RBAC) system to manage permissions across your organization, workspaces, and projects. This guide will help you understand the different roles available at each level, how to assign them, and how to create custom roles tailored to your team's needs. Please note that roles can be assigned to both individual users and [teams](teams.md). ## Resource Ownership and Permissions ZenML Pro implements a resource ownership model where users have full CRUDS (Create, Read, Update, Delete, Share) permissions on resources they create. This applies across all levels of the system: * Users can always manage resources they've created themselves * The specific level of access to resources created by others depends on the user's role * This ownership model ensures that creators maintain control over their resources while still enabling collaboration ## Resource Sharing and Implicit Membership ZenML Pro allows for flexible resource sharing across the platform: * Users can share resources (like stacks) with other users who aren't yet members of a workspace * When a resource is shared with a non-member user: * That user automatically gains limited access to the workspace (implicit membership) * They can see the workspace in their dashboard and access the shared resource * However, they don't appear in the standard members list for the workspace * If a user with shared resources is later added as a full member of a workspace and then removed, they will lose access to all resources, including those explicitly shared with them ## Organization-Level Roles At the organization level, ZenML Pro provides the following predefined roles: 1. **Organization Admin** * Full permissions to any organization resource * Can manage all aspects of the organization * Can create and manage workspaces * Can manage billing and team members * Can see and access all workspaces and projects 2. **Organization Manager** * Permissions to create and view resources in the organization * Can manage most organization settings * Cannot access billing information * Does not automatically get access to all workspaces (needs explicit workspace role assignment) 3. **Organization Viewer** * Permissions to view resources in the organization * Can connect to a workspace and view default stack and components. * Read-only access to organization resources * Can see all workspaces in the organization, but cannot access their contents without explicit roles 4. **Billing Admin** * Permissions to manage the organization's billing information * Can view and modify billing settings * Does not automatically get access to workspaces 5. **Organization Member** * Minimal permissions in the organization * Basic access to organization resources * Can only see workspaces they've been explicitly granted access to * Recommended role for users who should only have access to specific workspaces To assign organization roles: {% stepper %} {% step %} Navigate to the **Organization** **Settings** page {% endstep %} {% step %} Click on the **Members** tab. Here you can update roles for existing members. {% endstep %} {% step %} Use the **Add members** button to add new members {% endstep %} {% endstepper %} Some points to note: * In addition to adding organization roles, you might also want to add workspace or project roles for people who you want to have access to specific resources. * However, organization viewers and members cannot add themselves to existing workspaces that they are not a part of. * Currently, you cannot create custom organization roles. ### Organization Role Inheritance Understanding how roles inherit access across organization, workspace, and project levels is important for proper permission management: * **Organization Admin**: Automatically has admin-level access to all workspaces and projects * **Organization Viewer**: Can see all workspaces in the organization list but cannot access their contents without explicit roles. They can also connect to the workspace, which means they can also view things like the default stacks and components. * **Organization Member**: Can only see workspaces they've been explicitly granted access to * **Organization Manager/Billing Admin**: Do not automatically get access to workspaces If you want to limit users to seeing only specific workspaces, assign them the "Organization Member" role and then explicitly grant them access to only the workspaces they need. ## Workspace-Level Roles Workspace roles determine a user's permissions within a specific ZenML workspace. The following predefined roles are available: 1. **Workspace Admin** * Full permissions to any workspace resource * Can manage workspace settings and members * Can create and manage projects * Has complete control over all workspace resources * Has full CRUDS (Create, Read, Update, Delete, Share) permissions on all stacks in the workspace 2. **Workspace Developer** * Permissions to create and view resources in the workspace and all projects * Can work with pipelines, artifacts, and models * Cannot modify workspace settings * Can create new stacks and has full CRUDS permissions on their own stacks * Has Read and Update permissions for all other stacks in the workspace * Has access to all projects in the workspace 3. **Workspace Contributor** * Permissions to create resources in the workspace, but not access or create projects * Can add new resources to the workspace * Limited access to project resources * Can create new stacks and has full CRUDS permissions on their own stacks * Has no permissions on stacks created by others (cannot see them) * Does not have access to projects unless explicitly granted 4. **Workspace Viewer** * Permissions to view resources in the workspace and all projects * Read-only access to workspace resources * Can only view/read stacks in the workspace * Has read-only access to all projects in the workspace (due to backward compatibility) 5. **Stack Admin** * Permissions to manage stacks, components and service connectors * Specialized role for infrastructure management * Has full CRUDS permissions on ALL stacks in the workspace * Does not inherently grant access to projects ### Workspace Role Inheritance Understanding how workspace roles affect access to projects and stacks is important for proper permission configuration: * **Workspace Admin**: Has full access to all projects and stacks in the workspace * **Workspace Developer**: Has access to all projects in the workspace but limited permissions on stacks created by others * **Workspace Viewer**: Has read-only access to all projects in the workspace (for backward compatibility) but can only view stacks * **Workspace Contributor**: Can only work with stacks they create and has no inherent access to projects * **Stack Admin**: Has full access to all stacks but no inherent access to projects If you want to give users access to specific stacks but not projects, consider using the Workspace Contributor or Stack Admin roles. If you want users to have access to projects, use Workspace Developer or Workspace Viewer roles, or assign project-specific roles. ## Project-Level Roles Projects have their own set of roles that provide fine-grained control over project-specific resources. These roles are scoped to the project level: 1. **Project Admin** * Full permissions to any project resource * Can manage project members and their roles * Can configure project settings * Has complete control over project resources 2. **Project Developer** * Permissions to create and view resources in the project * Can work with pipelines, artifacts, and models * Cannot modify project settings or member roles 3. **Project Contributor** * Permissions to create resources in the project * Can add new pipelines, artifacts, and models * Cannot modify existing resources or settings 4. **Project Viewer** * Permissions to view resources in the project * Read-only access to project resources * Cannot create or modify any resources Note that project-level roles do not grant any permissions to stacks, as stacks are managed at the workspace level. ## Custom Roles ZenML Pro allows you to create custom roles with fine-grained permissions to meet your specific team requirements: * **Organization Level**: Currently, you cannot create custom organization roles via the ZenML Pro dashboard. However, this is possible via the [ZenML Pro API](https://cloudapi.zenml.io/). * **Workspace Level**: You can create custom workspace roles via the Workspace Settings page. This allows you to define specific combinations of permissions tailored to your team's workflow. * **Project Level**: Custom project roles can be created through the Project Settings page, enabling precise control over project-specific permissions. ### When to Use Custom Roles Custom roles are particularly useful in the following scenarios: * When predefined roles are either too permissive or too restrictive for your use case * When you need to separate responsibilities more precisely within your team * For implementing principle of least privilege by granting only the exact permissions needed * When you have specialized team members who need access to specific resources without full admin privileges * For creating role-based workflows that match your organization's processes For example, you might create a custom "Pipeline Operator" role that can run and monitor pipelines but cannot create or modify them, or a "Model Reviewer" role that can access model artifacts and evaluation results but cannot modify pipeline configurations. ## Team-Based Role Assignments In addition to assigning roles to individual users, ZenML Pro allows you to assign roles to [teams](teams.md). A team is a collection of users that acts as a single entity, making permission management more efficient. ### How Team Roles Work When you assign a role to a team: * All members of that team inherit the permissions associated with that role * Changes to team membership automatically update permissions for all affected users * Users can have different permissions from multiple teams they belong to * Team roles can be assigned at all levels: organization, workspace, and project * Individual user roles and team roles are cumulative - users get the highest permission level from either source For more information on creating and managing teams, see the [Teams](teams.md) documentation. ## Best Practices 1. **Least Privilege**: Assign the minimum necessary permissions to each role. 2. **Regular Audits**: Periodically review and update role assignments and permissions. 3. **Role Hierarchy**: Consider the relationship between organization, workspace, and project roles when assigning permissions. 4. **Team-Based Access**: Use teams to manage access control more efficiently across all levels. 5. **Documentation**: Maintain clear documentation about role assignments and their purposes. 6. **Regular Reviews**: Periodically audit role assignments to ensure they align with current needs. 7. **Organization Member Role**: Use the Organization Member role for users who should only see specific workspaces. By leveraging ZenML Pro's comprehensive role-based access control, you can ensure that your team members have the right level of access to resources while maintaining security and enabling collaboration across your MLOps projects.
ZenML Scarf
--- ### Book/Getting Started/Zenml Pro/Saas Deployment (docs/book/getting-started/zenml-pro/saas-deployment.md) --- description: >- Learn about ZenML Pro SaaS deployment - the fastest way to get started with production-ready MLOps. icon: cloud --- # SaaS Deployment ZenML Pro SaaS is the fastest and easiest way to get started with enterprise-grade MLOps. With zero infrastructure setup required, you can be running production pipelines within minutes while maintaining full control over your data and compute resources. {% hint style="info" %} To get access to ZenML Pro, [book a call](https://www.zenml.io/book-your-demo). {% endhint %} ## Overview In a SaaS deployment, ZenML manages all server infrastructure while your sensitive data and compute resources remain in your own cloud environment. This architecture provides the fastest time-to-value while maintaining data sovereignty for your ML workloads. ## Architecture ### What Runs Where | Component | Location | Purpose | |-----------|----------|---------| | ZenML Pro Server | ZenML Infrastructure | Manages pipeline orchestration and metadata | | Pro Control Plane | ZenML Infrastructure | Handles authentication, RBAC, and workspace management | | Metadata Store | ZenML Infrastructure | Stores pipeline runs, model metadata, and tracking information | | Secrets Store | ZenML Infrastructure (default) | Stores credentials for accessing your infrastructure | | Compute Resources | Your infrastructure through [stacks](https://docs.zenml.io/concepts/stack_components) | Executes pipeline steps and training jobs | | Data & Artifacts | Your infrastructure through [stacks](https://docs.zenml.io/concepts/stack_components) | Stores datasets, models, and pipeline artifacts | ## Key Benefits ### Fastest Setup Get to production in minutes rather than weeks. There's no infrastructure provisioning required for ZenML services—updates and patches are handled automatically, and the infrastructure scales with your needs without any manual intervention. ### Security & Compliance ZenML Pro SaaS is SOC 2 Type II and ISO 27001 certified. Your ML data stays in your infrastructure, maintaining data sovereignty, while all communications are encrypted in transit. If needed, you can optionally use your own secret management solution instead of the ZenML-managed one. ### Production Ready from Day 1 The platform comes with built-in redundancy and failover for high availability. Metadata is backed up continuously, health checks and alerting are pre-configured, and you get direct access to ZenML engineers through professional support. ### Collaboration Features ZenML Pro SaaS supports full team collaboration with multi-user capabilities. You can connect your identity provider through SSO integration, manage granular permissions with role-based access control, and organize teams and resources using workspaces and projects. ## Ideal Use Cases ZenML Pro SaaS works well for startups and scale-ups that need production MLOps quickly without infrastructure overhead, as well as teams without dedicated DevOps who want managed infrastructure and support. It's also a good fit for organizations with existing cloud infrastructure that are comfortable with SaaS tools, teams prioritizing velocity over complete infrastructure control, and POC or pilot projects that need to demonstrate value quickly. ## Secret Management Options ### Default: ZenML-Managed Secrets Store By default, ZenML Pro SaaS stores your cloud credentials securely in our managed secrets store. This requires zero configuration and provides automatic encryption at rest and in transit, with access controls managed via RBAC. ### Alternative: Customer-Managed Secrets Store For organizations with strict security requirements, you can configure ZenML to use your own [secrets management](../deploying-zenml/secret-management.md) solution such as AWS Secrets Manager, Google Cloud Secret Manager, Azure Key Vault, or HashiCorp Vault. This keeps all credentials within your infrastructure while still benefiting from managed ZenML services. [Book a call](https://www.zenml.io/book-your-demo) with us if you want this set up. ## Network Architecture ### Core Platform ZenML Pro SaaS generally requires no inbound connectivity into your infrastructure. All communication is initiated from your environment to ZenML, keeping your systems protected behind your firewall. ### Features Requiring Limited Ingress Some optional features may require you to whitelist ZenML to access specific resources in your environment. These include artifact visualizations (which need limited access to your artifact store), step logs (which need limited access to your artifact store or log collector), and running Snapshots (which relies on limited access to your orchestration environment). You control this access by configuring appropriate cloud IAM permissions. ## Getting Started Start by [booking a demo](https://www.zenml.io/book-your-demo) to get access to ZenML Pro SaaS. Once your account is set up, connect your cloud infrastructure by configuring an artifact store (S3, GCS, Azure Blob, etc.), setting up compute resources (AWS, GCP, Azure, or Kubernetes), and providing the necessary credentials via secrets. After that, you're ready to run your pipelines and monitor them through the dashboard. ## Pricing & Support ZenML Pro SaaS includes managed infrastructure and updates, professional support with SLA, regular security patches, and access to pro-exclusive features. Pricing follows a usage-based model. [Contact us](https://www.zenml.io/book-your-demo) for pricing details and custom plans. ## Comparison with Other Deployments | Feature | SaaS | Hybrid SaaS | Self-hosted | |---------|------|-------------|-------------| | Setup Time | Minutes | Hours | Days | | Maintenance | Zero | Workspace only | Full stack | | Infrastructure Control | Minimal | Moderate | Complete | | Data Sovereignty | Metadata on ZenML | Full | Full | | Best For | Fast time-to-value | Security requirements | Strictest compliance | [Compare all deployment options →](scenarios.md) ## Migration Path Already running ZenML OSS? Migrating to SaaS is possible with the assistance of the ZenML support team. Reach out to us at hello@zenml.io or on [Slack](https://zenml.io/slack) to learn more. ## Related Resources - [System Architecture](system-architecture.md) - [Scenarios](scenarios.md) - [Hybrid SaaS Deployment](hybrid-deployment.md) - [Self-hosted Deployment](self-hosted-deployment.md) - [Deployment Details](deploy-details.md) - [Upgrades and Updates](upgrades-updates.md) ## Get Started Ready to get started with ZenML Pro SaaS? [Book a Demo](https://www.zenml.io/book-your-demo) or [contact us](mailto:cloud@zenml.io) with questions.
ZenML Scarf
--- ### Book/Getting Started/Zenml Pro/Scenarios (docs/book/getting-started/zenml-pro/scenarios.md) --- description: Compare ZenML Pro deployment scenarios to find the right fit for your organization. icon: code-merge --- # Scenarios ZenML Pro offers three flexible deployment options to match your organization's security, compliance, and operational needs. This page helps you understand the differences and choose the right scenario for your use case. ## Quick Comparison | Entity | SaaS | Hybrid SaaS | Self-hosted | |-------------------|------|-------------|-------------| | **ZenML Workspace Server** | ZenML infrastructure | Your infrastructure | Your infrastructure | | **ZenML Control Plane** | ZenML infrastructure | ZenML infrastructure | Your infrastructure | | **ZenML Pro UI** | ZenML infrastructure | ZenML infrastructure | Your infrastructure | | **Stack (Pipeline Compute & Data)** | Your infrastructure | Your infrastructure | Your infrastructure | | **Setup Time** | ⚡ ~1 hour | ~4 hours | ~8 hours | | **Maintenance Responsibility** | Fully managed | Partially managed (workspace maintenance required) | Fully customer managed | | **Best For** | Teams wanting minimal infrastructure overhead and fastest time-to-value | Organizations with security/compliance requirements but wanting simplified user management | Organizations requiring complete data isolation and on-premises control | {% hint style="info" %} In all of these cases the client SDK that you pip install into your development environment is the same one found here: https://pypi.org/project/zenml/ {% endhint %} ## Which Scenario is Right for You? ### SaaS Deployment Choose **SaaS** if you want to get started immediately with zero infrastructure overhead. **What runs where:** - ZenML Server: ZenML infrastructure - Metadata and RBAC: ZenML infrastructure - Compute and Data: Your infrastructure **Key Benefits:** - ⚡ Fastest setup (minutes) - ✅ Fully managed by ZenML - 🚀 Immediate production readiness - 💰 Minimal operational overhead **Ideal for:** Startups, teams prioritizing time-to-value and operational simplicity, organizations comfortable leveraging managed cloud services. [Set up SaaS deployment →](saas-deployment.md) ### Hybrid SaaS Deployment Choose **Hybrid** if you need to keep sensitive metadata in your infrastructure while benefiting from centralized user management. **What runs where:** - ZenML Control Plane: ZenML infrastructure - ZenML Pro UI: ZenML infrastructure - ZenML Pro Server: Your infrastructure - Run metadata: Your infrastructure - Compute and Data: Your infrastructure **Key Benefits:** - 🔐 Metadata stays in your infrastructure - 👥 Centralized user management - ⚖️ Balance of control and convenience - 🏢 Control plane and UI fully maintained and patched by ZenML - 🔗 Private cross-cloud networking is possible for Google Cloud workspaces using AWS Interconnect - multicloud and Google Cloud Cross-Cloud Interconnect - ✅ Day 1 production ready **Ideal for:** Organizations with security policies requiring metadata sovereignty, teams wanting simplified identity management without full infrastructure control. [Set up Hybrid deployment →](hybrid-deployment.md) ### Self-hosted Deployment Choose **Self-hosted** if you need complete control with no external dependencies. **What runs where:** - All components: Your infrastructure (completely isolated) **Key Benefits:** - 🔒 Complete data sovereignty - 🚫 No external network dependencies - 🛡️ Maximum security posture **Ideal for:** Regulated industries (healthcare, finance, defense), government organizations, enterprises with strict data residency requirements, environments requiring offline operation. [Set up Self-hosted deployment →](self-hosted-deployment.md) ## Making Your Choice Consider these factors when deciding: 1. **Metadata Storage Requirements**: Where must your ML metadata and run data reside? - Cloud-hosted is acceptable → **SaaS** - Must stay in your infrastructure → **Hybrid** - Must be completely isolated on-premises → **Self-hosted** 2. **Infrastructure Complexity**: How much infrastructure control do you want? - Minimal → **SaaS** - Moderate → **Hybrid** - Full control → **Self-hosted** 3. **Time to Value**: How quickly do you need to be productive? - Within 1 hour → **SaaS** - Within 4 hours → **Hybrid** - Hours to Days (depending on your complexity) → **Self-hosted** 4. **Compliance Requirements**: What regulations apply to your organization? - General business → **SaaS** - Data residency rules → **Hybrid** - Strict isolation requirements → **Self-hosted** {% hint style="info" %} Not sure which option is right for you? [Book a call](https://www.zenml.io/book-your-demo) with our team to discuss your specific requirements. {% endhint %} ## Next Steps - **Ready to start?** [Choose SaaS Deployment](saas-deployment.md) - **Need metadata control?** [Set up Hybrid Deployment](hybrid-deployment.md) - **Require complete isolation?** [Configure Self-hosted Deployment](self-hosted-deployment.md)
ZenML Scarf
--- ### Book/Getting Started/Zenml Pro/Secrets Stores (docs/book/getting-started/zenml-pro/secrets-stores.md) --- icon: lock description: >- Learn how to link your own secrets store backend to your ZenML Pro workspace. --- # Secrets Stores The secrets you configure in your ZenML Pro workspaces are by default stored in the same database as your other workspace resources. However, you have the option to link your own backend to your workspace and store the secrets in your own infrastructure. This functionality is powered by the same [ZenML Secrets Store functionality](https://docs.zenml.io/deploying-zenml/deploying-zenml/secret-management) that is available in ZenML OSS and several options are available for you to choose from: AWS Secrets Manager, GCP Secret Manager, Azure Key Vault and HashiCorp Vault. ## How to configure a secrets store This operation has two main stages: 1. first, you prepare the authentication credentials and necessary permissions for the secrets store. This varies depending on the secrets store backend and the authentication method you want to use (see following sections for more details). 2. then, you communicate these credentials to the ZenML Pro support team, who will update your workspace to use the new secrets store and also migrate all your existing secrets in the process. ## AWS Secrets Manager The authentication used by the AWS secrets store is built on the [ZenML Service Connector](https://docs.zenml.io/stacks/service-connectors/auth-management) of the same type as the secrets store. This means that you can use any of the [authentication methods supported by the Service Connector](https://docs.zenml.io/stacks/service-connectors/connector-types/aws-service-connector#authentication-methods) to authenticate with the secrets store. The recommended authentication method documented here is to use the [implicit authentication method](https://docs.zenml.io/stacks/service-connectors/connector-types/aws-service-connector#implicit-authentication), because this doesn't need any sensitive credentials to be exchanged with the ZenML Pro support team. The process is as follows: 1. Identify the AWS IAM role of your ZenML Pro workspace. Every ZenML Pro workspace is associated with a particular AWS IAM role that bears all the AWS permissions granted to the workspace. The ARN of this role is formed as follows: `arn:aws:iam::715803424590:role/zenml-`. For example, if your workspace UUID is `123e4567-e89b-12d3-a456-426614174000`, the ARN of the role is `arn:aws:iam::715803424590:role/zenml-123e4567-e89b-12d3-a456-426614174000`. 2. Create an AWS IAM role in your AWS account that will be assumed by the ZenML Pro workspace role: * use the following trust relationship to allow the ZenML Pro workspace role to assume the new role: ```json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::715803424590:role/zenml-" } } ] } ``` * attach the following custom IAM policy to the new role to allow it to access the AWS Secrets Manager service: ```json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "secretsmanager:CreateSecret", "secretsmanager:GetSecretValue", "secretsmanager:DescribeSecret", "secretsmanager:PutSecretValue", "secretsmanager:UpdateSecret", "secretsmanager:TagResource", "secretsmanager:DeleteSecret" ], "Resource": "arn:aws:secretsmanager:::secret:zenml/*" } ] } ``` 3. Contact the ZenML Pro support team to update your ZenML Pro workspace to use the new secrets store. You will need to provide the ARN of the new role you created in step 2 and the region where the AWS Secrets Manager service is located. After your workspace is updated, you will see the following changes in the workspace configuration: ```json { "id": "...", "name": "...", "zenml_service": { "configuration": { "version": "...", "secrets_store": { "type": "aws", "settings": { "auth_method": "implicit", "auth_config": { "region": "", "role_arn": "arn:aws:iam:::role/" } } } } } } ``` Here is an example Terraform code to create the new role and attach the custom policy: ``` /* Detailed source-code truncated for AI context efficiency. */ ``` If you choose a different authentication method, your will need to provide different credentials. See the [AWS Secrets Manager](https://docs.zenml.io/stacks/service-connectors/connector-types/aws-service-connector#authentication-methods) documentation on the available authentication methods and their configuration options for more details. ## HashiCorp Vault The HashiCorp Vault secrets store supports the following authentication methods: * [Token authentication](https://python-hvac.org/en/stable/usage/auth_methods/token.html) - authentication using a static token * [App Role authentication](https://python-hvac.org/en/stable/usage/auth_methods/approle.html) - authentication using a Vault App Role (app role ID and secret ID) * [AWS authentication](https://python-hvac.org/en/stable/usage/auth_methods/aws.html) - implicit authentication using an AWS IAM role (IAM role ARN) The recommended authentication method documented here is to use the implicit AWS authentication, because this doesn't need any sensitive credentials to be exchanged with the ZenML Pro support team. The process is as follows: 1. Identify the AWS IAM role of your ZenML Pro workspace. Every ZenML Pro workspace is associated with a particular AWS IAM role that bears all the AWS permissions granted to the workspace. The ARN of this role is formed as follows: `arn:aws:iam::715803424590:role/zenml-`. For example, if your workspace UUID is `123e4567-e89b-12d3-a456-426614174000`, the ARN of the role is `arn:aws:iam::715803424590:role/zenml-123e4567-e89b-12d3-a456-426614174000`. 2. Enable the AWS authentication method for your HashiCorp Vault: ```shell vault auth enable aws ``` 3. Enable the AWS authentication method for your HashiCorp Vault and configure an AWS role to use for authentication, e.g.: ```shell vault auth enable aws vault write auth/aws/config/client \ iam_server_id_header_value="" \ sts_region="eu-central-1" vault write auth/aws/role/zenml- \ auth_type=iam \ bound_iam_principal_arn=arn:aws:iam::715803424590:role/zenml- \ resolve_aws_unique_ids=false \ policies="zenml-" \ ttl=1h max_ttl=24h ``` A few points to note: * use the IAM role ARN of your ZenML Pro workspace as the bound IAM principal ARN. * it's recommended to use a header value to further secure the authentication process. Use a value that is unique to your workspace. * configuring `resolve_aws_unique_ids` to `false` is required for the authentication to work. * you can point to a custom policy to further restrict the permissions of the authenticated role to a particular mount point. 4. Contact the ZenML Pro support team to update your ZenML Pro workspace to use the new secrets store. You will need to provide the following information: * the URL of the HashiCorp Vault server * the name of the AWS Hashicorp Vault role you created in step 2 (e.g. `zenml-`) * the header value you used for the authentication process (e.g. ``) * the namespace of the HashiCorp Vault server (if applicable) * the mount point to use (if applicable) After your workspace is updated, you will see the following changes in the workspace configuration: ```json { "id": "...", "name": "...", "zenml_service": { "configuration": { "version": "...", "secrets_store": { "type": "hashicorp", "settings": { "auth_method": "aws", "auth_config": { "vault_addr": "https://vault.example.com", "vault_namespace": "zenml", "mount_point": "secrets-", "aws_role": "zenml-", "aws_header_value": "" } } } } } } ```
ZenML Scarf
--- ### Book/Getting Started/Zenml Pro/Self Hosted Deployment (docs/book/getting-started/zenml-pro/self-hosted-deployment.md) --- description: >- Learn about ZenML Pro Self-hosted deployment - complete control and data sovereignty for the strictest security requirements. icon: shield-halved --- # Self-hosted Deployment ZenML Pro Self-hosted deployment provides complete control and data sovereignty for organizations with the strictest security, compliance, or regulatory requirements. All ZenML components run entirely within your infrastructure with no external dependencies or internet connectivity required. {% hint style="info" %} To learn more about Self-hosted deployment, [book a call](https://www.zenml.io/book-your-demo). {% endhint %} ## Overview In a Self-hosted deployment, every component of ZenML Pro runs within your isolated network environment. This architecture is designed for organizations that must operate in completely disconnected environments or have regulatory requirements preventing any external communication. ## Architecture ### What Runs Where | Component | Location | Purpose | |-----------|----------|---------| | Pro Control Plane | Your Infrastructure | Manages authentication, RBAC, and workspace coordination | | ZenML Pro Server(s) | Your Infrastructure | Handles pipeline orchestration and execution | | Pro Metadata Store | Your Infrastructure | Stores user management, RBAC, and organizational data | | Workspace Metadata Store | Your Infrastructure | Stores pipeline runs, model metadata, and tracking information | | Secrets Store | Your Infrastructure | Stores all credentials and sensitive configuration | | Identity Provider | Your Infrastructure | Handles authentication (OIDC/LDAP/SAML) | | Pro Dashboard | Your Infrastructure | Web interface for all ZenML Pro features | | Compute Resources | Your infrastructure through [stacks](https://docs.zenml.io/concepts/stack_components) | Executes pipeline steps and training jobs | | Data & Artifacts | Your infrastructure through [stacks](https://docs.zenml.io/concepts/stack_components) | Stores datasets, models, and pipeline artifacts | {% hint style="success" %} Zero data leaves your environment. All components, metadata, and ML artifacts remain within your infrastructure boundaries. {% endhint %} ### Complete Isolation Users authenticate via local password protected accounts or through your internal identity provider (LDAP/AD/OIDC), and the control plane running in your infrastructure handles both authentication and RBAC. All communication happens entirely within your infrastructure boundary with zero external dependencies or internet connectivity required. ## Key Benefits ### Maximum Security & Control Self-hosted deployment operates with complete air-gap capability, requiring no internet connectivity for operation. All components are self-contained with zero external dependencies. You have full control over all security configurations, the system operates entirely within your security perimeter, and all logging and monitoring stays within your infrastructure for audit compliance. ### Regulatory Compliance All data stays within your jurisdiction, meeting data residency requirements. The deployment is suitable for controlled data environments requiring ITAR/EAR compliance, healthcare and privacy regulations like HIPAA and GDPR, government and defense classified environments, and banking and financial regulations. ### Enterprise Control You can integrate with your existing identity provider (LDAP/AD/OIDC) and deploy on any infrastructure including cloud, on-premises, or edge. You control update schedules and versions, implement your own backup and disaster recovery policies, and have full control over resource allocation and costs. ## Ideal Use Cases Self-hosted deployment is essential for government and defense organizations with classified data requirements, regulated industries (healthcare, finance) with strict data residency requirements, and organizations in restricted regions with limited or no internet connectivity. It's also the right choice for research institutions handling sensitive or proprietary research data, critical infrastructure operators requiring isolated systems, companies with ITAR/EAR compliance requirements, enterprises with zero-trust policies prohibiting external communication, and organizations requiring full control over all aspects of their MLOps platform. ## Deployment Options ### On-Premises Data Center Deploy on your own hardware with physical servers or private cloud infrastructure. This option provides complete infrastructure control, integration with existing systems, and support for custom hardware configurations. ### Private Cloud (AWS, Azure, GCP) Deploy in an isolated cloud VPC with no internet gateway and private networking only. You can use cloud-native services while leveraging cloud scalability within your security boundary. ### Hybrid Multi-Cloud Deploy across multiple environments combining on-premises infrastructure with private cloud, multi-region setups for disaster recovery, or edge plus datacenter hybrid configurations. This option maintains complete isolation across all environments. ## Operations & Maintenance ### Updates & Upgrades ZenML provides new versions as offline bundles. The update process involves receiving the new bundle (typically by pulling Docker images via your approved transfer method), carefully reviewing the release notes and migration instructions to understand all changes and requirements, testing in a staging environment first, backing up your current database and configuration state, applying updates using Helm upgrade commands or your Infrastructure-as-Code tools, verifying functionality with health checks and tests, and monitoring for any issues post-upgrade. ### Disaster Recovery Your disaster recovery plan should include MySQL streaming replication to a backup site, artifact store synchronization to a DR location, version-controlled infrastructure as code for configuration backup, documented DR runbooks, and regular quarterly testing of DR procedures. ## Security Hardening ### Network Security Isolate ZenML components in dedicated network segments, restrict traffic to only required ports with firewall rules, encrypt all communication with TLS, and use an internal CA for certificate issuance. ### Access Control Apply the principle of least privilege by granting minimal required permissions. Use dedicated service accounts for automation and log all authentication and authorization events for audit purposes. ### Container Security Scan all container images before deployment, monitor container behavior at runtime, enforce security standards with pod security policies, and configure resource limits to prevent resource exhaustion attacks. ## Support & Documentation ### What ZenML Provides ZenML provides complete offline installation bundles, comprehensive setup and operation guides, a full software bill of materials (SBOM) for compliance, security assessment documentation with vulnerability reports, pre-deployment planning support through architecture consultation, guidance during initial setup, and new versions as offline bundles. ### What You Manage You are responsible for infrastructure (hardware, networking, storage), day-to-day operations (monitoring, backups, user management), security policies (firewall rules, access controls), compliance (audit logs, security assessments), and applying new versions using the provided bundles. ### Support Model Contact [cloud@zenml.io](mailto:cloud@zenml.io) for pre-sales architecture consultation, deployment planning and sizing, security documentation requests, offline support packages, and update and upgrade assistance. ## Licensing Air-gapped deployments are provided under commercial software license agreements, with license fees and terms defined on a per-customer basis. Each contract includes detailed license terms and conditions appropriate to the deployment. ## Security Documentation The following documentation is available on request for compliance and security reviews: vulnerability assessment reports with full security analysis, software bill of materials (SBOM) with complete dependency list, architecture security review with threat model and mitigations, compliance mappings for NIST, CIS, GDPR, and HIPAA, and a security hardening guide with best practices for your deployment. ## Comparison with Other Deployments | Feature | SaaS | Hybrid SaaS | Self-hosted | |---------|------|-------------|-------------| | Internet Required | Yes (metadata) | Yes (control plane) | No | | Setup Time | Minutes | Hours/Days | Days/Weeks | | Maintenance | Zero | Partial | Full control | | Data Location | Mixed | Your infra | 100% yours | | User Management | ZenML | ZenML | Your IDP | | Update Control | Automatic | Automatic CP | You decide | | Customization | Limited | Moderate | Complete | | Best For | Fast start | Balance | Max security | [Compare all deployment options →](scenarios.md) ## Migration Path ### From ZenML OSS to Self-hosted Pro If you're interested in migrating from ZenML OSS to a Self-hosted Pro deployment, we're here to help guide you through every step of the process. Migration paths are highly dependent on your specific environment, infrastructure setup, and current ZenML OSS deployment configuration. It's possible to migrate existing stacks or even existing metadata from existing OSS deployments—we can figure out how and what to migrate together in a call. [Book a migration consultation](https://www.zenml.io/book-your-demo) or email us at [cloud@zenml.io](mailto:cloud@zenml.io). Your ZenML representative will work with you to assess your current setup, understand your Self-hosted requirements, and provide a tailored migration plan that fits your environment. ### From Other Pro Deployments If you're moving from SaaS or Hybrid to Self-hosted, migration paths can vary significantly depending on your organization's size, data residency requirements, and current ZenML setup. We recommend discussing your plans with a ZenML solutions architect. [Book a migration consultation](https://www.zenml.io/book-your-demo) or email us at [cloud@zenml.io](mailto:cloud@zenml.io). Your ZenML representative will provide you with a tailored migration checklist, technical documentation, and direct support to ensure a smooth transition with minimal downtime. ## Related Resources - [System Architecture](system-architecture.md) - [Scenarios](scenarios.md) - [SaaS Deployment](saas-deployment.md) - [Hybrid SaaS Deployment](hybrid-deployment.md) - [Deployment Details](deploy-details.md) - [Upgrades and Updates](upgrades-updates.md) ## Get Started Ready to deploy ZenML Pro in a Self-hosted environment? [Book a Demo](https://www.zenml.io/book-your-demo) or [contact us](mailto:cloud@zenml.io) for detailed deployment planning.
ZenML Scarf
--- ### Book/Getting Started/Zenml Pro/Service Accounts (docs/book/getting-started/zenml-pro/service-accounts.md) --- description: Learn how to manage and use service accounts and API keys . icon: key --- # Service Accounts Service accounts in ZenML Pro provide a secure way to authenticate automated systems, CI/CD pipelines, and other non-interactive applications with your ZenML Pro organization. Unlike user accounts, service accounts are designed specifically for programmatic access and can be managed centrally through the Organization Settings interface. ## Accessing Service Account Management To manage service accounts in your ZenML Pro organization, navigate to your ZenML Pro dashboard, click on **"Settings"** in the organization navigation menu and select **"Service Accounts"** from the settings sidebar. This is the main interface where you can perform all service account and API key operations. ## Using Service Account API Keys Once you have created a service account and API key, you can use them to authenticate to the ZenML Pro API and use it to programmatically manage your organization. You can also use the API key to access all the workspaces in your organization to e.g. run pipelines from the ZenML Python client. ### ZenML Pro API programmatic access The API key can be used to authenticate to the ZenML Pro management REST API programmatically. There are two methods to do this - one is simpler but less secure, the other is secure and recommended but more complex: {% tabs %} {% tab title="Direct API key authentication" %} {% hint style="warning" %} This approach, albeit simple, is not recommended because the long-lived API key is exposed with every API request, which makes it easier to be compromised. Use it only in low-risk circumstances. {% endhint %} To authenticate to the REST API, simply pass the API key directly in the `Authorization` header used with your API calls: * using curl: ```bash curl -H "Authorization: Bearer YOUR_API_KEY" https://cloudapi.zenml.io/users/me ``` * using wget: ```bash wget -qO- --header="Authorization: Bearer YOUR_API_KEY" https://cloudapi.zenml.io/users/me ``` * using python: ```python import requests response = requests.get( "https://cloudapi.zenml.io/users/me", headers={"Authorization": f"Bearer YOUR_API_KEY"} ) print(response.json()) ``` {% endtab %} {% tab title="Token exchange authentication" %} Reduce the risk of API key exposure by periodically exchanging the API key for a short-lived API token: 1. To obtain a short-lived API token using your API key, send a POST request to the `/auth/login` endpoint. Here are examples using common HTTP clients: * using curl: ```bash curl -X POST -d "password=" https://cloudapi.zenml.io/auth/login ``` * using wget: ```bash wget -qO- --post-data="password=" \ --header="Content-Type: application/x-www-form-urlencoded" \ https://cloudapi.zenml.io/auth/login ``` * using python: ```python import requests import json response = requests.post( "https://cloudapi.zenml.io/auth/login", data={"password": ""}, headers={"Content-Type": "application/x-www-form-urlencoded"} ) print(response.json()) ``` This will return a response like this (the short-lived API token is the `access_token` field): ```json { "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3MGJjZTg5NC1hN2VjLTRkOTYtYjE1Ny1kOTZkYWY5ZWM2M2IiLCJpc3MiOiJmMGQ5NjI1Ni04YmQyLTQxZDctOWVjZi0xMmYwM2JmYTVlMTYiLCJhdWQiOiJmMGQ5NjI1Ni04YmQyLTQxZDctOWVjZi0xMmYwM2JmYTVlMTYiLCJleHAiOjE3MTk0MDk0NjAsImFwaV9rZXlfaWQiOiIzNDkyM2U0NS0zMGFlLTRkMjctODZiZS0wZGRhNTdkMjA5MDcifQ.ByB1ngCPtBenGE6UugsWC6Blga3qPqkAiPJUSFDR-u4", "token_type": "bearer", "expires_in": 3600, "device_id": null, "device_metadata": null } ``` 2. Once you have obtained a short-lived API token, you can use it to authenticate your API requests by including it in the `Authorization` header. When the token expires, simply repeat the steps above to obtain a new short-lived API token. For example, you can use the following command to check your current user: * using curl: ```bash curl -H "Authorization: Bearer YOUR_API_TOKEN" https://cloudapi.zenml.io/users/me ``` * using wget: ```bash wget -qO- --header="Authorization: Bearer YOUR_API_TOKEN" https://cloudapi.zenml.io/users/me ``` * using python: ```python import requests response = requests.get( "https://cloudapi.zenml.io/users/me", headers={"Authorization": f"Bearer {YOUR_API_TOKEN}"} ) print(response.json()) ``` {% endtab %} {% endtabs %} See the [API documentation](https://docs.zenml.io/api-reference/pro-api/getting-started) for detailed information on programmatic access patterns. It is also possible to authenticate as the service account using the OpenAPI UI available at https://cloudapi.zenml.io: The session token is stored as a cookie, which essentially authenticates your entire OpenAPI UI session. Not only that, but you can now open https://cloud.zenml.io and navigate your organization and its resources as the service account. ### Workspace access You can also use the ZenML Pro API key to access all the workspaces in your organization: * with environment variables: ```bash # set this to the ZenML Pro workspace URL export ZENML_STORE_URL=https://your-org.zenml.io export ZENML_STORE_API_KEY= # optional, for self-hosted ZenML Pro API servers, set this to the ZenML Pro # API URL, if different from the default https://cloudapi.zenml.io export ZENML_PRO_API_URL=https://... ``` * with the CLI: ```bash zenml login --api-key # You will be prompted to enter your API key ``` #### ZenML Pro Workspace API programmatic access Similar to the ZenML Pro API programmatic access, the API key can be used to authenticate to the ZenML Pro workspace REST API programmatically. This is no different from [using the OSS API key to authenticate to the OSS workspace REST API programmatically](https://docs.zenml.io/api-reference/oss-api/getting-started#using-a-service-account-and-an-api-key). There are two methods to do this - one is simpler but less secure, the other is secure and recommended but more complex: {% tabs %} {% tab title="Direct Pro API key authentication" %} {% hint style="warning" %} This approach, albeit simple, is not recommended because the long-lived Pro API key is exposed with every API request, which makes it easier to be compromised. Use it only in low-risk circumstances. {% endhint %} Use the Pro API key directly to authenticate your API requests by including it in the `Authorization` header. For example, you can use the following command to check your current workspace user: * using curl: ```bash curl -H "Authorization: Bearer YOUR_API_KEY" https://your-workspace-url/api/v1/current-user ``` * using wget: ```bash wget -qO- --header="Authorization: Bearer YOUR_API_KEY" https://your-workspace-url/api/v1/current-user ``` * using python: ```python import requests response = requests.get( "https://your-workspace-url/api/v1/current-user", headers={"Authorization": f"Bearer {YOUR_API_KEY}"} ) print(response.json()) ``` {% endtab %} {% tab title="Token exchange authentication" %} Reduce the risk of Pro API key exposure by periodically exchanging the Pro API key for a short-lived workspace API token. 1. To obtain a short-lived workspace API token using your Pro API key, send a POST request to the `/api/v1/login` endpoint. Here are examples using common HTTP clients: * using curl: ```bash curl -X POST -d "password=" https://your-workspace-url/api/v1/login ``` * using wget: ```bash wget -qO- --post-data="password=" \ --header="Content-Type: application/x-www-form-urlencoded" \ https://your-workspace-url/api/v1/login ``` * using python: ```python import requests import json response = requests.post( "https://your-workspace-url/api/v1/login", data={"password": ""}, headers={"Content-Type": "application/x-www-form-urlencoded"} ) print(response.json()) ``` This will return a response like this (the workspace API token is the `access_token` field): ```json { "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3MGJjZTg5NC1hN2VjLTRkOTYtYjE1Ny1kOTZkYWY5ZWM2M2IiLCJpc3MiOiJmMGQ5NjI1Ni04YmQyLTQxZDctOWVjZi0xMmYwM2JmYTVlMTYiLCJhdWQiOiJmMGQ5NjI1Ni04YmQyLTQxZDctOWVjZi0xMmYwM2JmYTVlMTYiLCJleHAiOjE3MTk0MDk0NjAsImFwaV9rZXlfaWQiOiIzNDkyM2U0NS0zMGFlLTRkMjctODZiZS0wZGRhNTdkMjA5MDcifQ.ByB1ngCPtBenGE6UugsWC6Blga3qPqkAiPJUSFDR-u4", "token_type": "bearer", "expires_in": 3600, "refresh_token": null, "scope": null } ``` 2. Once you have obtained a short-lived workspace API token, you can use it to authenticate your API requests by including it in the `Authorization` header. When the short-lived workspace API token expires, simply repeat the steps above to obtain a new one. For example, you can use the following command to check your current workspace user: * using curl: ```bash curl -H "Authorization: Bearer YOUR_API_TOKEN" https://your-workspace-url/api/v1/current-user ``` * using wget: ```bash wget -qO- --header="Authorization: Bearer YOUR_API_TOKEN" https://your-workspace-url/api/v1/current-user ``` * using python: ```python import requests response = requests.get( "https://your-workspace-url/api/v1/current-user", headers={"Authorization": f"Bearer {YOUR_API_TOKEN}"} ) print(response.json()) ``` {% endtab %} {% endtabs %} ## Service Account Operations ### Managing Service Account Roles and Permissions Service accounts are no different from regular users in that they can be assigned different [Organization, Workspace and Project roles](roles.md) to control their access to different parts of the organization and they can be organized into [teams](teams.md). They are marked as "BOT" in the UI, to clearly identify them as non-human users. ### Activating and Deactivating Service Accounts Service account activation controls whether the account can be used for authentication. Deactivating a service account immediately prevents all associated API keys from working. {% hint style="danger" %} **Immediate Effect** Deactivating a service account has immediate effect on all ZenML Pro API calls using any of its API keys. Ensure you coordinate with your team before deactivating production service accounts. {% endhint %} {% hint style="warning" %} **Delayed workspace token effect** Short-lived API tokens associated with the deactivated service account issued for workspaces in your organization may still be valid for up to one hour after the service account is deactivated. {% endhint %} ### Deleting a Service Account Deleting a service account permanently removes it and all associated API keys from your organization. {% hint style="warning" %} **Delayed workspace token effect** Short-lived API tokens associated with the deleted service account issued for workspaces in your organization may still be valid for up to one hour after the service account is deleted. {% endhint %} ## API Key Management API keys are the credentials used by applications to authenticate as a service account. Each service account can have multiple API keys, allowing for different access patterns. When you create a new service account, you have the option to automatically create a default API key for it. ### Creating an API Key {% hint style="danger" %} **One-Time Display** The API key value is only shown once during creation and cannot be retrieved later. If you lose an API key, you must create a new one or rotate the existing key. {% endhint %} ### Activating and Deactivating API Keys Individual API keys can be activated or deactivated independently of the service account status. {% hint style="warning" %} **Delayed workspace token effect** Short-lived API tokens associated with the deactivated API key issued for workspaces in your organization may still be valid for up to one hour after the API key is deactivated. {% endhint %} ### Rotating API Keys API key rotation creates a new key value while optionally preserving the old key for a transition period. This is essential for maintaining security without service interruption. {% hint style="info" %} **Zero-Downtime Rotation** By setting a retention period, you can update your applications to use the new API key while the old key remains functional. This enables zero-downtime key rotation for production systems. {% endhint %} ### Deleting API Keys {% hint style="warning" %} **Delayed workspace token effect** Short-lived API tokens associated with the deleted API key issued for workspaces in your organization may still be valid for up to one hour after the API key is deleted. {% endhint %} ## Security Best Practices ### Key Management * **Regular Rotation**: Rotate API keys regularly (recommended: every 90 days for production keys) * **Principle of Least Privilege**: Create separate service accounts for different purposes rather than sharing keys * **Secure Storage**: Store API keys in secure credential management systems, never in code repositories * **Monitor Usage**: Regularly review the "last used" timestamps to identify unused keys ### Access Control * **Descriptive Naming**: Use clear, descriptive names for service accounts and API keys to track their purposes * **Documentation**: Maintain documentation of which systems use which service accounts * **Regular Audits**: Periodically review and clean up unused service accounts and API keys ### Operational Security * **Immediate Deactivation**: Deactivate service accounts and API keys immediately when they're no longer needed * **Incident Response**: Have procedures in place to quickly rotate or deactivate compromised keys * **Team Coordination**: Coordinate with your team before making changes to production service accounts ## Migration of workspace level service accounts Workspace-level service accounts and API keys in ZenML Pro are deprecated. Existing workspace-level API keys may continue to authenticate temporarily for compatibility, but workloads that keep using workspace-level service account API keys will lose access during a future workspace upgrade. Please migrate them to organization-level service accounts as soon as possible. The reason for this deprecation is that workspace-level service accounts and API keys cannot be subjected to RBAC rules and therefore always have full access to the workspace. Use ZenML Pro organization service accounts instead. They are managed centrally in the ZenML Pro control plane and can be used to authenticate both to the ZenML Pro API and to the Workspace API for the workspaces in your organization. To migrate automation that still uses a workspace-level service account API key, follow these steps: 1. Create a ZenML Pro organization service account in **Organization** > **Settings** > **Service Accounts**. Use the exact same username as the old workspace-level service account. This allows ZenML Pro to adopt resources owned by the old workspace-level service account and preserve lineage/history under the migrated organization-level service account. Be aware that the organization-level service account is shared across all workspaces in the organization. 2. [Assign Organization and Workspace roles](roles.md) to the new service account. To preserve the same unrestricted permissions that the workspace-level service account currently has but limited to the affected workspace, grant the **Organization Member** role at the organization level and the **Workspace Admin** role in the affected workspace. For better security, we strongly recommend making full use of ZenML Pro RBAC and granting only the specific roles and permissions that the service account actually needs. 3. Create an API key for the new organization-level service account and update your automation, CI/CD jobs, and pipeline workloads to use that key instead of the old workspace-level API key. You can keep using the workspace URL as the ZenML store URL: ```bash export ZENML_STORE_URL=https://your-workspace-url export ZENML_STORE_API_KEY= ``` For self-hosted ZenML Pro API servers, also set the ZenML Pro API URL: ```bash export ZENML_PRO_API_URL=https://your-pro-api-url ``` 4. Run the affected workloads once to verify that they authenticate successfully with the organization-level service account. 5. If the migration is successful and the correct username is used at step 1, the old workspace-level service account is automatically "adopted" by the new organization-level service account and will no longer be listed in the workspace settings. {% hint style="info" %} **Existing API keys continue to work after adoption** Adopting a workspace-level service account does not invalidate its existing workspace-level API keys. Those keys continue to authenticate to the workspace as the adopted organization-level service account, allowing you to migrate workloads without interruption. This compatibility period also gives you time to identify workloads that still use legacy keys: inspect the **Last used** timestamp of each workspace-level API key in the UI to see whether it is still authenticating. The keys remain workspace-level credentials and do not become ZenML Pro organization API keys, so you should still replace them with an organization-level API key as part of the migration. {% endhint %} 6. Monitor the **Last used** timestamp for each existing workspace-level API key in the UI. If a timestamp continues to update after you believe a workload has migrated, that workload is likely still using the old key. Account for infrequent jobs before concluding that a key is unused. 7. Once you have confirmed that the workspace-level API keys are no longer used, deactivate the keys or their service account. Deactivation immediately prevents the keys from being used. You can also delete the service account, but deletion can fail if it already owns resources such as pipeline runs; in that case, deactivate it instead. ## Troubleshooting ### Common Issues **API Key Not Working** * Verify the service account is active * Verify the specific API key is active * Check that the API key hasn't expired (if using rotation with retention) * Ensure the API key is correctly formatted in your environment variables **Cannot Delete Service Account** * Verify you have the necessary permissions in the organization **API Key Creation Failed** * Ensure you have write permissions in the organization * Check that the service account is active * Verify the API key name doesn't conflict with existing keys {% hint style="info" %} **Need Help?** If you encounter issues with service account management, check the ZenML Pro documentation or contact your organization administrator for assistance with permissions and access control. {% endhint %}
ZenML Scarf
--- ### Book/Getting Started/Zenml Pro/Snapshots (docs/book/getting-started/zenml-pro/snapshots.md) --- description: Trigger pipelines from the dashboard, SDK, CLI, or REST API. icon: camera --- # Snapshots A **Pipeline Snapshot** is an immutable snapshot of your pipeline that includes the pipeline DAG, code, configuration, and container images. Snapshots enable you to trigger pipeline runs without direct access to the codebase—from the ZenML Pro dashboard, Python SDK, CLI, or REST API. {% hint style="success" %} Running snapshots is a ZenML Pro feature. For comprehensive documentation including advanced usage patterns, see the [main Snapshots documentation](../../how-to/snapshots/snapshots.md). {% endhint %} ## Why Use Snapshots? Snapshots solve common production challenges: - **Data Scientists** can experiment with different parameters without modifying code - **MLOps Engineers** can schedule retraining or integrate with CI/CD systems - **Stakeholders** can trigger model training through the dashboard - **External Systems** can invoke pipelines via REST API calls ## Requirements {% hint style="warning" %} Snapshots require a **remote stack** with at least: - Remote orchestrator - Remote artifact store - Container registry Local stacks cannot run snapshots. {% endhint %} {% hint style="info" %} **Platform Engineers:** For snapshots to work in Hybrid or Self-hosted deployments, you must configure the workload manager on your Workspace Server. See [Enable Snapshot (Workload Manager) Support for the Workspace Server](deploy-workspace-snapshots.md) for the required environment variables and Kubernetes RBAC setup. {% endhint %} ## Creating Snapshots ### From the CLI ```bash zenml pipeline snapshot create --name= ``` You can also specify a configuration file and stack: ```bash zenml pipeline snapshot create \ --name= \ --config= \ --stack= ``` ### From Python SDK ```python from zenml import pipeline @pipeline def my_pipeline(): ... snapshot = my_pipeline.create_snapshot(name="production-training") ``` ### From the Dashboard 1. Navigate to a pipeline run 2. Click `...` in the top right corner 3. Select `+ New Snapshot` 4. Enter a name and click `Create` ## Running Snapshots ### From the CLI ```bash zenml pipeline snapshot run # With custom configuration zenml pipeline snapshot run --config=config.yaml ``` ### From Python SDK ```python from zenml.client import Client # Get the snapshot snapshot = Client().get_snapshot("") # Optionally modify configuration config = snapshot.config_template config.steps["my_step"].parameters["my_param"] = new_value # Trigger the run Client().trigger_pipeline( snapshot_name_or_id=snapshot.id, run_configuration=config, ) ``` ### From the Dashboard 1. Click `Run a Pipeline` on the Pipelines page, or navigate to a snapshot and click `Run Snapshot` 2. Modify configuration using the built-in editor or upload a YAML file 3. Click `Run` ### From REST API ```bash curl -X 'POST' \ '/api/v1/pipeline_snapshots//runs' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "run_configuration": { "steps": { "train_model": { "parameters": {"model_type": "rf"} } } } }' ``` {% hint style="info" %} For REST API authentication, use [Personal Access Tokens](personal-access-tokens.md) or [Service Accounts](service-accounts.md). {% endhint %} ## Deleting Snapshots ```bash zenml pipeline snapshot delete ``` Or via Python: ```python from zenml.client import Client Client().delete_snapshot(name_id_or_prefix="") ``` ## Important Notes {% hint style="warning" %} **After upgrading your ZenML server**, you need to recreate your snapshots. Snapshots are tied to specific server versions and may not work correctly after an upgrade. {% endhint %} ## Related Documentation - [Snapshots - Full Documentation](../../how-to/snapshots/snapshots.md) - Complete reference with advanced usage - [Trigger Pipelines from External Systems](../../user-guide/tutorial/trigger-pipelines-from-external-systems.md) - Tutorial for CI/CD integration - [Workspace Server Configuration](deploy-workspace-snapshots.md) - Configure the workload manager that powers snapshots - [Service Accounts](service-accounts.md) - Set up API authentication for automated triggers
ZenML Scarf
--- ### Book/Getting Started/Zenml Pro/Sso (docs/book/getting-started/zenml-pro/sso.md) --- description: Configure Single Sign-On (SSO) authentication for ZenML Pro self-hosted deployments. layout: title: visible: true description: visible: true tableOfContents: visible: true outline: visible: true pagination: visible: true --- # Single Sign-On (SSO) This guide covers Single Sign-On (SSO) configuration for ZenML Pro self-hosted deployments. SSO allows users to authenticate through an external identity provider instead of using local accounts with password authentication. {% hint style="info" %} SSO configuration is only applicable to [the fully self-hosted scenario](self-hosted-deployment.md). [The hybrid scenario](hybrid-deployment.md) uses the ZenML Pro SaaS SSO for authentication. {% endhint %} ## Overview By default, the ZenML Pro control plane uses local accounts with password authentication. Enabling SSO provides several benefits: - **Centralized user management**: Users are managed in your identity provider, not in ZenML Pro - **Enhanced security**: Leverage your organization's existing authentication policies (MFA, password policies, etc.) - **Simplified access**: Users authenticate with credentials they already know - **Automated provisioning**: ZenML Pro user accounts are created automatically on first login You can start with local accounts and enable SSO later without losing existing data. SSO and password authentication can also be enabled simultaneously during a transition period. ## Trusted domains Trusted domains are an organization-level ZenML Pro setting. Together with SSO/OAuth sign-in, they affect **which users appear** when searching for people to invite and whether some **organization invitations** can complete automatically after login instead of an explicit accept step. Full behavior, visibility rules, and configuration paths are documented in [Trusted domains](trusted-domains.md) under Access Management. ## SSO Prerequisites ### Supported Identity Providers Any OIDC-compatible identity provider can be used, including: | Provider Type | Examples | |---------------|----------| | Cloud identity services | Google Workspace, Microsoft Entra ID (Azure AD), Okta, Auth0 | | Self-hosted solutions | Keycloak, Authentik, Dex, Gluu | | Enterprise directories | ADFS, Ping Identity, OneLogin | ### Identity Provider Requirements Your identity provider must meet the following specifications: | Requirement | Description | |-------------|-------------| | OAuth 2.0 authorization code flow | Must support the standard authorization code grant | | JWT ID token | Must issue JWT ID tokens during the authorization code flow | | Required scopes | `openid`, `email`, `profile` | | OpenID configuration endpoint | Must expose `/.well-known/openid-configuration` at a URL reachable by the ZenML Pro control plane | | JWKS | Must implement JSON Web Key Set for signing ID tokens | | Logout endpoint (optional) | If supported, enables single logout functionality | ### ZenML Pro Client Configuration in the Identity Provider When registering ZenML Pro as an OIDC client in your identity provider, configure the following: | Setting | Value | |---------|-------| | Redirect URI | `https:///api/auth/callback` | | Post-logout redirect URI | `https:///api/auth/logout-complete` (if logout is supported) | | Allowed scopes | `openid`, `email`, `profile` | | Client type | Confidential (requires client secret) | After registration, your identity provider will issue a **client ID** and **client secret** that you will need when configuring ZenML Pro, in addition to the URL to the OpenID configuration endpoint (`/.well-known/openid-configuration`). ### Information to Collect Before configuring SSO, gather the following information from your identity provider: | Parameter | Value | |-----------|-------| | Identity provider | `_______________` (e.g., Okta, Azure AD, Keycloak) | | OIDC discovery URL | `_______________` (e.g., `https://idp.example.com/.well-known/openid-configuration`) | | Client ID | `_______________` | | Client secret | `_______________` | | Redirect URI (configured in IdP) | `https:///api/auth/callback` | | Post-logout redirect URI (if applicable) | `https:///api/auth/logout-complete` | | IDP logout URI to call (if applicable) | `https:///v2/logout` | ## Configuring SSO SSO is configured differently depending on your deployment method. Refer to the deployment guide for your chosen infrastructure: - [Control Plane Kubernetes Deployment](deploy-control-plane-k8s.md) — SSO configuration via Helm values ## Migrating from Password Authentication to SSO If you have an existing ZenML Pro deployment using local accounts with password authentication, you can migrate to SSO authentication while preserving all existing resources and their ownership. This section describes the recommended migration process. ### Migration Overview The migration process involves running both authentication methods simultaneously during a transition period, then disabling password authentication once all users have migrated to SSO. During this period: - Existing local users continue to have access to their resources - New SSO users can be invited and granted permissions - Resource ownership can be transferred if needed ### Step 1: Enable SSO While Keeping Password Authentication Update your ZenML Pro control plane configuration to enable SSO authentication while keeping password authentication enabled. This allows both authentication methods to work simultaneously. {% hint style="warning" %} Do not disable password authentication at this stage. Doing so would lock out all existing local user accounts immediately. {% endhint %} The specific configuration depends on your deployment method. Refer to your deployment guide for details on how to enable SSO. ### Step 2: Log In with a Local User Account After enabling SSO, log in to the ZenML Pro UI using an existing local user account that has administrative privileges (e.g., a super-user account or an organization owner). This account will be used to invite SSO users and grant them the necessary permissions to manage the organization and its resources. ### Step 3: Invite SSO Users to Organizations While logged in with the local user account, invite the SSO users who will take over management of the organization: 1. Navigate to **Organization Settings** → **Members** 2. Click **Invite Member** 3. Enter the email address of the SSO user (this should match the email address in your identity provider) 4. Assign appropriate roles (e.g., Organization Owner, Organization Admin) 5. Repeat for all SSO users who need access {% hint style="info" %} The invited users don't need to exist in ZenML Pro yet. Their accounts will be created automatically when they first log in via SSO and accept the invitation. {% endhint %} ### Step 4: SSO Users Accept Invitations Each invited SSO user should: 1. Access the ZenML Pro UI 2. Log in using the SSO authentication flow (click "Sign in with SSO" or similar) 3. After authentication, accept any pending organization invitations (unless an invitation was completed automatically; see [Trusted domains](trusted-domains.md)) Once an SSO user accepts an invitation, they have full access to the organization according to their assigned roles. ### Step 5: Transfer Resource Ownership (Optional) If local users own resources that should be transferred to SSO users, you can update the ownership through the UI or API. This step is optional—resources owned by local users remain accessible to organization members based on their roles even after disabling password authentication. {% hint style="info" %} Resource ownership primarily affects who can delete or transfer resources. Organization members with appropriate roles can still view and use resources owned by other users within the organization. {% endhint %} ### Step 6: Disable Password Authentication Once all SSO users have been invited, have accepted their invitations, and have verified they can access all necessary resources, you can disable password authentication. {% hint style="warning" %} Disabling password authentication will immediately lock out all local user accounts. Ensure all users who need access have successfully authenticated via SSO before proceeding. {% endhint %} Update your ZenML Pro control plane configuration to disable password authentication. The specific configuration depends on your deployment method. After disabling password authentication: - Local user accounts can no longer log in - Resources owned by local users remain in the system and are accessible to organization members - The admin account used for initial setup is also disabled - Only SSO authentication is available ### Rollback Procedure If you need to re-enable password authentication after disabling it: 1. Update your ZenML Pro control plane configuration to re-enable password authentication 2. Local user accounts will be able to log in again with their original passwords {% hint style="info" %} Local user account passwords are preserved in the database even when password authentication is disabled. Re-enabling password authentication restores access without requiring password resets. {% endhint %} ## Troubleshooting ### Common SSO Issues | Issue | Possible Cause | Solution | |-------|----------------|----------| | "Invalid redirect URI" error | Redirect URI in ZenML Pro doesn't match the one configured in your IdP | Verify the redirect URI matches exactly, including the protocol (https) | | Users can't log in after SSO is enabled | OIDC discovery URL is not reachable from the ZenML Pro control plane | Check network connectivity and firewall rules | | User email doesn't match invitation | Email claim in the ID token differs from the invited email | Verify the email address format in your IdP matches what was invited | | SSO login works but user has no access | User hasn't accepted organization invitation | User should check for pending invitations after logging in | | User expected to click "Accept" but was added without it | Trusted domains and SSO auto-completion for an in-domain invitation | See [Trusted domains](trusted-domains.md); this can be expected when the invitation email matches the organization's trusted domains | ### Verifying SSO Configuration Before disabling password authentication, verify that SSO is working correctly: 1. Open an incognito/private browser window 2. Navigate to the ZenML Pro UI 3. Attempt to log in using SSO 4. Verify you can access the expected organizations and resources ## Synchronizing SSO Users with External Directories When using SSO authentication, ZenML Pro creates user accounts automatically on first login. However, ZenML Pro does not automatically synchronize user roles, group memberships, or other attributes from your identity provider. {% hint style="info" %} ZenML Pro does not consume roles, groups, or other claims from OIDC ID tokens. All ZenML Pro roles, team memberships, and organization assignments must be configured within ZenML Pro. {% endhint %} To maintain consistency between your identity provider and ZenML Pro, you can implement automated synchronization using the ZenML Pro API. ### Invitations as User Placeholders A key concept for synchronization is that **invitations act as placeholders** for user accounts before users log in via SSO: - You can create an invitation for an email address before the user has ever logged in - Invitations can be assigned organization roles, workspace roles, project roles, and team memberships—just like user accounts - When the user logs in via SSO and accepts the invitation, all permissions linked to the invitation are **automatically transferred** to their newly created user account When trusted domains and SSO are configured for the organization, eligible invitations may complete automatically on first SSO login instead of requiring an explicit accept step. See [Trusted domains](trusted-domains.md). This allows you to pre-provision access for users based on their IdP group memberships, even before they've logged into ZenML Pro for the first time. ### Synchronization Architecture A typical synchronization workflow involves: 1. **Fetch users from IdP**: Query your identity provider for users, their groups, and roles 2. **Map to ZenML Pro concepts**: Define how IdP groups/roles map to ZenML Pro organizations, teams, and roles 3. **Synchronize via API**: - For users who have logged in: update their user accounts directly - For users who haven't logged in yet: create invitations and assign permissions to them 4. **Run periodically**: Execute the synchronization on a schedule (e.g., via cron, Kubernetes CronJob, or CI/CD pipeline) ### Mapping Conventions Define a mapping strategy that works for your organization. Understanding ZenML Pro's hierarchy is essential for effective mapping: **ZenML Pro Resource Hierarchy**: - **Organizations** contain workspaces and teams - **Teams** are organization-local (each team belongs to exactly one organization) - **Roles** are scoped to specific levels: - Organization-level roles (e.g., Organization Admin, Organization Member) - Workspace-level roles (e.g., Workspace Admin, Workspace Developer) - Project-level roles (e.g., Project Admin, Project Viewer) See [Roles & Permissions](roles.md) for the full list of predefined roles at each level. **Common Mapping Approaches**: | Approach | IdP Concept | ZenML Pro Concept | Example | |----------|-------------|-------------------|---------| | **By Organization** | Group membership | Organization membership + role | `zenml-org--admin` → Org Admin in specific org | | **By Team** | Group membership | Team membership (within an org) | `zenml-team--` → Team in specific org | | **By Workspace** | Group membership | Workspace role assignment | `zenml-ws--developer` → Workspace Developer | | **By Project** | Group membership | Project role assignment | `zenml-proj--viewer` → Project Viewer | | **Global Admin** | Role/attribute | Organization Admin in all orgs | `zenml-global-admin` → Admin across organizations | **Example Mapping with UUIDs**: ``` # IdP Group Name → ZenML Pro Assignment # Organization membership with role zenml-org-a1b2c3d4-admin → Organization Admin in org a1b2c3d4-... zenml-org-a1b2c3d4-member → Organization Member in org a1b2c3d4-... # Team membership (teams are org-scoped) zenml-team-a1b2c3d4-e5f6g7h8 → Member of team e5f6g7h8-... in org a1b2c3d4-... # Workspace role assignment zenml-ws-i9j0k1l2-admin → Workspace Admin in workspace i9j0k1l2-... zenml-ws-i9j0k1l2-developer → Workspace Developer in workspace i9j0k1l2-... zenml-ws-i9j0k1l2-viewer → Workspace Viewer in workspace i9j0k1l2-... # Project role assignment zenml-proj-m3n4o5p6-admin → Project Admin in project m3n4o5p6-... zenml-proj-m3n4o5p6-viewer → Project Viewer in project m3n4o5p6-... ``` **Alternative: Human-Readable Mapping with Configuration File**: Instead of embedding UUIDs in group names, maintain a separate mapping configuration: ```yaml # mapping_config.yaml organizations: production: id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890" idp_groups: admin: "zenml-production-admins" member: "zenml-production-users" teams: data-science: id: "e5f6g7h8-i9j0-1234-klmn-opqrstuvwxyz" idp_group: "zenml-ds-team" mlops: id: "f6g7h8i9-j0k1-2345-lmno-pqrstuvwxyza" idp_group: "zenml-mlops-team" workspaces: ml-platform: id: "i9j0k1l2-m3n4-5678-opqr-stuvwxyzabcd" idp_groups: admin: "zenml-ml-platform-admins" developer: "zenml-ml-platform-devs" viewer: "zenml-ml-platform-readonly" ``` This approach keeps IdP group names human-readable while the synchronization script resolves them to ZenML Pro UUIDs. ### Programmatic API Access To synchronize users programmatically, authenticate using either a [Personal Access Token](personal-access-tokens.md) or a [Service Account API key](service-accounts.md). Service accounts are recommended for automated synchronization jobs. For detailed API documentation, see: - [ZenML Pro API Reference](https://docs.zenml.io/api-reference/pro-api/getting-started) - Interactive OpenAPI documentation at `https:///api/v1` ### Key API Patterns Before reviewing the script, note these important ZenML Pro API patterns: | Operation | API Pattern | |-----------|-------------| | **Add user to organization** | Assign an organization-level role via `POST /roles/{role_id}/assignments?user_id={id}` | | **Add invitation to organization** | Create invitation via `POST /organizations/{org_id}/invitations` with a role | | **Assign additional roles** | `POST /roles/{role_id}/assignments` with `user_id`, `invitation_id`, or `team_id` | | **Add user/invitation to team** | `POST /teams/{team_id}/members?user_id={id}` or `?invitation_id={id}` | | **Remove user from organization** | `DELETE /organizations/{org_id}/members?user_id={id}` | | **Revoke role** | `DELETE /roles/{role_id}/assignments` with appropriate query params | ### Example Synchronization Script We provide a complete example synchronization script that demonstrates how to: - Fetch users from an IdP (with a placeholder for your IdP implementation) - Map IdP groups to ZenML Pro organizations, teams, and roles - Handle both existing users and users who haven't logged in yet (via invitations) - Use the ZenML Pro API correctly for all operations {% file src="scripts/sync_sso_users.py" %} Download the example SSO user synchronization script {% endfile %} **Script highlights:** | Component | Description | |-----------|-------------| | `IdPClient` | Placeholder class for your IdP integration (Okta, Azure AD, Keycloak, etc.) | | `ZenMLProClient` | Complete API client for ZenML Pro with methods for users, organizations, invitations, roles, and teams | | `UserSynchronizer` | Orchestrates the sync, handling both existing users and pre-provisioning via invitations | **Key features:** - **Dual-mode synchronization**: Syncs directly to user accounts for users who have logged in, creates invitations for users who haven't - **Caching**: Reduces API calls by caching organizations, roles, and teams - **Error handling**: Gracefully handles conflicts (e.g., role already assigned) and logs errors - **Configurable mapping**: Easy-to-modify mapping dictionaries for your organization's group naming conventions ### Running the Synchronization **Manual execution**: ```bash export ZENML_PRO_API_URL="https://zenml-pro.example.com/api/v1" export ZENML_PRO_API_KEY="" export IDP_URL="https://your-idp.example.com" export IDP_TOKEN="" python sync_sso_users.py ``` **Kubernetes CronJob**: ```yaml apiVersion: batch/v1 kind: CronJob metadata: name: zenml-user-sync spec: schedule: "0 */6 * * *" # Every 6 hours jobTemplate: spec: template: spec: containers: - name: sync image: python:3.11-slim command: ["python", "/scripts/sync_sso_users.py"] env: - name: ZENML_PRO_API_URL value: "https://zenml-pro.example.com/api/v1" - name: ZENML_PRO_API_KEY valueFrom: secretKeyRef: name: zenml-sync-credentials key: api-key # Add IdP credentials as appropriate volumeMounts: - name: scripts mountPath: /scripts volumes: - name: scripts configMap: name: zenml-sync-scripts restartPolicy: OnFailure ``` ## Related Resources - [Self-hosted Deployment Overview](self-hosted-deployment.md) - [Roles & Permissions](roles.md) - [Organizations](organization.md)
ZenML Scarf
--- ### Book/Getting Started/Zenml Pro/System Architecture (docs/book/getting-started/zenml-pro/system-architecture.md) --- description: Understanding ZenML Pro services and how they communicate. icon: diagram-project --- # System Architecture ZenML Pro's architecture consists of two core services that work together to execute, track, and manage your ML pipelines. Understanding these services helps you make informed decisions about deployment, security, and infrastructure. ## Core Services A single **Control Plane** manages one or more **Workspace Servers**. This allows you to have separate workspaces for different teams, projects, or environments (dev/staging/prod) while maintaining centralized authentication and organization management. | Service | Purpose | Deployment Location | |---------|---------|---------------------| | [**Control Plane**](#control-plane) | Authentication, RBAC, organization management (1 per organization) | ZenML infrastructure (SaaS/Hybrid) or yours (Self-hosted) | | [**Workspace Server**](#workspace-server) | Stores metadata, serves APIs, manages entities, runs pipelines from UI (1 or more per Control Plane) | Your infrastructure (Hybrid/Self-hosted) or ZenML (SaaS) | ## Control Plane The **Control Plane** is the organization-level management layer. It sits above individual workspaces and provides centralized authentication, authorization, and administrative functions. **Key responsibilities:** - **Authentication & Identity:** User authentication with SSO integration, identity federation via OIDC and social login providers, API key management for personal access tokens and service accounts - **Authorization & RBAC:** Role management (Admin, Editor, Viewer), permission enforcement across workspaces, team management with shared permissions - **Organization Management:** Workspace lifecycle management (SaaS), user invitations and membership handling - **Workspace Coordination:** Workspace registry, health monitoring for Hybrid/Self-hosted deployments, version management for SaaS upgrades | Deployment | Control Plane Location | |------------|------------------------| | **SaaS** | ZenML infrastructure (fully managed) | | **Hybrid** | ZenML infrastructure (fully managed) | | **Self-hosted** | Your infrastructure (you manage) | ## Workspace Server The **Workspace Server** is the central hub for your ML operations. It provides the API layer that your SDK, dashboard, and orchestrators connect to for all pipeline-related operations. **Key responsibilities:** - **Metadata Storage & API:** Pipeline run tracking with status, timing, and lineage; step execution details; artifact registry (pointers to your artifact store); model registry with versions and stages - **Entity Management:** Stacks and components, pipeline definitions, artifact versions, code repository connections - **Token & Credential Management:** Short-lived service connector tokens for cloud resources, stack component authentication, API validation - **Integration Hub:** REST API for Python SDK, dashboard backend, orchestrator callbacks for status updates - **Pipeline Execution from UI:** The workspace server includes a workload manager that creates ad-hoc runner pods in a Kubernetes cluster to execute pipelines triggered from the dashboard | Deployment | Workspace Server Location | |------------|---------------------------| | **SaaS** | ZenML infrastructure (fully managed) | | **Hybrid** | Your infrastructure (you manage) | | **Self-hosted** | Your infrastructure (you manage) | ## Where Data Lives Understanding data residency is crucial for security and compliance: | Data Type | Description | Location | |-----------|-------------|----------| | **Pipeline Metadata** | Run status, step execution details, artifact pointers | Workspace Server database | | **Artifacts** | Model weights, datasets, evaluation results | Your artifact store (S3, GCS, etc.) | | **Container Images** | Docker images with your code and dependencies | Your container registry | | **Logs** | Execution logs from pipeline runs | Your configured log backend | | **Secrets** | Credentials and sensitive configuration | ZenML secrets store or external vault | | **User/Org Data** | Authentication, RBAC, organization settings | Control Plane database | {% hint style="success" %} In all ZenML deployment scenarios, your actual ML data (models, datasets, artifacts) stays in your infrastructure. Only metadata flows to the ZenML services. {% endhint %} ## Security Considerations The Control Plane handles sensitive authentication data but never accesses your ML data, artifacts, or pipeline code: | Data Type | Sensitivity | Storage | |-----------|-------------|---------| | User credentials | High | Managed through IDP | | API tokens | High | Secure cookie storage | | Organization settings | Medium | Control Plane database | | Audit logs | Medium | Control Plane database | | Workspace metadata | Low | Control Plane database | ## Related Documentation - [Scenarios](scenarios.md) - Choose the right deployment option - [Deployment Details](deploy-details.md) - Detailed configuration reference for each component - [Upgrades and Updates](upgrades-updates.md) - How to upgrade components
ZenML Scarf
--- ### Book/Getting Started/Zenml Pro/Teams (docs/book/getting-started/zenml-pro/teams.md) --- icons: users description: >- Learn about Teams in ZenML Pro and how they can be used to manage groups of users across your organization and workspaces. icon: people-group --- # Teams ZenML Pro introduces the concept of Teams to help you manage groups of users efficiently. A team is a collection of users that acts as a single entity within your organization and workspaces. This guide will help you understand how teams work, how to create and manage them, and how to use them effectively in your MLOps workflows. ## Understanding Teams Teams in ZenML Pro offer several key benefits: 1. **Group Management**: Easily manage permissions for multiple users at once. 2. **Organizational Structure**: Reflect your company's structure or project teams in ZenML. 3. **Simplified Access Control**: Assign roles to entire teams rather than individual users. ## Creating and Managing Teams Teams are created at the organization level and can be assigned roles within workspaces, similar to individual users. To create a team: {% stepper %} {% step %} **Go to the Organization Settings** Click on the **Settings** tab from your **Organization** page.
{% endstep %} {% step %} **Click on the Teams tab** Go to the **Members** section from the sidebar and select the **Teams** tab. {% endstep %} {% step %} **Add a New Team** Use the **Add team** button to add a new team.
When creating a team, you'll need to provide: * Team name * Description (optional) * Initial team members {% endstep %} {% endstepper %} ## Adding Users to Teams To add users to an existing team: {% stepper %} {% step %} Go to the **Teams** tab in **Organization** settings {% endstep %} {% step %} Select the team you want to modify {% endstep %} {% step %} Click on **Add Members** {% endstep %} {% step %} Choose users from your organization to add to the team {% endstep %} {% endstepper %} ## Assigning Teams to Workspaces Teams can be assigned to workspaces just like individual users. To add a team to a workspace: {% stepper %} {% step %} Go to the **Workspace Settings** page {% endstep %} {% step %} Click on **Members** tab and click on the **Teams** tab. {% endstep %} {% step %} Select **Add Team** {% endstep %} {% step %} Choose the team and assign a role {% endstep %} {% endstepper %} ## Team Roles and Permissions When you assign a role to a team within a workspace, all members of that team inherit the permissions associated with that role. This can be a predefined role (Admin, Editor, Viewer) or a custom role you've created. For example, if you assign the "Editor" role to a team in a specific workspace, all members of that team will have Editor permissions in that workspace. ## Best Practices for Using Teams 1. **Reflect Your Organization**: Create teams that mirror your company's structure or project groups. 2. **Combine with Custom Roles**: Use custom roles with teams for fine-grained access control. 3. **Regular Audits**: Periodically review team memberships and their assigned roles. 4. **Document Team Purposes**: Maintain clear documentation about each team's purpose and associated projects or workspaces. By leveraging Teams in ZenML Pro, you can streamline user management, simplify access control, and better organize your MLOps workflows across your organization and workspaces.
ZenML Scarf
--- ### Book/Getting Started/Zenml Pro/Toc (docs/book/getting-started/zenml-pro/toc.md) # Table of contents * [Introduction](README.md) * [System Architecture](system-architecture.md) ## Deployments * [Scenarios](scenarios.md) * [SaaS](saas-deployment.md) * [Hybrid](hybrid-deployment.md) * [Self-hosted](self-hosted-deployment.md) * [Deployment Details](deploy-details.md) * [Prerequisites](deploy-prerequisites.md) * Control Plane * [Kubernetes with Helm](deploy-control-plane-k8s.md) * Workspace Server * [Enroll Workspaces](enroll-workspace.md) * [Kubernetes with Helm](deploy-workspace-k8s.md) * [AWS ECS](deploy-workspace-ecs.md) * [Enable Snapshot Support](deploy-workspace-snapshots.md) * [Enable Event Triggers and Schedules](deploy-workspace-event-triggers-and-schedules.md) * [Enable Resource Pools](deploy-workspace-resource-pools.md) ## Manage * [Single Sign-On (SSO)](sso.md) * [User Accounts](user-accounts.md) * [Upgrades and Updates](upgrades-updates.md) * [Control Plane](upgrades-control-plane.md) * [Workspace Server](upgrades-workspace-server.md) ## Core Concepts * [Hierarchy](hierarchy.md) * [Organizations](organization.md) * [Workspaces](workspaces.md) * [Projects](projects.md) * [Teams](teams.md) * [Snapshots](snapshots.md) * [Triggers](triggers.md) * [Resource Pools](resource-pools.md) * [Core Concepts](resource-pools-core-concepts.md) * [Reconciliation Process](resource-pools-reconciliation.md) * [Examples](resource-pools-examples.md) ## Access Management * [Roles & Permissions](roles.md) * [Trusted domains](trusted-domains.md) * [Personal Access Tokens](personal-access-tokens.md) * [Service Accounts](service-accounts.md) * [Secrets Stores](secrets-stores.md) --- ### Book/Getting Started/Zenml Pro/Triggers (docs/book/getting-started/zenml-pro/triggers.md) --- description: Trigger pipelines by schedule or event. icon: server --- # Triggers {% hint style="info" %} Triggers are part of ZenML's paid features. For details on availability and supported plans, visit the [pricing page](https://www.zenml.io/pricing) {% endhint %} In the [snapshots](./snapshots.md) section, you learned how to prepare snapshots and execute them on demand via the dashboard, CLI, or SDK. In many cases, however, pipelines need to run automatically - either on a schedule or in response to an event. Triggers enable this behavior. A trigger is a configuration that defines one or more conditions under which a pipeline is automatically started. ## Schedule Triggers *Schedule Triggers* allow pipelines to run automatically based on time-based rules, such as fixed intervals or cron expressions. They are ideal for recurring workflows, on a predictable timeline, like daily retraining, batch processing, or periodic data ingestion. When defining a scheduled trigger, you can configure both when and how your pipeline runs. Choose between one-off executions, interval-based schedules, or cron expressions for fine-grained timing control. Additional options, such as time boundaries, concurrency limits, and activation settings, let you tailor the trigger to your workflow requirements. | Attribute | Description | Notes | |---------------------|-------------------------------------------------------------------------|------------------------------| | name | The name of the schedule | Unique within project | | cron_expression | A cron expression describing your schedule's frequency | Standard 5-field cron format | | interval | An interval (in seconds) describing the schedule's frequency | Combined with start_time | | run_once_start_time | One-off execution at a specific time in the future | UTC | | start_time | The beginning of the schedule | UTC | | end_time | The end time of the schedule | UTC | | active | Status of the schedule (active/inactive) | - | | concurrency | Option to control how concurrent runs should be handled | Skip is the default option | | max_runs | Option to control maximum runs (per attached snapshot) for the schedule | - | ### Create a schedule Let's start by creating a schedule. We can do so, via the SDK or the CLI. Via the SDK: ~~~python from zenml.client import Client from zenml.enums import TriggerRunConcurrency client = Client() daily_schedule = client.create_schedule_trigger( name='daily-schedule-6-am', cron_expression='0 6 * * *', active=True, concurrency=TriggerRunConcurrency.SKIP, ) ~~~ Via the CLI: ~~~bash zenml trigger schedule create daily-schedule-6-am --cron-expression "0 6 * * *" ~~~ ### Attach/Detach schedules and snapshots So far we have instructed our system with *when* to execute but not *what*. To do so, we need to *attach* a schedule to a snapshot. For the CLI, commands that take a trigger or snapshot accept its name or ID (exact match, not a prefix). Positional order is always trigger first, then snapshot. Via the SDK: ~~~python from uuid import UUID from zenml.client import Client client = Client() client.attach_trigger_to_snapshot( trigger_id=UUID(""), pipeline_snapshot_id=UUID(""), ) ~~~ Via the CLI: ~~~bash zenml trigger schedule attach "" "" ~~~ Users can provide a configuration object to define the parameters of pipeline runs triggered from this attachment. Via the SDK: ~~~python from uuid import UUID from zenml.client import Client from zenml.config.pipeline_run_configuration import PipelineRunConfiguration client = Client() client.attach_trigger_to_snapshot( trigger_id=UUID(""), pipeline_snapshot_id=UUID(""), run_configuration=PipelineRunConfiguration( enable_step_logs=True, enable_pipeline_logs=True, ) ) ~~~ Via the CLI (using a `PipelineRunConfiguration` YAML file): ~~~bash zenml trigger schedule attach "" "" \ --config=.yml ~~~ To stop a trigger from launching runs for a specific snapshot, you can *detach* the trigger from that snapshot. Via the SDK: ~~~python from uuid import UUID from zenml.client import Client client = Client() client.detach_trigger_from_snapshot( trigger_id=UUID(""), pipeline_snapshot_id=UUID(""), ) ~~~ Via the CLI: ~~~bash zenml trigger schedule detach "" "" ~~~ The ability to detach and attach snapshots is particularly useful as pipelines evolve. When a new pipeline version becomes available, you can update the schedule to use it by detaching the previous snapshot and attaching the new one. {% hint style="warning" %} As with on-demand execution, scheduling requires snapshots with a **remote stack** with at least: - Remote orchestrator - Remote artifact store - Container registry {% endhint %} ### Update schedules You can update a schedule's configuration at any point. In the example, we will de-activate and rename the schedule. Via the SDK: ~~~python from zenml.client import Client client = Client() client.update_schedule_trigger( trigger_name_id_or_prefix="daily-schedule-6-am", active=False, name="daily-schedule-6-am[DO NOT TOUCH]", ) ~~~ Via the CLI: ~~~bash zenml trigger schedule update "daily-schedule-6-am" --active=false \ --name="daily-schedule-6-am-(dont-touch)" ~~~ ### View Schedules Triggers are a first-level citizen of the ZenML platform. You can view detailed information in the dashboard as well as via the SDK and CLI. Via the dashboard: To view schedules, you need to navigate to the `Triggers` tab: You can inspect a schedule's information: Or its attached snapshots and executed pipeline runs: For each trigger–snapshot attachment, ZenML can persist the **dispatch state**. This is the small status record that answers, "what happened the last time this trigger tried to launch this snapshot?" The status can be: - `SUCCESS`: a run was launched successfully. - `SKIPPED_CONCURRENCY`: a run was skipped because the trigger's concurrency rule said not to start another one yet. - `SKIPPED_MAX_RUNS`: a run was skipped because the configured run limit for this trigger-snapshot attachment has already been reached. - `SKIPPED_TRIGGER_CYCLE`: a platform event trigger dispatch was skipped because it would revisit a pipeline already present in the trigger chain. - `ERROR`: ZenML tried to dispatch the run, but something failed. When the last status is `ERROR`, the dispatch state can also include the last error message, error type, severity, stack trace, first/last error timestamps, and an error count. After you fix the underlying problem, you can acknowledge the error and clear the stored dispatch state (see the next section). In the normal failure case, clearing errors removes the `ERROR` status record for that trigger-snapshot attachment, so the next dispatch can start from a clean state. Use `get_schedule_trigger` with `trigger_name_id_or_prefix` to load a schedule by name, full ID, or ID prefix; set `allow_name_prefix_match=False` if you need an exact name match. Via the SDK: ~~~python from zenml.client import Client client = Client() active_schedules = client.list_schedule_triggers( active=True, ) # list schedules schedule = client.get_schedule_trigger( trigger_name_id_or_prefix="my-schedule", ) for snapshot in schedule.snapshots: # iterate a schedule's attached snapshots print(snapshot.id) ~~~ Via the CLI: ~~~bash zenml trigger schedule list --active=true ~~~ ### Stopping Criteria You can control when a schedule stops by limiting it in time or by number of runs. This helps avoid unintended long-running schedules and gives you tighter control over resource usage. #### `end_time` Defines the point in time after which the schedule will no longer trigger runs. The schedule remains visible but inactive for future executions. ~~~python from datetime import datetime, timedelta from zenml.client import Client client = Client() client.create_schedule_trigger( name="limited-schedule", cron_expression="0 0 * * *", end_time=datetime.now() + timedelta(days=2), ) ~~~ #### `max_runs` Limits how many times a schedule can trigger a pipeline. Once the limit is reached for a trigger-snapshot attachment, no further runs are scheduled for that attachment. Later dispatch attempts can show up as `SKIPPED_MAX_RUNS` in the dispatch state. This is not an error that needs to be cleared; it is ZenML saying, "the run limit you configured has been reached." Note that this limit applies per attached snapshot, for example, with a limit of 2 and two attached snapshots, you will see a total of 4 runs. ~~~python from zenml.client import Client client = Client() client.create_schedule_trigger( name="limited-schedule", cron_expression="0 0 * * *", max_runs=24, ) ~~~ #### Combined usage If both are set, the schedule stops when the first condition is reached (time or run limit). #### Clear dispatch errors To clear stored dispatch error details after the issue is resolved, use the SDK or the `clear-errors` command under `zenml trigger schedule`. With no snapshot argument, errors are cleared for **all** snapshots attached to that trigger. Via the SDK: ~~~python from uuid import UUID from zenml.client import Client client = Client() trigger = client.get_schedule_trigger( trigger_name_id_or_prefix="my-schedule", allow_name_prefix_match=False, hydrate=False, ) client.clear_trigger_dispatch_error( trigger_id=trigger.id, pipeline_snapshot_id=None, ) ~~~ To clear the error only for one attached snapshot, pass that snapshot’s UUID (in addition to the `client` and `trigger` values from the example above): ~~~python from uuid import UUID client.clear_trigger_dispatch_error( trigger_id=trigger.id, pipeline_snapshot_id=UUID(""), ) ~~~ Via the CLI: ~~~bash zenml trigger schedule clear-errors "my-schedule" zenml trigger schedule clear-errors "my-schedule" "my-snapshot" ~~~ ### Delete schedules Triggers in ZenML are archivable objects. When a Trigger is archived (soft-deleted), it is deactivated and can no longer be used, but it remains in the system to preserve references for visibility and debugging. Archiving (soft deletion) is the default deletion mode. Triggers can also be permanently deleted. Neither operation can be reversed. Via the dashboard: You can view archived schedules by setting the `Display archived` filter, where you can also hard delete them. Via the SDK: ~~~python from uuid import UUID from zenml.client import Client client = Client() client.delete_trigger( trigger_id=UUID(""), soft=True, ) ~~~ Via the CLI: Default behavior is **soft** deletion (the trigger is archived). Pass `--hard` to remove the trigger and its associated references permanently. To operate on an **archived** trigger, add `--archived` (for example, to hard delete a trigger that is already archived). ~~~bash zenml trigger schedule delete "my-schedule" zenml trigger schedule delete "my-schedule" --hard zenml trigger schedule delete "my-old-schedule" --archived --hard ~~~ ## Triggers vs OSS schedules ZenML provides [scheduling](../../how-to/steps-pipelines/scheduling.md) as an open-source feature. This section outlines the differences between open-source schedules and schedule-based Triggers, and explains why Triggers are better suited for production workloads: * Lifecycle management * Triggers: You can update or delete a schedule at any time, and changes are automatically applied across the system. * OS Schedules: Updates and deletions must be managed manually on the orchestrator side (except when using the `KubernetesOrchestrator`). * Feature support * Triggers: All scheduling features are consistently available across stacks. * OS Schedules: Feature availability depends on the scheduling capabilities of the selected orchestrator. * Flexibility * Triggers: Snapshots and schedules are managed independently. You can dynamically attach or detach snapshots to or from schedules. * OS Schedules: Schedules are bound to individual pipelines and cannot be shared across multiple pipelines. * Visibility * Triggers: Extended dashboard visibility and management. * OS Schedules: Limited dashboard exposure.
ZenML Scarf
## Platform Event Triggers *Platform Event Triggers* extend ZenML’s trigger system by enabling pipelines to run automatically in response to events occurring within the ZenML platform itself. Instead of relying on time-based schedules, these triggers allow users to define downstream pipeline executions that react to lifecycle events, such as the completion of another pipeline. This makes it easy to build event-driven workflows where pipelines are seamlessly chained based on platform activity. When defining a platform event trigger, you configure what resource to listen to and which events should initiate a pipeline run. The source_type and source_id identify the ZenML entity you want to react to (e.g., a specific pipeline), while target_events define the events of interest (e.g., completed or failed). Additional options, such as activation state and concurrency behavior, allow you to control how the trigger operates within your workflow. | Attribute | Description | Notes | |---------------|-----------------------------------------------------------|------------------------------------| | name | The name of the trigger | Unique within project | | source_type | The type of ZenML entity to listen to (e.g., pipeline) | Defines the event source category | | source_id | The UUID of the source entity | e.g., a specific pipeline UUID | | target_events | List of events that will activate the trigger | Depends on the source type | | active | Status of the trigger (active/inactive) | - | | concurrency | Option to control how concurrent runs should be handled | Skip is the default option | You can manage platform event triggers using the same set of commands (create, update, attach, detach, list, delete, clear-errors) as for schedule triggers. While some parameters and responses differ slightly, additional utilities are available to help you work more effectively with this trigger type. Supported target events depend on the source type: | Source type | Meaning | Target events | |-------------|---------|---------------| | `pipeline` | React to runs of a pipeline | `run_completed`, `run_failed` | | `pipeline_run` | React to one specific pipeline run | `completed`, `failed` | | `pipeline_snapshot` | React to runs of a pipeline snapshot | `run_completed`, `run_failed` | That distinction is easy to miss. If the source is a pipeline, the event name includes the word `run` because the pipeline itself is not what completes; one of its runs does. Pipeline snapshot events follow the same naming pattern. If the source is already a pipeline run, the event is simply `completed` or `failed`. ### Create Platform Event Trigger Let's start by creating a Platform Event Trigger. In this example, we want to react to the successful completion of a specific pipeline. Via the SDK: ```python from zenml.client import Client from zenml.enums import PipelineEvent from zenml.utils.trigger_utils import create_platform_event_trigger source_pipeline = Client().get_pipeline(name_id_or_prefix="hello-pipeline") trigger = create_platform_event_trigger( name="on-hello-pipeline-complete", source_pipeline_id=source_pipeline.id, target_events=[PipelineEvent.RUN_COMPLETED], ) ``` Via the CLI: The SDK provides helpful overloads that guide you toward valid configurations by suggesting the supported target events for a given source type as you type. To improve discoverability in the CLI, an additional helper command is available: ```bash zenml trigger platform-event list-supported-events pipeline ``` Then we can create the trigger as follows: ```bash zenml trigger platform-event create "on-hello-pipeline-complete" pipeline --target_events=run_completed ``` ### Update Platform Event Triggers Let's continue by updating our existing trigger. In this example, we want to add multiple target events. The trigger initially reacted to successful completion, and we will now extend it to react to failures as well. Via the SDK: ```python from zenml.enums import PipelineEvent from zenml.utils.trigger_utils import update_platform_event_trigger trigger = update_platform_event_trigger( trigger_name_id_or_prefix="on-hello-pipeline-complete", target_events=[PipelineEvent.RUN_FAILED, PipelineEvent.RUN_COMPLETED], ) ``` Via the CLI: ```bash zenml trigger platform-event update "on-hello-pipeline-complete" \ --target_events=run_completed --target_events=run_failed ``` ### Remaining Platform Event Trigger operations The remaining operations (list, attach, detach, delete, and clear-errors) match schedule triggers. In the CLI, they are grouped under a different command namespace, but the syntax remains the same. You can explore the available platform event trigger commands with: ```bash zenml trigger platform-event ``` To view platform event triggers via the SDK: ```python from uuid import UUID from zenml.client import Client # By name, ID, or ID prefix trigger = Client().get_platform_event_trigger( trigger_name_id_or_prefix="on-hello-pipeline-complete", ) # List with filtering & sorting triggers = Client().list_platform_event_triggers() ``` To attach, detach, and delete triggers via the SDK, the methods are shared across all trigger types (IDs are UUIDs): ```python from uuid import UUID from zenml.client import Client c = Client() c.attach_trigger_to_snapshot( trigger_id=UUID(""), pipeline_snapshot_id=UUID(""), ) c.detach_trigger_from_snapshot( trigger_id=UUID(""), pipeline_snapshot_id=UUID(""), ) c.delete_trigger(trigger_id=UUID(""), soft=True) ``` ### Upstream information When a downstream pipeline is executed, it can be useful to access information about the upstream run that triggered it. The following example shows how to retrieve this information within a running pipeline step: ```python from zenml import step, get_step_context from zenml.utils.trigger_utils import get_upstream_run @step def my_step(): current_run = get_step_context().pipeline_run upstream_run = get_upstream_run(pipeline_run=current_run) print(f"Upstream run ID: {upstream_run.id} name: {upstream_run.name}") ``` ### Chaining pipelines with triggers Platform Event Triggers can be used to build simple multi-pipeline workflows by chaining pipelines together. For example, you can configure a trigger so that when Pipeline A completes, it starts Pipeline B, which in turn can trigger Pipeline C. This enables lightweight orchestration patterns directly within ZenML, allowing you to break down complex workflows into smaller, reusable pipeline components that execute in sequence based on platform events. {% hint style="warning" %} ZenML detects trigger loops at the **pipeline level** while dispatching platform event triggers. Snapshots, runs, trigger IDs, and trigger configuration do not create separate nodes in the cycle: two different snapshots of the same pipeline still refer to the same pipeline in the trigger chain. If a downstream snapshot would revisit a pipeline that is already in the current chain, ZenML skips only that trigger-snapshot dispatch and records `SKIPPED_TRIGGER_CYCLE`. Other non-cyclic snapshots attached to the same trigger continue to dispatch normally. This protection does not disable or modify the trigger configuration, and it does not cancel runs that have already started. {% endhint %} Dispatch states are stored per attached snapshot, and `cycle_pipeline_ids` contains the ordered closed cycle (the first and last IDs are the pipeline that closes the loop): ```python from zenml.client import Client from zenml.models import TriggerDispatchStatusCode client = Client() trigger = client.get_platform_event_trigger( trigger_name_id_or_prefix="on-hello-pipeline-complete", ) for snapshot_id, state in trigger.snapshot_dispatch_states.items(): if state.last_status == TriggerDispatchStatusCode.SKIPPED_TRIGGER_CYCLE: cycle_pipeline_ids = (state.last_status_details or {}).get( "cycle_pipeline_ids", [] ) print( f"Snapshot {snapshot_id} was skipped because of pipeline cycle: " f"{cycle_pipeline_ids}" ) ``` --- ### Book/Getting Started/Zenml Pro/Trusted Domains (docs/book/getting-started/zenml-pro/trusted-domains.md) --- description: >- Organization trusted domains in ZenML Pro — user visibility, invitations, SSO, and how operators configure them. icon: globe layout: title: visible: true description: visible: true tableOfContents: visible: true outline: visible: true pagination: visible: true --- # Trusted domains Trusted domains are an optional ZenML Pro control-plane feature. Each organization can store a list of DNS hostnames (for example `example.com` and `example.org`) that ZenML treats as trusted email domains for that organization. The setting affects: - Who can appear when you search for ZenML users (for example when inviting someone to an organization or workspace) - How organization invitations complete when the invitee signs in with Single Sign-On (SSO), including whether an invitation can finish automatically after OAuth login instead of requiring an explicit accept step Trusted domains work together with email addresses on ZenML user accounts and with SSO/OAuth sign-in. They do not replace [roles and permissions](roles.md); they constrain discovery and change invitation completion rules where the product applies them. ## User visibility and discovery ZenML Pro surfaces a global user listing for flows such as picking someone to invite. Which ZenML accounts you see in that listing depends on your account, your organization memberships, and how trusted domains are configured. When trusted domains are set on organizations you belong to, visibility can expand beyond a minimal baseline: accounts whose email addresses match those domains (including typical subdomain forms of a registered domain, such as addresses under `eng.example.com` when `example.com` is trusted) may become discoverable where policy allows. Across multiple organizations, effective discovery follows the combined trusted-domain policy associated with your memberships (you effectively see the union of what those policies allow), subject to deployment rules and server-side limits. {% hint style="info" %} Trusted domains adjust discovery and invitation behavior; they are not a substitute for granting roles. A user must still receive the right organization or workspace roles to use resources. {% endhint %} When trusted domains are not configured for your organizations, user discovery falls back to the default behavior for your deployment (ZenML-managed cloud versus self-hosted control plane). Exact defaults can differ by deployment type. ## Invitations and SSO Trusted domains change organization invitations, not workspace-only flows by themselves: - If an invitation is created for an email address that matches the target organization's trusted domains at invitation creation time, ZenML may record that invitation as eligible for automatic completion when the invitee later signs in with SSO/OAuth. - If the address does not match those domains, the invitation behaves like a classic invitation: the invitee accepts or declines through the usual UI or API flows. Automatic completion still respects subscription limits and other organization constraints when the invitation would add a member. Trusted domains do not remove invitations as a mechanism; they change how in-domain invitations can complete when SSO is used. ## Deployments without SSO If users authenticate with only local passwords and SSO is not used (typical for some self-hosted setups), trusted domains do not apply the same invitation auto-completion and discovery rules in the same way. Plan on [SSO](sso.md) if you want automatic completion for colleagues whose email domains match your organization. ## Configuring trusted domains ### ZenML-managed ZenML Pro Changing trusted domains requires operator-level access (super-users on the ZenML-managed service). Customers cannot set this alone in the UI. **ZenML-managed deployments:** To enable or change trusted domains for your organization, [contact ZenML support](https://zenml.io/slack) or your account contact. ### Self-hosted control plane **Self-hosted deployments:** Accounts with super-user privileges can configure trusted domains through the ZenML Pro API on organization create and update operations using the `trusted_domains` field: - Sending a list replaces the entire configured set - Omitting the field leaves the current list unchanged - Sending an empty list clears trusted domains Use your deployment's OpenAPI UI or the published [ZenML Pro API](https://cloudapi.zenml.io/) reference for request bodies and validation rules. ## Related documentation - [Organizations](organization.md) — billing, members, and organization settings in the UI - [Roles & Permissions](roles.md) — who can do what after someone is a member - [Single Sign-On (SSO)](sso.md) — configuring OIDC and SSO on self-hosted control planes - [User Accounts](user-accounts.md) — account types on self-hosted deployments --- ### Book/Getting Started/Zenml Pro/Upgrades Control Plane (docs/book/getting-started/zenml-pro/upgrades-control-plane.md) --- description: How to upgrade the ZenML Control Plane. icon: shield-halved --- # Upgrading the Control Plane This page covers upgrade procedures for the ZenML Control Plane across different deployment scenarios. {% hint style="warning" %} Always upgrade the Control Plane first, before upgrading Workspace Servers. This ensures compatibility and prevents potential issues. {% endhint %} ## SaaS Deployments & Hybrid Deployments The ZenML SaaS Control Plane is periodically upgraded by the ZenML team. When an upgrade is planned, any changes to the minimum compatible workspace server version are communicated to all affected users ahead of time. This gives organizations ample time to perform required workspace server upgrades and maintain a compatible environment across their infrastructure. **No action required** - ZenML handles all Control Plane upgrades for SaaS or Hybrid deployments. ## Self-hosted Deployments In self-hosted deployments, you manage the Control Plane yourself. **Tip:** Always review the [release notes](https://docs.zenml.io/changelog/pro-control-plane) before upgrading. For any issues or questions, contact ZenML Support. ### Preparing updated software bundle (only in case of Air-gapped environments) For air-gapped environments: 1. Request offline bundle from ZenML Support containing: - Updated container images - Updated Helm charts - Release notes and migration guide - Vulnerability assessment (if applicable) 2. If using a private registry, copy the new container images to your private registry 3. Transfer bundle to your air-gapped environment using approved methods 4. Extract and load new images, tag and push to your internal registry ### Upgrade Procedure To upgrade the Control Plane in a self-hosted deployment: 1. **Update Helm Values:** Change the Control Plane version in your `values.yaml` file to reference the new image tag. 2. **Apply the Upgrade:** **Option A - In-place upgrade with existing values** (if no config changes needed): ```bash helm upgrade zenml-pro ./zenml-pro-.tgz \ --namespace \ --reuse-values ``` **Option B - Retrieve, modify and reapply values** (if config changes needed): ```bash # Get the current values helm --namespace get values zenml-pro > current-values.yaml # Edit current-values.yaml if needed, then upgrade helm upgrade zenml-pro ./zenml-pro-.tgz \ --namespace \ --values current-values.yaml ``` 3. **Monitor the Upgrade:** Watch the logs and pod statuses to verify a healthy rollout: ```bash kubectl -n get pods kubectl -n logs ``` 4. **Verify the Upgrade:** - Check pod status - Review logs - Test connectivity - Access the dashboard ## Rollback Procedures If the upgrade fails or causes issues: 1. **Helm rollback:** ```bash helm rollback zenml-pro --namespace ``` 2. **Verify rollback:** ```bash kubectl -n get pods ``` 3. **Review logs** to understand what went wrong before attempting the upgrade again. ## Related Documentation - [Upgrades and Updates](upgrades-updates.md) - Overview of upgrade procedures - [Upgrading Workspace Server](upgrades-workspace-server.md) - Workspace Server upgrade procedures - [Control Plane Deployment](deploy-control-plane-k8s.md) - Configuration reference
ZenML Scarf
--- ### Book/Getting Started/Zenml Pro/Upgrades Updates (docs/book/getting-started/zenml-pro/upgrades-updates.md) --- description: How to upgrade ZenML Pro components. icon: arrow-up-right-dots --- # Upgrades and Updates This section covers upgrading ZenML Pro components for all deployment types. Each component has its own upgrade procedures and considerations. {% hint style="warning" %} Always upgrade the Control Plane first, then upgrade Workspace Servers. This ensures compatibility and prevents potential issues. {% endhint %}
Control PlaneUpgrade procedures for the Control Plane across SaaS, Hybrid, and Self-hosted deployments.upgrades-control-plane.md
Workspace ServerUpgrade procedures for Workspace Servers across all deployment scenarios (includes Workload Manager updates).upgrades-workspace-server.md
## Before You Upgrade ### Check Release Notes - For ZenML Pro Control Plane: Check available versions in the [ZenML Pro ArtifactHub repository](https://artifacthub.io/packages/helm/zenml-pro/zenml-pro) - For ZenML Pro Workspace Servers: Check available versions in the [ZenML OSS ArtifactHub repository](https://artifacthub.io/packages/helm/zenml/zenml) and review the [ZenML GitHub releases page](https://github.com/zenml-io/zenml/releases) for release notes and breaking changes ### Backup Checklist Before any upgrade: 1. **Database backup** - Export your database 2. **Values.yaml files** - Save copies of your Helm values 3. **TLS certificates** - Ensure certificates are backed up ### Database Migrations Some updates may require database migrations: 1. **Review migration related changes** in release notes 2. **Monitor logs** for any migration-related errors 3. **Verify data integrity** after upgrade 4. **Test key features** (workspace access, pipeline runs, etc.) ## Post-Upgrade Verification After upgrading any component: 1. **Health Checks** - Verify all pods are running 2. **Test Connectivity** - Confirm SDK can connect 3. **Validate Functionality** - Test pipeline execution 4. **Review Logs** - Check for errors or warnings ## Related Documentation - [Deployment Details](deploy-details.md) - Component configuration reference - [System Architecture](system-architecture.md) - Understand component interactions - [Scenarios](scenarios.md) - Deployment scenarios and guides
ZenML Scarf
--- ### Book/Getting Started/Zenml Pro/Upgrades Workspace Server (docs/book/getting-started/zenml-pro/upgrades-workspace-server.md) --- description: How to upgrade ZenML Workspace Servers. icon: database --- # Upgrading the Workspace Server This page covers upgrade procedures for ZenML Workspace Servers across different deployment scenarios. {% hint style="warning" %} Always upgrade the Control Plane first, then upgrade Workspace Servers. This ensures compatibility and prevents potential issues. {% endhint %} ## SaaS Deployments For SaaS deployments, workspace servers can be upgraded in a self-service manner directly through the ZenML frontend. ## Hybrid or self-hosted Deployments In hybrid or self-hosted deployments, you manage the Control Plane yourself. **Tip:** Always review the [release notes](https://docs.zenml.io/changelog/server-sdk) for workspace server updates before upgrading. For any issues or questions, contact ZenML Support. **Upgrade Process:** 1. Navigate to workspace settings in the ZenML Pro UI 2. Initiate the workspace upgrade 3. The system automatically performs a database backup to ensure rollback is possible 4. Monitor the upgrade progress in the UI This provides a safe and reliable process to keep your workspaces up to date with minimal operational overhead. ## Hybrid Deployments To upgrade workspace servers in a hybrid deployment: 1. **Update Helm Values:** Change the Workspace Server version in your `values.yaml` file to reference the new image tag (the version you want to upgrade to). 2. **Apply the Upgrade:** Re-apply the Helm chart to perform the upgrade: ```bash helm upgrade zenml/zenml \ --namespace \ --values values.yaml ``` 3. **Automatic Backup:** As part of the upgrade process, the system takes a database backup automatically before proceeding. This ensures you can safely roll back if anything goes wrong. 4. **Monitor the Upgrade:** Watch the logs and pod statuses to verify a healthy rollout: ```bash kubectl -n get pods kubectl -n logs ``` 5. **Rollback on Failure:** If the upgrade fails for any reason, the system will automatically roll back to the previous workspace server version using the backup. No manual intervention is required. 6. **Zero Downtime:** Workspace upgrades are orchestrated to be highly available—users should not experience downtime during the upgrade process. {% hint style="info" %} **Workload Manager Updates:** When upgrading, check the [release notes](https://docs.zenml.io/changelog/server-sdk) for any changes to workload manager configuration. If you have configured a workload manager, you may need to update environment variables in your Helm values. See [Workspace Server Configuration](deploy-workspace-snapshots.md) for the full configuration reference. {% endhint %} ## Rollback Procedures If the upgrade fails or causes issues: 1. **Helm rollback:** ```bash helm rollback zenml --namespace zenml-workspace ``` 2. **Restore database** if needed from the backup taken before the upgrade. 3. **Verify rollback:** ```bash kubectl -n zenml-workspace get pods ``` ## Related Documentation - [Upgrades and Updates](upgrades-updates.md) - Overview of upgrade procedures - [Upgrading Control Plane](upgrades-control-plane.md) - Control Plane upgrade procedures - [Workspace Server Deployment](deploy-workspace-k8s.md) - Configuration reference
ZenML Scarf
--- ### Book/Getting Started/Zenml Pro/User Accounts (docs/book/getting-started/zenml-pro/user-accounts.md) --- description: Understand and manage user accounts in ZenML Pro self-hosted deployments. layout: title: visible: true description: visible: true tableOfContents: visible: true outline: visible: true pagination: visible: true --- # User Accounts This guide covers user account types and management for ZenML Pro self-hosted deployments. Understanding the different account types and their capabilities is essential for effectively managing access to your ZenML Pro instance. {% hint style="info" %} This guide applies to [the fully self-hosted scenario](self-hosted-deployment.md) where you manage the ZenML Pro control plane. In the [hybrid](hybrid-deployment.md) and [SaaS](saas-deployment.md) scenarios, user management is handled through the ZenML Pro cloud service. {% endhint %} ## User Account Types ZenML Pro self-hosted deployments support several types of user accounts, each with different characteristics and use cases. ### SSO Users SSO users authenticate through an external OIDC identity provider configured in your ZenML Pro deployment. These accounts: - Are created automatically when a user first logs in via SSO - Are associated with the email address from the identity provider - Cannot set a local password — authentication is always delegated to the IdP - Inherit any MFA or security policies enforced by the identity provider SSO users are the recommended account type for production deployments as they centralize user management in your organization's identity provider. See the [Single Sign-On (SSO)](sso.md) guide for configuration details. Organization trusted domains (see [Trusted domains](trusted-domains.md)) interact with SSO when issuing or accepting invitations: in-domain invitations may complete automatically on SSO login depending on how your operators configured the organization. ### Local Users Local users authenticate directly with the ZenML Pro control plane using a username and password. These accounts: - Are created manually by administrators through the API - Store credentials in the ZenML Pro database - Are only available when password authentication is enabled - Are prompted to reset their password on first login Local users are useful for: - Initial setup before SSO is configured - Emergency access when the identity provider is unavailable - Testing and development environments - Organizations that prefer not to use SSO ### Super-User Accounts Super-users are user accounts (either SSO or local) with elevated privileges that allow them to: - Bypass RBAC restrictions across the entire ZenML Pro instance - Create, modify, and delete organizations - Manage all user accounts, including granting or revoking super-user privileges - Access and manage all workspaces and resources {% hint style="warning" %} Super-user privileges should be granted sparingly and only to trusted administrators. These accounts have unrestricted access to the entire ZenML Pro instance. {% endhint %} Super-user privileges can be granted to any user account: - The first user to log in via SSO is automatically granted super-user privileges - The first user account created via the API is automatically granted super-user privileges - Local users can be created with super-user privileges or have them granted later - Super-user status can be revoked at any time through the API ### The Admin Reserved Account The admin account is a special reserved account that exists specifically for bootstrap and emergency operations: - **Username**: `admin` (can be customized via configuration) - **Availability**: Only when password authentication is enabled - **Password**: Auto-generated during deployment (can be customized via configuration) The admin account has capabilities similar to a super-user but with some restrictions: - Can create and manage user accounts - Can grant super-user privileges to other users - Cannot be deleted or have its username changed - Is disabled when password authentication is disabled {% hint style="warning" %} The admin account should only be used for: - Initial onboarding: creating the first super-user account after deployment - Emergency access: restoring super-user privileges if all super-users are locked out Use a regular user account with appropriate roles for day-to-day operations. {% endhint %} To retrieve the admin password after deployment (Kubernetes with Helm): ```bash kubectl get secret --namespace zenml-pro zenml-pro \ -o jsonpath="{.data.ZENML_CLOUD_ADMIN_PASSWORD}" | base64 --decode; echo ``` ## Managing Local Users Local user management is currently only supported through the ZenML Pro API. There is no UI support for creating or managing local user accounts. {% hint style="info" %} Only users with super-user privileges or the admin reserved account can manage other user accounts. {% endhint %} ### Accessing the API There are several ways to access the ZenML Pro API for user management: **OpenAPI Interface**: Access the interactive API documentation at `https:///api/v1`. Authenticate using the username and password (for admin or local users) **Programmatic Access**: Use tools like `curl`, `wget`, or any HTTP client. Authenticate using: - Bearer token obtained via username/password login - Direct PAT or API key in the Authorization header See the [Personal Access Tokens](personal-access-tokens.md) and [Service Accounts](service-accounts.md) documentation for details on programmatic authentication. ### Creating Local Users {% tabs %} {% tab title="OpenAPI Interface" %} 1. Navigate to `https:///api/v1` 2. Authenticate with the admin account or a super-user account 3. Find the `POST /api/v1/users` endpoint 4. Provide the following parameters: - `username`: The username for the new account - `password`: Initial password (user will be prompted to change it) - `is_superuser`: Set to `true` to grant super-user privileges (optional) {% endtab %} {% tab title="curl" %} First, obtain a bearer token: ```bash # Authenticate with admin credentials curl -X POST https:///api/v1/auth/login \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "username=admin&password=" ``` Create the user: ```bash # Create a new local user curl -X POST "https:///api/v1/users?username=newuser&password=initial-password" \ -H "Authorization: Bearer " # Create a new super-user curl -X POST "https:///api/v1/users?username=newsuperuser&password=initial-password&is_superuser=true" \ -H "Authorization: Bearer " ``` {% endtab %} {% endtabs %} {% hint style="info" %} New local users are required to reset their password on first login. {% endhint %} ### Updating Local Users {% tabs %} {% tab title="curl" %} ```bash # Update user details curl -X PATCH "https:///api/v1/users/" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "name": "updated-username" }' # Grant super-user privileges curl -X PATCH "https:///api/v1/users/" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "is_superuser": true }' # Revoke super-user privileges curl -X PATCH "https:///api/v1/users/" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "is_superuser": false }' ``` {% endtab %} {% endtabs %} ### Resetting Passwords Administrators can reset a local user's password: {% tabs %} {% tab title="curl" %} ```bash # Reset a user's password curl -X PATCH "https:///api/v1/users/" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "password": "new-temporary-password" }' ``` {% endtab %} {% endtabs %} {% hint style="info" %} After an administrator resets a password, the user will be prompted to change it on their next login. {% endhint %} ### Deleting Local Users {% tabs %} {% tab title="curl" %} ```bash # Delete a user curl -X DELETE "https:///api/v1/users/" \ -H "Authorization: Bearer " ``` {% endtab %} {% endtabs %} {% hint style="warning" %} Deleting a user removes their account but does not delete resources they created. Those resources remain accessible to other organization members based on their roles. {% endhint %} ## Related Resources - [Single Sign-On (SSO)](sso.md) - [Trusted domains](trusted-domains.md) - [Roles & Permissions](roles.md) - [Teams](teams.md) - [Organizations](organization.md) - [Personal Access Tokens](personal-access-tokens.md) - [Service Accounts](service-accounts.md)
ZenML Scarf
--- ### Book/Getting Started/Zenml Pro/Workspaces (docs/book/getting-started/zenml-pro/workspaces.md) --- description: Learn how to use workspaces in ZenML Pro. icon: briefcase --- # Workspaces {% hint style="info" %} **Note**: Workspaces were previously called "Tenants" in earlier versions of ZenML Pro. We've updated the terminology to better reflect their role in organizing MLOps resources. {% endhint %} Workspaces are individual, isolated deployments of the ZenML server. Each workspace has its own set of users, roles, projects, and resources. Essentially, everything you do in ZenML Pro revolves around a workspace: all of your projects, pipelines, stacks, runs, connectors and so on are scoped to a workspace. This includes both traditional ML workflows and AI agent development projects. The ZenML server that you get through a workspace is a supercharged version of the open-source ZenML server. This means that you get all the features of the open-source version, plus some extra Pro features. ## Connecting to Your Workspace ### Using the CLI To use a workspace, you first need to log in using the ZenML CLI. The basic command is: ```bash zenml login ``` If you're using a self-hosted version of ZenML Pro, you'll need to specify the API URL: ```bash zenml login --pro-api-url ``` {% hint style="info" %} The `--pro-api-url` parameter is only required for self-hosted deployments. If you're using the SaaS version of ZenML Pro, you can omit this parameter. {% endhint %} After logging in, you can initialize your ZenML repository and start working with your workspace resources: ```bash # Initialize a new ZenML repository zenml init # Set up your active project (recommended) zenml project set default # Set up your active stack zenml stack set default ``` ### Using the Dashboard You can also access your workspace through the web dashboard, which provides a graphical interface for managing all your MLOps resources. ## Create a Workspace in your organization A workspace is a crucial part of your Organization and serves as a container for your projects, which in turn hold your pipelines, experiments and models, among other things. You need to have a workspace to fully utilize the benefits that ZenML Pro brings. The following is how you can create a workspace yourself: {% stepper %} {% step %} **Go to your organization page** {% endstep %} {% step %} **Click on the "New Workspace" button**

Image showing the "New Workspace" button

{% endstep %} {% step %} **Add a name and id** Give your workspace a name, an id, and click on the "**Create Workspace**" button. {% hint style="warning" %} **Important**: The workspace ID must be globally unique across all ZenML instances and cannot be changed after creation. Choose carefully as this permanent identifier will be used in all future API calls and references. {% endhint %}
{% endstep %} {% step %} **Your workspace is ready!** The workspace will then be created and added to your organization. In the meantime, you can already get started with setting up your environment for the onboarding experience. The image below shows you how the overview page looks like when you are being onboarded. Follow the instructions on the screen to get started. {% hint style="info" %} You can also create a workspace through the Cloud API by navigating to https://cloudapi.zenml.io/ and using the `POST /organizations` endpoint to create a workspace. {% endhint %} {% endstep %} {% endstepper %} ## Organizing your workspaces Organizing your workspaces effectively is crucial for managing your MLOps infrastructure efficiently. There are primarily two dimensions to consider when structuring your workspaces: ### Organizing workspaces in `staging` and `production` One common approach is to separate your workspaces based on the development stage of your ML projects. This typically involves creating at least two types of workspaces: 1. **Staging Workspaces**: These are used for development, testing, and experimentation. They provide a safe environment where data scientists and ML engineers can: * Develop and test new pipelines * Experiment with different models and hyperparameters * Validate changes before moving to production 2. **Production Workspaces**: These host your live, customer-facing ML services. They are characterized by: * Stricter access controls * More rigorous monitoring and alerting * Optimized for performance and reliability This separation allows for a clear distinction between experimental work and production-ready systems, reducing the risk of untested changes affecting live services. ### Organizing workspaces by business logic Another approach is to create workspaces based on your organization's structure or specific use cases. This method can help in: 1. **Department-based Separation**: Create workspaces for different departments or business units: * Data Science Department Workspace * Research Department Workspace * Production Department Workspace * AI Agent Development Workspace 2. **Team-based Separation**: Align workspaces with your organizational structure: * ML Engineering Team Workspace * Research Team Workspace * Operations Team Workspace * Agent Development Team Workspace 3. **Data Classification**: Separate workspaces based on data sensitivity: * Public Data Workspace * Internal Data Workspace * Highly Confidential Data Workspace This organization method offers several benefits: * Improved resource allocation and cost tracking * Better alignment with team structures and workflows * Enhanced data security and compliance management Of course, both approaches of organizing your workspaces can be mixed and matched to create a structure that works best for you. ### Best Practices for Workspace Organization Regardless of the approach you choose, consider these best practices: 1. **Clear Naming Conventions**: Use consistent, descriptive names for your workspaces to easily identify their purpose. 2. **Access Control**: Implement [role-based access control](roles.md) within each workspace to manage permissions effectively. 3. **Project Organization**: Structure [projects](projects.md) within workspaces to provide additional resource isolation and access control. 4. **Documentation**: Maintain clear documentation about the purpose and contents of each workspace and its projects. 5. **Regular Reviews**: Periodically review your workspace structure to ensure it still aligns with your organization's needs. 6. **Scalability**: Design your workspace structure to accommodate future growth and new projects. By thoughtfully organizing your workspaces and their projects, you can create a more manageable, secure, and efficient MLOps environment that scales with your organization's needs. ## Using your workspace As previously mentioned, a workspace is a supercharged ZenML server that you can use to manage projects, run pipelines, carry out experiments and perform all the other actions you expect out of your ZenML server. Some Pro-only features that you can leverage in your workspace are as follows: * [Projects for Resource Organization](projects.md) * [Model Control Plane](https://docs.zenml.io/how-to/model-management-metrics/model-control-plane/register-a-model) * [Artifact Control Plane](https://docs.zenml.io/how-to/data-artifact-management/handle-data-artifacts) * [Create snapshots out of your pipeline runs](https://docs.zenml.io/concepts/snapshots#using-the-dashboard) * [Run snapshots from the Dashboard](https://docs.zenml.io/concepts/snapshots#running-the-dashboard) and [more](https://zenml.io/pro)! ### Accessing workspace docs Every workspace (formerly known as tenant) has a name which you can use to connect your `zenml` client to your deployed Pro server via the `zenml login` CLI command. {% hint style="info" %} In the API documentation and some error messages, you might still see references to "tenant" instead of "workspace". These terms refer to the same concept and will be updated in future releases. {% endhint %} Read more about to access the API [here](https://docs.zenml.io/api-reference).
ZenML Scarf
--- ### Book/Getting Started/Zenml Pro/.Gitbook.Yaml (docs/book/getting-started/zenml-pro/.gitbook.yaml) root: . structure: readme: README.md summary: toc.md redirects: deployments/self-hosted: self-hosted-deployment.md deployments/scenarios/hybrid-deployment/hybrid-deployment-ecs: deploy-workspace-ecs.md deployments/scenarios/hybrid-deployment/hybrid-deployment-helm: deploy-workspace-k8s.md deployments/scenarios/self-hosted-deployment/self-hosted-deployment-helm: deploy-control-plane-k8s.md manage/configuration-details: deploy-details.md manage/configuration-details/config-control-plane: deploy-control-plane-k8s.md manage/configuration-details/config-workspace-server: deploy-workspace-k8s.md --- ### Book/How To/Artifacts/Artifacts (docs/book/how-to/artifacts/artifacts.md) --- description: >- Learn how ZenML manages data artifacts, tracks versioning and lineage, and enables effective data flow between steps. icon: binary --- # Artifacts Artifacts are a cornerstone of ZenML's ML pipeline management system. This guide explains what artifacts are, how they work, and how to use them effectively in your pipelines. ### Artifacts in the Pipeline Workflow Here's how artifacts fit into the ZenML pipeline workflow: 1. A step produces data as output 2. ZenML automatically stores this output as an artifact 3. Other steps can use this artifact as input 4. ZenML tracks the relationships between artifacts and steps This system creates a complete data lineage for every artifact in your ML workflows, enabling reproducibility and traceability. ## Basic Artifact Usage ### Creating Artifacts (Step Outputs) Any value returned from a step becomes an artifact: ```python from zenml import pipeline, step import pandas as pd @step def create_data() -> pd.DataFrame: """Creates a dataframe that becomes an artifact.""" return pd.DataFrame({ "feature_1": [1, 2, 3], "feature_2": [4, 5, 6], "target": [10, 20, 30] }) @step def create_prompt_template() -> str: """Creates a prompt template that becomes an artifact.""" return """ You are a helpful customer service agent. Customer Query: {query} Previous Context: {context} Please provide a helpful response following our company guidelines. """ ``` ### Consuming Artifacts (Step Inputs) You can use artifacts by receiving them as inputs to other steps: ```python @step def process_data(df: pd.DataFrame) -> pd.DataFrame: """Takes an artifact as input and returns a new artifact.""" df["feature_3"] = df["feature_1"] * df["feature_2"] return df @step def test_agent_response(prompt_template: str, test_query: str) -> dict: """Uses a prompt template artifact to test agent responses.""" filled_prompt = prompt_template.format( query=test_query, context="Previous customer complained about delayed shipping" ) # Your agent logic here response = call_llm_agent(filled_prompt) return {"query": test_query, "response": response, "prompt_used": filled_prompt} @pipeline def simple_pipeline(): """Pipeline that creates and processes artifacts.""" # Traditional ML artifacts data = create_data() # Produces an artifact processed_data = process_data(data) # Uses and produces artifacts # AI agent artifacts prompt = create_prompt_template() # Produces a prompt artifact agent_test = test_agent_response(prompt, "Where is my order?") # Uses prompt artifact ``` ### Artifacts vs. Parameters When calling a step, inputs can be either artifacts or parameters: * **Artifacts** are outputs from other steps in the pipeline. They are tracked, versioned, and stored in the artifact store. * **Parameters** are literal values provided directly to the step. They aren't stored as artifacts but are recorded with the pipeline run. ```python import pandas as pd from zenml import step, pipeline @step def train_model(data: pd.DataFrame, learning_rate: float) -> object: """Step with both artifact and parameter inputs.""" # data is an artifact (output from another step) # learning_rate is a parameter (literal value) # Note: create_model would be your own model creation function model = create_model(learning_rate) model.fit(data) return model @pipeline def training_pipeline(): # data is an artifact data = create_data() # data is passed as an artifact, learning_rate as a parameter model = train_model(data=data, learning_rate=0.01) ``` Parameters are limited to JSON-serializable values (numbers, strings, lists, dictionaries, etc.). More complex objects should be passed as artifacts. ### Accessing Artifacts After Pipeline Runs You can access artifacts from completed runs using the ZenML Client: ```python from zenml.client import Client # Get a specific run client = Client() pipeline_run = client.get_pipeline_run("") # Get an artifact from a specific step train_data = pipeline_run.steps["split_data"].outputs["train_data"].load() # Use the artifact print(train_data.shape) ``` ## Working with Artifact Types ### Type Annotations Type annotations are important when working with artifacts as they: 1. Help ZenML select the appropriate materializer for storage 2. Validate inputs and outputs at runtime 3. Document the data flow of your pipeline ```python from typing import Tuple import numpy as np import pandas as pd from zenml import step @step def preprocess_data(df: pd.DataFrame) -> np.ndarray: """Type annotation tells ZenML this returns a numpy array.""" return df.values @step def split_data(data: np.ndarray) -> Tuple[np.ndarray, np.ndarray]: """Type annotation tells ZenML this returns a tuple of numpy arrays.""" split_point = len(data) // 2 return data[:split_point], data[split_point:] ``` ZenML supports many common data types out of the box: * Primitive types (`int`, `float`, `str`, `bool`) * Container types (`dict`, `list`, `tuple`) * NumPy arrays * Pandas DataFrames * Many ML model formats (through integrations) ### Returning Multiple Outputs Steps can return multiple artifacts using tuples: ```python from typing import Tuple, Annotated import numpy as np @step def split_data( data: np.ndarray, target: np.ndarray ) -> Tuple[ Annotated[np.ndarray, "X_train"], Annotated[np.ndarray, "X_test"], Annotated[np.ndarray, "y_train"], Annotated[np.ndarray, "y_test"] ]: """Split data into training and testing sets.""" # Implement split logic X_train, X_test = data[:80], data[80:] y_train, y_test = target[:80], target[80:] return X_train, X_test, y_train, y_test ``` ZenML differentiates between: * A step with multiple outputs: `return a, b` or `return (a, b)` * A step with a single tuple output: `return some_tuple` ### Naming Your Artifacts By default, artifacts are named based on their position or variable name: * Single outputs are named `output` * Multiple outputs are named `output_0`, `output_1`, etc. You can give your artifacts more meaningful names using the `Annotated` type: ```python from typing import Tuple from typing import Annotated import pandas as pd from zenml import step @step def split_dataset( df: pd.DataFrame ) -> Tuple[ Annotated[pd.DataFrame, "train_data"], Annotated[pd.DataFrame, "test_data"] ]: """Split a dataframe into training and testing sets.""" train = df.sample(frac=0.8, random_state=42) test = df.drop(train.index) return train, test ``` You can even use dynamic naming with placeholders: ```python from typing import Annotated import pandas as pd from zenml import step, pipeline @step def extract_data(source: str) -> Annotated[pd.DataFrame, "{dataset_type}_data"]: """Extract data with a dynamically named output.""" # Implementation... data = pd.DataFrame() # Your data extraction logic here return data @pipeline def data_pipeline(): # These will create artifacts named "train_data" and "test_data" train_df = extract_data.with_options( substitutions={"dataset_type": "train"} )(source="train_source") test_df = extract_data.with_options( substitutions={"dataset_type": "test"} )(source="test_source") ``` ZenML supports these placeholders: * `{date}`: Current date (e.g., "2023_06_15") * `{time}`: Current time (e.g., "14_30_45_123456") * Custom placeholders can be defined using `substitutions` ## How Artifacts Work Under the Hood ### Materializers: How Data Gets Stored Materializers are a key concept in ZenML's artifact system. They handle: * **Serializing data** when saving artifacts to storage * **Deserializing data** when loading artifacts from storage * **Generating visualizations** for the dashboard * **Extracting metadata** for tracking and searching When a step produces an output, ZenML automatically selects the appropriate materializer based on the data type (using type annotations). ZenML includes built-in materializers for common data types like: * Primitive types (`int`, `float`, `str`, `bool`) * Container types (`dict`, `list`, `tuple`) * NumPy arrays, Pandas DataFrames and many other ML-related formats (through integrations) Here's how materializers work in practice: ```python from zenml import step from sklearn.linear_model import LinearRegression @step def train_model(X_train, y_train) -> LinearRegression: """Train a model and return it as an artifact.""" model = LinearRegression() model.fit(X_train, y_train) return model # ZenML uses a specific materializer for scikit-learn models ``` For custom data types, you can create your own materializers. See the [Materializers](materializers.md) guide for details. ### Lineage and Caching ZenML automatically tracks the complete lineage of each artifact: * Which step produced it * Which pipeline run it belongs to * Which other artifacts it depends on * Which steps have consumed it This lineage tracking enables powerful caching capabilities. When you run a pipeline, ZenML checks if any steps have been run before with the same inputs, code, and configuration. If so, it reuses the cached outputs instead of rerunning the step: ```python @pipeline def cached_pipeline(): # If create_data has been run before with the same code and inputs, # the cached artifact will be used data = create_data() # If process_data has been run before with the same code and inputs # (including the exact same data artifact), the cached output will be used processed_data = process_data(data) ``` ## Advanced Artifact Usage ### Accessing Artifacts from Previous Runs You can access artifacts from any previous run by name or ID: ```python from zenml.client import Client # Get a specific artifact version artifact = Client().get_artifact_version("my_model", "1.0") # Get the latest version of an artifact latest_artifact = Client().get_artifact_version("my_model") # Load it into memory model = latest_artifact.load() ``` You can also access artifacts within steps: ```python from zenml.client import Client from zenml import step @step def evaluate_against_previous(model, X_test, y_test) -> float: """Compare current model with the previous best model.""" client = Client() # Get the previous best model best_model = client.get_artifact_version("best_model") # Use it for comparison previous_accuracy = best_model.data.score(X_test, y_test) current_accuracy = model.score(X_test, y_test) return current_accuracy - previous_accuracy ``` ### Cross-Pipeline Artifact Usage You can use artifacts produced by one pipeline in another pipeline: ```python from zenml.client import Client from zenml import step, pipeline @step def use_trained_model(data: pd.DataFrame, model) -> pd.Series: """Use a model loaded from a previous pipeline run.""" return pd.Series(model.predict(data)) @pipeline def inference_pipeline(): # Load data data = load_data() # Get the latest model from another pipeline model = Client().get_artifact_version("trained_model") # Use it for predictions predictions = use_trained_model(data=data, model=model) ``` This allows you to build modular pipelines that can work together as part of a larger ML system. ### Visualizing Artifacts ZenML automatically generates visualizations for many types of artifacts, viewable in the dashboard: ```python # You can also view visualizations in notebooks from zenml.client import Client artifact = Client().get_artifact_version("") artifact.visualize() ``` For detailed information on visualizations, see [Visualizations](visualizations.md). ### Managing Artifacts Individual artifacts cannot be deleted directly (to prevent broken references). However, you can clean up unused artifacts: ```bash zenml artifact prune ``` This deletes artifacts that are no longer referenced by any pipeline run. You can control this behavior with flags: * `--only-artifact`: Only delete the physical files, keep database entries * `--only-metadata`: Only delete database entries, keep files * `--ignore-errors`: Continue pruning even if some artifacts can't be deleted ### Registering Existing Data as Artifacts Sometimes, you may have data created externally (outside of ZenML pipelines) that you want to use within your ZenML workflows. Instead of reading and materializing this data within a step, you can register existing files or folders as ZenML artifacts directly. #### Register an Existing Folder To register a folder as a ZenML artifact: ```python from zenml.client import Client from zenml import register_artifact import os from pathlib import Path # Path to an existing folder in your artifact store prefix = Client().active_stack.artifact_store.path existing_folder = os.path.join(prefix, "my_folder") # Register it as a ZenML artifact register_artifact( folder_or_file_uri=existing_folder, name="my_folder_artifact" ) # Later, load the artifact folder_path = Client().get_artifact_version("my_folder_artifact").load() assert isinstance(folder_path, Path) assert os.path.isdir(folder_path) ``` #### Register an Existing File Similarly, you can register individual files: ```python from zenml.client import Client from zenml import register_artifact import os from pathlib import Path # Path to an existing file in your artifact store prefix = Client().active_stack.artifact_store.path existing_file = os.path.join(prefix, "my_folder/model.pkl") # Register it as a ZenML artifact register_artifact( folder_or_file_uri=existing_file, name="my_model_artifact" ) # Later, load the artifact file_path = Client().get_artifact_version("my_model_artifact").load() assert isinstance(file_path, Path) assert not os.path.isdir(file_path) ``` This approach is particularly useful for: * Integrating with external ML frameworks that save their own data * Working with pre-existing datasets * Registering model checkpoints created during training When you load these artifacts, you'll receive a `pathlib.Path` pointing to a temporary location in your executing environment, ready for use as a normal local path. #### Register Framework Checkpoints A common use case is registering model checkpoints from training frameworks like PyTorch Lightning: ```python import os from uuid import uuid4 from zenml.client import Client from zenml import register_artifact from pytorch_lightning import Trainer from pytorch_lightning.callbacks import ModelCheckpoint # Define checkpoint location in your artifact store prefix = Client().active_stack.artifact_store.path checkpoint_dir = os.path.join(prefix, uuid4().hex) # Configure PyTorch Lightning trainer with checkpointing model = YourLightningModel() trainer = Trainer( default_root_dir=checkpoint_dir, callbacks=[ ModelCheckpoint( every_n_epochs=1, save_top_k=-1, # Keep all checkpoints filename="checkpoint-{epoch:02d}" ) ], ) # Train the model trainer.fit(model) # Register all checkpoints as a ZenML artifact register_artifact( folder_or_file_uri=checkpoint_dir, name="lightning_checkpoints" ) # Later, you can load the checkpoint folder checkpoint_path = Client().get_artifact_version("lightning_checkpoints").load() ``` You can also extend the `ModelCheckpoint` callback to register each checkpoint as a separate artifact version during training. This approach enables better version control of intermediate checkpoints. ## Conclusion Artifacts are a central part of ZenML's approach to ML pipelines. They provide: * Automatic versioning and lineage tracking * Efficient storage and caching * Type-safe data handling * Visualization capabilities * Cross-pipeline data sharing Whether you're working with traditional ML models, prompt templates, agent configurations, or evaluation datasets, ZenML's artifact system treats them all uniformly. This enables you to apply the same MLOps principles across your entire AI stack - from classical ML to complex multi-agent systems. By understanding how artifacts work, you can build more effective, maintainable, and reproducible ML pipelines and AI workflows. For more information on specific aspects of artifacts, see: * [Materializers](materializers.md): Creating custom serializers for your data types * [Visualizations](visualizations.md): Customizing artifact visualizations
ZenML Scarf
--- ### Book/How To/Artifacts/Materializers (docs/book/how-to/artifacts/materializers.md) --- description: Understanding and creating materializers to handle custom data types in ZenML pipelines --- # Materializers Materializers are a core concept in ZenML that enable the serialization, storage, and retrieval of artifacts in your ML pipelines. This guide explains how materializers work and how to create custom materializers for your specific data types. ## What Are Materializers? A materializer is a class that defines how a particular data type is: - **Serialized**: Converted from Python objects to a storable format - **Saved**: Written to the artifact store - **Loaded**: Read from the artifact store - **Deserialized**: Converted back to Python objects - **Visualized**: Displayed in the ZenML dashboard - **Analyzed**: Metadata extraction for tracking and search Materializers act as the bridge between your Python code and the underlying storage system, ensuring that any artifact can be saved, loaded, and visualized correctly, regardless of the data type. ## Built-In Materializers ZenML includes built-in materializers for many common data types: ### Core Materializers
MaterializerHandled Data TypesStorage Format
BuiltInMaterializerbool, float, int, str, None.json
BytesInMaterializerbytes.txt
BuiltInContainerMaterializerdict, list, set, tupleDirectory
NumpyMaterializernp.ndarray.npy
PandasMaterializerpd.DataFrame, pd.Series.csv (or .gzip if parquet is installed)
PydanticMaterializerpydantic.BaseModel.json
DataclassMaterializerJSON-serializable Python dataclass types.json
ServiceMaterializerzenml.services.service.BaseService.json
StructuredStringMaterializerzenml.types.CSVString, zenml.types.HTMLString, zenml.types.MarkdownString.csv / .html / .md (depending on type)
PathMaterializerpathlib.Path.tar.gz (directories) or direct copy (files)
ZenML also provides a CloudpickleMaterializer that can handle any object by saving it with [cloudpickle](https://github.com/cloudpipe/cloudpickle). However, this is not production-ready because the resulting artifacts cannot be loaded when running with a different Python version. For production use, you should implement a custom materializer for your specific data types. {% hint style="info" %} Pydantic artifacts created by current ZenML versions are stored in `data_v2.json`. ZenML can still load older Pydantic artifacts stored as `data.json` by ZenML `<= 0.94.2`, so existing runs remain readable after an upgrade. {% endhint %} ### Dataclass artifacts The `DataclassMaterializer` handles JSON-serializable Python dataclasses without requiring you to write a custom materializer. ```python from dataclasses import dataclass from zenml import step @dataclass class TrainingConfig: learning_rate: float epochs: int @step def make_config() -> TrainingConfig: return TrainingConfig(learning_rate=0.01, epochs=10) ``` This works for dataclasses that Pydantic can serialize to JSON. If your dataclass contains objects such as open file handles, live model objects, database connections, or other arbitrary Python objects, use a custom materializer instead. ### Passing Files and Directories Between Steps The `PathMaterializer` lets you pass `pathlib.Path` objects between steps. This is especially useful when working with files or directories that need to be shared across steps — for example, dataset directories, exported model files, or any file-based artifacts. When a step returns a `Path`: - **Directories** are compressed into a `.tar.gz` archive and uploaded to the artifact store - **Single files** are copied directly to the artifact store When a downstream step receives the `Path`, the materializer downloads the contents to a local temporary directory and returns a `Path` pointing to it. ```python from pathlib import Path from typing import Annotated from zenml import step, pipeline @step def prepare_dataset(num_samples: int = 100) -> Annotated[Path, "dataset_dir"]: """Prepare a dataset directory with training files.""" output_dir = Path("training_data") output_dir.mkdir(exist_ok=True) # Write training files into the directory (output_dir / "features.csv").write_text("feature1,feature2\n1.0,2.0\n") (output_dir / "labels.csv").write_text("label\n1\n") # ZenML will tar.gz this directory and upload it to the artifact store return output_dir @step def train_model(dataset_dir: Path) -> None: """Train a model using the dataset directory.""" # dataset_dir points to a local temp directory with the extracted contents features = (dataset_dir / "features.csv").read_text() labels = (dataset_dir / "labels.csv").read_text() print(f"Training with features: {features}") @pipeline def training_pipeline(): dataset = prepare_dataset() train_model(dataset) ``` This works transparently with remote orchestrators (Kubernetes, Vertex AI, etc.) where each step runs on a different pod — the artifact store acts as the shared transport layer. {% hint style="info" %} If you prefer the previous behavior where `Path` objects were serialized with `cloudpickle` (which only preserves the path string, not the file contents), you can disable the `PathMaterializer` by setting the environment variable `ZENML_DISABLE_PATH_MATERIALIZER=true`. {% endhint %} ### Integration-Specific Materializers When you install ZenML integrations, additional materializers become available:
IntegrationMaterializerHandled Data TypesStorage Format
bentomlBentoMaterializerbentoml.Bento.bento
deepchecksDeepchecksResultMateriailzerdeepchecks.CheckResult, deepchecks.SuiteResult.json
evidentlyEvidentlyProfileMaterializerevidently.Profile.json
great_expectationsGreatExpectationsMaterializergreat_expectations.ExpectationSuite, great_expectations.CheckpointResult.json
huggingfaceHFDatasetMaterializerdatasets.Dataset, datasets.DatasetDictDirectory
huggingfaceHFPTModelMaterializertransformers.PreTrainedModelDirectory
huggingfaceHFTFModelMaterializertransformers.TFPreTrainedModelDirectory
huggingfaceHFTokenizerMaterializertransformers.PreTrainedTokenizerBaseDirectory
lightgbmLightGBMBoosterMaterializerlgbm.Booster.txt
lightgbmLightGBMDatasetMaterializerlgbm.Dataset.binary
neural_prophetNeuralProphetMaterializerNeuralProphet.pt
pillowPillowImageMaterializerPillow.Image.PNG
polarsPolarsMaterializerpl.DataFrame, pl.Series.parquet
pycaretPyCaretMaterializerAny sklearn, xgboost, lightgbm or catboost model.pkl
pytorchPyTorchDataLoaderMaterializertorch.Dataset, torch.DataLoader.pt
pytorchPyTorchModuleMaterializertorch.Module.pt
scipySparseMaterializerscipy.spmatrix.npz
sparkSparkDataFrameMaterializerpyspark.DataFrame.parquet
sparkSparkModelMaterializerpyspark.Transformerpyspark.Estimator
tensorflowKerasMaterializertf.keras.ModelDirectory
tensorflowTensorflowDatasetMaterializertf.DatasetDirectory
whylogsWhylogsMaterializerwhylogs.DatasetProfileView.pb
xgboostXgboostBoosterMaterializerxgb.Booster.json
xgboostXgboostDMatrixMaterializerxgb.DMatrix.binary
jaxJAXArrayMaterializerjax.Array.npy
mlxMLXArrayMaterializermlx.core.array.npy
> **Note**: When using Docker-based orchestrators, you must specify the appropriate integrations in your `DockerSettings` to ensure the materializers are available inside the container. ## Creating Custom Materializers When working with custom data types, you'll need to create materializers to handle them. Here's how: ### 1. Define Your Materializer Class Create a new class that inherits from `BaseMaterializer`: ``` /* Detailed source-code truncated for AI context efficiency. */ ``` ### 2. Using Your Custom Materializer Once you've defined the materializer, you can use it in your pipeline: ```python from zenml import step, pipeline # from mymodule import MyClass, MyClassMaterializer @step(output_materializers=MyClassMaterializer) def create_my_class() -> MyClass: """Create an instance of MyClass.""" return MyClass(name="test", records=[1, 2, 3]) @step def use_my_class(my_obj: MyClass) -> None: """Use the MyClass instance.""" print(f"Name: {my_obj.name}, Records: {my_obj.records}") @pipeline def custom_pipeline(): data = create_my_class() use_my_class(data) ``` ### 3. Multiple Outputs with Different Materializers When a step has multiple outputs that need different materializers: ```python from typing import Tuple, Annotated @step(output_materializers={ "obj1": MyClass1Materializer, "obj2": MyClass2Materializer }) def create_objects() -> Tuple[ Annotated[MyClass1, "obj1"], Annotated[MyClass2, "obj2"] ]: """Create instances of different classes.""" return MyClass1(), MyClass2() ``` ### 4. Registering a Materializer Globally You can register a materializer globally to override the default materializer for a specific type: ```python from zenml.materializers.materializer_registry import materializer_registry from zenml.materializers.base_materializer import BaseMaterializer import pandas as pd # Create a custom pandas materializer class FastPandasMaterializer(BaseMaterializer): # Implementation here ... # Register it for pandas DataFrames globally materializer_registry.register_and_overwrite_type( key=pd.DataFrame, type_=FastPandasMaterializer ) ``` ## Materializer Implementation Details When implementing a custom materializer, consider these aspects: ### Handling Storage The `self.uri` property contains the path to the directory where your artifact should be stored. Use this path to create files or subdirectories for your data. When reading or writing files, always use `self.artifact_store.open()` rather than direct file I/O to ensure compatibility with different artifact stores (local filesystem, cloud storage, etc.). ### Visualization Support The `save_visualizations()` method allows you to create visualizations that will be shown in the ZenML dashboard. You can return multiple visualizations of different types: - `VisualizationType.HTML`: Embedded HTML content - `VisualizationType.MARKDOWN`: Markdown content - `VisualizationType.IMAGE`: Image files - `VisualizationType.CSV`: CSV tables ##### Configuring Visualizations Some materializers support configuration via environment variables to customize their visualization behavior. For example: - `ZENML_PANDAS_SAMPLE_ROWS`: Controls the number of rows shown in sample visualizations created by the `PandasMaterializer`. Default is 10 rows. ### Metadata Extraction The `extract_metadata()` method allows you to extract key information about your artifact for indexing and searching. This metadata will be displayed alongside the artifact in the dashboard. ### Temporary Files If you need a temporary directory while processing artifacts, use the `get_temporary_directory()` helper: ```python with self.get_temporary_directory() as temp_dir: # Process files in the temporary directory # Files will be automatically cleaned up ``` ### Example: A Complete Materializer Here's a complete example of a custom materializer for a simple class: ```python import os import json from typing import Type, Any, Dict from zenml.materializers.base_materializer import BaseMaterializer from zenml.enums import ArtifactType class MyObj: def __init__(self, name: str): self.name = name def to_dict(self): return {"name": self.name} @classmethod def from_dict(cls, data): return cls(name=data["name"]) class MyMaterializer(BaseMaterializer): """Materializer for MyObj objects.""" ASSOCIATED_TYPES = (MyObj,) ASSOCIATED_ARTIFACT_TYPE = ArtifactType.DATA def load(self, data_type: Type[Any]) -> MyObj: """Load MyObj from storage.""" filepath = os.path.join(self.uri, "data.json") with self.artifact_store.open(filepath, "r") as f: data = json.load(f) return MyObj.from_dict(data) def save(self, data: MyObj) -> None: """Save MyObj to storage.""" filepath = os.path.join(self.uri, "data.json") with self.artifact_store.open(filepath, "w") as f: json.dump(data.to_dict(), f) # Usage in a pipeline @step(output_materializers=MyMaterializer) def create_my_obj() -> MyObj: return MyObj(name="my_object") @step def use_my_obj(my_obj: MyObj) -> None: print(f"Object name: {my_obj.name}") @pipeline def my_pipeline(): obj = create_my_obj() use_my_obj(obj) ``` ## Unmaterialized artifacts Whenever you pass artifacts as outputs from one pipeline step to other steps as inputs, the corresponding materializer for the respective data type defines how this artifact is first serialized and written to the artifact store, and then deserialized and read in the next step.handle-custom-data-types. However, there are instances where you might **not** want to materialize an artifact in a step, but rather use a reference to it instead. This is where skipping materialization comes in. {% hint style="warning" %} Skipping materialization might have unintended consequences for downstream tasks that rely on materialized artifacts. Only skip materialization if there is no other way to do what you want to do. {% endhint %} #### How to skip materialization While materializers should in most cases be used to control how artifacts are returned and consumed from pipeline steps, you might sometimes need to have a completely unmaterialized artifact in a step, e.g., if you need to know the exact path to where your artifact is stored. An unmaterialized artifact is a [`zenml.materializers.UnmaterializedArtifact`](https://sdkdocs.zenml.io/latest/core_code_docs/core-artifacts.html#zenml.artifacts.unmaterialized_artifact). Among others, it has a property `uri` that points to the unique path in the artifact store where the artifact is persisted. One can use an unmaterialized artifact by specifying `UnmaterializedArtifact` as the type in the step: ```python from zenml.artifacts.unmaterialized_artifact import UnmaterializedArtifact from zenml import step @step def my_step(my_artifact: UnmaterializedArtifact): # rather than pd.DataFrame pass ``` The following shows an example of how unmaterialized artifacts can be used in the steps of a pipeline. The pipeline we define will look like this: ```shell s1 -> s3 s2 -> s4 ``` `s1` and `s2` produce identical artifacts, however `s3` consumes materialized artifacts while `s4` consumes unmaterialized artifacts. `s4` can now use the `dict_.uri` and `list_.uri` paths directly rather than their materialized counterparts. ```python from typing import Annotated from typing import Dict, List, Tuple from zenml.artifacts.unmaterialized_artifact import UnmaterializedArtifact from zenml import pipeline, step @step def step_1() -> Tuple[ Annotated[Dict[str, str], "dict_"], Annotated[List[str], "list_"], ]: return {"some": "data"}, [] @step def step_2() -> Tuple[ Annotated[Dict[str, str], "dict_"], Annotated[List[str], "list_"], ]: return {"some": "data"}, [] @step def step_3(dict_: Dict, list_: List) -> None: assert isinstance(dict_, dict) assert isinstance(list_, list) @step def step_4( dict_: UnmaterializedArtifact, list_: UnmaterializedArtifact, ) -> None: print(dict_.uri) print(list_.uri) @pipeline def example_pipeline(): step_3(*step_1()) step_4(*step_2()) example_pipeline() ``` You can see another example of using an `UnmaterializedArtifact` when triggering a [pipeline from another](../snapshots/snapshots.md#advanced-usage-running-snapshots-from-other-pipelines). ## Best Practices When working with materializers: 1. **Prefer structured formats** over pickle or other binary formats for better cross-environment compatibility. 2. **Test your materializer** with different artifact stores (local, S3, etc.) to ensure it works consistently. 3. **Consider versioning** if your data structure might change over time. 4. **Create visualizations** to help users understand your artifacts in the dashboard. 5. **Extract useful metadata** to make artifacts easier to find and understand. 6. **Be explicit** about materializer assignments for clarity, even if ZenML can detect them automatically. 7. **Avoid using the CloudpickleMaterializer** in production as it's not reliable across different Python versions. ## Conclusion Materializers are a powerful part of ZenML's artifact system, enabling proper storage and handling of any data type. By creating custom materializers for your specific data structures, you ensure that your ML pipelines are robust, efficient, and can handle any data type required by your workflows.
ZenML Scarf
--- ### Book/How To/Artifacts/Visualizations (docs/book/how-to/artifacts/visualizations.md) --- description: Learn how to visualize the data artifacts produced by your ZenML pipelines. --- # Visualizations Data visualization is a powerful tool for understanding your ML pipeline outputs. ZenML provides built-in capabilities to visualize artifacts, helping you gain insights into your data, model performance, and pipeline execution. ## Accessing Visualizations ZenML automatically generates visualizations for many common data types, making it easy to inspect your artifacts without additional code. ### Dashboard Visualizations The ZenML dashboard displays visualizations for artifacts produced by your pipeline runs: To view visualizations in the dashboard: 1. Navigate to the **Runs** tab 2. Select a specific pipeline run 3. Click on any step to view its outputs 4. Select an artifact to view its visualizations ### Notebook Visualizations You can also display artifact visualizations in Jupyter notebooks using the `visualize()` method: ```python from zenml.client import Client # Get an artifact from a previous pipeline run run = Client().get_pipeline_run("") artifact = run.steps[""].outputs[][0] # Display the visualization artifact.visualize() ``` ## Supported Visualization Types ZenML supports visualizations for many common data types out of the box: - A statistical representation of a [Pandas](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.html) Dataframe represented as a png image. - Drift detection reports by [Evidently](https://docs.zenml.io/stacks/stack-components/data-validators/evidently), [Great Expectations](https://docs.zenml.io/stacks/stack-components/data-validators/great-expectations), and [whylogs](https://docs.zenml.io/stacks/stack-components/data-validators/whylogs). - A [Hugging Face](https://zenml.io/integrations/huggingface) datasets viewer embedded as a HTML iframe. ## Creating Custom Visualizations It is simple to associate a custom visualization with an artifact in ZenML, if the visualization is one of the supported visualization types. Currently, the following visualization types are supported: * **HTML:** Embedded HTML visualizations such as data validation reports, * **Image:** Visualizations of image data such as Pillow images (e.g. `PIL.Image`) or certain numeric numpy arrays, * **CSV:** Tables, such as the pandas DataFrame `.describe()` output, * **Markdown:** Markdown strings or pages. * **JSON:** JSON strings or objects. There are three ways how you can add custom visualizations to the dashboard: * If you are already handling HTML, Markdown, CSV or JSON data in one of your steps, you can have them visualized in just a few lines of code by casting them to a [special class](#visualization-via-special-return-types) inside your step. * If you want to automatically extract visualizations for all artifacts of a certain data type, you can define type-specific visualization logic by [building a custom materializer](#visualization-via-materializers). ### Curated Visualizations Across Resources Curated visualizations let you surface a specific artifact visualization across multiple ZenML resources. Each curated visualization links to exactly one resource—for example, a model performance report that appears on the model detail page, or a deployment health dashboard that shows up in the deployment view. Curated visualizations currently support the following resources: - **Projects** – high-level dashboards and KPIs that summarize the state of a project. - **Deployments** – monitoring pages for deployed pipelines. - **Models** – evaluation dashboards and health views for registered models. - **Pipelines** – reusable visual documentation attached to pipeline definitions. - **Pipeline Runs** – detailed diagnostics for specific executions. - **Pipeline Snapshots** – configuration/version comparisons for snapshot history. You can create a curated visualization programmatically by linking an artifact visualization to a single resource. Provide the resource identifier and resource type directly when creating the visualization. The example below shows how to create separate visualizations for different resource types: ``` /* Detailed source-code truncated for AI context efficiency. */ ``` After creation, the returned response includes the visualization ID. You can retrieve a specific visualization later with `Client.get_curated_visualization`: ```python retrieved = client.get_curated_visualization(pipeline_viz.id, hydrate=True) print(retrieved.display_name) print(retrieved.resource.type) print(retrieved.resource.id) ``` Curated visualizations are tied to their parent resources and automatically surface in the ZenML dashboard wherever those resources appear, so keep track of the IDs returned by `create_curated_visualization` if you need to reference them later. #### Updating curated visualizations Once you've created a curated visualization, you can update its display name, order, or tile size using `Client.update_curated_visualization`: ```python from uuid import UUID client.update_curated_visualization( visualization_id=UUID(""), display_name="Updated Dashboard Title", display_order=10, layout_size=CuratedVisualizationSize.HALF_WIDTH, ) ``` When a visualization is no longer relevant, you can remove it entirely: ```python client.delete_curated_visualization(visualization_id=UUID("")) ``` #### Controlling display order and size The optional `display_order` field determines how visualizations are sorted when displayed. Visualizations with lower order values appear first, while those with `None` (the default) appear at the end in creation order. When setting display orders, consider leaving gaps between values (e.g., 10, 20, 30 instead of 1, 2, 3) to make it easier to insert new visualizations later without renumbering everything: ```python # Leave gaps for future insertions visualization_a = client.create_curated_visualization( artifact_visualization_id=artifact_visualizations[0].id, resource_type=VisualizationResourceTypes.PIPELINE, resource_id=pipeline_id, display_name="Model performance at a glance", display_order=10, # Primary dashboard layout_size=CuratedVisualizationSize.HALF_WIDTH, ) visualization_b = client.create_curated_visualization( artifact_visualization_id=artifact_visualizations[1].id, resource_type=VisualizationResourceTypes.PIPELINE, resource_id=pipeline_id, display_name="Drill-down metrics", display_order=20, # Secondary metrics layout_size=CuratedVisualizationSize.HALF_WIDTH, # Compact chart beside the primary tile ) # Later, easily insert between them visualization_c = client.create_curated_visualization( artifact_visualization_id=artifact_visualizations[2].id, resource_type=VisualizationResourceTypes.PIPELINE, resource_id=pipeline_id, display_name="Raw output preview", display_order=15, # Now appears between A and B layout_size=CuratedVisualizationSize.FULL_WIDTH, ) ``` #### RBAC visibility Curated visualizations respect the access permissions of the resource they're linked to. A user can only see a curated visualization if they have read access to the specific resource it targets. If a user lacks permission for the linked resource, the visualization will be hidden from their view. For example, if you create a visualization linked to a specific deployment, only users with read access to that deployment will see the visualization. If you need the same visualization to appear in different contexts with different access controls (e.g., on both a project page and a deployment page), create separate curated visualizations for each resource. This ensures that visualizations never inadvertently expose information from resources a user shouldn't access, while giving you fine-grained control over visibility. ### Visualization via Special Return Types If you already have HTML, Markdown, CSV or JSON data available as a string inside your step, you can simply cast them to one of the following types and return them from your step: * `zenml.types.HTMLString` for strings in HTML format, e.g., `"

Header

Some text"`, * `zenml.types.MarkdownString` for strings in Markdown format, e.g., `"# Header\nSome text"`, * `zenml.types.CSVString` for strings in CSV format, e.g., `"a,b,c\n1,2,3"`. * `zenml.types.JSONString` for strings in JSON format, e.g., `{"key": "value"}`. #### Example: ```python from zenml import step from zenml.types import CSVString @step def my_step() -> CSVString: some_csv = "a,b,c\n1,2,3" return CSVString(some_csv) ``` This would create the following visualization in the dashboard: {% hint style="info" %} **Shared CSS for Consistent Visualizations** When creating multiple HTML visualizations across your pipeline, consider using a shared CSS file to maintain consistent styling. Create a central CSS file with your design system (colors, components, layouts) and Python utilities to load it into your HTML templates. This approach eliminates code duplication, ensures visual consistency across all reports, and makes it easy to update styling across all visualizations from a single location. You can create helper functions that return complete HTML templates with shared styles, and use CSS variables for theme management. This pattern is especially valuable for teams generating multiple HTML reports or dashboards where maintaining a professional, cohesive appearance is important. {% endhint %} Another example is visualizing a matplotlib plot by embedding the image in an HTML string: ```python import matplotlib.pyplot as plt import base64 import io from zenml.types import HTMLString from zenml import step, pipeline @step def create_matplotlib_visualization() -> HTMLString: """Creates a matplotlib visualization and returns it as embedded HTML.""" # Create plot fig, ax = plt.subplots() ax.plot([1, 2, 3, 4], [1, 4, 2, 3]) ax.set_title('Sample Plot') # Convert plot to base64 string buf = io.BytesIO() fig.savefig(buf, format='png', bbox_inches='tight', dpi=300) plt.close(fig) # Clean up image_base64 = base64.b64encode(buf.getvalue()).decode('utf-8') # Create HTML with embedded image html = f'''
''' return HTMLString(html) @pipeline def visualization_pipeline(): create_matplotlib_visualization() if __name__ == "__main__": visualization_pipeline() ``` ### Visualization via Materializers If you want to automatically extract visualizations for all artifacts of a certain data type, you can do so by overriding the `save_visualizations()` method of the corresponding [materializer](materializers.md). Let's look at an example of how to visualize matplotlib figures in your ZenML dashboard: #### Example: Matplotlib Figure Visualization **1. Custom Class** First, we create a custom class to hold our matplotlib figure: ```python from typing import Any from pydantic import BaseModel class MatplotlibVisualization(BaseModel): """Custom class to hold matplotlib figures.""" figure: Any # This will hold the matplotlib figure ``` **2. Materializer** Next, we create a [custom materializer](materializers.md#creating-custom-materializers) that handles this class and implements the visualization logic: ```python import os from typing import Dict from zenml.materializers.base_materializer import BaseMaterializer from zenml.enums import VisualizationType from zenml.io import fileio class MatplotlibMaterializer(BaseMaterializer): """Materializer that handles matplotlib figures.""" ASSOCIATED_TYPES = (MatplotlibVisualization,) def save_visualizations( self, data: MatplotlibVisualization ) -> Dict[str, VisualizationType]: """Create and save visualizations for the matplotlib figure.""" visualization_path = os.path.join(self.uri, "visualization.png") with fileio.open(visualization_path, 'wb') as f: data.figure.savefig(f, format='png', bbox_inches='tight') return {visualization_path: VisualizationType.IMAGE} ``` **3. Step** Finally, we create a step that returns our custom type: ```python import matplotlib.pyplot as plt from zenml import step @step def create_matplotlib_visualization() -> MatplotlibVisualization: """Creates a matplotlib visualization.""" fig, ax = plt.subplots() ax.plot([1, 2, 3, 4], [1, 4, 2, 3]) ax.set_title('Sample Plot') return MatplotlibVisualization(figure=fig) ``` {% hint style="info" %} When you use this step in your pipeline: 1. The step creates and returns a `MatplotlibVisualization` 2. ZenML finds the `MatplotlibMaterializer` and calls `save_visualizations()` 3. The figure is saved as a PNG file in your artifact store 4. The dashboard loads and displays this PNG when you view the artifact {% endhint %} For another example, see our [Hugging Face datasets materializer](https://github.com/zenml-io/zenml/blob/main/src/zenml/integrations/huggingface/materializers/huggingface_datasets_materializer.py) which visualizes datasets by embedding their preview viewer. ## Controlling Visualizations ### Access to Visualizations In order for the visualizations to show up on the dashboard, the following must be true: #### Configuring a Service Connector Visualizations are usually stored alongside the artifact, in the [artifact store](https://docs.zenml.io/stacks/stack-components/artifact-stores). Therefore, if a user would like to see the visualization displayed on the ZenML dashboard, they must give access to the server to connect to the artifact store. The [service connector](https://docs.zenml.io/stacks/service-connectors/auth-management) documentation goes deeper into the concept of service connectors and how they can be configured to give the server permission to access the artifact store. For a concrete example, see the [AWS S3](https://docs.zenml.io/stacks/stack-components/artifact-stores/s3) artifact store documentation. {% hint style="info" %} When using the default/local artifact store with a deployed ZenML, the server naturally does not have access to your local files. In this case, the visualizations are also not displayed on the dashboard. Please use a service connector enabled and remote artifact store alongside a deployed ZenML to view visualizations. {% endhint %} #### Configuring Artifact Stores If all visualizations of a certain pipeline run are not showing up in the dashboard, it might be that your ZenML server does not have the required dependencies or permissions to access that artifact store. See the [custom artifact store docs page](https://docs.zenml.io/stacks/stack-components/artifact-stores/custom#enabling-artifact-visualizations-with-custom-artifact-stores) for more information. ### Enabling/Disabling Visualizations You can control whether visualizations are generated at the pipeline or step level: ```python # Disable visualizations for a pipeline @pipeline(enable_artifact_visualization=False) def my_pipeline(): ... # Disable visualizations for a step @step(enable_artifact_visualization=False) def my_step(): ... ``` You can also configure this in YAML: ```yaml enable_artifact_visualization: False steps: my_step: enable_artifact_visualization: True ``` ## Conclusion Visualizing artifacts is a powerful way to gain insights from your ML pipelines. ZenML's built-in visualization capabilities make it easy to understand your data and model outputs, identify issues, and communicate results. By leveraging these visualization tools, you can better understand your ML workflows, debug problems more effectively, and make more informed decisions about your models.
ZenML Scarf
--- ### Book/How To/Code Repositories/Code Repositories (docs/book/how-to/code-repositories/code-repositories.md) --- description: >- Tracking your code and avoiding unnecessary Docker builds by connecting your git repo. icon: code-compare --- # Code Repositories A code repository in ZenML refers to a remote storage location for your code. Some commonly known code repository platforms include [GitHub](https://github.com/) and [GitLab](https://gitlab.com/).

A visual representation of how the code repository fits into the general ZenML architecture.

Connecting code repositories to ZenML solves two fundamental challenges in machine learning workflows. First, it enhances reproducibility by tracking which specific code version (commit hash) was used for each pipeline run, creating a clear audit trail between your code and its results. Second, it dramatically improves development efficiency by optimizing Docker image building. Instead of including source code in each build, ZenML builds images without the code and downloads it at runtime, eliminating the need to rebuild images after every code change. This not only speeds up individual development cycles but allows team members to share and reuse builds, saving time and computing resources across your organization. Learn more about how code repositories optimize Docker builds [here](https://docs.zenml.io/how-to/customize-docker-builds/how-to-reuse-builds). ## Registering a code repository If you are planning to use one of the available implementations of code repositories, first, you need to install the corresponding ZenML integration: ``` zenml integration install ``` Afterward, code repositories can be registered using the CLI: ```shell zenml code-repository register --type= [--CODE_REPOSITORY_OPTIONS] ``` For concrete options, check out the section on the `GitHubCodeRepository`, the `GitLabCodeRepository` or how to develop and register a custom code repository implementation. ## Available implementations ZenML comes with builtin implementations of the code repository abstraction for the `GitHub` and `GitLab` platforms, but it's also possible to use a custom code repository implementation. ### GitHub ZenML provides built-in support for using GitHub as a code repository for your ZenML pipelines. You can register a GitHub code repository by providing the URL of the GitHub instance, the owner of the repository, the name of the repository, and a GitHub Personal Access Token (PAT) with access to the repository. Before registering the code repository, first, you have to install the corresponding integration: ```sh zenml integration install github ``` Afterward, you can register a GitHub code repository by running the following CLI command: ```shell zenml code-repository register --type=github \ --owner= --repository= \ --token= ``` where `` is the name of the code repository you are registering, `` is the owner of the repository, `` is the name of the repository and `` is your GitHub Personal Access Token. If you're using a self-hosted GitHub Enterprise instance, you'll need to also pass the `--api_url=` and `--host=` options. `` should point to where the GitHub API is reachable (defaults to `https://api.github.com/`) and `` should be the [hostname of your GitHub instance](https://docs.github.com/en/enterprise-server@3.10/admin/configuring-settings/configuring-network-settings/configuring-the-hostname-for-your-instance?learn=deploy_an_instance\&learnProduct=admin). {% hint style="warning" %} Please refer to the section on using secrets for stack configuration in order to securely store your GitHub\ Personal Access Token. ```shell # Using central secrets management zenml secret create github_secret \ --pa_token= # Then reference the username and password zenml code-repository register ... --token={{github_secret.pa_token}} ... ``` {% endhint %} After registering the GitHub code repository, ZenML will automatically detect if your source files are being tracked by GitHub and store the commit hash for each pipeline run.
How to get a token for GitHub 1. Go to your GitHub account settings and click on [Developer settings](https://github.com/settings/tokens?type=beta). 2. Select "Personal access tokens" and click on "Generate new token". 3. Give your token a name and a description. 4. We recommend selecting the specific repository and then giving `contents` read-only access. 5. Click on "Generate token" and copy the token to a safe place.
### GitLab ZenML also provides built-in support for using GitLab as a code repository for your ZenML pipelines. You can register a GitLab code repository by providing the URL of the GitLab project, the group of the project, the name of the project, and a GitLab Personal Access Token (PAT) with access to the project. Before registering the code repository, first, you have to install the corresponding integration: ```sh zenml integration install gitlab ``` Afterward, you can register a GitLab code repository by running the following CLI command: ```shell zenml code-repository register --type=gitlab \ --group= --project= \ --token= ``` where `` is the name of the code repository you are registering, `` is the group of the project, `` is the name of the project and `` is your GitLab Personal Access Token. If you're using a self-hosted GitLab instance, you'll need to also pass the `--instance_url=` and `--host=` options. `` should point to your GitLab instance (defaults to `https://gitlab.com/`) and `` should be the hostname of your GitLab instance (defaults to `gitlab.com`). {% hint style="warning" %} Please refer to the section on using secrets for stack configuration in order to securely store your GitLab\ Personal Access Token. ```shell # Using central secrets management zenml secret create gitlab_secret \ --pa_token= # Then reference the username and password zenml code-repository register ... --token={{gitlab_secret.pa_token}} ... ``` {% endhint %} After registering the GitLab code repository, ZenML will automatically detect if your source files are being tracked by GitLab and store the commit hash for each pipeline run.
How to get a token for GitLab 1. Go to your GitLab account settings and click on Access Tokens. 2. Name the token and select the scopes that you need (e.g. `read_repository`, `read_user`, `read_api`) 3. Click on "Create personal access token" and copy the token to a safe place.
## Developing a custom code repository If you're using some other platform to store your code, and you still want to use a code repository in ZenML, you can implement and register a custom code repository. First, you'll need to subclass and implement the abstract methods of the `zenml.code_repositories.BaseCodeRepository` class: ```python from abc import ABC, abstractmethod from typing import Optional class BaseCodeRepository(ABC): """Base class for code repositories.""" @abstractmethod def login(self) -> None: """Logs into the code repository.""" @abstractmethod def download_files( self, commit: str, directory: str, repo_sub_directory: Optional[str] ) -> None: """Downloads files from the code repository to a local directory. Args: commit: The commit hash to download files from. directory: The directory to download files to. repo_sub_directory: The subdirectory in the repository to download files from. """ @abstractmethod def get_local_context( self, path: str ) -> Optional["LocalRepositoryContext"]: """Gets a local repository context from a path. Args: path: The path to the local repository. Returns: The local repository context object. """ ``` After you're finished implementing this, you can register it as follows: ```shell # The `CODE_REPOSITORY_OPTIONS` are key-value pairs that your implementation will receive # as configuration in its __init__ method. This will usually include stuff like the username # and other credentials necessary to authenticate with the code repository platform. zenml code-repository register --type=custom --source=my_module.MyRepositoryClass \ [--CODE_REPOSITORY_OPTIONS] ```
ZenML Scarf
--- ### Book/How To/Containerization/Containerization (docs/book/how-to/containerization/containerization.md) --- description: >- Customize Docker builds to run your pipelines in isolated, well-defined environments. icon: docker --- # Containerization ZenML executes pipeline steps sequentially in the active Python environment when running locally. However, with remote [orchestrators](https://docs.zenml.io/stacks/orchestrators) or [step operators](https://docs.zenml.io/stacks/step-operators), ZenML builds [Docker](https://www.docker.com/) images to run your pipeline in an isolated, well-defined environment. This page explains how ZenML's Docker build process works and how you can customize it to meet your specific requirements. ## Understanding Docker Builds in ZenML When a pipeline is run with a remote orchestrator, a Dockerfile is dynamically generated at runtime. It is then used to build the Docker image using the image builder component of your stack. The Dockerfile consists of the following steps: 1. **Starts from a parent image** that has ZenML installed. By default, this will use the [official ZenML image](https://hub.docker.com/r/zenmldocker/zenml/) for the Python and ZenML version that you're using in the active Python environment. 2. **Installs additional pip dependencies**. ZenML automatically detects which integrations are used in your stack and installs the required dependencies. 3. **Optionally copies your source files**. Your source files need to be available inside the Docker container so ZenML can execute your step code. 4. **Sets user-defined environment variables.** The process described above is automated by ZenML and covers most basic use cases. This page covers various ways to customize the Docker build process to fit your specific needs. ### Docker Build Process ZenML uses the following process to decide how to build Docker images: * **No `dockerfile` specified**: If any of the options regarding requirements, environment variables, or copying files require us to build an image, ZenML will build this image. Otherwise, the `parent_image` will be used to run the pipeline. * **`dockerfile` specified**: ZenML will first build an image based on the specified Dockerfile. If any additional options regarding requirements, environment variables, or copying files require an image built on top of that, ZenML will build a second image. If not, the image built from the specified Dockerfile will be used to run the pipeline. ### Requirements Installation Order Depending on the configuration of your Docker settings, requirements will be installed in the following order (each step is optional): 1. The packages installed in your local Python environment (if enabled) 2. The packages required by the stack (unless disabled by setting `install_stack_requirements=False`) 3. The packages specified via the `required_integrations` 4. The packages specified via the `requirements` attribute For a full list of configuration options, check out [the DockerSettings object on the SDKDocs](https://sdkdocs.zenml.io/latest/core_code_docs/core-config.html#zenml.config.DockerSettings). ## Configuring Docker Settings You can customize Docker builds for your pipelines and steps using the `DockerSettings` class: ```python from zenml.config import DockerSettings ``` There are multiple ways to supply these settings: ### Pipeline-Level Settings Configuring settings on a pipeline applies them to all steps of that pipeline: ```python from zenml import pipeline, step from zenml.config import DockerSettings docker_settings = DockerSettings() @step def my_step() -> None: """Example step.""" pass # Either add it to the decorator @pipeline(settings={"docker": docker_settings}) def my_pipeline() -> None: my_step() # Or configure the pipelines options my_pipeline = my_pipeline.with_options( settings={"docker": docker_settings} ) ``` ### Step-Level Settings For more fine-grained control, configure settings on individual steps. This is particularly useful when different steps have conflicting requirements or when some steps need specialized environments: ```python from zenml import step from zenml.config import DockerSettings docker_settings = DockerSettings() # Either add it to the decorator @step(settings={"docker": docker_settings}) def my_step() -> None: pass # Or configure the step options my_step = my_step.with_options( settings={"docker": docker_settings} ) ``` ### Using YAML Configuration Define settings in a YAML configuration file for better separation of code and configuration: ```yaml settings: docker: parent_image: python:3.11-slim apt_packages: - git - curl requirements: - tensorflow==2.8.0 - pandas steps: training_step: settings: docker: parent_image: pytorch/pytorch:2.2.0-cuda11.8-cudnn8-runtime required_integrations: - wandb - mlflow ``` Check out [this page](https://docs.zenml.io/concepts/steps_and_pipelines/configuration) for more information on the hierarchy and precedence of the various ways in which you can supply the settings. ### Specifying Docker Build Options You can customize the build process by specifying build options that get passed to the build method of the image builder: ```python from zenml import pipeline from zenml.config import DockerSettings docker_settings = DockerSettings( build_config={"build_options": {"buildargs": {"MY_ARG": "value"}}} ) @pipeline(settings={"docker": docker_settings}) def my_pipeline(...): ... ``` For the default local image builder with the **Docker** engine, these options are passed to the Docker build API (or to the `docker build` CLI if you enabled subprocess mode on the local image builder). With the **Podman** engine, builds always use the Podman CLI and the same option mapping as subprocess mode on Docker. {% hint style="info" %} If you're running your pipelines on MacOS with ARM architecture, the local Docker caching does not work unless you specify the target platform of the image: ```python from zenml import pipeline from zenml.config import DockerSettings docker_settings = DockerSettings( build_config={"build_options": {"platform": "linux/amd64"}} ) @pipeline(settings={"docker": docker_settings}) def my_pipeline(...): ... ``` {% endhint %} ## Using Custom Parent Images ### Pre-built Parent Images To use a static parent image (e.g., with internal dependencies pre-installed): ```python from zenml import pipeline from zenml.config import DockerSettings docker_settings = DockerSettings(parent_image="my_registry.io/image_name:tag") @pipeline(settings={"docker": docker_settings}) def my_pipeline(...): ... ``` ZenML will use this image as the base and still perform the following steps: 1. Install additional pip dependencies 2. Copy source files (if configured) 3. Set environment variables {% hint style="info" %} If you're going to use a custom parent image, you need to make sure that it has Python, pip, and ZenML installed for it to work. If you need a starting point, you can take a look at the Dockerfile that ZenML uses [here](https://github.com/zenml-io/zenml/blob/main/docker/base.Dockerfile). {% endhint %} ### Skip Build Process To use the image directly to run your steps without including any code or installing any requirements on top of it, skip the Docker builds by setting `skip_build=True`: ```python docker_settings = DockerSettings( parent_image="my_registry.io/image_name:tag", skip_build=True ) @pipeline(settings={"docker": docker_settings}) def my_pipeline(...): ... ``` When `skip_build` is enabled, the `parent_image` will be used directly to run the steps of your pipeline without any additional Docker builds on top of it. This means that **none** of the following will happen: - No installation of local Python environment packages - No installation of stack requirements - No installation of required integrations - No installation of specified requirements - No installation of apt packages - No inclusion of source files in the container - No setting of environment variables {% hint style="warning" %} This is an advanced feature and may cause unintended behavior when running your pipelines. If you use this, ensure your image contains everything necessary to run your pipeline: 1. Your stack requirements 2. Integration requirements 3. Project-specific requirements 4. Any system packages 5. Your project code files (unless a code repository is registered or `allow_download_from_artifact_store` is enabled) Make sure that Python, `pip` and `zenml` are installed in your image, and that your code is in the `/app` directory set as the active working directory. Also note that the Docker settings validator will raise an error if you set `skip_build=True` without specifying a `parent_image`. A parent image is required when skipping the build as it will be used directly to run your pipeline steps. {% endhint %} ### Custom Dockerfiles For greater control, you can specify a custom Dockerfile and build context: ```python docker_settings = DockerSettings( dockerfile="/path/to/dockerfile", build_context_root="/path/to/build/context", parent_image_build_config={ "build_options": {"buildargs": {"MY_ARG": "value"}}, "dockerignore": "/path/to/.dockerignore" } ) @pipeline(settings={"docker": docker_settings}) def my_pipeline(...): ... ``` Here is how the build process looks like with a custom Dockerfile: * **`Dockerfile` specified**: ZenML will first build an image based on the specified `Dockerfile`. If any options regarding requirements, environment variables, or copying files require an additional image built on top of that, ZenML will build a second image. Otherwise, the image built from the specified `Dockerfile` will be used to run the pipeline. {% hint style="info" %} Important notes about using a custom Dockerfile: * When you specify a custom `dockerfile`, the `parent_image` attribute will be ignored * The image built from your Dockerfile must have ZenML installed * If you set `build_context_root`, that directory will be used as the build context for the Docker build. If left empty, the build context will only contain the Dockerfile * You can configure the build options by setting `parent_image_build_config` with specific build options and dockerignore settings {% endhint %} ## Managing Dependencies ZenML offers several ways to specify dependencies for your Docker containers: ### Python Dependencies By default, ZenML automatically installs all packages required by your active ZenML stack. {% hint style="warning" %} In future versions, if none of the `replicate_local_python_environment`, `pyproject_path` or `requirements` attributes on `DockerSettings` are specified, ZenML will try to automatically find a `requirements.txt` and `pyproject.toml` files inside your current [source root](../steps-pipelines/sources.md#source-root) and install packages from the first one it finds. You can disable this behavior by setting `disable_automatic_requirements_detection=True`. If you already want this automatic detection in current versions of ZenML, set `disable_automatic_requirements_detection=False`. {% endhint %} 1. **Replicate Local Environment**: ```python from zenml import pipeline from zenml.config import DockerSettings docker_settings = DockerSettings(replicate_local_python_environment=True) @pipeline(settings={"docker": docker_settings}) def my_pipeline(...): ... ``` This will run `pip freeze` to get a list of the installed packages in your local Python environment and will install them in the Docker image. This ensures that the same exact dependencies will be installed. {% hint style="warning" %} This does not work when you have a local project installed. To install local projects, check out the `Install Local Projects` section below. {% endhint %} 2. **Specify a `pyproject.toml` file**: ```python from zenml import pipeline from zenml.config import DockerSettings docker_settings = DockerSettings(pyproject_path="/path/to/pyproject.toml") @pipeline(settings={"docker": docker_settings}) def my_pipeline(...): ... ``` By default, ZenML will try to export the dependencies specified in the `pyproject.toml` by trying to run `uv export` and `poetry export`. If both of these commands do not work for your `pyproject.toml` file or you want to customize the command (for example to install certain extras), you can specify a custom command using the `pyproject_export_command` attribute. This command must output a list of requirements following the format of the [requirements file](https://pip.pypa.io/en/stable/reference/requirements-file-format/). The command can contain a `{directory}` placeholder which will be replaced with the directory in which the `pyproject.toml` file is stored. ```python from zenml import pipeline from zenml.config import DockerSettings docker_settings = DockerSettings(pyproject_export_command=[ "uv", "export", "--extra=train", "--format=requirements-txt", "--directory={directory}" ]) @pipeline(settings={"docker": docker_settings}) def my_pipeline(...): ... ``` 3. **Specify Requirements Directly**: ```python from zenml.config import DockerSettings docker_settings = DockerSettings(requirements=["torch==1.12.0", "torchvision"]) ``` 4. **Use Requirements File**: ```python from zenml.config import DockerSettings docker_settings = DockerSettings(requirements="/path/to/requirements.txt") ``` 5. **Specify ZenML Integrations**: ```python from zenml.integrations.constants import PYTORCH, EVIDENTLY from zenml.config import DockerSettings docker_settings = DockerSettings(required_integrations=[PYTORCH, EVIDENTLY]) ``` 6. **Control Stack Requirements**: By default, ZenML installs the requirements needed by your active stack. You can disable this behavior if needed: ```python from zenml.config import DockerSettings docker_settings = DockerSettings(install_stack_requirements=False) ``` 7. **Control Deployment Requirements**: By default, if you have a Deployer stack component in your active stack, ZenML installs the requirements needed by the deployment application configured in your deployment settings. You can disable this behavior if needed: ```python from zenml.config import DockerSettings docker_settings = DockerSettings(install_deployment_requirements=False) ``` 8. **Install Local Projects**: If your code requires the installation of some local code files as a python package, you can specify a command that installs it as follows: ```python from zenml.config import DockerSettings docker_settings = DockerSettings(local_project_install_command="pip install . --no-deps") ``` {% hint style="warning" %} Installing a local python package only works if your code files are included in the Docker image, so make sure you have `allow_including_files_in_images=True` in your Docker settings. If you want to instead use the [code download functionality](#source-code-management) to avoid building new Docker images for each pipeline run, you can follow [this example](https://github.com/zenml-io/zenml-patterns/tree/main/docker-local-pkg). {% endhint %} Depending on the options specified in your Docker settings, ZenML installs the requirements in the following order (each step optional): 1. The packages installed in your local Python environment 2. The packages required by the stack (unless disabled by setting `install_stack_requirements=False`) 3. The packages specified via the `required_integrations` 4. The packages defined in the pyproject.toml file specified by the `pyproject_path` attribute 5. The packages specified via the `requirements` attribute ### System Packages Specify apt packages to be installed in the Docker image: ```python from zenml.config import DockerSettings docker_settings = DockerSettings(apt_packages=["git", "curl", "libsm6", "libxext6"]) ``` ### Installation Control Control how packages are installed: ```python # Use custom installer arguments docker_settings = DockerSettings(python_package_installer_args={"timeout": 1000}) # Use pip instead of uv from zenml.config import DockerSettings, PythonPackageInstaller docker_settings = DockerSettings(python_package_installer=PythonPackageInstaller.PIP) # Or as a string docker_settings = DockerSettings(python_package_installer="pip") # Use uv (default) docker_settings = DockerSettings(python_package_installer=PythonPackageInstaller.UV) ``` The available package installers are: - `uv`: The default python package installer - `pip`: An alternative python package installer Full documentation for how `uv` works with PyTorch can be found on the Astral Docs website [here](https://docs.astral.sh/uv/guides/integration/pytorch/). It covers some of the particular gotchas and details you might need to know. {% hint style="info" %} If you're using `uv` and specify a custom parent image or Dockerfile that does not have an activated virtual environment, you need to pass `python_package_installer_args={"system": None}` in your DockerSettings so that `uv` installs the packages for the Python system installation. Depending on the parent image, you might also need to include `"break-system-packages": None` in the installer args as well to make it work. {% endhint %} To speed up repeated image builds, set `python_package_installer_cache_mount` to a BuildKit `--mount` spec. ZenML emits `RUN --mount= ...` on the install steps and drops the default `--no-cache-dir` flag so the mount is actually used. The value is passed through verbatim, so any valid `--mount` spec works (e.g. `type=cache` or `type=bind`). Requires BuildKit to be enabled on the builder. ```python # uv (default installer) docker_settings = DockerSettings( python_package_installer_cache_mount="type=cache,target=/root/.cache/uv", ) # pip docker_settings = DockerSettings( python_package_installer="pip", python_package_installer_cache_mount="type=cache,target=/root/.cache/pip", ) ``` ### Using custom python executable To use a custom python executable, instead of a standard `python` you can use the `ZENML_CONTAINER_PYTHON_EXECUTABLE` environment variable to control the python executable being used in the entrypoint command, for example `ZENML_CONTAINER_PYTHON_EXECUTABLE=/home/user1/custom-venv/bin/python` can be used. ## Private PyPI Repositories For packages that require authentication from private repositories: ```python import os docker_settings = DockerSettings( requirements=["my-internal-package==0.1.0"], environment={ 'PIP_EXTRA_INDEX_URL': f"https://{os.environ.get('PYPI_TOKEN', '')}@my-private-pypi-server.com/{os.environ.get('PYPI_USERNAME', '')}/"} ) ``` Be cautious with handling credentials. Always use secure methods to manage and distribute authentication information within your team. Consider using secrets management tools or environment variables passed securely. ## Source Code Management You can specify how the files inside your [source root directory](../steps-pipelines/sources.md#source-root) are handled for containerized steps: ```python docker_settings = DockerSettings( # Download files from code repository if available allow_download_from_code_repository=True, # If no code repository, upload code to artifact store allow_download_from_artifact_store=True, # If neither of the above, include files in the image allow_including_files_in_images=True ) ``` ZenML handles your source code in the following order: 1. If `allow_download_from_code_repository` is `True` and your files are inside a registered [code repository](https://docs.zenml.io/user-guides/production-guide/connect-code-repository) and the repository has no local changes, the files will be downloaded from the code repository and not included in the image. 2. If the previous option is disabled or no code repository without local changes exists for the root directory, ZenML will archive and upload your code to the artifact store if `allow_download_from_artifact_store` is `True`. 3. If both previous options were disabled or not possible, ZenML will include your files in the Docker image if `allow_including_files_in_images` is enabled. This means a new Docker image has to be built each time you modify one of your code files. {% hint style="warning" %} Setting all of the above attributes to `False` is not recommended and will most likely cause unintended and unanticipated behavior when running your pipelines. If you do this, you're responsible that all your files are at the correct paths in the Docker images that will be used to run your pipeline steps. {% endhint %} ### Controlling Included Files * When downloading files from a code repository, use a `.gitignore` file to exclude files. * When including files in the image, use a `.dockerignore` file to exclude files and keep the image smaller: ```python # Have a file called .dockerignore in your source root directory # Or explicitly specify a .dockerignore file to use: docker_settings = DockerSettings(build_config={"dockerignore": "/path/to/.dockerignore"}) ``` ## Environment Variables You can configure two types of environment variables: 1. Environment variables that will be set in the beginning of the Docker image building process before any python or apt packages are installed: ```python docker_settings = DockerSettings( environment={ "PYTHONUNBUFFERED": "1", "MODEL_DIR": "/models", "API_KEY": "${GLOBAL_API_KEY}" # Reference a local environment variable } ) ``` 2. Environment variables that will be set at the end of the Docker image building process after the python and apt packages are installed, right before the container entrypoint (useful for setting proxy environment variables for example): ```python docker_settings = DockerSettings( runtime_environment={ "HTTP_PROXY": "http://proxy.example.com:8080", "HTTPS_PROXY": "http://proxy.example.com:8080", "NO_PROXY": "localhost,127.0.0.1" } ) ``` Environment variables can reference other environment variables set in your client environment by using the `${VAR_NAME}` syntax. ZenML will substitute these before building the images. ## Build Reuse and Optimization ZenML automatically reuses Docker builds when possible to save time and resources: ### What is a Pipeline Build? A pipeline build is an encapsulation of a pipeline and the stack it was run on. It contains the Docker images that were built for the pipeline with all required dependencies from the stack, integrations and the user. Optionally, it also contains the pipeline code. List all available builds for a pipeline: ```bash zenml pipeline builds list --pipeline_id='startswith:ab53ca' ``` Create a build manually (useful for pre-building images): ```bash zenml pipeline build --stack vertex-stack my_module.my_pipeline_instance ``` You can use options to specify the configuration file and the stack to use for the build. Learn more about the build function [here](https://sdkdocs.zenml.io/latest/cli.html#zenml.cli.Pipeline.build). ### Reusing Builds By default, when you run a pipeline, ZenML will check if a build with the same pipeline and stack exists. If it does, it will reuse that build automatically. However, you can also force using a specific build by providing its ID: ```python pipeline_instance.run(build="") ``` You can also specify this in configuration files: ```yaml build: your-build-id-here ``` {% hint style="warning" %} Specifying a custom build when running a pipeline will **not run the code on your client machine** but will use the code **included in the Docker images of the build**. Even if you make local code changes, reusing a build will _always_ execute the code bundled in the Docker image, rather than the local code. {% endhint %} ### Controlling Image Repository Names You can control where your Docker image is pushed by specifying a target repository name: ```python from zenml.config import DockerSettings docker_settings = DockerSettings(target_repository="my-custom-repo-name") ``` The repository name will be appended to the registry URI of your container registry stack component. For example, if your container registry URI is `gcr.io/my-project` and you set `target_repository="zenml-pipelines"`, the full image name would be `gcr.io/my-project/zenml-pipelines`. If you don't specify a target repository, the default repository name configured in your container registry stack component settings will be used. ### Specifying Image tags You can control the tag of the generated Docker images using the image tag option: ```python from zenml.config import DockerSettings docker_settings = DockerSettings(image_tag="1.0.0") ``` Keep in mind that this will be applied to all images built using the DockerSettings object. If there are multiple such images, only one of them will keep the tag while the rest will be untagged. ### Decoupling Code from Builds To reuse Docker builds while still using your latest code changes, you need to decouple your code from the build. There are two main approaches: #### 1. Using the Artifact Store to Upload Code You can let ZenML use the artifact store to upload your code. This is the default behavior if no code repository is detected and the `allow_download_from_artifact_store` flag is not set to `False` in your `DockerSettings`. #### 2. Using Code Repositories for Faster Builds Registering a [code repository](../code-repositories/code-repositories.md) lets you avoid building images each time you run a pipeline **and** quickly iterate on your code. When running a pipeline that is part of a local code repository checkout, ZenML can instead build the Docker images without including any of your source files, and download the files inside the container before running your code. ZenML will **automatically figure out which builds match your pipeline and reuse the appropriate build id**. Therefore, you **do not** need to explicitly pass in the build id when you have a clean repository state and a connected git repository. {% hint style="warning" %} In order to benefit from the advantages of having a code repository in a project, you need to make sure that **the relevant integrations are installed for your ZenML installation.**. For instance, let's assume you are working on a project with ZenML and one of your team members has already registered a corresponding code repository of type `github` for it. If you do `zenml code-repository list`, you would also be able to see this repository. However, in order to fully use this repository, you still need to install the corresponding integration for it, in this example the `github` integration. ```sh zenml integration install github ``` {% endhint %} #### Detecting local code repository checkouts Once you have registered one or more code repositories, ZenML will check whether the files you use when running a pipeline are tracked inside one of those code repositories. This happens as follows: * First, the [source root](../steps-pipelines/sources.md#source-root) is computed * Next, ZenML checks whether this source root directory is included in a local checkout of one of the registered code repositories #### Tracking code versions for pipeline runs If a local code repository checkout is detected when running a pipeline, ZenML will store a reference to the current commit for the pipeline run, so you'll be able to know exactly which code was used. Note that this reference is only tracked if your local checkout is clean (i.e. it does not contain any untracked or uncommitted files). This is to ensure that your pipeline is actually running with the exact code stored at the specific code repository commit. {% hint style="info" %} If you want to ignore untracked files, you can set the `ZENML_CODE_REPOSITORY_IGNORE_UNTRACKED_FILES` environment variable to `True`. When doing this, you're responsible that the files committed to the repository includes everything necessary to run your pipeline. {% endhint %} #### Preventing Build Reuse There might be cases where you want to force a new build, even if a suitable existing build is available. You can do this by setting `prevent_build_reuse=True`: ```python docker_settings = DockerSettings(prevent_build_reuse=True) ``` This is useful in scenarios like: - When you've made changes to your image building process that aren't tracked by ZenML - When troubleshooting issues in your Docker image - When you want to ensure your Docker image uses the most up-to-date base images #### Tips and Best Practices for Build Reuse * **Clean Repository State**: The file download is only possible if the local checkout is clean (no untracked or uncommitted files) and the latest commit has been pushed to the remote repository. * **Configuration Options**: If you want to disable or enforce downloading of files, check the [DockerSettings](https://sdkdocs.zenml.io/latest/index.html#zenml.config.DockerSettings) for available options. * **Team Collaboration**: Using code repositories allows team members to reuse images that colleagues might have built for the same stack, enhancing collaboration efficiency. * **Build Selection**: ZenML automatically selects matching builds, but you can override this with explicit build IDs for special cases. ## Image Build Location By default, execution environments are created on your machine using a **local container engine** (see [Choosing the Container Engine](#choosing-the-container-engine) below). ZenML also offers [image builders](https://docs.zenml.io/stacks/image-builders), a special [stack component](https://docs.zenml.io/stacks), so you can build and push images in a different specialized _image builder environment_ instead. Note that even if you don't configure an image builder in your stack, ZenML still uses the [local image builder](https://docs.zenml.io/stacks/image-builders/local) to retain consistency across all builds. In this case, the image builder environment is the same as the [client environment](https://docs.zenml.io/user-guides/best-practices/configure-python-environments#client-environment-or-the-runner-environment). You don't need to directly interact with any image builder in your code. As long as the image builder that you want to use is part of your active [ZenML stack](https://docs.zenml.io/user-guides/production-guide/understand-stacks), it will be used automatically by any component that needs to build container images. ### Choosing the Container Engine ZenML uses the container engine (**Docker** or **Podman** currently) available on your machine for work that happens locally: building and pushing images with the [local image builder](https://docs.zenml.io/stacks/image-builders/local), preparing pipeline images before remote runs, and similar client-side steps. This is a global setting of your ZenML client. **Default behavior:** if you do not configure an engine, ZenML **auto-selects**: it tries **Docker** first, then **Podman** if Docker is not available. So on a typical developer laptop with Docker Desktop, **Docker is used** without any extra configuration. **To pin an engine**, use the environment variable **`ZENML_CONTAINER_ENGINE`** set to `docker` or `podman`. For example, to use Podman everywhere on that machine: ```shell export ZENML_CONTAINER_ENGINE=podman ``` Parts of ZenML that require the Docker daemon API (for example the [local Docker orchestrator](https://docs.zenml.io/stacks/orchestrators/local-docker)) still expect Docker; switching the global engine to Podman affects local image build/push and client-side image preparation, not those Docker-only features. More detail on credentials, subprocess builds, and prerequisites is on the [local image builder](https://docs.zenml.io/stacks/image-builders/local) page. ## Container User Permissions By default, Docker containers often run as the `root` user, which can pose security risks. ZenML allows you to specify a different user to run your containers: ```python docker_settings = DockerSettings(user="non-root-user") ``` When you set the `user` parameter: - The specified user will become the owner of the `/app` directory, which contains all your code - The container entrypoint will run as this user instead of root - This can help improve security by following the principle of least privilege ## Best Practices 1. **Use code repositories** to speed up builds and enable team collaboration. This approach is highly recommended for production environments. 2. **Keep dependencies minimal** to reduce build times. Only include packages you actually need. 3. **Use fine-grained Docker settings** at the step level for conflicting requirements. This prevents dependency conflicts and reduces image sizes. 4. **Use pre-built images** for common environments. This can significantly speed up your workflow. 5. **Configure dockerignore files** to reduce image size. Large Docker images take longer to build, push, and pull. 6. **Leverage build caching** by structuring your Dockerfiles and build processes to maximize cache hits. 7. **Use environment variables** for configuration instead of hardcoding values in your images. 8. **Test your Docker builds locally** before using them in production pipelines. 9. **Keep your repository clean** (no uncommitted changes) when running pipelines to ensure ZenML can correctly track code versions. 10. **Use metadata and labels** to help identify and manage your Docker images. 11. **Run containers as non-root users** when possible to improve security. By following these practices, you can optimize your Docker builds in ZenML and create a more efficient workflow.
ZenML Scarf
--- ### Book/How To/Contribute To Zenml/Implement A Custom Integration (docs/book/how-to/contribute-to-zenml/implement-a-custom-integration.md) --- description: Creating an external integration and contributing to ZenML icon: cubes --- # Custom Integration One of the main goals of ZenML is to find some semblance of order in the ever-growing MLOps landscape. ZenML already provides [numerous integrations](https://zenml.io/integrations) into many popular tools, and allows you to come up with ways to [implement your own stack component flavors](../../component-guide/contribute/custom-stack-component.md) in order to fill in any gaps that are remaining. _However, what if you want to make your extension of ZenML part of the main codebase, to share it with others?_ If you are such a person, e.g., a tooling provider in the ML/MLOps space, or just want to contribute a tooling integration to ZenML, this guide is intended for you. ### Step 1: Plan out your integration In [the previous page](../../component-guide/contribute/custom-stack-component.md), we looked at the categories and abstractions that core ZenML defines. In order to create a new integration into ZenML, you would need to first find the categories that your integration belongs to. The list of categories can be found [here](https://docs.zenml.io/stacks) as well. Note that one integration may belong to different categories: For example, the cloud integrations (AWS/GCP/Azure) contain [container registries](https://docs.zenml.io/stacks/container-registries), [artifact stores](https://docs.zenml.io/stacks/artifact-stores) etc. ### Step 2: Create individual stack component flavors Each category selected above would correspond to a [stack component type](https://docs.zenml.io/stacks). You can now start developing individual stack component flavors for this type by following the detailed instructions on the respective pages. Before you package your new components into an integration, you may want to use/test them as a regular custom flavor. For instance, if you are [developing a custom orchestrator](https://docs.zenml.io/stacks/orchestrators/custom) and your flavor class `MyOrchestratorFlavor` is defined in `flavors/my_flavor.py`, you can register it by using: ```shell zenml orchestrator flavor register flavors.my_flavor.MyOrchestratorFlavor ``` {% hint style="warning" %} ZenML resolves the flavor class by taking the path where you initialized zenml (via `zenml init`) as the starting point of resolution. Therefore, please ensure you follow [the best practice](https://docs.zenml.io/user-guides/best-practices/set-up-your-repository) of initializing zenml at the root of your repository. If ZenML does not find an initialized ZenML repository in any parent directory, it will default to the current working directory, but usually it's better to not have to rely on this mechanism, and initialize zenml at the root. {% endhint %} Afterward, you should see the new flavor in the list of available flavors: ```shell zenml orchestrator flavor list ``` See the docs on extensibility of the different components [here](https://docs.zenml.io/stacks) or get inspired by the many integrations that are already implemented such as [the MLflow experiment tracker](https://docs.zenml.io/stacks/experiment-trackers/mlflow). ### Step 3: Create an integration class Once you are finished with your flavor implementations, you can start the process of packaging them into your integration and ultimately the base ZenML package. Follow this checklist to prepare everything: **1. Clone Repo** Once your stack components work as a custom flavor, you can now [clone the main zenml repository](https://github.com/zenml-io/zenml) and follow the [contributing guide](https://github.com/zenml-io/zenml/blob/main/CONTRIBUTING.md) to set up your local environment for develop. **2. Create the integration directory** All integrations live within [`src/zenml/integrations/`](https://github.com/zenml-io/zenml/tree/main/src/zenml/integrations) in their own sub-folder. You should create a new folder in this directory with the name of your integration. An example integration directory would be structured as follows: ``` /src/zenml/integrations/ <- ZenML integration directory <- Root integration directory | ├── artifact-stores <- Separated directory for | ├── __init_.py every type | └── <- Implementation class for the | artifact store flavor ├── flavors | ├── __init_.py | └── <- Config class and flavor | └── __init_.py <- Integration class ``` **3. Define the name of your integration in constants** In [`zenml/integrations/constants.py`](https://github.com/zenml-io/zenml/blob/main/src/zenml/integrations/constants.py), add: ```python EXAMPLE_INTEGRATION = "" ``` This will be the name of the integration when you run: ```shell zenml integration install ``` **4. Create the integration class \_\_init\_\_.py** In `src/zenml/integrations//init__.py` you must now create a new class, which is a subclass of the `Integration` class, set some important attributes (`NAME` and `REQUIREMENTS`), and overwrite the `flavors` class method. ```python from typing import List, Type from zenml.integrations.constants import from zenml.integrations.integration import Integration from zenml.stack import Flavor # This is the flavor that will be used when registering this stack component # `zenml register ... -f example-orchestrator-flavor` EXAMPLE_ORCHESTRATOR_FLAVOR = <"example-orchestrator-flavor"> # Create a Subclass of the Integration Class class ExampleIntegration(Integration): """Definition of Example Integration for ZenML.""" NAME = REQUIREMENTS = [""] @classmethod def flavors(cls) -> List[Type[Flavor]]: """Declare the stack component flavors for the integration.""" from zenml.integrations. import return [] ExampleIntegration.check_installation() # this checks if the requirements are installed ``` Have a look at the [MLflow Integration](https://github.com/zenml-io/zenml/blob/main/src/zenml/integrations/mlflow/__init__.py) as an example for how it is done. **5. Import in all the right places** The Integration itself must be imported within [`src/zenml/integrations/__init__.py`](https://github.com/zenml-io/zenml/blob/main/src/zenml/integrations/__init__.py). ### Step 4: Create a PR and celebrate :tada: You can now [create a PR](https://github.com/zenml-io/zenml/compare) to ZenML and wait for the core maintainers to take a look. Thank you so much for your contribution to the codebase, rock on! 💜
ZenML Scarf
--- ### Book/How To/Dashboard/Dashboard Features (docs/book/how-to/dashboard/dashboard-features.md) --- description: Explore the features and capabilities of the ZenML dashboard icon: gauge-high --- The ZenML dashboard serves as a visual control center for your ML operations, offering intuitive interfaces to navigate pipelines, artifacts, models, and metadata. This guide offers a comprehensive overview of the dashboard's features, helping you leverage its full potential for monitoring, managing, and optimizing your machine learning workflows. ## Open Source Dashboard The open-source version of ZenML includes a robust set of dashboard features that provide significant value for individual practitioners and teams. ### Pipeline Visualization Options ZenML offers two complementary ways to visualize pipeline executions: the **DAG View** and the **Timeline View**. Each is optimized for different aspects of pipeline analysis, helping you understand both the structure and performance of your workflows. #### DAG View The DAG (Directed Acyclic Graph) view displays your pipeline as a network graph, showing how data flows between steps. It explicitly visualizes parallel branches, artifact connections, and the overall architecture of your workflow. This view is best for understanding pipeline architecture, tracing data lineage, and debugging dependency issues. While comprehensive, it can become visually dense in pipelines with a very large number of steps. #### Timeline View The Timeline View offers a Gantt chart-style visualization where each step is represented by a horizontal bar whose length corresponds to its execution duration. This view excels at performance analysis, making it easy to spot bottlenecks and understand the runtime characteristics of your pipeline. This view is ideal for performance optimization, identifying bottlenecks, and monitoring pipeline efficiency, especially for large pipelines. For pipelines with a high number of steps (e.g., over 100), ZenML automatically defaults to the Timeline View to ensure a responsive and clear user experience. These views are complementary and work best when used together. The DAG view helps you understand **what** your pipeline does and **how** it's structured, while the Timeline view shows you **when** things happen and **where** to focus optimization efforts. **Use the DAG View when you need to:** - Understand how data flows through your pipeline. - Debug issues related to step dependencies. - Explain the pipeline architecture to stakeholders. - Verify that parallel execution paths are configured correctly. **Use the Timeline View when you need to:** - Identify performance bottlenecks. - Optimize pipeline execution time. - Compare execution duration across steps. - Get a quick overview of which steps dominate runtime. ### Pipeline Run Management The dashboard maintains a comprehensive history of pipeline runs, allowing you to: - Browse through previous executions - Compare configurations across runs - Track changes in pipeline structure over time - Filter runs by status, name, or other attributes ### Artifact Visualization The dashboard provides built-in visualization capabilities for artifacts produced during pipeline execution. #### Automatic Data Type Visualizations Common data types receive automatic visualizations, including: - Pandas DataFrames displayed as interactive tables - NumPy arrays rendered as appropriate charts or heatmaps - Images shown directly in the browser - Text data formatted for readability #### Artifact Lineage Tracking The dashboard shows how artifacts are connected across pipeline steps, enabling you to: - Trace data transformations through your pipeline - Understand how intermediate outputs contribute to final results - Verify data flow through complex workflows ### Step Execution Details #### Logs and Outputs Access detailed logs for each step execution directly in the dashboard: - View standard output and error logs - Monitor execution progress - Troubleshoot errors with full context - Search through logs to identify specific events #### Runtime Metrics Monitor runtime performance metrics for each step: - Execution duration - Resource utilization patterns - Start and end timestamps - Cache hit/miss information ### Stack and Component Management The dashboard provides a visual interface for managing your ZenML infrastructure through stacks and components. This graphical approach to MLOps infrastructure management simplifies what would otherwise require complex CLI commands or code. #### Stack Creation and Configuration Creating ML infrastructure stacks through the dashboard is intuitive and visual. The interface guides you through selecting compatible components and configuring their settings. You can see the entire stack architecture at a glance, making it easier to understand the relationships between different infrastructure pieces. When building a stack, the dashboard helps you browse available components by category and suggests compatible options. Once created, stacks can be shared with team members, enabling consistent infrastructure across your organization. #### Component Registration The dashboard streamlines the process of registering individual components like orchestrators, artifact stores, and container registries. Instead of writing configuration code, you can use form-based interfaces to set up each component. The UI helps connect components to appropriate service connectors and validates settings before saving. This visual approach to component management reduces configuration errors and simplifies the setup process, especially for team members who may not be familiar with the underlying infrastructure details. ### Integration-Specific Visualizations The dashboard supports specialized visualizations for outputs from popular integrations: - Evidently reports as interactive HTML - Great Expectations validation results with detailed insights - WhyLogs profile visualizations - Confusion matrices and classification reports - Custom visualization components for specialized data types ## ZenML Pro Dashboard ZenML Pro extends the open-source dashboard with additional capabilities for collaboration, governance, and workflow management. For a broader overview of ZenML Pro features, see [ZenML Pro](https://www.zenml.io/pro). For a side-by-side comparison of ZenML OSS vs ZenML Pro, see [ZenML Open Source vs Pro](https://www.zenml.io/open-source-vs-pro). This page focuses on Pro features that surface in the dashboard UI. ZenML Pro also includes platform capabilities that are not specific to the dashboard. ### Projects and Access Management ZenML Pro introduces organizations, workspaces, teams, and projects. **Projects** are the main way to organize related pipelines, runs, artifacts, and models and to separate concerns across teams or environments. In the Pro dashboard, you can create and manage projects, switch project context, and view assets scoped to the active project. #### Organization and Team Structure - **Organizations**: Top-level entities containing users, teams, and workspaces - **Teams**: Groups of users with assigned roles for simplified permission management - **Workspaces**: Isolated ZenML deployments with separate resources - **Projects**: Logical subdivisions for organizing related ML assets #### Role Management The dashboard provides intuitive interfaces for managing roles at different levels: - **Organization roles**: Admin, Manager, Viewer, Billing Admin, Member - **Workspace roles**: Admin, Developer, Contributor, Viewer, Stack Admin - **Project roles**: Admin, Developer, Contributor, Viewer - **Custom roles**: Create roles with fine-grained permissions #### Access Control UI The dashboard makes it easy to: - Configure user and team permissions - Manage resource sharing - Implement least-privilege access policies - Review and audit access rights - Visualize permission hierarchies ### Pipeline Snapshots A **pipeline snapshot** is an immutable, runnable representation of a pipeline that captures its structure and configuration. Snapshots enable teams to run pipelines from the dashboard without direct access to the codebase and help standardize execution across different users and environments. To learn how to create and run snapshots from the dashboard, see [Pipeline Snapshots](../snapshots/snapshots.md). ### Artifact Control Plane ZenML Pro provides a sophisticated artifact control plane that enhances your ability to manage and understand data flowing through your pipelines. #### Comprehensive Metadata Management The Pro dashboard transforms how you interact with pipeline and model metadata through its powerful exploration tools. When examining ML workflows, metadata provides crucial context about performance metrics, parameters, and execution details. With the dashboard, you can browse the full set of metadata attributes and apply filters to focus on specific metrics. The interface tracks historical changes to these values, making it easy to understand how your models evolve over time. Customizable metadata views adapt to different analysis needs, whether you're comparing accuracy across runs or examining resource utilization patterns. This metadata visualization integrates seamlessly with artifact lineage tracking, creating a complete picture of your ML workflow from inputs to outputs. ```python from zenml import step, log_metadata, get_step_context @step def evaluate(): # Log metrics that will be visualized in the dashboard log_metadata( metadata={ "accuracy": 0.95, "precision": 0.92, "recall": 0.91, "f1_score": 0.93 } ) ``` ### Model Control Plane The Model Control Plane provides centralized model management capabilities designed for production ML workflows that allows you to have: - Clear visualization of model version history - Detailed comparisons between versions - Performance metrics for each version - Linkage to generating pipelines and input artifacts Additionally, the Pro dashboard allows you to manage model lifecycle stages: - Move models between stages (latest, staging, production, archived) - Track transition history and approvals - Configure automated promotion rules - Monitor model status across environments ### Experiment Comparison Tools ZenML Pro offers powerful tools for comparing experiments and understanding the relationships between different runs. #### Table View Comparisons Compare metadata, configurations, and outcomes across runs: - Side-by-side comparison of metrics - Highlight differences between runs - Sort and filter by any attribute - Export comparison data for further analysis #### Parallel Coordinates Visualization Understand complex relationships between parameters and outcomes: - Visualize multiple dimensions simultaneously - Identify patterns and correlations - Filter runs interactively - Focus on specific parameter ranges {% hint style="info" %} For the complete ZenML Pro feature set (including capabilities that are not dashboard-specific), see [ZenML Pro](https://www.zenml.io/pro) and [ZenML Open Source vs Pro](https://www.zenml.io/open-source-vs-pro). {% endhint %}
ZenML Scarf
--- ### Book/How To/Deployment/Deployment (docs/book/how-to/deployment/deployment.md) --- description: Deploy pipelines as HTTP services for real-time execution icon: rocket-launch --- # Pipeline Deployment Pipeline deployment allows you to run ZenML pipelines as long-running HTTP services for real-time execution, rather than traditional batch mode execution. This enables you to invoke pipelines through HTTP requests and receive immediate responses. ## What is a Pipeline Deployment? A pipeline deployment is a long-running HTTP server that wraps your pipeline for real-time, request-response interactions. While traditional (batch) pipeline execution (via orchestrators) is ideal for scheduled batch processing, data transformations, and offline training workflows, deployments are designed for scenarios where you need immediate responses - like serving predictions to a web app, processing user requests, or powering interactive AI agents. Deployments create persistent services that stay running and can handle multiple concurrent requests through HTTP endpoints. When you deploy a pipeline, ZenML creates an HTTP server (called a **Deployment**) that can execute your pipeline multiple times in parallel by invoking HTTP endpoints. ## Deployments vs Snapshots ZenML offers two distinct ways to execute pipelines beyond direct Python calls. They serve different operating models: | | **Deployments** | **Snapshots** | |---|---|---| | **What it is** | A long-running HTTP service wrapping your pipeline | A batch-style pipeline run triggered on demand | | **Stable URL** | Yes — persistent endpoint you can call repeatedly | No — each run is a one-off server-managed job | | **Response model** | Synchronous request/response with pipeline outputs | Fire-and-forget (poll for run status) | | **Custom endpoints, auth, middleware** | Yes — full ASGI app customization | No | | **Persistent state across calls** | Yes — `on_init` hooks, shared model loading | No | | **Best for** | Real-time inference, agents, interactive APIs | Scheduled retraining, batch jobs, CI/CD triggers | | **Availability** | OSS and Pro | Running snapshots requires Pro | {% hint style="info" %} Use **Deployments** when you need an always-on HTTP service (inference APIs, agents, interactive workflows). Use **Snapshots** when you need to trigger batch pipeline runs from a UI, SDK, or external system without maintaining a running service. See the [Snapshots documentation](../snapshots/snapshots.md) for more details. {% endhint %} ## Common Use Cases Pipeline deployments are ideal for scenarios requiring real-time, on-demand execution of ML workflows: **Online ML Inference**: Deploy trained models as HTTP services for real-time predictions, such as fraud detection in payment systems, recommendation engines for e-commerce, or image classification APIs. Pipeline deployments handle feature preprocessing, model loading, and prediction logic while managing concurrent requests efficiently. **LLM Agent Workflows**: Build intelligent agents that combine multiple AI capabilities like intent analysis, retrieval-augmented generation (RAG), and response synthesis. These deployments can power chatbots, customer support systems, or document analysis services that require multi-step reasoning and context retrieval. See the [Agent Outer Loop](https://github.com/zenml-io/zenml/tree/main/examples/agent_outer_loop) and [Deploying Agents](https://github.com/zenml-io/zenml/tree/main/examples/deploying_agent) examples for practical implementations. **Real-time Data Processing**: Process streaming events or user interactions that require immediate analysis and response, such as real-time analytics dashboards, anomaly detection systems, or personalization engines. **Multi-step Business Workflows**: Orchestrate complex processes involving multiple AI/ML components, like document processing pipelines that combine OCR, entity extraction, sentiment analysis, and classification into a single deployable service. ## Traditional Model Serving vs. Deployed Pipelines If you're reaching for tools like Seldon or KServe, consider this: deployed pipelines give you all the core serving primitives, plus the power of a full application runtime. - Equivalent functionality: A pipeline handles the end-to-end inference path out of the box — request validation, feature pre-processing, model loading and inference, post-processing, and response shaping. - More flexible: Deployed pipelines are unopinionated, so you can layer in retrieval, guardrails, rules, A/B routing, canary logic, human-in-the-loop, or any custom orchestration. You're not constrained by a model-server template. - More customizable: The deployment is a real ASGI app. Tailor endpoints, authentication, authorization, rate limiting, structured logging, tracing, correlation IDs, or SSO/OIDC — all with first-class middleware and framework-level hooks. - More features: Serve single-page apps alongside the API. Ship admin/ops dashboards, experiment playgrounds, model cards, or customer-facing UIs from the very same deployment for tighter operational feedback loops. This approach aligns better with production realities: inference is rarely "just call a model." There are policies, data dependencies, and integrations that need a programmable, evolvable surface. Deployed pipelines give you that without sacrificing the convenience of a managed deployer and a clean HTTP contract. {% hint style="info" %} Deprecation notice: ZenML is phasing out the Model Deployer stack components in favor of pipeline deployments. Pipeline deployments are the strategic direction for real-time serving: they are more dynamic, more extensible, and offer deeper integration points with your security, observability, and product requirements. Existing model deployers will continue to function during the transition period, but new investments will focus on pipeline deployments. {% endhint %} ## How Deployments Work To deploy a pipeline or snapshot, a **Deployer** stack component needs to be in your active stack. You can use the default stack, which has a default local deployer that will deploy the pipeline directly on your local machine as a background process: ```bash zenml stack set default ``` or set up a new stack with a deployer in it: ```bash zenml deployer register --flavor= zenml stack update -d ``` The [**Deployer** stack component](https://docs.zenml.io/stacks/stack-components/deployers) manages the deployment of pipelines as long-running HTTP servers. It integrates with a specific infrastructure back-end like Docker, AWS App Runner, GCP Cloud Run etc., in order to implement the following functionalities: - Creating and managing persistent containerized services - Exposing HTTP endpoints for pipeline invocation - Managing the lifecycle of deployments (creation, updates, deletion) - Providing connection information and management commands {% hint style="info" %} The **Deployer** and **Model Deployer** represent distinct stack components with slightly overlapping responsibilities. The **Deployer** component orchestrates the deployment of arbitrary pipelines as persistent HTTP services, while the **Model Deployer** component focuses exclusively on the deployment and management of ML models for real-time inference scenarios. The **Deployer** component can easily accommodate ML model deployment through deploying ML inference pipelines. This approach provides enhanced flexibility for implementing custom business logic and preprocessing workflows around the deployed model artifacts. Conversely, specialized **Model Deployer** integrations may offer optimized deployment strategies, superior performance characteristics, and resource utilization efficiencies that exceed the capabilities of general-purpose pipeline deployments. When deciding which component to use, consider the trade-offs between how much control you need over the deployment process and how much you want to offload to a particular integration specialized for ML model serving. {% endhint %} With a **Deployer** stack component in your active stack, a pipeline or snapshot can be deployed using the ZenML CLI: ```bash # Deploy the pipeline `weather_pipeline` in the `weather_agent` module as a # deployment named `my_deployment` zenml pipeline deploy weather_agent.weather_pipeline --name my_deployment # Deploy a snapshot named `weather_agent_snapshot` as a deployment named # `my_deployment` zenml pipeline snapshot deploy weather_agent_snapshot --deployment my_deployment ``` To deploy a pipeline using the ZenML SDK: ```python from zenml.pipeline import pipeline @pipeline def weather_agent(city: str = "Paris", temperature: float = 20) -> str: return process_weather(city=city, temperature=temperature) # Deploy the pipeline `weather_agent` as a deployment named `my_deployment` deployment = weather_agent.deploy(deployment_name="my_deployment") print(f"Deployment URL: {deployment.url}") ``` It is also possible to deploy snapshots programmatically: ```python from zenml.client import Client client = Client() snapshot = client.get_snapshot(snapshot_name_or_id="weather_agent_snapshot") # Deploy the snapshot `weather_agent_snapshot` as a deployment named # `my_deployment` deployment = client.provision_deployment( name_id_or_prefix="my_deployment", snapshot_id=snapshot.id, ) print(f"Deployment URL: {deployment.url}") ``` Once deployed, a pipeline can be invoked through the URL exposed by the deployment. Every invocation of the deployment will create a new pipeline run. The ZenML CLI provides a convenient command to invoke a deployment: ```bash zenml deployment invoke my_deployment --city="London" --temperature=20 ``` which is the equivalent of the following HTTP request: ```bash curl -X POST http://localhost:8000/invoke \ -H "Content-Type: application/json" \ -d '{"parameters": {"city": "London", "temperature": 20}}' ``` ## Deployment Lifecycle Once a Deployment is created, it is tied to the specific **Deployer** stack component that was used to provision it and can be managed independently of the active stack as a standalone entity with its own lifecycle. A Deployment contains the following key information: - **`name`**: Unique deployment name within the project - **`url`**: HTTP endpoint URL where the deployment can be accessed - **`status`**: Current deployment status. This can take one of the following values `DeploymentStatus` enum values: - **`RUNNING`**: The deployment is running and accepting HTTP requests - **`ABSENT`**: The deployment is not currently provisioned - **`PENDING`**: The deployment is currently undergoing some operation (e.g. being created, updated or deleted) - **`ERROR`**: The deployment is in an error state. When in this state, more information about the error can be found in the ZenML logs, the Deployment `metadata` field or in the Deployment logs. - **`UNKNOWN`**: The deployment is in an unknown state - **`metadata`**: Deployer-specific metadata describing the deployment's operational state ### Managing Deployments To list all the deployments managed in your project by all the available Deployers: ```bash zenml deployment list ``` This shows a table with deployment details: ``` ╭──────────────────────┬────────────────────────┬──────────────────────┬───────────────────────┬───────────┬─────────────────┬─────────────────╮ │ NAME │ PIPELINE │ SNAPSHOT │ URL │ STATUS │ STACK │ OWNER │ ├──────────────────────┼────────────────────────┼──────────────────────┼───────────────────────┼───────────┼─────────────────┼─────────────────┤ │ zenpulse-endpoint │ zenpulse_agent │ │ http://localhost:8000 │ ⚙ RUNNING │ aws-stack │ hamza@zenml.io │ ├──────────────────────┼────────────────────────┼──────────────────────┼───────────────────────┼───────────┼─────────────────┼─────────────────┤ │ docker-weather-agent │ weather_agent_pipeline │ docker-weather-agent │ http://localhost:8000 │ ⚙ RUNNING │ docker-deployer │ stefan@zenml.io │ ├──────────────────────┼────────────────────────┼──────────────────────┼───────────────────────┼───────────┼─────────────────┼─────────────────┤ │ weather_agent │ weather_agent │ │ http://localhost:8001 │ ⚙ RUNNING │ docker-deployer │ stefan@zenml.io │ ╰──────────────────────┴────────────────────────┴──────────────────────┴───────────────────────┴───────────┴─────────────────┴─────────────────╯ ``` Detailed information about a specific deployment can be obtained with the following command: ```bash zenml deployment describe weather_agent ``` This provides comprehensive deployment details, including its state and access information: ``` 🚀 Deployment: weather_agent is: RUNNING ⚙ Pipeline: weather_agent Snapshot: 0866c821-d73f-456d-a98d-9aa82f41282e Stack: docker-deployer 📡 Connection Information: Endpoint URL: http://localhost:8001 Swagger URL: http://localhost:8001/docs CLI Command Example: zenml deployment invoke weather_agent --city="London" cURL Example: curl -X POST http://localhost:8001/invoke \ -H "Content-Type: application/json" \ -d '{ "parameters": { "city": "London" } }' ⚙️ Management Commands ╭────────────────────────────────────────────┬─────────────────────────────────────────────────────╮ │ zenml deployment logs weather_agent -f │ Follow deployment logs in real-time │ │ zenml deployment describe weather_agent │ Show detailed deployment information │ │ zenml deployment deprovision weather_agent │ Deprovision this deployment and keep a record of it │ │ zenml deployment delete weather_agent │ Deprovision and delete this deployment │ ╰────────────────────────────────────────────┴─────────────────────────────────────────────────────╯ ``` {% hint style="info" %} Additional information regarding the deployment can be shown with the same command: * schema information about the deployment's input and output * backend-specific metadata information about the deployment * authentication information, if present {% endhint %} Deploying or redeploying a pipeline or snapshot on top of an existing deployment will update the deployment in place: ```bash # Update the existing deployment named `my_deployment` with a new pipeline # code version zenml pipeline deploy weather_agent.weather_pipeline --name my_deployment --update # Update the existing deployment named `my_deployment` with a new snapshot # named `other_weather_agent_snapshot` zenml deployment provision my_deployment --snapshot other_weather_agent_snapshot ``` {% hint style="warning" %} **Deployment update checks and limitations** - Updating a deployment owned by a different user requires additional confirmation. This is to avoid unintentionally updating someone else's deployment. - An existing deployment cannot be updated using a stack different from the one it was originally deployed with. - A pipeline snapshot can only have one deployment running at a time. You cannot deploy the same snapshot multiple times. You either have to delete the existing deployment and deploy the snapshot again or create a different snapshot. {% endhint %} Deprovisioning and deleting a deployment are two different operations. Deprovisioning a deployment keeps a record of it in the ZenML database so that it can be easily restored later if needed. Deleting a deployment completely removes it from the ZenML store: ```bash # Deprovision the deployment named `my_deployment` zenml deployment deprovision my_deployment # Re-provision the deployment named `my_deployment` with the same configuration as before zenml deployment provision my_deployment # Deprovision and delete the deployment named `my_deployment` zenml deployment delete my_deployment ``` {% hint style="warning" %} **Deployer deletion** A Deployer stack component cannot be deleted as long as there is at least one deployment managed by it that is not in an `ABSENT` state. To delete a Deployer stack component, you need to first deprovision or delete all the deployments managed by it. If some deployments are stuck in an `ERROR` state, you can use the `--force` flag to delete them without the need to deprovision them first, but be aware that this may leave some infrastructure resources orphaned. {% endhint %} The server logs of a deployment can be accessed with the following command: ```bash zenml deployment logs my_deployment ``` ## Deployable Pipeline Requirements While any pipeline can technically be deployed, following these guidelines ensures practical usability: ### Pipeline Input Parameters Pipelines should accept explicit parameters to enable dynamic invocation: ```python @pipeline def weather_agent(city: str = "Paris", temperature: float = 20) -> str: return process_weather(city=city, temperature=temperature) ``` {% hint style="info" %} **Input Parameter Requirements:** - All pipeline input parameters must have default values. This is a current limitation of the deployment mechanism. - Input parameters must use JSON-serializable data types (`int`, `float`, `str`, `bool`, `list`, `dict`, `tuple`, Pydantic models). Other data types are not currently supported and will result in an error when deploying the pipeline. - Pipeline input parameter names must match step parameter names. E.g. if the pipeline has an input parameter named `city` that is passed to a step input argument, that step argument must also be named `city`. {% endhint %} When deployed, the example pipeline above can be invoked: * with a CLI command like the following: ```bash zenml deployment invoke my_pipeline --city=Paris --temperature=20 ``` * or with an HTTP request like the following: ```bash curl -X POST http://localhost:8000/invoke \ -H "Content-Type: application/json" \ -d '{"parameters": {"city": "Paris", "temperature": 20}}' ``` {% hint style="warning" %} Pipeline input parameters behave differently when pipelines are deployed than when they are run as a batch job. When running a parameterized pipeline, its input parameters are evaluated before the pipeline run even starts and can be used to configure the structure of the pipeline DAG. When invoking a deployment, the input parameters do not have an effect on the pipeline DAG structure, so a pipeline like the following will not work as expected: ```python @pipeline def switcher( mode: str = "analyze", city: str = "Paris", topic: str = "ML", ) -> str: return ( analyze(city) if mode == "analyze" else generate(topic) ) # this will always use the "analyze" step when deploying the pipeline ``` {% endhint %} ### Pipeline Outputs Pipelines should return meaningful values for useful HTTP responses: ```python @step def process_weather(city: str, temperature: float) -> Annotated[str, "weather_analysis"]: return f"The weather in {city} is {temperature} degrees Celsius." @pipeline def weather_agent(city: str = "Paris", temperature: float = 20) -> str: weather_analysis = process_weather(city=city, temperature=temperature) return weather_analysis ``` {% hint style="info" %} **Output Requirements:** - Return values must be step outputs. - Return values must be JSON-serializable (`int`, `float`, `str`, `bool`, `list`, `dict`, `tuple`, Pydantic models). Other data types are not currently supported and will result in an error when deploying the pipeline. - The names of the step output artifacts determine the response structure (see example below) - For clashing output names, the naming convention used to differentiate them is `.` {% endhint %} Invoking a deployment of this pipeline will return the response below. Note how the `outputs` field contains the value returned by the `process_weather` step and the name of the output artifact is used as the key. ```json { "success": true, "outputs": { "weather_analysis": "The weather in Utopia is 25 degrees Celsius" }, "execution_time": 8.160255432128906, "metadata": { "deployment_id": "e0b34be2-d743-4686-a45b-c12e81627bbe", "deployment_name": "weather_agent", "snapshot_id": "0866c821-d73f-456d-a98d-9aa82f41282e", "snapshot_name": null, "pipeline_name": "weather_agent", "run_id": "f2e9a3a7-afa3-459e-a970-8558358cf1fb", "run_name": "weather_agent-2025_09_29-14_09_55_726165", "parameters_used": { "city": "Utopia", "temperature": 25 } }, "error": null } ``` ### Deployment Authentication A rudimentary form of HTTP Basic authentication can be enabled for deployments by configuring one of two deployer configuration options: * `generate_auth_key`: set to `True` to automatically generate a shared secret key for the deployment. This is not set by default. * `auth_key`: configure the shared secret key manually. ```python @pipeline( settings={ "deployer": { "generate_auth_key": True, } } ) def weather_agent(city: str = "Paris", temperature: float = 20) -> str: return process_weather(city=city, temperature=temperature) ``` Deploying the above pipeline automatically generates and returns a key that will be required in the `Authorization` header of HTTP requests made to the deployment: ```bash curl -X POST http://localhost:8000/invoke \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"parameters": {"city": "Paris", "temperature": 20}}' ``` ## Deployment Initialization, Cleanup and State It often happens that the HTTP requests made to the same deployment share some type of initialization or cleanup or need to share the same global state or. For example: * a machine learning model needs to be loaded in memory, initialized and then shared between all the HTTP requests made to the deployment in order to be used by the deployed pipeline to make predictions * a database client must be initialized and shared across all the HTTP requests made to the deployment in order to read and write data To achieve this, it is possible to configure custom initialization and cleanup hooks for the pipeline being deployed: ```python def init_llm(model_name: str): # Initialize and store the LLM in memory when the deployment is started, to # be shared by all the HTTP requests made to the deployment return LLM(model_name=model_name) def cleanup_llm(llm: LLM): # Cleanup the LLM when the deployment is stopped llm.cleanup() @step def process_weather(city: str, temperature: float) -> Annotated[str, "weather_analysis"]: step_context = get_step_context() # The value returned by the on_init hook is stored in the pipeline state llm = step_context.pipeline_state return generate_llm_response(llm, city, temperature) @pipeline( on_init=init_llm, on_cleanup=cleanup_llm, ) def weather_agent(city: str = "Paris", temperature: float = 20) -> str: return process_weather(city=city, temperature=temperature) weather_agent_deployment = weather_agent.with_options( on_init_kwargs={"model_name": "gpt-4o"}, ).deploy(deployment_name="my_deployment") ``` The following happens when the pipeline is deployed and then later invoked: 1. The on_init hook is executed only once, when the deployment is started 2. The value returned by the on_init hook is stored in memory in the deployment and can be accessed by pipeline steps using the `pipeline_state` property of the step context 3. The on_cleanup hook is executed only once, when the deployment is stopped This mechanism can be used to initialize and share global state between all the HTTP requests made to the deployment or to execute long-running initialization or cleanup operations when the deployment is started or stopped rather than on each HTTP request. ## Deployment Configuration The deployer settings cover aspects of the pipeline deployment process and specific back-end infrastructure used to provision and manage the resources required to run the deployment servers. Independently of that, `DeploymentSettings` can be used to fully customize all aspects pertaining to the deployment ASGI application itself, including: * HTTP endpoints * middleware * secure headers * CORS settings * mounting and serving static files to support deploying single-page applications alongside the pipeline * for more advanced cases, even the ASGI framework (e.g. FastAPI, Django, Flask, Falcon, Quart, BlackSheep, etc.) and its configuration can be customized Example: ```python from zenml.config import DeploymentSettings, EndpointSpec, EndpointMethod from zenml import pipeline async def custom_health_check() -> Dict[str, Any]: from zenml.client import Client client = Client() return { "status": "healthy", "info": client.zen_store.get_store_info().model_dump(), } @pipeline(settings={"deployment": DeploymentSettings( custom_endpoints=[ EndpointSpec( path="/health", method=EndpointMethod.GET, handler=custom_health_check, auth_required=False, ), ], )}) def my_pipeline(): ... ``` For more detailed information on deployment options, see the [deployment settings guide](./deployment_settings.md). ## Best Practices 1. **Design for Parameters**: Structure your pipelines to accept meaningful parameters that control behavior 2. **Provide Default Values**: Ensure all parameters have sensible defaults 3. **Return Useful Data**: Design pipeline outputs to provide meaningful responses 4. **Use Type Annotations**: Leverage Pydantic models for complex parameter types 5. **Use Global Initialization and State**: Use the `on_init` and `on_cleanup` hooks along with the `pipeline_state` step context property to initialize and share global state between all the HTTP requests made to the deployment. Also use these hooks to execute long-running initialization or cleanup operations when the deployment is started or stopped rather than on each HTTP request. 5. **Handle Errors Gracefully**: Implement proper error handling in your steps 6. **Test Locally First**: Validate your deployable pipeline locally before deploying to production ## Conclusion Pipeline deployment transforms ZenML pipelines from batch processing workflows into real-time services. By following the guidelines for deployable pipelines and understanding the deployment lifecycle, you can create robust, scalable ML services that integrate seamlessly with web applications and real-time systems. See also: - [Steps & Pipelines](../steps-pipelines/steps_and_pipelines.md) - Core building blocks - [Deployer Stack Component](../../component-guide/deployers/README.md) - The stack component that manages the deployment of pipelines as long-running HTTP servers
ZenML Scarf
--- ### Book/How To/Deployment/Deployment Settings (docs/book/how-to/deployment/deployment_settings.md) --- description: Customize the pipeline deployment ASGI application with DeploymentSettings. --- ## Deployment servers and ASGI apps ZenML pipeline deployments run an ASGI application under a production-grade `uvicorn` server. This makes your pipelines callable over HTTP for online workloads like real-time ML inference, LLM agents/workflows, and even full web apps co-located with pipelines. At runtime, three core components work together: - the ASGI application: the HTTP surface that exposes endpoints (health, invoke, metrics, docs) and any custom routes or middleware you configure. This is powered by an ASGI framework like FastAPI, Starlette, Django, Flask, etc. - the ASGI application factory (aka the Deployment App Runner): this component is responsible for constructing the ASGI application piece by piece based on the instructions provided by users via runtime configuration. - the Deployment Service: the component responsible for the business logic that backs the pipeline deployment and its invocation lifecycle. Both the Deployment App Runner and the Deployment Service are customizable at runtime, through the `DeploymentSettings` configuration mechanism. They can also be extended via inheritance to support different ASGI frameworks or to tweak existing functionality. The `DeploymentSettings` class lets you shape both server behavior and the ASGI app composition without changing framework code. Typical reasons to customize include: - Tight security posture: CORS controls, strict headers, authentication, API surface minimization. - Observability: request/response logging, tracing, metrics, correlation identifiers. - Enterprise integration: policy gateways, SSO/OIDC/OAuth, audit logging, routing and network architecture constraints. - Product UX: single-page application (SPA) static files served alongside deployment APIs or custom docs paths. - Performance/SRE: thread pool sizing, uvicorn worker settings, log levels, max request sizes and platform-specific fine-tuning. All `DeploymentSettings` are pipeline-level settings. They apply to the deployment that serves the pipeline as a whole. They are not available at step-level. ## Configuration overview You can configure `DeploymentSettings` in Python or via YAML, the same way as other settings classes. The settings can be attached to a pipeline decorator or via `with_options`. These settings are only valid at pipeline level. ### Python configuration Use the `DeploymentSettings` class to configure the deployment settings for your pipeline in-code ```python from zenml import pipeline from zenml.config import DeploymentSettings deploy_settings = DeploymentSettings( app_title="Fraud Scoring Service", app_description=( "Online scoring API exposing synchronous and batch inference" ), app_version="1.2.0", root_url_path="", api_url_path="", docs_url_path="/docs", redoc_url_path="/redoc", invoke_url_path="/invoke", health_url_path="/health", info_url_path="/info", metrics_url_path="/metrics", cors={ "allow_origins": ["https://app.example.com"], "allow_methods": ["GET", "POST", "OPTIONS"], "allow_headers": ["*"], "allow_credentials": True, }, thread_pool_size=32, uvicorn_host="0.0.0.0", uvicorn_port=8080, uvicorn_workers=2, ) @pipeline(settings={"deployment": deploy_settings}) def scoring_pipeline() -> None: ... # Alternatively scoring_pipeline = scoring_pipeline.with_options( settings={"deployment": deploy_settings} ) ``` ### YAML configuration Define settings in a YAML configuration file for better separation of code and configuration: ```yaml settings: deployment: app_title: Fraud Scoring Service app_description: >- Online scoring API exposing synchronous and batch inference app_version: "1.2.0" root_url_path: "" api_url_path: "" docs_url_path: "/docs" redoc_url_path: "/redoc" invoke_url_path: "/invoke" health_url_path: "/health" info_url_path: "/info" metrics_url_path: "/metrics" cors: allow_origins: ["https://app.example.com"] allow_methods: ["GET", "POST", "OPTIONS"] allow_headers: ["*"] allow_credentials: true thread_pool_size: 32 uvicorn_host: 0.0.0.0 uvicorn_port: 8080 uvicorn_workers: 2 ``` Check out [this page](https://docs.zenml.io/concepts/steps_and_pipelines/configuration) for more information on the hierarchy and precedence of the various ways in which you can supply the settings. ## Basic customization options `DeploymentSettings` expose the following basic customization options. The sections below provide short examples and guidance. - application metadata and paths - built-in endpoints and middleware toggles - static files (SPAs) and dashboards - CORS - secure headers - startup and shutdown hooks - uvicorn server options, logging level, and thread pool size ### Application metadata You can set `app_title`, `app_description`, and `app_version` to be reflected in the ASGI application's metadata: ```python from zenml.config import DeploymentSettings settings = DeploymentSettings( app_title="LLM Agent Service", app_description=( "Agent endpoints for tools, state inspection, and tracing" ), app_version="0.7.0", ) ``` ### Default URL paths, endpoints and middleware The ASGI application exposes the following built-in endpoints by default: * documentation endpoints: * `/docs` - The OpenAPI documentation UI generated based on the endpoints and their signatures. * `/redoc` - The ReDoc documentation UI generated based on the endpoints and their signatures. * REST API endpoints: * `/invoke` - The main pipeline invocation endpoint for synchronous inference. * `/health` - The health check endpoint. * `/info` - The info endpoint providing extensive information about the deployment and its service. * `/metrics` - Simple metrics endpoint. * dashboard endpoints - present only if the accompanying UI is enabled: * `/`, `/index.html`, `/static` - Endpoints for serving the dashboard files from the `dashboard_files_path` directory. The ASGI application includes the following built-in middleware by default: * secure headers middleware: for setting security headers. * CORS middleware: for handling CORS requests. You can include or exclude these default endpoints and middleware either globally or individually by setting the `include_default_endpoints` and `include_default_middleware` settings. It is also possible to remap the built-in endpoint URL paths. ```python from zenml.config import ( DeploymentSettings, DeploymentDefaultEndpoints, DeploymentDefaultMiddleware, ) settings = DeploymentSettings( # Include only the endpoints you need include_default_endpoints=( DeploymentDefaultEndpoints.DOCS | DeploymentDefaultEndpoints.INVOKE | DeploymentDefaultEndpoints.HEALTH ), # Customize the root URL path root_url_path="/pipeline", # Include only the middleware you need include_default_middleware=DeploymentDefaultMiddleware.CORS, # Customize the base API URL path used for all REST API endpoints api_url_path="/api", # Customize the documentation URL path docs_url_path="/documentation", # Customize the health check URL path health_url_path="/healthz", ) ``` With the above settings, the ASGI application will only expose the following endpoints and middleware: - `/pipeline/documentation` - The API documentation (OpenAPI schema) - `/pipeline/api/invoke` - The REST API pipeline invocation endpoint - `/pipeline/api/healthz` - The REST API health check endpoint - CORS middleware: for handling CORS requests ### Static files (single-page applications) Deployed pipelines can serve full single-page applications (React/Vue/Svelte) from the same origin as your inference API. This eliminates CORS/auth/routing friction and lets you ship user-facing UI components alongside your endpoints, such as: * operator dashboards * governance portals * experiment browsers * feature explorers * custom data labeling interfaces * model cards * observability dashboards * customer-facing playgrounds Co-locating UI and API streamlines delivery (one image, one URL, one CI/CD), improves latency, and keeps telemetry and auth consistent. To enable this, point `dashboard_files_path` to a directory containing an `index.html` and any static assets. The path must be relative to the [source root](../steps-pipelines/sources.md#source-root): ```python settings = DeploymentSettings( dashboard_files_path="web/build" # contains index.html and assets/ ) ``` A rudimentary playground dashboard is included with the ZenML python package that features a simple UI useful for sending pipeline invocations and viewing the pipeline's response. {% hint style="info" %} When supplying your own custom dashboard, you may also need to [customize the security headers](./deployment_settings.md#secure-headers) to allow the dashboard to access various resources. For example, you may want to tweak the `Content-Security-Policy` header to allow the dashboard to access external javascript libraries, images, etc. {% endhint %} #### Jinja2 templates You can use a Jinja2 template to dynamically generate the `index.html` file that hosts the single-page application. This is useful if you want to dynamically generate the dashboard files based on the pipeline configuration, step configuration or stack configuration. A `service_info` variable is passed to the template that contains the service information, such as the service name, version, and description. This variable has the same structure as the `zenml.deployers.server.models.ServiceInfo` model. Example: ```jinja2 Pipeline: {{ service_info.pipeline.pipeline_name }}

Pipeline: {{ service_info.pipeline.pipeline_name }}

Deployment: {{ service_info.deployment.name }}

``` ### CORS Fine-tune cross-origin access: ```python from zenml.config import DeploymentSettings, CORSConfig settings = DeploymentSettings( cors=CORSConfig( allow_origins=["https://app.example.com", "https://admin.example.com"], allow_methods=["GET", "POST", "OPTIONS"], allow_headers=["authorization", "content-type", "x-request-id"], allow_credentials=True, ) ) ``` ### Secure headers Harden responses with strict headers. Each field supports either a boolean or string. Using `True` selects a safe default, `False` disables the header, and custom strings allow fully custom policies: ```python from zenml.config import ( DeploymentSettings, SecureHeadersConfig, ) settings = DeploymentSettings( secure_headers=SecureHeadersConfig( server=True, # emit default ZenML server header value hsts=True, # default: 63072000; includeSubdomains xfo=True, # default: SAMEORIGIN content=True, # default: nosniff csp=( "default-src 'none'; connect-src 'self' https://api.example.com; " "img-src 'self' data:; style-src 'self' 'unsafe-inline'" ), referrer=True, cache=True, permissions=True, ) ) ``` Set any field to `False` to omit that header. Set to a string for a custom value. The defaults are strong, production-safe policies. ### Startup and shutdown hooks Lifecycle startup and shutdown hooks are called as part of the ASGI application's lifespan. This is an alternative to [the `on_init` and `on_cleanup` hooks that can be configured at pipeline level](./deployment.md#deployment-initialization-cleanup-and-state). Common use-cases: - Model inference - load models/tokenizers and warm caches (JIT/ONNX/TensorRT, HF, sklearn) - hydrate feature stores, connect to vector DBs (FAISS, Milvus, PGVector) - initialize GPU memory pools and thread/process pools - set global config, download artifacts from registry or object store - prefetch embeddings, label maps, lookup tables - create connection pools for databases, Redis, Kafka, SQS, Pub/Sub - LLM agent workflows - initialize LLM client(s), tool registry, and router/policy engine - build or load RAG indexes; warm retrieval caches and prompts - configure rate limiting, concurrency guards, circuit breakers - load guardrails (PII filters, toxicity, jailbreak detection) - configure tracing/observability for token usage and tool calls - Shutdown - flush metrics/traces/logs, close pools/clients, persist state/caches - graceful draining: wait for in-flight requests before teardown Hooks can be provided as: - A Python callable object - A source path string to be loaded dynamically (e.g. `my_project.runtime.hooks.on_startup`) The callable must accept an `app_runner` argument of type `BaseDeploymentAppRunner` and any additional keyword arguments. The `app_runner` argument is the application factory that is responsible for building the ASGI application. You can use it to access information such as: * the ASGI application instance that is being built * the deployment service instance that is being deployed * the `DeploymentResponse` object itself, which also contains details about the snapshot, pipeline, etc. ```python from zenml.deployers.server import BaseDeploymentAppRunner def on_startup(app_runner: BaseDeploymentAppRunner, warm: bool = False) -> None: # e.g., warm model cache, connect tracer, prefetch embeddings ... def on_shutdown(app_runner: BaseDeploymentAppRunner, drain_timeout_s: int = 2) -> None: # e.g., flush metrics, close clients ... settings = DeploymentSettings( startup_hook=on_startup, shutdown_hook=on_shutdown, startup_hook_kwargs={"warm": True}, shutdown_hook_kwargs={"drain_timeout_s": 2}, ) ``` YAML using source strings: ```yaml settings: deployment: startup_hook: my_project.runtime.hooks.on_startup shutdown_hook: my_project.runtime.hooks.on_shutdown startup_hook_kwargs: warm: true shutdown_hook_kwargs: drain_timeout_s: 2 ``` ### Uvicorn and threading Tune server runtime parameters for performance and topology: The following settings are available for tuning the uvicorn server: * `thread_pool_size`: the size of the thread pool for CPU-bound work offload. * `uvicorn_host`: the host to bind the uvicorn server to. * `uvicorn_port`: the port to bind the uvicorn server to. * `uvicorn_workers`: the number of workers to use for the uvicorn server. * `log_level`: the log level to use for the uvicorn server. * `uvicorn_reload`: whether to enable auto-reload for the uvicorn server. This is useful when using [the local Deployer stack component](https://docs.zenml.io/stacks/stack-components/deployers/docker) to speed up local development by automatically restarting the server when code changes are detected. NOTE: the `uvicorn_reload` setting has no effect on changes in the pipeline configuration, step configuration or stack configuration. * `uvicorn_kwargs`: a dictionary of keyword arguments to pass to the uvicorn server. The following settings are available: ```python from zenml.config import DeploymentSettings from zenml.enums import LoggingLevels settings = DeploymentSettings( thread_pool_size=64, # CPU-bound work offload uvicorn_host="0.0.0.0", uvicorn_port=8000, uvicorn_workers=2, # multi-process model log_level=LoggingLevels.INFO, uvicorn_kwargs={ "proxy_headers": True, "forwarded_allow_ips": "*", "timeout_keep_alive": 15, }, ) ``` ## Advanced customization options When the built-in ASGI application, endpoints and middleware are not enough, you can take customizing your deployment to the next level by providing your own implementation for endpoints, middleware and other ASGI application extensions. ZenML `DeploymentSettings` provides a flexible and extensible mechanism to inject your own custom code into the ASGI application at runtime: - custom endpoints - to expose your own HTTP endpoints. - custom middleware - to insert your own ASGI middleware. - free-form ASGI application building extensions - to take full control of the ASGI application and its lifecycle for truly advanced use-cases when endpoints and middleware are not enough. ### Custom endpoints In production, custom endpoints are often required alongside the main pipeline invoke route. Common use-cases include: - Online inference controls - model (re)load, warm-up, and cache priming - dynamic model/version switching and traffic shaping (A/B, canary) - async/batch prediction submission and job-status polling - feature store materialization/backfills and online/offline sync triggers - Enterprise integration - authentication bootstrap (API key issuance/rotation), JWKS rotation - OIDC/OAuth device-code flows and SSO callback handlers - external system webhooks (CRM, billing, ticketing, audit sink) - Observability and operations - detailed health/readiness endpoints (subsystems, dependencies) - metrics/traces/log shipping toggles; log level switch (INFO/DEBUG) - maintenance-mode enable/disable and graceful drain controls - LLM agent serving - tool registry CRUD, tool execution sandboxes, guardrail toggles - RAG index CRUD (upsert documents, rebuild embeddings, vacuum/compact) - prompt template catalogs and runtime overrides - session memory inspection/reset, conversation export/import - Governance and data management - payload redaction policy updates and capture sampling controls - schema/contract discovery (sample payloads, test vectors) - tenant provisioning, quotas/limits, and per-tenant configuration You can configure `custom_endpoints` in `DeploymentSettings` to expose your own HTTP endpoints. Endpoints support multiple definition modes (see code examples below): 1) Direct callable - a simple function that takes in request parameters and returns a response. Framework-specific arguments such as FastAPI's `Request`, `Response` and dependency injection patterns are supported. 2) Builder class - a callable class with a `__call__` method that is the actual endpoint callable described at 1). The builder class constructor is called by the ASGI application factory and can be leveraged to execute any global initialization logic before the endpoint is called. 3) Builder function - a function that returns the actual endpoint callable described at 1). Similar to the builder class. 4) Native framework-specific object (`native=True`). This can vary from ASGI framework to framework. Definitions can be provided as Python objects or as loadable source path strings. The builder class and builder function must accept an `app_runner` argument of type `BaseDeploymentAppRunner`. This is the application factory that is responsible for building the ASGI application. You can use it to access information such as: * the ASGI application instance that is being built * the deployment service instance that is being deployed * the `DeploymentResponse` object itself, which also contains details about the snapshot, pipeline, etc. The final endpoint callable can take any input arguments and return any output that are JSON-serializable or Pydantic models. The application factory will handle converting these into the appropriate schema for the ASGI application. You can also use framework-specific request/response types (e.g. FastAPI `Request`, `Response`) or dependency injection patterns for your endpoint callable if needed. However, this will limit the portability of your endpoint to other frameworks. The following code examples demonstrate the different definition modes for custom endpoints: 1. a custom detailed health check endpoint implemented as a direct callable ```python from typing import Any, Callable, Dict, List from pydantic import BaseModel from zenml.client import Client from zenml.config import ( DeploymentSettings, EndpointSpec, EndpointMethod, ) from zenml.deployers.server import BaseDeploymentAppRunner from zenml.models import DeploymentResponse async def health_detailed() -> Dict[str, Any]: import psutil client = Client() return { "status": "healthy", "cpu_percent": psutil.cpu_percent(), "memory_percent": psutil.virtual_memory().percent, "disk_percent": psutil.disk_usage("/").percent, "zenml": client.zen_store.get_store_info().model_dump(), } settings = DeploymentSettings( custom_endpoints=[ EndpointSpec( path="/health", method=EndpointMethod.GET, handler=health_detailed, auth_required=False, ), ] ) ``` 2. a custom ML model inference endpoint, implemented as a builder function. Note how the builder function loads the model only once at runtime, and then reuses it for all subsequent requests. ``` /* Detailed source-code truncated for AI context efficiency. */ ``` NOTE: a similar way to do this is to implement a proper ZenML pipeline that loads the model in the `on_init` hook and then runs pre-processing and inference steps in the pipeline. 3. a custom deployment info endpoint implemented as a builder class ```python from typing import Any, Awaitable, Callable, Dict, List from pydantic import BaseModel from zenml.client import Client from zenml.config import ( DeploymentSettings, EndpointSpec, EndpointMethod, ) from zenml.deployers.server import BaseDeploymentAppRunner from zenml.models import DeploymentResponse def build_deployment_info(app_runner: BaseDeploymentAppRunner) -> Callable[[], Awaitable[DeploymentResponse]]: async def endpoint() -> DeploymentResponse: return app_runner.deployment return endpoint settings = DeploymentSettings( custom_endpoints=[ EndpointSpec( path="/deployment", method=EndpointMethod.GET, handler=build_deployment_info, auth_required=True, ), ] ) ``` 4. a custom model selection endpoint, implemented as a FastAPI router. This example is more involved and demonstrates how to coordinate multiple endpoints with the main pipeline invoke endpoint. ``` /* Detailed source-code truncated for AI context efficiency. */ ``` And here is a minimal ZenML inference pipeline that uses the globally loaded model. The prediction step reads the model from the global variable set by the FastAPI router above. You can invoke this pipeline via the built-in `/invoke` endpoint once a model has been loaded through `/model/load`. ```python from typing import List from pydantic import BaseModel from zenml import pipeline, step class InferenceRequest(BaseModel): features: List[float] class InferenceResponse(BaseModel): prediction: float @step def preprocess_step(request: InferenceRequest) -> List[float]: # Replace with real transformations, scaling, encoding, etc. return request.features @step def predict_step(features: List[float]) -> InferenceResponse: """Run model inference using the globally loaded model.""" if GLOBAL_CURRENT_MODEL is None: raise RuntimeError( "No model loaded. Call /model/load before invoking." ) pred = float(GLOBAL_CURRENT_MODEL.predict([features])[0]) return InferenceResponse(prediction=pred) @pipeline(settings={"deployment": deploy_settings}) def inference_pipeline(request: InferenceRequest) -> InferenceResponse: processed = preprocess_step(request) return predict_step(processed) ``` ### Custom middleware Middleware is where you enforce cross-cutting concerns consistently across every endpoint. Common use-cases include: - Security and access control - API key/JWT verification, tenant extraction and context injection - IP allow/deny lists, basic WAF-style request filtering, mTLS header checks - Request body/schema validation and max body size enforcement - Governance and privacy - PII detection/redaction on inputs/outputs; payload sampling/scrubbing - Policy enforcement (data residency, retention, consent) at request time - Reliability and traffic shaping - Rate limiting, quotas, per-tenant concurrency limits - Idempotency keys, deduplication, retries with backoff, circuit breakers - Timeouts, slow-request detection, maintenance mode and graceful drain - Observability - Correlation/trace IDs, OpenTelemetry spans, structured logging - Metrics for latency, throughput, error rates, request/response sizes - Performance and caching - Response caching/ETags, compression (gzip/br), streaming/chunked responses - Adaptive content negotiation and serialization tuning - LLM/agent-specific controls - Token accounting/limits, cost guards per tenant/user - Guardrails (toxicity/PII/jailbreak) and output filtering - Tool execution sandboxing gates and allowlists - Data and feature enrichment - Feature store prefetch, user/tenant profile enrichment, AB bucketing tags You can configure `custom_middlewares` in `DeploymentSettings` to insert your own ASGI middleware. Middlewares support multiple definition modes (see code examples below): 1) Middleware class - a standard ASGI middleware class that implements the `__call__` method that takes the traditional `scope`, `receive` and `send` arguments. The constructor must accept an `app` argument of type `ASGIApplication` and any additional keyword arguments. 2) Middleware callable - a callable that takes all arguments in one go: `app`, `scope`, `receive` and `send`. 3) Native framework-specific middleware (`native=True`) - this can vary from ASGI framework to framework. Definitions can be provided as Python objects or as loadable source path strings. The `order` parameter controls the insertion order in the middleware chain. Lower `order` values insert the middleware earlier in the chain. The following code examples demonstrate the different definition modes for custom middlewares: 1. a custom middleware that adds a processing time header to every response, implemented as a middleware class: ``` /* Detailed source-code truncated for AI context efficiency. */ ``` 2. a custom middleware that injects a correlation ID into responses (and generates one if missing), implemented as a middleware callable: ``` /* Detailed source-code truncated for AI context efficiency. */ ``` 4. a FastAPI/Starlette-native middleware that adds GZIP support, implemented as a native middleware: ```python from starlette.middleware.gzip import GZipMiddleware from zenml.config import DeploymentSettings, MiddlewareSpec settings = DeploymentSettings( custom_middlewares=[ MiddlewareSpec( middleware=GZipMiddleware, native=True, order=20, extra_kwargs={"minimum_size": 1024}, ), ] ) ``` ### App extensions App extensions are pluggable components that are running as part of the ASGI application factory that can install complex, possibly framework-specific structures. The following are usual scenarios for using a full-blown extension instead of endpoints/middleware: - Advanced authentication and authorization - install org-wide dependencies (e.g., OAuth/OIDC auth, RBAC guards) - register custom exception handlers for uniform error envelopes - augment OpenAPI with security schemes and per-route security policies - Multi-tenant and routing topology - programmatically include routers per tenant/region/version - mount sub-apps for internal admin vs public APIs under different prefixes - dynamic route rewrites/switches for blue/green or canary rollouts - Observability and platform integration - wire OpenTelemetry instrumentation at the app level (tracer/meter providers) - register global request/response logging with redaction policies - expose or mount vendor-specific observability apps (e.g., Prometheus) - LLM agent control plane - attach a tool registry/router and lifecycle hooks for tools - register guardrail handlers and policy engines across routes - install runtime prompt/template catalogs and index management routers - API ergonomics and governance - reshape OpenAPI (tags, servers, components) and versioned docs - global response model wrapping, pagination conventions, error mappers - maintenance-mode switch and graceful-drain controls at the app level App extensions support multiple definition modes (see code examples below): 1) Extension class - a class that implements the `BaseAppExtension` abstract class. The class constructor must accept any keyword arguments and the `install` method must accept an `app_runner` argument of type `BaseDeploymentAppRunner`. 2) Extension callable - a callable that takes the `app_runner` argument of type `BaseDeploymentAppRunner`. Both classes and callables must take in an `app_runner` argument of type `BaseDeploymentAppRunner`. This is the application factory that is responsible for building the ASGI application. You can use it to access information such as: * the ASGI application instance that is being built * the deployment service instance that is being deployed * the `DeploymentResponse` object itself, which also contains details about the snapshot, pipeline, etc. Definitions can be provided as Python objects or as loadable source path strings. The extensions are summoned to take part in the ASGI application building process near the end of the initialization - after the ASGI app has been built according to the deployment configuration settings. The example below installs API key authentication at the FastAPI application level, attaches the dependency to selected routes, registers an auth error handler, and augments the OpenAPI schema with the security scheme. ``` /* Detailed source-code truncated for AI context efficiency. */ ``` ## Implementation customizations for advanced use cases For cases where you need deeper control over how the ASGI app is created or how the deployment logic is implemented, you can swap/extend the core components using the following `DeploymentSettings` fields: - `deployment_app_runner_flavor` and `deployment_app_runner_kwargs` let you choose or extend the app runner that constructs and runs the ASGI app. This needs to be set to a subclass of `BaseDeploymentAppRunnerFlavor`, which is basically a descriptor of an app runner implementation that itself is a subclass of `BaseDeploymentAppRunner`. - `deployment_service_class` and `deployment_service_kwargs` let you provide your own deployment service to customize the pipeline deployment logic. This needs to be set to a subclass of `BasePipelineDeploymentService`. Both accept loadable sources or objects. We cover how to implement custom runner flavors and services in a dedicated guide.
ZenML Scarf
--- ### Book/How To/Environment Variables/Environment Variables (docs/book/how-to/environment-variables/environment-variables.md) --- description: Configuring environment variables. icon: globe --- # Environment variables Environment variables can be configured to be available at runtime during step execution. ZenML provides two ways to set environment variables: 1. **Plain text environment variables**: Configure key-value pairs directly 2. **Secrets as environment variables**: Use ZenML secrets where the secret values become environment variables. Check out [this page](../secrets/secrets.md) for more information on secret management in ZenML. {% hint style="info" %} If you need environment variables to be available at image built time, check out the [containerization documentation](../containerization/containerization.md#environment-variables) for more information. {% endhint %} ## Configuration levels Environment variables and secrets can be configured at different levels with increasing precedence: 1. **Stack components** - Available for all pipelines executed on stacks containing this component 2. **Stack** - Available for all pipelines executed on this stack 3. **Pipeline** - Available for all steps in this pipeline 4. **Step** - Available only for this specific step {% hint style="info" %} **Precedence order**: Step configuration overrides pipeline configuration, which overrides stack configuration, which overrides stack component configuration. Additionally, secrets always take precedence over direct environment variables when both are configured with the same key. {% endhint %} ## ZenML repository directory name By default, `zenml init` creates a `.zen` directory that marks the root of a ZenML repository. ZenML uses that marker later when it resolves local code, for example custom stack component flavors that live in your repository. If your workspace cannot use `.zen` as the marker directory name, set `ZENML_REPOSITORY_DIRECTORY_NAME` before initializing or discovering the repository: ```bash export ZENML_REPOSITORY_DIRECTORY_NAME=.my-zenml zenml init ``` After that, ZenML looks for `.my-zenml` instead of `.zen`. Keep this setting consistent for everyone and every process that works with the repository; if one shell uses `.zen` and another uses `.my-zenml`, they will not agree on where the ZenML repository root is. ## Automatic environment variable injection When executing a pipeline, ZenML automatically scans your local environment for any variables that start with the `__ZENML__` prefix and adds them to the pipeline environment. The prefix is removed during this process. For example, if you set: ```bash export __ZENML__MY_VAR=my_value ``` It will be available in your steps as follows: ```python import os from zenml import step @step def my_step(): my_var = os.environ["MY_VAR"] # "my_value" ``` ## Configuring environment variables on stack components Configure environment variables and secrets that will be available for all pipelines executed on stacks containing this component. {% tabs %} {% tab title="CLI" %} ```bash # Configure environment variables zenml orchestrator update --env = # Remove environment variables (set empty value) zenml orchestrator update --env = # Attach secrets (secret values become environment variables) zenml orchestrator update --secret # Remove secrets zenml orchestrator update --remove-secret ``` {% endtab %} {% tab title="Python" %} ```python from zenml import Client Client().update_stack_component( name_id_or_prefix=, component_type=, environment={ "": "", # Set to `None` to remove from previously configured environment "": None }, add_secrets=["", ""], remove_secrets=[""] ) ``` {% endtab %} {% endtabs %} ## Setting environment variables on stacks Configure environment variables and secrets for all pipelines executed on this stack. {% tabs %} {% tab title="CLI" %} ```bash # Configure environment variables zenml stack update --env = # Remove environment variables zenml stack update --env = # Attach secrets zenml stack update --secret # Remove secrets zenml stack update --remove-secret ``` {% endtab %} {% tab title="Python" %} ```python from zenml import Client Client().update_stack( name_id_or_prefix=, environment={ "": "", # Set to `None` to remove from previously configured environment "": None }, add_secrets=[""], remove_secrets=[""] ) ``` {% endtab %} {% endtabs %} ## Configuring environment variables on pipelines Configure environment variables and secrets for all steps of a pipeline. See [this page](../steps-pipelines/configuration.md) for more details on how to configure pipelines. ```python from zenml import pipeline # On the decorator @pipeline( environment={ "": "", "": "" }, secrets=["", ""] ) def my_pipeline(): ... # Using the `with_options(...)` method my_pipeline = my_pipeline.with_options( environment={ "": "", "": "" }, secrets=["", ""] ) ``` ## Setting environment variables on steps Configure environment variables and secrets for individual steps. See [this page](../steps-pipelines/configuration.md) for more details on how to configure steps. ```python from zenml import step # On the decorator @step( environment={ "": "", "": "" }, secrets=[""] ) def my_step() -> str: ... # Using the `with_options(...)` method my_step = my_step.with_options( environment={ "": "", "": "" }, secrets=["", ""] ) ``` ## When environment variables are set The timing of when environment variables are set depends on the orchestrator being used: - The [Databricks](../../component-guide/orchestrators/databricks.md) and [Lightning](../../component-guide/orchestrators/lightning.md) orchestrators will set the environment variables right before your step code is being executed - **All other orchestrators** set environment variables already at container startup time {% hint style="info" %} **Environment variables from secrets** are always set right before your step code is being executed for security reasons, regardless of the orchestrator. {% endhint %}
ZenML Scarf
--- ### Book/How To/Infrastructure Deployment/Infrastructure As Code/README (docs/book/how-to/infrastructure-deployment/infrastructure-as-code/README.md) --- description: Leverage Infrastructure as Code to manage your ZenML stacks and components. icon: network-wired --- # Infrastructure as code with Terraform [Infrastructure as Code (IaC)](https://aws.amazon.com/what-is/iac) is\ the practice of managing and provisioning infrastructure through code\ instead of through manual processes. In this section, we will show you how to integrate ZenML with popular\ IaC tools such as [Terraform](https://developer.hashicorp.com/terraform). Terraform is a powerful tool for managing infrastructure as code, and is by far the most popular IaC tool. Many companies already have existing Terraform setups, and it is often desirable to integrate ZenML with this setup. We already got a glimpse on how to [deploy a cloud stack with Terraform](../stack-deployment/deploy-a-cloud-stack-with-terraform.md) using existing Terraform modules that are maintained by the ZenML team. While this is a great solution for quickly getting started, it might not always be suitable for your use case. This guide is for advanced users who want to manage their own custom Terraform code but want to use ZenML to manage their stacks. For this, the [ZenML provider](https://registry.terraform.io/providers/zenml-io/zenml/latest) is a better choice. ## Understanding the Two-Phase Approach When working with ZenML stacks, there are two distinct phases: 1. **Infrastructure Deployment**: Creating cloud resources (typically handled by platform teams) 2. **ZenML Registration**: Registering these resources as ZenML stack components While our official modules ([`zenml-stack/aws`](https://registry.terraform.io/modules/zenml-io/zenml-stack/aws/latest), [`zenml-stack/gcp`](https://registry.terraform.io/modules/zenml-io/zenml-stack/gcp/latest), [`zenml-stack/azure`](https://registry.terraform.io/modules/zenml-io/zenml-stack/azure/latest)) handle both phases, you might already have infrastructure deployed. Let's explore how to register existing infrastructure with ZenML. ## Phase 1: Infrastructure Deployment You likely already have this handled in your existing Terraform configurations: ```hcl # Example of existing GCP infrastructure resource "google_storage_bucket" "ml_artifacts" { name = "company-ml-artifacts" location = "US" } resource "google_artifact_registry_repository" "ml_containers" { repository_id = "ml-containers" format = "DOCKER" } ``` ## Phase 2: ZenML Registration ### Setup the ZenML Provider First, configure the [ZenML provider](https://registry.terraform.io/providers/zenml-io/zenml/latest) to communicate with your ZenML server: ```hcl terraform { required_providers { zenml = { source = "zenml-io/zenml" } } } provider "zenml" { # Configuration options will be loaded from environment variables: # ZENML_SERVER_URL (for Pro users, this should be your Workspace URL from the dashboard) # ZENML_API_KEY } {% hint style="info" %} **For ZenML Pro users:** The `ZENML_SERVER_URL` should be your Workspace URL, which can be found in your dashboard. It typically looks like: `https://1bfe8d94-zenml.cloudinfra.zenml.io`. Make sure you use the complete URL of your workspace, not just the domain. The `ZENML_API_KEY` should be [the ZenML Pro API key](https://docs.zenml.io/pro/access-management/service-accounts) or [Personal Access Token](https://docs.zenml.io/pro/access-management/personal-access-tokens). {% endhint %} ``` To generate an API key for an OSS server, use the command: ```bash zenml service-account create ``` This will create a service account and generate an API key that you can use to authenticate with the ZenML server. {% hint style="info" %} The API key is shown only once during creation. Make sure to save it securely, as you cannot retrieve it later. If you lose it, you'll need to create a new key. {% endhint %} You can learn more about how to generate a `ZENML_API_KEY` via service accounts [here](https://docs.zenml.io/how-to/manage-zenml-server/connecting-to-zenml/connect-with-a-service-account). If you're using a ZenML Pro server, you will need to create a Personal Access Token or an organization-level service account and an API key for it. You can find more about Personal Access Tokens [here](https://docs.zenml.io/pro/access-management/personal-access-tokens) and organization-level service accounts and API keys [here](https://docs.zenml.io/pro/access-management/service-accounts). ### Create the service connectors The key to successful registration is proper authentication between the components. [Service connectors](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management) are ZenML's way of managing this: ```hcl # First, create a service connector resource "zenml_service_connector" "gcp_connector" { name = "gcp-${var.environment}-connector" type = "gcp" auth_method = "service-account" configuration = { project_id = var.project_id service_account_json = file("service-account.json") } } # Create a stack component referencing the connector resource "zenml_stack_component" "artifact_store" { name = "existing-artifact-store" type = "artifact_store" flavor = "gcp" configuration = { path = "gs://${google_storage_bucket.ml_artifacts.name}" } connector_id = zenml_service_connector.gcp_connector.id } ``` ### Register the stack components Register different types of [components](https://docs.zenml.io/stacks): ```hcl # Generic component registration pattern locals { component_configs = { artifact_store = { type = "artifact_store" flavor = "gcp" configuration = { path = "gs://${google_storage_bucket.ml_artifacts.name}" } } container_registry = { type = "container_registry" flavor = "gcp" configuration = { uri = "${var.region}-docker.pkg.dev/${var.project_id}/${google_artifact_registry_repository.ml_containers.repository_id}" } } orchestrator = { type = "orchestrator" flavor = "vertex" configuration = { project = var.project_id region = var.region } } } } # Register multiple components resource "zenml_stack_component" "components" { for_each = local.component_configs name = "existing-${each.key}" type = each.value.type flavor = each.value.flavor configuration = each.value.configuration connector_id = zenml_service_connector.env_connector.id } ``` ### Assemble the stack Finally, assemble the components into a stack: ```hcl resource "zenml_stack" "ml_stack" { name = "${var.environment}-ml-stack" components = { for k, v in zenml_stack_component.components : k => v.id } } ``` ## Practical Walkthrough: Registering Existing GCP Infrastructure Let's see a complete example of registering an existing GCP infrastructure stack with ZenML. ### Prerequisites - A GCS bucket for artifacts - An Artifact Registry repository - A service account for ML operations - Vertex AI enabled for orchestration ### Step 1: Variables Configuration ```hcl # variables.tf variable "zenml_server_url" { description = "URL of the ZenML server (for Pro users, this is your Workspace URL)" type = string } variable "zenml_api_key" { description = "API key for ZenML server authentication" type = string sensitive = true } variable "project_id" { description = "GCP project ID" type = string } variable "region" { description = "GCP region" type = string default = "us-central1" } variable "environment" { description = "Environment name (e.g., dev, staging, prod)" type = string } variable "gcp_service_account_key" { description = "GCP service account key in JSON format" type = string sensitive = true } ``` ### Step 2: Main Configuration ``` /* Detailed source-code truncated for AI context efficiency. */ ``` ### Step 3: Outputs Configuration ```hcl # outputs.tf output "stack_id" { description = "ID of the created ZenML stack" value = zenml_stack.gcp_stack.id } output "stack_name" { description = "Name of the created ZenML stack" value = zenml_stack.gcp_stack.name } output "artifact_store_path" { description = "GCS path for artifacts" value = "${google_storage_bucket.artifacts.name}/artifacts" } output "container_registry_uri" { description = "URI of the container registry" value = "${var.region}-docker.pkg.dev/${var.project_id}/${google_artifact_registry_repository.containers.repository_id}" } ``` ### Step 4: terraform.tfvars Configuration Create a `terraform.tfvars` file (remember to never commit this to version control): ```hcl zenml_server_url = "https://your-zenml-server.com" # For Pro users: your Workspace URL from dashboard project_id = "your-gcp-project-id" region = "us-central1" environment = "dev" ``` Store sensitive variables in environment variables: ```bash export TF_VAR_zenml_api_key="your-zenml-api-key" export TF_VAR_gcp_service_account_key=$(cat path/to/service-account-key.json) ``` ### Usage Instructions 1. Install required providers and initializing Terraform: ```bash terraform init ``` 2. Install required ZenML integrations: ```bash zenml integration install gcp ``` 3. Review the planned changes: ```bash terraform plan ``` 4. Apply the configuration: ```bash terraform apply ``` 5. Set the newly created stack as active: ```bash zenml stack set $(terraform output -raw stack_name) ``` 6. Verify the configuration: ```bash zenml stack describe ``` This complete example demonstrates: - Setting up necessary GCP infrastructure - Creating a service connector with proper authentication - Registering stack components with the infrastructure - Creating a complete ZenML stack - Proper variable management and output configuration - Best practices for sensitive information handling The same pattern can be adapted for AWS and Azure infrastructure by adjusting the provider configurations and resource types accordingly. Remember to: - Use appropriate IAM roles and permissions - Follow your organization's security practices for handling credentials - Consider using Terraform workspaces for managing multiple environments - Regular backup of your Terraform state files - Version control your Terraform configurations (excluding sensitive files) To learn more about the ZenML terraform provider, visit the [ZenML provider](https://registry.terraform.io/providers/zenml-io/zenml/latest).
ZenML Scarf
--- ### Book/How To/Infrastructure Deployment/Stack Deployment/Deploy A Cloud Stack With Terraform (docs/book/how-to/infrastructure-deployment/stack-deployment/deploy-a-cloud-stack-with-terraform.md) --- description: Deploy a cloud stack using Terraform icon: globe --- # Terraform Modules ZenML maintains a collection of [Terraform modules](https://registry.terraform.io/modules/zenml-io/zenml-stack) designed to streamline the provisioning of cloud resources and seamlessly integrate them with ZenML Stacks. These modules simplify the setup process, allowing users to quickly provision cloud resources as well as configure and authorize ZenML to utilize them for running pipelines and other AI/ML operations. By leveraging these Terraform modules, users can ensure a more efficient and scalable deployment of their machine learning infrastructure, ultimately enhancing their development and operational workflows. The modules' implementation can also be used as a reference for creating custom Terraform\ configurations tailored to specific cloud environments and requirements. {% hint style="info" %} Terraform requires you to manage your infrastructure as code yourself. Among other things, this means that you will need to have Terraform installed on your machine, and you will need to manually manage the state of your infrastructure. If you prefer a more automated approach, you can use [the 1-click stack deployment feature](deploy-a-cloud-stack.md) to deploy a cloud stack with ZenML with minimal knowledge of Terraform or cloud infrastructure for that matter. If you have the required infrastructure pieces already deployed on your cloud, you can also use [the stack wizard to seamlessly register your stack](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/register-a-cloud-stack). {% endhint %} ## Pre-requisites To use this feature, you need a deployed ZenML server instance that is reachable from the cloud provider where you wish to have the stack provisioned (this can't be a local server started via `zenml login --local`). If you do not already have one set up, you can fast-track to trying out a ZenML Pro server by simply running `zenml login --pro` or [registering for a free ZenML Pro account](https://zenml.io/pro). If you prefer to host your own, you can learn about self-hosting a ZenML server [here](https://docs.zenml.io/getting-started/deploying-zenml). Once you are connected to your deployed ZenML server, you need to create a service account and an API key for it. You will use the API key to give the Terraform module programmatic access to your ZenML server. You can find more about service accounts and API keys [here](https://docs.zenml.io/how-to/manage-zenml-server/connecting-to-zenml/connect-with-a-service-account). If you're using an OSS server, the process is as simple as running the following CLI command while connected to your ZenML server: ```shell zenml service-account create ``` Example output: ```shell $ zenml service-account create terraform-account Created service account 'terraform-account'. Successfully created API key `default`. The API key value is: 'ZENKEY_...' Please store it safely as it will not be shown again. To configure a ZenML client to use this API key, run: zenml login https://842ed6a9-zenml.staging.cloudinfra.zenml.io --api-key and enter the following API key when prompted: ZENKEY_... ``` If you're using a ZenML Pro server, you will need to create a Personal Access Token or an organization-level service account and an API key for it. You can find more about Personal Access Tokens [here](https://docs.zenml.io/pro/access-management/personal-access-tokens) and organization-level service accounts and API keys [here](https://docs.zenml.io/pro/access-management/service-accounts). Finally, you will need the following on the machine where you will be running Terraform: * [Terraform](https://developer.hashicorp.com/terraform/install) installed on your machine (version at least 1.9). * the ZenML Terraform stack modules assume you are already locally authenticated with your cloud provider through the provider's CLI or SDK tool and have permissions to create the resources that the modules will provision. This is different depending on the cloud provider you are using and is covered in the following sections. ## How to use the Terraform stack deployment modules If you are already knowledgeable about using Terraform and the cloud provider where you want to deploy the stack, this process will be straightforward. The ZenML Terraform provider lets you manage your ZenML resources (stacks, stack components, etc.) as infrastructure-as-code. In a nutshell, you will need to: 1. Set up the ZenML Terraform provider with your ZenML server URL and the API key or ZenML Pro API key. It is recommended to use environment variables for this rather than hardcoding the values in your Terraform configuration file: ```shell export ZENML_SERVER_URL="https://your-zenml-server.com" export ZENML_API_KEY="" ``` {% hint style="info" %} **For ZenML Pro users:** The `ZENML_SERVER_URL` should be your Workspace URL, which can be found in your dashboard. It typically looks like: `https://1bfe8d94-zenml.cloudinfra.zenml.io`. Make sure you use the complete URL of your workspace, not just the domain. The `ZENML_API_KEY` should be [the ZenML Pro API key](https://docs.zenml.io/pro/access-management/service-accounts). {% endhint %} 2. Create a new Terraform configuration file (e.g., `main.tf`), preferably in a new directory, with the content that looks like this (`` can be`aws`, `gcp`, or `azure`): ```hcl terraform { required_providers { aws = { source = "hashicorp/aws" } zenml = { source = "zenml-io/zenml" } } } provider "zenml" { # server_url = # For ZenML Pro users, this should be your Workspace URL from the dashboard # api_key = } module "zenml_stack" { source = "zenml-io/zenml-stack/" version = "x.y.z" # Optional inputs zenml_stack_name = "" orchestrator = "" # e.g., "local", "sagemaker", "vertex", "azureml", "skypilot" } output "zenml_stack_id" { value = module.zenml_stack.zenml_stack_id } output "zenml_stack_name" { value = module.zenml_stack.zenml_stack_name } ``` There might be a few additional required or optional inputs depending on the cloud provider you are using. You can find the full list of inputs for each module in the [Terraform Registry](https://registry.terraform.io/modules/zenml-io/zenml-stack) documentation for the relevant module, or you can read on in the following sections. 3. Run the following commands in the directory where you have your Terraform configuration file: ```shell terraform init terraform apply ``` {% hint style="warning" %} The directory where you keep the Terraform configuration file and where you run the `terraform` commands is important. This is where Terraform will store the state of your infrastructure. Make sure you do not delete this directory or the state file it contains unless you are sure you no longer need to manage these resources with Terraform or after you have deprovisioned them up with`terraform destroy`. {% endhint %} 4. Terraform will prompt you to confirm the changes it will make to your cloud infrastructure. If you are happy with the changes, type `yes` and hit enter. 5. Terraform will then provision the resources you have specified in your configuration file. Once the process is complete, you will see a message indicating that the resources have been successfully created and printing out the ZenML stack ID and name: ```shell ... Apply complete! Resources: 15 added, 0 changed, 0 destroyed. Outputs: zenml_stack_id = "04c65b96-b435-4a39-8484-8cc18f89b991" zenml_stack_name = "terraform-gcp-588339e64d06" ``` At this point, a ZenML stack has also been created and registered with your\ ZenML server, and you can start using it to run your pipelines: ```shell zenml integration install zenml stack set ``` You can find more details specific to the cloud provider of your choice in the\ next section: {% tabs %} {% tab title="AWS" %} The [original documentation for the ZenML AWS Terraform module](https://registry.terraform.io/modules/zenml-io/zenml-stack/aws/latest) contains extensive information about required permissions, inputs, outputs, and provisioned resources. This is a summary of the key points from that documentation. **Authentication** To authenticate with AWS, you need to have [the AWS CLI](https://aws.amazon.com/cli/) installed on your machine, and you need to have run `aws configure` to set up your credentials. **Example Terraform Configuration** Here is an example Terraform configuration file for deploying a ZenML stack on AWS: ```hcl terraform { required_providers { aws = { source = "hashicorp/aws" } zenml = { source = "zenml-io/zenml" } } } provider "zenml" { # server_url = # For ZenML Pro users, this should be your Workspace URL from the dashboard # api_key = } provider "aws" { region = "eu-central-1" } module "zenml_stack" { source = "zenml-io/zenml-stack/aws" # Optional inputs orchestrator = "" # e.g., "local", "sagemaker", "skypilot" zenml_stack_name = "" } output "zenml_stack_id" { value = module.zenml_stack.zenml_stack_id } output "zenml_stack_name" { value = module.zenml_stack.zenml_stack_name } ``` **Stack Components** The Terraform module will create a ZenML stack configuration with the\ following components: 1. An S3 Artifact Store linked to an S3 bucket via an AWS Service Connector configured with IAM role credentials 2. An ECR Container Registry linked to an ECR repository via an AWS Service Connector configured with IAM role credentials 3. Depending on the `orchestrator` input variable: 4. A local Orchestrator, if `orchestrator` is set to `local`. This can be used in combination with the SageMaker Step Operator to selectively run some steps locally and some on SageMaker. 5. If `orchestrator` is set to `sagemaker` (default): a SageMaker Orchestrator linked to the AWS account via an AWS Service Connector configured with IAM role credentials 6. If `orchestrator` is set to `skypilot`: a SkyPilot Orchestrator linked to the AWS account via an AWS Service Connector configured with IAM role credentials 7. An AWS App Runner Deployer linked to the AWS account via an AWS Service Connector configured with IAM role credentials 8. An AWS CodeBuild Image Builder linked to the AWS account via an AWS Service Connector configured with IAM role credentials 9. a SageMaker Step Operator linked to the AWS account via an AWS Service Connector configured with IAM role credentials To use the ZenML stack, you will need to install the required integrations: * For the local or SageMaker orchestrator: ```shell zenml integration install aws s3 ``` * For the SkyPilot orchestrator: ```shell zenml integration install aws s3 skypilot_aws ``` {% endtab %} {% tab title="GCP" %} The [original documentation for the ZenML GCP Terraform module](https://registry.terraform.io/modules/zenml-io/zenml-stack/gcp/latest) contains extensive information about required permissions, inputs, outputs, and provisioned resources. This is a summary of the key points from that documentation. **Authentication** To authenticate with GCP, you need to have [the `gcloud` CLI](https://cloud.google.com/sdk/gcloud) installed on your machine, and you need to have run `gcloud init` or `gcloud auth application-default login` to set up your credentials. **Example Terraform Configuration** Here is an example Terraform configuration file for deploying a ZenML stack on GCP: ```hcl terraform { required_providers { google = { source = "hashicorp/google" } zenml = { source = "zenml-io/zenml" } } } provider "zenml" { # server_url = # For ZenML Pro users, this should be your Workspace URL from the dashboard # api_key = } provider "google" { region = "europe-west3" project = "my-project" } module "zenml_stack" { source = "zenml-io/zenml-stack/gcp" # Optional inputs orchestrator = "" # e.g., "local", "vertex", "skypilot" or "airflow" zenml_stack_name = "" } output "zenml_stack_id" { value = module.zenml_stack.zenml_stack_id } output "zenml_stack_name" { value = module.zenml_stack.zenml_stack_name } ``` **Stack Components** The Terraform module will create a ZenML stack configuration with the\ following components: 1. An GCP Artifact Store linked to a GCS bucket via a GCP Service Connector configured with the GCP service account credentials 2. An GCP Container Registry linked to a Google Artifact Registry via a GCP Service Connector configured with the GCP service account credentials 3. Depending on the `orchestrator` input variable: 4. a local Orchestrator, if `orchestrator` is set to `local`. This can be used in combination with the Vertex AI Step Operator to selectively run some steps locally and some on Vertex AI. 5. If `orchestrator` is set to `vertex` (default): a Vertex AI Orchestrator linked to the GCP project via a GCP Service Connector configured with the GCP service account credentials 6. If `orchestrator` is set to `skypilot`: a SkyPilot Orchestrator linked to the GCP project via a GCP Service Connector configured with the GCP service account credentials 7. If `orchestrator` is set to `airflow`: an Airflow Orchestrator linked to the Cloud Composer environment 8. A GCP Cloud Run Deployer linked to the GCP project via a GCP Service Connector configured with the GCP service account credentials 9. A Google Cloud Build Image Builder linked to your GCP project via a GCP Service Connector configured with the GCP service account credentials 10. A Vertex AI Step Operator linked to the GCP project via a GCP Service Connector configured with the GCP service account credentials To use the ZenML stack, you will need to install the required integrations: * For the local and Vertex AI orchestrators: ```shell zenml integration install gcp ``` * For the SkyPilot orchestrator: ```shell zenml integration install gcp skypilot_gcp ``` * For the Airflow orchestrator: ```shell zenml integration install gcp airflow ``` {% endtab %} {% tab title="Azure" %} The original documentation for the ZenML Azure Terraform module contains extensive information about required permissions, inputs, outputs, and provisioned resources. This is a summary of the key points from that documentation. **Authentication** To authenticate with Azure, you need to have [the Azure CLI](https://learn.microsoft.com/en-us/cli/azure/) installed on your machine, and you need to have run `az login` to set up your credentials. **Example Terraform Configuration** Here is an example Terraform configuration file for deploying a ZenML stack on Azure: ```hcl terraform {{ required_providers {{ azurerm = {{ source = "hashicorp/azurerm" }} azuread = {{ source = "hashicorp/azuread" }} zenml = {{ source = "zenml-io/zenml" }} }} }} provider "zenml" { # server_url = # For ZenML Pro users, this should be your Workspace URL from the dashboard # api_key = } provider "azurerm" {{ features {{ resource_group {{ prevent_deletion_if_contains_resources = false }} }} }} module "zenml_stack" { source = "zenml-io/zenml-stack/azure" # Optional inputs location = "" orchestrator = "" # e.g., "local", "skypilot_azure" zenml_stack_name = "" } output "zenml_stack_id" { value = module.zenml_stack.zenml_stack_id } output "zenml_stack_name" { value = module.zenml_stack.zenml_stack_name } ``` **Stack Components** The Terraform module will create a ZenML stack configuration with the\ following components: 1. An Azure Artifact Store linked to an Azure Storage Account and Blob Container via an Azure Service Connector configured with Azure Service Principal credentials 2. An ACR Container Registry linked to an Azure Container Registry via an Azure Service Connector configured with Azure Service Principal credentials 3. Depending on the `orchestrator` input variable: 4. if `orchestrator` is set to `local`: a local Orchestrator. This can be used in combination with the AzureML Step Operator to selectively run some steps locally and some on AzureML. 5. If `orchestrator` is set to `skypilot` (default): an Azure SkyPilot Orchestrator linked to the Azure subscription via an Azure Service Connector configured with Azure Service Principal credentials 6. If `orchestrator` is set to `azureml`: an AzureML Orchestrator linked to an AzureML Workspace via an Azure Service Connector configured with Azure Service Principal credentials 7. An AzureML Step Operator linked to an AzureML Workspace via an Azure Service Connector configured with Azure Service Principal credentials To use the ZenML stack, you will need to install the required integrations: * For the local and AzureML orchestrators: ```shell zenml integration install azure ``` * For the SkyPilot orchestrator: ```shell zenml integration install azure skypilot_azure ``` {% endtab %} {% endtabs %} ## How to clean up the Terraform stack deployments Cleaning up the resources provisioned by Terraform is as simple as running the`terraform destroy` command in the directory where you have your Terraform configuration file. This will remove all the resources that were provisioned by the Terraform module and will also delete the ZenML stack that was registered with your ZenML server. ```shell terraform destroy ```
ZenML Scarf
--- ### Book/How To/Infrastructure Deployment/Stack Deployment/Deploy A Cloud Stack (docs/book/how-to/infrastructure-deployment/stack-deployment/deploy-a-cloud-stack.md) --- description: Deploy a cloud stack from scratch with a single click icon: hand-back-point-up --- # 1-click Deployment In ZenML, the [stack](https://docs.zenml.io/user-guides/production-guide/understand-stacks) is a fundamental concept that represents the configuration of your infrastructure. In a normal workflow, creating a stack requires you to first deploy the necessary pieces of infrastructure and then define them as stack components in ZenML with proper authentication. Especially in a remote setting, this process can be challenging and time-consuming, and it may create multi-faceted problems. This is why we implemented a feature that allows you to **deploy the necessary pieces of infrastructure on your selected cloud provider and get you started on a remote stack with a single click**. {% hint style="info" %} If you prefer to have more control over where and how resources are provisioned in your cloud, you can [use one of our Terraform modules](deploy-a-cloud-stack-with-terraform.md) to manage your infrastructure as code yourself. If you have the required infrastructure pieces already deployed on your cloud, you can also use [the stack wizard to seamlessly register your stack](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/register-a-cloud-stack). {% endhint %} ## How to use the 1-click deployment tool? The first thing that you need in order to use this feature is a deployed instance of ZenML (not a local server via `zenml login --local`). If you do not already have it set up for you, feel free to learn how to do so [here](https://docs.zenml.io/getting-started/deploying-zenml). Once you are connected to your deployed ZenML instance, you can use the 1-click deployment tool either through the dashboard or the CLI: {% tabs %} {% tab title="Dashboard" %} In order to create a remote stack over the dashboard, go to the stacks page\ on the dashboard and click "+ New Stack". Since we will be deploying it from scratch, select "New Infrastructure" on the\ next page:
AWS If you choose `aws` as your provider, you will see a page where you will have to select a region and a name for your new stack: Configuring the new stack Once the configuration is finished, you will see a deployment page: Deploying the new stack Clicking on the "Deploy in AWS" button will redirect you to a Cloud Formation page on AWS Console. Cloudformation page You will have to log in to your AWS account, review and confirm the pre-filled configuration, and create the stack. Finalizing the new stack
GCP If you choose `gcp` as your provider, you will see a page where you will have to select a region and a name for your new stack: Once the configuration is finished, you will see a deployment page: Deploy GCP Stack - Step 2 Make a note of the configuration values provided to you in the ZenML dashboard. You will need these in the next step. Clicking on the "Deploy in GCP" button will redirect you to a Cloud Shell session on GCP. GCP Cloud Shell start page {% hint style="warning" %} The Cloud Shell session will warn you that the ZenML GitHub repository is untrusted. We recommend that you review [the contents of the repository](https://github.com/zenml-io/zenml/tree/main/infra/gcp) and then check the `Trust repo` checkbox to proceed with the deployment, otherwise, the Cloud Shell session will not be authenticated to access your GCP projects. You will also get a chance to review the scripts that will be executed in the Cloud Shell session before proceeding. {% endhint %} GCP Cloud Shell intro After the Cloud Shell session starts, you will be guided through the process of authenticating with GCP, configuring your deployment, and finally provisioning the resources for your new GCP stack using Deployment Manager. First, you will be asked to create or choose an existing GCP project with billing enabled and to configure your terminal with the selected project: GCP Cloud Shell tutorial step 1 Next, you will be asked to configure your deployment by pasting the configuration values that were provided to you earlier in the ZenML dashboard. You may need to switch back to the ZenML dashboard to copy these values if you did not do so earlier: GCP Cloud Shell tutorial step 2Deploy GCP Stack pending You can take this opportunity to review the script that will be executed at the next step. You will notice that this script starts by enabling some necessary GCP service APIs and configuring some basic permissions for the service accounts involved in the stack deployment, and then deploys the stack using a GCP Deployment Manager template. You can proceed with the deployment by running the script in your terminal: GCP Cloud Shell tutorial step 3 The script will deploy a GCP Deployment Manager template that provisions the necessary resources for your new GCP stack and automatically registers the stack with your ZenML server. You can monitor the progress of the deployment in your GCP console: GCP Deployment Manager progress Once the deployment is complete, you may close the Cloud Shell session and return to the ZenML dashboard to view the newly created stack: GCP Cloud Shell tutorial step 4GCP Stack dashboard output
Azure If you choose `azure` as your provider, you will see a page where you will have to select a location and a name for your new stack: Deploy Azure Stack - Step 1 You will also find a list of resources that will be deployed as part of the stack: Deploy Azure Stack - Step 1 Continued Once the configuration is finished, you will see a deployment page. Make a note of the values in the `main.tf` file that is provided to you. Deploy Azure Stack - Step 2 Clicking on the "Deploy in Azure" button will redirect you to a Cloud Shell session on Azure. Azure Cloud Shell start page You should now paste the content of the `main.tf` file into a file in the Cloud Shell session and run the `terraform init --upgrade` and `terraform apply` commands. The `main.tf` file uses the `zenml-io/zenml-stack/azure` module hosted on the Terraform registry to deploy the necessary resources for your Azure stack and then automatically registers the stack with your ZenML server. You can check out the module documentation [here](https://registry.terraform.io/modules/zenml-io/zenml-stack/azure). Azure Cloud Shell Terraform Outputs Once the Terraform deployment is complete, you may close the Cloud Shell session and return to the ZenML Dashboard to view the newly created stack: Azure Stack Dashboard output
{% endtab %} {% tab title="CLI" %} In order to create a remote stack over the CLI, you can use the following\ command: ```shell zenml stack deploy -p {aws|gcp|azure} ``` **AWS** If you choose `aws` as your provider, the command will walk you through deploying a Cloud Formation stack on AWS. It will start by showing some information about the stack that will be created: Upon confirmation, the command will redirect you to a Cloud Formation page on AWS Console where you will have to deploy the stack: You will have to log in to your AWS account, have permission to deploy an AWS Cloud Formation stack, review and confirm the pre-filled configuration and create the stack. The Cloud Formation stack will provision the necessary resources for your new\ AWS stack and automatically register the stack with your ZenML server. You can\ monitor the progress of the stack in your AWS console: Once the provisioning is complete, you may close the AWS Cloud Formation page\ and return to the ZenML CLI to view the newly created stack: **GCP** If you choose `gcp` as your provider, the command will walk you through deploying a Deployment Manager template on GCP. It will start by showing some information about the stack that will be created: Upon confirmation, the command will redirect you to a Cloud Shell session on GCP. {% hint style="warning" %} The Cloud Shell session will warn you that the ZenML GitHub repository is untrusted. We recommend that you review [the contents of the repository](https://github.com/zenml-io/zenml/tree/main/infra/gcp) and then check the `Trust repo` checkbox to proceed with the deployment, otherwise the Cloud Shell session will not be authenticated to access your GCP projects. You will also get a chance to review the scripts that will be executed in the Cloud Shell session before proceeding. {% endhint %} After the Cloud Shell session starts, you will be guided through the process of authenticating with GCP, configuring your deployment, and finally provisioning the resources for your new GCP stack using Deployment Manager. First, you will be asked to create or choose an existing GCP project with billing enabled and to configure your terminal with the selected project: Next, you will be asked to configure your deployment by pasting the configuration values that were provided to you in the ZenML CLI. You may need to switch back to the ZenML CLI to copy these values if you did not do so earlier: You can take this opportunity to review the script that will be executed at the next step. You will notice that this script starts by enabling some necessary GCP service APIs and configuring some basic permissions for the service accounts involved in the stack deployment, and then deploys the stack using a GCP Deployment Manager template. You can proceed with the deployment by running the script in your terminal: The script will deploy a GCP Deployment Manager template that provisions the necessary resources for your new GCP stack and automatically registers the stack with your ZenML server. You can monitor the progress of the deployment in your GCP console: Once the deployment is complete, you may close the Cloud Shell session and return to the ZenML CLI to view the newly created stack: **Azure** If you choose `azure` as your provider, the command will walk you through deploying [the ZenML Azure Stack Terraform module](https://registry.terraform.io/modules/zenml-io/zenml-stack/azure). It will start by showing some information about the stack that will be created: Upon confirmation, the command will redirect you to a Cloud Shell session on Azure. After the Cloud Shell session starts, you will have to use Terraform to deploy the stack, as instructed by the CLI. First, you will have to open a file named `main.tf` in the Cloud Shell session using the editor of your choice (e.g. `vim`, `nano`) and paste in the Terraform configuration provided by the CLI. You may need to switch back to the ZenML CLI to copy these values if you did not do so earlier: The Terraform file is a simple configuration that uses [the ZenML Azure Stack Terraform module](https://registry.terraform.io/modules/zenml-io/zenml-stack/azure) to deploy the necessary resources for your Azure stack and then automatically register the stack with your ZenML server. You can read more about the module and its configuration options in the module's documentation. You can proceed with the deployment by running the `terraform init` and`terraform apply` Terraform commands in your terminal: Once the Terraform deployment is complete, you may close the Cloud Shell session and return to the ZenML CLI to view the newly created stack: {% endtab %} {% endtabs %} ## What will be deployed? Here is an overview of the infrastructure that the 1-click deployment will prepare for you based on your cloud provider: {% tabs %} {% tab title="AWS" %} **Resources** * An S3 bucket that will be used as a ZenML Artifact Store. * An ECR container registry that will be used as a ZenML Container Registry. * A CloudBuild project that will be used as a ZenML Image Builder. * Permissions to use SageMaker as a ZenML Orchestrator and Step Operator. * An IAM user and IAM role with the minimum necessary permissions to access the resources listed above. * An AWS access key used to give access to ZenML to connect to the above resources through a ZenML service connector. **Permissions** The configured IAM service account and AWS access key will grant ZenML the following AWS permissions in your AWS account: * S3 Bucket: * s3:ListBucket * s3:GetObject * s3:PutObject * s3:DeleteObject * s3:GetBucketVersioning * s3:ListBucketVersions * s3:DeleteObjectVersion * ECR Repository: * ecr:DescribeRepositories * ecr:ListRepositories * ecr:DescribeRegistry * ecr:BatchGetImage * ecr:DescribeImages * ecr:BatchCheckLayerAvailability * ecr:GetDownloadUrlForLayer * ecr:InitiateLayerUpload * ecr:UploadLayerPart * ecr:CompleteLayerUpload * ecr:PutImage * ecr:GetAuthorizationToken * CloudBuild (Client): * codebuild:CreateProject * codebuild:BatchGetBuilds * CloudBuild (Service): * s3:GetObject * s3:GetObjectVersion * logs:CreateLogGroup * logs:CreateLogStream * logs:PutLogEvents * ecr:BatchGetImage * ecr:DescribeImages * ecr:BatchCheckLayerAvailability * ecr:GetDownloadUrlForLayer * ecr:InitiateLayerUpload * ecr:UploadLayerPart * ecr:CompleteLayerUpload * ecr:PutImage * ecr:GetAuthorizationToken * SageMaker (Client): * sagemaker:CreatePipeline * sagemaker:StartPipelineExecution * sagemaker:DescribePipeline * sagemaker:DescribePipelineExecution * SageMaker (Jobs): * AmazonSageMakerFullAccess {% endtab %} {% tab title="GCP" %} **Resources** * A GCS bucket that will be used as a ZenML Artifact Store. * A GCP Artifact Registry that will be used as a ZenML Container Registry. * Permissions to use Vertex AI as a ZenML Orchestrator and Step Operator. * Permissions to use GCP Cloud Builder as a ZenML Image Builder. * A GCP Service Account with the minimum necessary permissions to access the resources listed above. * An GCP Service Account access key used to give access to ZenML to connect to the above resources through a ZenML service connector. **Permissions** The configured GCP service account and its access key will grant ZenML the following GCP permissions in your GCP project: * GCS Bucket: * roles/storage.objectUser * GCP Artifact Registry: * roles/artifactregistry.createOnPushWriter * Vertex AI (Client): * roles/aiplatform.user * Vertex AI (Jobs): * roles/aiplatform.serviceAgent * Cloud Build (Client): * roles/cloudbuild.builds.editor {% endtab %} {% tab title="Azure" %} **Resources** * An Azure Resource Group to contain all the resources required for the ZenML stack * An Azure Storage Account and Blob Storage Container that will be used as a ZenML Artifact Store. * An Azure Container Registry that will be used as a ZenML Container Registry. * An AzureML Workspace that will be used as a ZenML Orchestrator and ZenML Step Operator. A Key Vault and Application Insights instance will also be created in the same Resource Group and used to construct the AzureML Workspace. * An Azure Service Principal with the minimum necessary permissions to access the above resources. * An Azure Service Principal client secret used to give access to ZenML to connect to the above resources through a ZenML service connector. **Permissions** The configured Azure service principal and its client secret will grant ZenML the following permissions in your Azure subscription: * Permissions granted for the created Storage Account: * Storage Blob Data Contributor * Permissions granted for the created Container Registry: * AcrPull * AcrPush * Contributor * Permissions granted for the created AzureML Workspace: * AzureML Compute Operator * AzureML Data Scientist {% endtab %} {% endtabs %} There you have it! With a single click, you just deployed a cloud stack, and you can start running your pipelines in a remote setting.
ZenML Scarf
--- ### Book/How To/Infrastructure Deployment/Stack Deployment/Register A Cloud Stack (docs/book/how-to/infrastructure-deployment/stack-deployment/register-a-cloud-stack.md) --- description: Seamlessly register a cloud stack by using existing infrastructure icon: cloud --- # Register a cloud stack In ZenML, the [stack](https://docs.zenml.io/user-guides/production-guide/understand-stacks) is a fundamental concept that represents the configuration of your\ infrastructure. In a normal workflow, creating a stack requires you to first deploy the necessary pieces of infrastructure and then define them as stack components in ZenML with proper authentication. Especially in a remote setting, this process can be challenging and time-consuming, and it may create multi-faceted problems. This is why we implemented a feature called the stack wizard, which allows you to **browse through your existing infrastructure and use it to register a ZenML cloud stack**. {% hint style="info" %} If you do not have the required infrastructure pieces already deployed on your cloud, you can also use [the 1-click deployment tool to build your cloud stack](deploy-a-cloud-stack.md). Alternatively, if you prefer to have more control over where and how resources are provisioned in your cloud, you can [use one of our Terraform modules](deploy-a-cloud-stack-with-terraform.md) to manage your infrastructure as code yourself. {% endhint %} ## How to use the Stack Wizard? The stack wizard is available to you through both our CLI and our dashboard. {% tabs %} {% tab title="Dashboard" %} If you are using the dashboard, the stack wizard is available through\ the stacks page. Here you can click on "+ New Stack" and choose the option "Use existing Cloud". Next, you have to select the cloud provider that you want to work with. Choose one of the possible authentication methods based on your provider and fill in the required fields.
AWS: Authentication methods If you select `aws` as your cloud provider, and you haven't selected a connector\ or declined auto-configuration, you will be prompted to select an authentication method for your cloud connector. {% code title="Available authentication methods for AWS" %} ``` /* Detailed source-code truncated for AI context efficiency. */ ``` {% endcode %}
GCP: Authentication methods If you select `gcp` as your cloud provider, and you haven't selected a connector\ or declined auto-configuration, you will be prompted to select an authentication\ method for your cloud connector. {% code title="Available authentication methods for GCP" %} ``` /* Detailed source-code truncated for AI context efficiency. */ ``` {% endcode %}
Azure: Authentication methods If you select `azure` as your cloud provider, and you haven't selected a\ connector or declined auto-configuration, you will be prompted to select an\ authentication method for your cloud connector. {% code title="Available authentication methods for Azure" %} ``` Available authentication methods for AZURE ┏━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃ Choice ┃ Name ┃ Required ┃ ┡━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ │ [0] │ Azure Service Principal │ client_secret (Service principal │ │ │ │ client secret) │ │ │ │ tenant_id (Azure Tenant ID) │ │ │ │ client_id (Azure Client ID) │ │ │ │ │ ├────────┼─────────────────────────┼────────────────────────────────────┤ │ [1] │ Azure Access Token │ token (Azure Access Token) │ │ │ │ │ └────────┴─────────────────────────┴────────────────────────────────────┘ ``` {% endcode %}
From this step forward, ZenML will show you different selections of resources that you can use from your existing infrastructure so that you can create the required stack components such as an artifact store, an orchestrator, and a container registry. {% endtab %} {% tab title="CLI" %} In order to register a remote stack over the CLI with the stack wizard, you can use the following command: ```shell zenml stack register -p {aws|gcp|azure} ``` To register the cloud stack, the first thing that the wizard needs is a [service connector](https://docs.zenml.io/stacks/service-connectors/auth-management). You can either use an existing connector by providing its ID or name`-sc ` (CLI-Only), or the wizard will create one for you. {% hint style="info" %} Similar to the service connector, if you use the CLI, you can also use existing\ stack components. However, this is only possible if these components are already\ configured with the same service connector that you provided through the\ parameter described above. {% endhint %} **Define Service Connector** As the very first step, the configuration wizard will check if the selected\ cloud provider credentials can be acquired automatically from the local environment.\ If the credentials are found, you will be offered to use them or proceed to\ manual configuration. {% code title="Example prompt for AWS auto-configuration" %} ``` AWS cloud service connector has detected connection credentials in your environment. Would you like to use these credentials or create a new configuration by providing connection details? [y/n] (y): ``` {% endcode %} If you decline auto-configuration next you might be offered the list of already created service connectors available on the server: pick one of them and proceed, or pick`0` to create a new one.
AWS: Authentication methods If you select `aws` as your cloud provider, and you haven't selected a connector\ or declined auto-configuration, you will be prompted to select an authentication\ method for your cloud connector. {% code title="Available authentication methods for AWS" %} ``` /* Detailed source-code truncated for AI context efficiency. */ ``` {% endcode %}
GCP: Authentication methods If you select `gcp` as your cloud provider, and you haven't selected a connector\ or declined auto-configuration, you will be prompted to select an authentication\ method for your cloud connector. {% code title="Available authentication methods for GCP" %} ``` /* Detailed source-code truncated for AI context efficiency. */ ``` {% endcode %}
Azure: Authentication methods If you select `azure` as your cloud provider, and you haven't selected a\ connector or declined auto-configuration, you will be prompted to select an\ authentication method for your cloud connector. {% code title="Available authentication methods for Azure" %} ``` Available authentication methods for AZURE ┏━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃ Choice ┃ Name ┃ Required ┃ ┡━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ │ [0] │ Azure Service Principal │ client_secret (Service principal │ │ │ │ client secret) │ │ │ │ tenant_id (Azure Tenant ID) │ │ │ │ client_id (Azure Client ID) │ │ │ │ │ ├────────┼─────────────────────────┼────────────────────────────────────┤ │ [1] │ Azure Access Token │ token (Azure Access Token) │ │ │ │ │ └────────┴─────────────────────────┴────────────────────────────────────┘ ``` {% endcode %}
**Defining cloud components** Next, you will define three major components of your target stack: * artifact store * orchestrator * container registry All three are crucial for a basic cloud stack. Extra components can be added later if they are needed. For each component, you will be asked: * if you would like to reuse one of the existing components connected via a defined\ service connector (if any) {% code title="Example Command Output for available orchestrator" %} ``` Available orchestrator ┏━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃ Choice ┃ Name ┃ ┡━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ │ [0] │ Create a new orchestrator │ ├──────────────────┼────────────────────────────────────────────────────┤ │ [1] │ existing_orchestrator_1 │ ├──────────────────┼────────────────────────────────────────────────────┤ │ [2] │ existing_orchestrator_2 │ └──────────────────┴────────────────────────────────────────────────────┘ ``` {% endcode %} * to create a new one from available to the service connector resources (if the existing not picked) {% code title="Example Command Output for Artifact Stores" %} ``` Available GCP storages ┏━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃ Choice ┃ Storage ┃ ┡━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ │ [0] │ gs://*************************** │ ├───────────────┼───────────────────────────────────────────────────────┤ │ [1] │ gs://*************************** │ └───────────────┴───────────────────────────────────────────────────────┘ ``` {% endcode %} Based on your selection, ZenML will create the stack component and ultimately register the stack for you. {% endtab %} {% endtabs %} There you have it! Through the wizard, you just registered a cloud stack, and you can start running your pipelines on a remote setting.
ZenML Scarf
--- ### Book/How To/Manage Zenml Server/Best Practices Upgrading Zenml (docs/book/how-to/manage-zenml-server/best-practices-upgrading-zenml.md) --- description: >- Simple, step-by-step guide for keeping your ZenML workspaces (servers) up to date without breaking your teams. --- # Keeping ZenML Upgraded — Without Breaking Things Upgrading ZenML doesn't have to be scary. Whether you're using the open-source (OSS) version or ZenML Pro (where servers are called _workspaces_), this guide will help you set up a clean, testable, and stress-free upgrade process using a production + staging pattern. 1. Always have **two environments**: _production_ and _staging_. 2. Mirror everything in both places. 3. Use GitOps to automate upgrades. 4. Run the right tests in staging. 5. Re-create snapshots. 6. Cut over to production once staging is green. That's it. The rest of this chapter just fills in the details. ## ☝️ Step #1: Always Use Two Environments Whether you're OSS or Pro: - You should **always have two environments**: - **Production** — where your team builds and runs real pipelines. - **Staging** — used *only* to test ZenML upgrades before they hit production. > 🏢 **ZenML Pro** users: use **two workspaces** (e.g. `prod-workspace`, `staging-workspace`) > 💻 **ZenML OSS** users: run **two ZenML servers** (same logic applies) ## 🧱 Step #2: Mirror Your Stacks in Both Environments At setup time: - For every **stack in production**, create a **mirrored stack in staging** - Ideally, they point to **separate infra**, but can also share infra if needed | Stack Component | Production | Staging | |---------------------|--------------------------|----------------------------| | Kubernetes cluster | `prod-k8s-cluster` | `staging-k8s-cluster` | | Artifact store | `s3://prod-bucket` | `s3://staging-bucket` | | Container registry | `gcr.io/prod-images` | `gcr.io/staging-images` | {% hint style="info" %} * Point staging stacks to **staging variants** of your infra (e.g., a smaller K8s cluster, a test S3 bucket). * When you change a stack in production, immediately update the twin in staging. {% endhint %} ## 🛠️ Step #3: Use [GitOps](https://about.gitlab.com/topics/gitops/) to Manage Upgrades Put your workspace configuration in a Git repository (Helm charts, Terraform, or the ZenML Pro API – pick your tool). Set up two long-lived branches: * `staging` – auto-deploys to the **staging workspace** * `main` – auto-deploys to **production** ```mermaid flowchart LR dev["PR → staging branch"] --> stg["CI/CD upgrades Staging workspace"] stg --> tests["Run upgrade test suite"] tests -->|✅| merge["Merge staging ➜ main"] merge --> prod["CI/CD upgrades Production workspace"] ``` ZenML Pro users can call the [Workspace API](https://cloudapi.zenml.io/) from CI to bump the version. OSS users typically re-deploy the Helm chart/Docker image with the new tag. ## 🤝 Step #4: Run a test suite in staging After upgrading staging, assume things might break — this is normal and expected. At this point, the platform and data science / ML engineering teams should have mutually: * Agree on a smoke test suite of pipelines or steps * Maintain shared expectations on what counts as "upgrade success" For example, the data science repo could contain a test suite that does the following checks: ```python def test_artifact_loading(): artifact = Client().get_artifact_version("xyz").load() assert artifact is not None def test_simple_pipeline(): run = run_pipeline(pipeline_name="...") assert run.status == "COMPLETED" ``` ## 🔄 Step #5: Update all snapshots Pipeline snapshots may now break as they have the older version of the ZenML client installed. Therefore, you would need to rebuild the snapshot and associated images. The easiest way to do this is to re-create a snapshot using the CLI: ```shell zenml pipeline snapshot create run.my_pipeline \ --name upgraded-template \ --stack staging-stack \ --config configs/run.yaml ``` {% hint style="info" %} Read about [how snapshots work](https://docs.zenml.io/user-guides/tutorial/trigger-pipelines-from-external-systems). {% endhint %} After building, execute all snapshots end-to-end as a smoke test. Ideally, your data science teams have a "smoke test" parameter in the pipeline to load mock data just for this scenario! ## 🚀 Step #6: Upgrade Production and Go Live Once staging is ✅ : 1. Merge `staging` ➜ `main`. 2. CI upgrades the production workspace. 3. Immediately: * Rebuild **all snapshots** in prod * **Reschedule** recurring pipelines (delete old schedules, create new ones). Read more [here](https://docs.zenml.io/user-guides/tutorial/managing-scheduled-pipelines) 4. Monitor for a few hours. Done. ## Ops Notes (OSS only) If you self-host the ZenML server: * Take a **database backup** before every upgrade. * Keep the old Docker image tag handy for rollbacks. * Store logs from the migration job. [ZenML Pro](http://zenml.io/pro) SaaS handles all of the above for you. ## ✅ Summary: The Upgrade Flow ``` ┌───────────────┐ │ Git PR to dev │ │ → staging env │ └──────┬────────┘ │ ▼ Upgrade staging server │ Run all pipelines / tests │ ✔ All tests pass? / \ Yes No | | Recreate snapshots Fix │ Upgrade prod | Rebuild & reschedule ``` * Two workspaces keep upgrades safe. * GitOps makes them repeatable. * A simple pipeline test suite keeps you honest. Upgrade with confidence 🚀. ## 🔚 Final Notes ZenML Pro: Hosted workspaces are upgraded automatically, but you still need to test your pipelines in staging before changes hit production. ZenML OSS: You are responsible for upgrades, backups, and reconfiguration — this guide helps you minimize downtime and bugs.
ZenML Scarf
--- ### Book/How To/Manage Zenml Server/Troubleshoot Your Deployed Server (docs/book/how-to/manage-zenml-server/troubleshoot-your-deployed-server.md) --- description: Troubleshooting tips for your ZenML deployment --- # Troubleshoot the deployed server In this document, we will go over some common issues that you might face when deploying ZenML and how to solve them. ## Viewing logs Analyzing logs is a great way to debug issues. Depending on whether you have a Kubernetes (using Helm or `zenml deploy`) or a Docker deployment, you can view the logs in different ways. {% tabs %} {% tab title="Kubernetes" %} If you are using Kubernetes, you can view the logs of the ZenML server using the following method: * Check all pods that are running your ZenML deployment. ```bash kubectl -n get pods ``` * If you see that the pods aren't running, you can use the command below to get the logs for all pods at once. ```bash kubectl -n logs -l app.kubernetes.io/name=zenml ``` Note that the error can either be from the `zenml-db-init` container that connects to the MySQL database or from the `zenml` container that runs the server code. If the get pods command shows that the pod is failing in the `Init` state then use `zenml-db-init` as the container name, otherwise use `zenml`. ```bash kubectl -n logs -l app.kubernetes.io/name=zenml -c ``` {% hint style="info" %} You can also use the `--tail` flag to limit the number of lines to show or the `--follow` flag to follow the logs in real-time. {% endhint %} {% endtab %} {% tab title="Docker" %} If you are using Docker, you can view the logs of the ZenML server using the following method: * If you used the `zenml login --local --docker` CLI command to deploy the Docker ZenML server, you can check the logs with the command: ```shell zenml logs -f ``` * If you used the `docker run` command to manually deploy the Docker ZenML server, you can check the logs with the command: ```shell docker logs zenml -f ``` * If you used the `docker compose` command to manually deploy the Docker ZenML server, you can check the logs with the command: ```shell docker compose -p zenml logs -f ``` {% endtab %} {% endtabs %} ## Fixing database connection problems If you are using a MySQL database, you might face issues connecting to it. The logs from the `zenml-db-init` container should give you a good idea of what the problem is. Here are some common issues and how to fix them: * If you see an error like `ERROR 1045 (28000): Access denied for user using password YES`, it means that the username or password is incorrect. Make sure that the username and password are correctly set for whatever deployment method you are using. * If you see an error like `ERROR 2003 (HY000): Can't connect to MySQL server on ()`, it means that the host is incorrect. Make sure that the host is correctly set for whatever deployment method you are using. You can test the connection and the credentials by running the following command from your machine: ```bash mysql -h -u -p ``` {% hint style="info" %} If you are using a Kubernetes deployment, you can use the `kubectl port-forward` command to forward the MySQL port to your local machine. This will allow you to connect to the database from your machine. {% endhint %} ## Fixing database initialization problems If you’ve migrated from a newer ZenML version to an older version and see errors like `Revision not found` in your `zenml-db-init` logs, one way out is to drop the database and create a new one with the same name. * Log in to your MySQL instance. ```bash mysql -h -u -p ``` * Drop the database for the server. ```sql drop database ; ``` * Create the database with the same name. ```sql create database ; ``` * Restart the Kubernetes pods or the docker container running your server to trigger the database initialization again.
ZenML Scarf
--- ### Book/How To/Manage Zenml Server/Upgrade Zenml Server (docs/book/how-to/manage-zenml-server/upgrade-zenml-server.md) --- description: >- Learn how to upgrade your server to a new version of ZenML for the different deployment options. icon: circle-arrow-up --- # Upgrading ZenML The way to upgrade your ZenML server depends a lot on how you deployed it. However, there are some best practices that apply in all cases. Before you upgrade, check out the [best practices for upgrading ZenML](best-practices-upgrading-zenml.md) guide. In general, upgrade your ZenML server as soon as you can once a new version is released. New versions come with a lot of improvements and fixes from which you can benefit. {% tabs %} {% tab title="Docker" %} To upgrade to a new version with docker, you have to delete the existing container and then run the new version of the `zenml-server` image. {% hint style="danger" %} Check that your data is persisted (either on persistent storage or on an external MySQL instance) before doing this. Optionally also perform a backup before the upgrade. {% endhint %} * Delete the existing ZenML container, for example like this: ```bash # find your container ID docker ps ``` ```bash # stop the container docker stop # remove the container docker rm ``` * Deploy the version of the `zenml-server` image that you want to use. Find all versions [here](https://hub.docker.com/r/zenmldocker/zenml-server/tags). ```bash docker run -it -d -p 8080:8080 --name zenmldocker/zenml-server: ``` {% endtab %} {% tab title="Kubernetes with Helm" %} To upgrade your ZenML server Helm release to a new version, follow the steps below. #### Simple in-place upgrade If you don't need to change any configuration values, you can perform a simple in-place upgrade that reuses your existing configuration: ```bash helm -n upgrade zenml-server oci://public.ecr.aws/zenml/zenml --version --reuse-values ``` #### Upgrade with configuration changes If you need to modify your ZenML server configuration during the upgrade, follow these steps instead: * Extract your current configuration values to a file: ```bash helm -n get values zenml-server > custom-values.yaml ``` * Make the necessary changes to your `custom-values.yaml` file (make sure they are compatible with the new version) * Upgrade the release using your modified values file: ```bash helm -n upgrade zenml-server oci://public.ecr.aws/zenml/zenml --version -f custom-values.yaml ``` {% hint style="info" %} It is not recommended to change the container image tag in the Helm chart to custom values, since every Helm chart\ version is tested to work only with the default image tag. However, if you know what you're doing you can change\ the `zenml.image.tag` value in your `custom-values.yaml` file to the desired ZenML version (e.g. `0.32.0`). {% endhint %} {% endtab %} {% endtabs %} ## Important Considerations After Upgrading - **Downgrading is not supported**: Downgrading the server to an older version is not supported and can lead to unexpected behavior. - **Client-server version alignment**: The version of the Python client that connects to the server should be kept at the same version as the server. - **Recreate snapshots**: After upgrading your ZenML server, you need to recreate any [snapshots](../snapshots/snapshots.md) that you were using. Snapshots are tied to specific server versions and will often not work correctly after an upgrade.
ZenML Scarf
--- ### Book/How To/Manage Zenml Server/Using Zenml Server In Prod (docs/book/how-to/manage-zenml-server/using-zenml-server-in-prod.md) --- description: Learn about best practices for using ZenML server in production environments. --- # Using ZenML server in production Setting up a ZenML server for testing is a quick process. However, most people have to move beyond so-called 'day zero' operations and in such cases, it helps to learn best practices around setting up your ZenML server in a production-ready way. This guide encapsulates all the tips and tricks we've learned ourselves and from working with people who use ZenML in production environments. Following are some of the best practices we recommend. {% hint style="info" %} If you are using ZenML Pro, you don't have to worry about any of these. We have got you covered!\ You can sign up for a free trial [here](https://zenml.io/pro). {% endhint %} ## Autoscaling replicas In production, you often have to run bigger and longer running pipelines that might strain your server's resources. It is a good idea to set up autoscaling for your ZenML server so that you don't have to worry about your pipeline runs getting interrupted or your Dashboard slowing down due to high traffic. How you do it depends greatly on the environment in which you have deployed your ZenML server. Below are some common deployment options and how to set up autoscaling for them. {% tabs %} {% tab title="Kubernetes with Helm" %} If you are using the official [ZenML Helm chart](https://artifacthub.io/packages/helm/zenml/zenml), you can take advantage of the `autoscaling.enabled` flag to enable autoscaling for your ZenML server. For example: ```yaml autoscaling: enabled: true minReplicas: 1 maxReplicas: 10 targetCPUUtilizationPercentage: 80 ``` This will create a horizontal pod autoscaler for your ZenML server that will scale the number of replicas up to 10 and down to 1 based on the CPU utilization of the pods. {% endtab %} {% tab title="ECS" %} For folks using AWS, [ECS](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/Welcome.html) is a popular choice for running ZenML server. ECS is a container orchestration service that allows you to run and scale your containers in a managed environment. To scale your ZenML server deployed as a service on ECS, you can follow the steps below: * Go to the ECS console, find you service pertaining to your ZenML server and click on it. * Click on the "Update Service" button. * If you scroll down, you will see the "Service auto scaling - optional" section. * Here you can enable autoscaling and set the minimum and maximum number of tasks to run for your service and also the ECS service metric to use for scaling. {% endtab %} {% tab title="Cloud Run" %} For folks on GCP, [Cloud Run](https://cloud.google.com/run) is a popular choice for running ZenML server. Cloud Run is a container orchestration service that allows you to run and scale your containers in a managed environment. In Cloud Run, each revision is automatically scaled to the number of instances needed to handle all incoming requests, events, or CPU utilization and by default, when a revision does not receive any traffic, it is scaled in to zero instances. For production use cases, we recommend setting the minimum number of instances to at least 1 so that you have "warm" instances ready to serve incoming requests. To scale your ZenML server deployed on Cloud Run, you can follow the steps below: * Go to the Cloud Run console, find you service pertaining to your ZenML server and click on it. * Click on the "Edit & Deploy new Revision" button. * Scroll down to the "Revision auto-scaling" section. * Here you can set the minimum and maximum number of instances to run for your service. {% endtab %} {% tab title="Docker Compose" %} If you use Docker Compose, you don't get autoscaling out of the box. However, you can scale your service to N number of replicas using the `scale` flag. For example: ```bash docker compose up --scale zenml-server=N ``` This will scale your ZenML server to N replicas. {% endtab %} {% endtabs %} ## High connection pool values One other way to improve the performance of your ZenML server is to increase the number of threads that your server process uses, provided that you have hardware that can support it. You can control this by setting the `zenml.threadPoolSize` value in the ZenML Helm chart values. For example: ```yaml server: threadPoolSize: 100 ``` By default, it is set to 40. If you are using any other deployment option, you can set the `ZENML_SERVER_THREAD_POOL_SIZE` environment variable to the desired value. Once this is set, you should also modify the `zenml.database.poolSize` and `zenml.database.maxOverflow` values to ensure that the ZenML server workers do not block on database connections (i.e. the sum of the pool size and max overflow should be greater than or equal to the thread pool size). If you manage your own database, ensure these values are set appropriately. ## Scaling the backing database An important component of the ZenML server deployment is the backing database. When you start scaling your ZenML server instances, you will also need to scale your database to avoid any bottlenecks. We would recommend starting out with a simple (single) database instance and then monitoring it to decide if it needs scaling. Some common metrics to look out for: * CPU Utilization: If the CPU Utilization is consistently above 50%, you may need to scale your database. Some spikes in the utilization are expected but it should not be consistently high. * Freeable Memory: It is natural for the freeable memory to go down with time as your database uses it for caching and buffering but if it drops below 100-200 MB, you may need to scale your database. ## Setting up an ingress/load balancer Exposing your ZenML server to the internet securely and reliably is a must for production use cases. One way to do this is to set up an ingress/load balancer. {% tabs %} {% tab title="Kubernetes with Helm" %} If you are using the official [ZenML Helm chart](https://artifacthub.io/packages/helm/zenml/zenml), you can take advantage of the `zenml.ingress.enabled` flag to enable ingress for your ZenML server. For example: ```yaml server: ingress: enabled: true className: "nginx" annotations: # nginx.ingress.kubernetes.io/ssl-redirect: "true" # nginx.ingress.kubernetes.io/rewrite-target: /$1 # kubernetes.io/ingress.class: nginx # kubernetes.io/tls-acme: "true" # cert-manager.io/cluster-issuer: "letsencrypt" ``` This will create an [NGINX ingress](https://github.com/kubernetes/ingress-nginx) for your ZenML service that will create a LoadBalancer on whatever cloud provider you are using. {% endtab %} {% tab title="ECS" %} With ECS, you can use Application Load Balancers to evenly route traffic to your tasks running your ZenML server. Follow the steps in the official [AWS documentation](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/service-load-balancing.html) to learn how to set this up. {% endtab %} {% tab title="Cloud Run" %} With Cloud Run, you can use Cloud Load Balancing to route traffic to your service. Follow the steps in the official [GCP documentation](https://cloud.google.com/load-balancing/docs/https/setting-up-https-serverless) to learn how to set this up. {% endtab %} {% tab title="Docker Compose" %} If you are using Docker Compose, you can set up an NGINX server as a reverse proxy to route traffic to your ZenML server. Here's a [blog](https://www.docker.com/blog/how-to-use-the-official-nginx-docker-image/) that shows how to do it. {% endtab %} {% endtabs %} ## Monitoring Monitoring your service is crucial to ensure that it is running smoothly and to catch any issues early before they can cause problems. Depending on the deployment option you are using, you can use different tools to monitor your service. {% tabs %} {% tab title="Kubernetes with Helm" %} You can set up Prometheus and Grafana to monitor your ZenML server. We recommend using the `kube-prometheus-stack` [Helm chart from the prometheus-community](https://artifacthub.io/packages/helm/prometheus-community/kube-prometheus-stack) to get started quickly. Once you have deployed the chart, you can find your grafana service by searching for services in the namespace you have deployed the chart in. Port-forward it to your local machine or deploy it through an ingress. You can now use queries like the following to monitor your ZenML server: ``` sum by(namespace) (rate(container_cpu_usage_seconds_total{namespace=~"zenml.*"}[5m])) ``` This query would give you the CPU utilization of your server pods in all namespaces that start with `zenml`. The image below shows how this query would look like in Grafana. {% endtab %} {% tab title="ECS" %} On ECS, you can utilize the [CloudWatch integration](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/cloudwatch-metrics.html) to monitor your ZenML server. In the "Health and metrics" section of your ECS console, you should see metrics pertaining to your ZenML service like CPU utilization and Memory utilization. {% endtab %} {% tab title="Cloud Run" %} In Cloud Run, you can utilize the [Cloud Monitoring integration](https://cloud.google.com/run/docs/monitoring) to monitor your ZenML server. The "Metrics" tab in the Cloud Run console will show you metrics like Container CPU utilization, Container memory utilization, and more. {% endtab %} {% endtabs %} ## Backups The data in your ZenML server is critical as it contains your pipeline runs, stack configurations, and other important information. It is, therefore, recommended to have a backup strategy in place to avoid losing any data. Some common strategies include: * Setting up automated backups with a good retention period (say 30 days). * Periodically exporting the data to an external storage (e.g. S3, GCS, etc.). * Manual backups before upgrading your server to avoid any problems.
ZenML Scarf
--- ### Book/How To/Manage Zenml Server/Connecting To Zenml/README (docs/book/how-to/manage-zenml-server/connecting-to-zenml/README.md) --- icon: globe-pointer description: Various means of connecting to ZenML. --- # Connect to a server Once [ZenML is deployed](../../../getting-started/deploying-zenml/README.md), there are various ways to connect to it. ## Choose how to connect Use this quick guide to pick the right method based on your context: | Context | Use | Credentials | Docs | |---|---|---|---| | You are a human using the CLI and browser | Interactive login (device flow) | Your user session (24h/30d) | [Connect with your user](./connect-in-with-your-user-interactive.md) | | Script/notebook needs to make quick API calls to an OSS server | Service account + API key | Long‑lived API key | [Connect with a service account](./connect-with-a-service-account.md) | | Script/notebook needs to make quick API calls to a ZenML Pro workspace | ZenML Pro Personal Access Token | Long‑lived PAT | [Connect with a personal access token](./connect-with-a-pat.md) | | CI/CD or long‑lived automation calling an OSS server | Service account + API key | Long‑lived API key | [Connect with a service account](./connect-with-a-service-account.md) | | CI/CD or long‑lived automation calling a ZenML Pro workspace | ZenML Pro API service account + API key | Long‑lived API key | [Connect with a ZenML Pro service account](https://docs.zenml.io/api-reference/pro-api/getting-started#programmatic-access-with-service-accounts-and-api-keys) | | CI/CD or long‑lived automation calling the ZenML Pro management API (`cloudapi.zenml.io`) | ZenML Pro service account + API key | Long-lived API key | [Connect with a ZenML Pro service account](https://docs.zenml.io/api-reference/pro-api/getting-started#programmatic-access-with-service-accounts-and-api-keys) | {% hint style="warning" %} Which base URL should you call? - Workspace/OSS API: your server or workspace URL (e.g., `https://.zenml.io`). - ZenML Pro management API: `https://cloudapi.zenml.io`. In ZenML Pro, use Personal Access Tokens or ZenML Pro service accounts and API keys. These credentials can be used for both the Workspace API and the Pro management API. See [ZenML Pro Personal Access Tokens](https://docs.zenml.io/pro/access-management/personal-access-tokens) and [ZenML Pro Service Accounts](https://docs.zenml.io/pro/access-management/service-accounts). {% endhint %} ## Common pitfalls - 401 Unauthorized: verify you’re using the correct base URL, the token hasn’t expired, and the header is `Authorization: Bearer `. - Automation fails after 1 hour: check the expiration date of the PAT or API key and rotate it if it has expired. - Can’t find Run Template endpoints: they exist on the Workspace/OSS API, not on `cloudapi.zenml.io`.
ZenML Scarf
--- ### Book/How To/Manage Zenml Server/Connecting To Zenml/Connect In With Your User Interactive (docs/book/how-to/manage-zenml-server/connecting-to-zenml/connect-in-with-your-user-interactive.md) --- description: >- Connect to the ZenML server using the ZenML CLI and the web based login. --- # Connect in with your User (interactive) You can authenticate your clients with the ZenML Server using the ZenML CLI and the web‑based login (device flow). This method is ideal for humans working locally and applies to OSS servers and ZenML Pro workspaces. ```bash zenml login https://... ``` This command starts a browser flow to validate the device you are connecting from. You can choose whether to mark the device as trusted. If you don’t trust the device, a 24‑hour token is issued; if you do, a 30‑day token is issued. {% hint style="warning" %} Managing authorized devices for ZenML Pro workspaces is not yet supported in the dashboard. CLI device management is available. {% endhint %} To see all devices you've permitted, use the following command: ```bash zenml authorized-device list ``` Additionally, the following command allows you to more precisely inspect one of these devices: ```bash zenml authorized-device describe ``` For increased security, you can invalidate a token using the `zenml authorized-device lock` command followed by the device ID. ``` zenml authorized-device lock ``` To keep things simple, we can summarize the steps: 1. Use the `zenml login ` command to start a device flow and connect to a zenml server. 2. Choose whether to trust the device when prompted. 3. Check permitted devices with `zenml authorized-device list`. 4. Invalidate a token with `zenml authorized-device lock ...`. ### Important notice Using the ZenML CLI is a secure and comfortable way to interact with your ZenML servers. It's important to always ensure that only trusted devices are used to maintain security and privacy. {% hint style="info" %} Calling the ZenML Pro management API (`cloudapi.zenml.io`)? Interactive CLI login does not apply there. Use a ZenML Pro Personal Access Token or a ZenML Pro Service Account and API key instead. See [ZenML Pro API Getting Started](https://docs.zenml.io/api-reference/pro-api/getting-started). {% endhint %} Don't forget to manage your device trust levels regularly for optimal security. Should you feel a device trust needs to be revoked, lock the device immediately. Every token issued is a potential gateway to access your data, secrets and infrastructure.
ZenML Scarf
--- ### Book/How To/Manage Zenml Server/Connecting To Zenml/Connect With A Pat (docs/book/how-to/manage-zenml-server/connecting-to-zenml/connect-with-a-pat.md) --- description: >- Connect to the ZenML server using a Personal Access Token. --- # Connect with a Personal Access Token If you are using ZenML Pro and need to call the ZenML Pro workspace API from a non-interactive environment, you also have the option of creating and using a Personal Access Token. Personal Access Tokens are scoped to your ZenML Pro user account and can be used to access all workspaces you are a member of in any organization. See the [ZenML Pro Personal Access Tokens](https://docs.zenml.io/pro/access-management/personal-access-tokens) documentation for more information. {% hint style="warning" %} **Personal Access Tokens are only available in ZenML Pro** If you are using ZenML OSS and need to call the ZenML OSS API from a non-interactive environment, you can use a service account and an API key. See the [Connect with a service account](./connect-with-a-service-account.md) documentation for more information. {% endhint %}
ZenML Scarf
--- ### Book/How To/Manage Zenml Server/Connecting To Zenml/Connect With A Service Account (docs/book/how-to/manage-zenml-server/connecting-to-zenml/connect-with-a-service-account.md) --- description: >- Connect to the ZenML server using a service account and an API key. --- # Connect with a Service Account Sometimes you may need to authenticate to a ZenML server from a non-interactive environment where the web login is not possible, like a CI/CD workload or a serverless function. In these cases, you can configure a service account and an API key and use the API key to authenticate to the ZenML server: ```bash zenml service-account create ``` This command creates a service account and an API key for it. The API key is displayed as part of the command output and cannot be retrieved later. You can then use the issued API key to connect your ZenML client to the server through one of the following methods: * using the CLI: ```bash # This command will prompt you to enter the API key zenml login https://... --api-key ``` * setting the `ZENML_STORE_URL` and `ZENML_STORE_API_KEY` environment variables when you set up your ZenML client for the first time. This method is particularly useful when you are using the ZenML client in an automated CI/CD workload environment like GitHub Actions or GitLab CI or in a containerized environment like Docker or Kubernetes: ```bash export ZENML_STORE_URL=https://... export ZENML_STORE_API_KEY= ``` {% hint style="info" %} You don't need to run `zenml login` after setting these two environment variables and can start interacting with your server right away. {% endhint %} {% hint style="info" %} Using ZenML Pro? Use a ZenML Pro service account and API key. Set the workspace URL and your service account API key as environment variables: ```bash export ZENML_STORE_URL=https://.zenml.io export ZENML_STORE_API_KEY= # Optional for self-hosted Pro deployments: export ZENML_PRO_API_URL=https:// ``` You can also authenticate via CLI: ```bash zenml login --api-key # You will be prompted to enter your service account API key ``` {% endhint %} To see all the service accounts you've created and their API keys, use the following commands: ```bash zenml service-account list zenml service-account api-key list ``` Additionally, the following command allows you to more precisely inspect one of these service accounts and an API key: ```bash zenml service-account describe zenml service-account api-key describe ``` API keys don't have an expiration date. For increased security, we recommend that you regularly rotate the API keys to prevent unauthorized access to your ZenML server. You can do this with the ZenML CLI: ```bash zenml service-account api-key rotate ``` Running this command will create a new API key and invalidate the old one. The new API key is displayed as part of the command output and cannot be retrieved later. You can then use the new API key to connect your ZenML client to the server just as described above. When rotating an API key, you can also configure a retention period for the old API key. This is useful if you need to keep the old API key for a while to ensure that all your workloads have been updated to use the new API key. You can do this with the `--retain` flag. For example, to rotate an API key and keep the old one for 60 minutes, you can run the following command: ```bash zenml service-account api-key rotate \ --retain 60 ``` For increased security, you can deactivate a service account or an API key using one of the following commands: ``` zenml service-account update --active false zenml service-account api-key update \ --active false ``` Deactivating a service account or an API key will prevent it from being used to authenticate and has immediate effect on all workloads that use it. To keep things simple, we can summarize the steps: 1. Use the `zenml service-account create` command to create a service account and an API key. 2. Use the `zenml login --api-key` command to connect your ZenML client to the server using the API key. 3. Check configured service accounts with `zenml service-account list`. 4. Check configured API keys with `zenml service-account api-key list`. 5. Regularly rotate API keys with `zenml service-account api-key rotate`. 6. Deactivate service accounts or API keys with `zenml service-account update` or `zenml service-account api-key update`. ## Programmatic access with API keys You can use a service account's API key to access the ZenML server's REST API programmatically. This is particularly useful when you need to make long-term securely authenticated HTTP requests to the ZenML API endpoints. This is the recommended way to access the ZenML API programmatically when you're not using the ZenML CLI or Python client. Accessing the OSS API with this method is thoroughly documented in the [API reference section](https://docs.zenml.io/api-reference/oss-api/getting-started#using-a-service-account-and-an-api-key). For ZenML Pro Workspace API access, use a ZenML Pro service account and API key as described in [Pro API Getting Started](https://docs.zenml.io/api-reference/pro-api/getting-started). ## Important notice Every API key issued is a potential gateway to access your data, secrets and infrastructure. It's important to regularly rotate API keys and deactivate or delete service accounts and API keys that are no longer needed.
ZenML Scarf
--- ### Book/How To/Manage Zenml Server/Migration Guide/Migration Guide (docs/book/how-to/manage-zenml-server/migration-guide/migration-guide.md) --- description: How to migrate your ZenML code to the newest version. --- # Migration guide Migrations are necessary for ZenML releases that include breaking changes, which are currently all releases that increment the minor version of the release, e.g., `0.X` -> `0.Y`. Furthermore, all releases that increment the first non-zero digit of the version contain major breaking changes or paradigm shifts that are explained in separate migration guides below. ## Release Type Examples * `0.40.2` to `0.40.3` contains _no breaking changes_ and requires no migration whatsoever, * `0.40.3` to `0.41.0` contains _minor breaking changes_ that need to be taken into account when upgrading ZenML, * `0.39.1` to `0.40.0` contains _major breaking changes_ that introduce major shifts in how ZenML code is written or used. ## Major Migration Guides The following guides contain detailed instructions on how to migrate between ZenML versions that introduced major breaking changes or paradigm shifts. The migration guides are sequential, meaning if there is more than one migration guide between your current version and the latest release, follow each guide in order. * [Migration guide 0.13.2 → 0.20.0](migration-zero-twenty.md) * [Migration guide 0.23.0 → 0.30.0](migration-zero-thirty.md) * [Migration guide 0.39.1 → 0.41.0](migration-zero-forty.md) * [Migration guide 0.58.2 → 0.60.0](migration-zero-sixty.md) ## Release Notes For releases with minor breaking changes, e.g., `0.40.3` to `0.41.0`, check out the official [ZenML Release Notes](https://github.com/zenml-io/zenml/releases) to see which breaking changes were introduced.
ZenML Scarf
--- ### Book/How To/Manage Zenml Server/Migration Guide/Migration Zero Forty (docs/book/how-to/manage-zenml-server/migration-guide/migration-zero-forty.md) --- description: How to migrate your ZenML pipelines and steps from version <=0.39.1 to 0.41.0. --- # Migration guide 0.39.1 → 0.41.0 ZenML versions 0.40.0 to 0.41.0 introduced a new and more flexible syntax to define ZenML steps and pipelines. This page contains code samples that show you how to upgrade your steps and pipelines to the new syntax. {% hint style="warning" %} Newer versions of ZenML still work with pipelines and steps defined using the old syntax, but the old syntax is deprecated and will be removed in the future. {% endhint %} ## Overview {% tabs %} {% tab title="Old Syntax" %} ```python from typing import Optional from zenml.steps import BaseParameters, Output, StepContext, step from zenml.pipelines import pipeline # Define a Step class MyStepParameters(BaseParameters): param_1: int param_2: Optional[float] = None @step def my_step( params: MyStepParameters, context: StepContext, ) -> Output(int_output=int, str_output=str): result = int(params.param_1 * (params.param_2 or 1)) result_uri = context.get_output_artifact_uri() return result, result_uri # Run the Step separately my_step.entrypoint() # Define a Pipeline @pipeline def my_pipeline(my_step): my_step() step_instance = my_step(params=MyStepParameters(param_1=17)) pipeline_instance = my_pipeline(my_step=step_instance) # Configure and run the Pipeline pipeline_instance.configure(enable_cache=False) schedule = Schedule(...) pipeline_instance.run(schedule=schedule) # Fetch the Pipeline Run last_run = pipeline_instance.get_runs()[0] int_output = last_run.get_step["my_step"].outputs["int_output"].read() ``` {% endtab %} {% tab title="New Syntax" %} ```python from typing import Annotated, Optional, Tuple from zenml import get_step_context, pipeline, step from zenml.client import Client # Define a Step @step def my_step( param_1: int, param_2: Optional[float] = None ) -> Tuple[Annotated[int, "int_output"], Annotated[str, "str_output"]]: result = int(param_1 * (param_2 or 1)) result_uri = get_step_context().get_output_artifact_uri() return result, result_uri # Run the Step separately my_step() # Define a Pipeline @pipeline def my_pipeline(): my_step(param_1=17) # Configure and run the Pipeline my_pipeline = my_pipeline.with_options(enable_cache=False, schedule=schedule) my_pipeline() # Fetch the Pipeline Run last_run = my_pipeline.last_run int_output = last_run.steps["my_step"].outputs["int_output"].load() ``` {% endtab %} {% endtabs %} ## Defining steps {% tabs %} {% tab title="Old Syntax" %} ```python from typing import Optional from zenml.steps import step, BaseParameters from zenml.pipelines import pipeline # Old: Subclass `BaseParameters` to define parameters for a step class MyStepParameters(BaseParameters): param_1: int param_2: Optional[float] = None @step def my_step(params: MyStepParameters) -> None: ... @pipeline def my_pipeline(my_step): my_step() step_instance = my_step(params=MyStepParameters(param_1=17)) pipeline_instance = my_pipeline(my_step=step_instance) ``` {% endtab %} {% tab title="New Syntax" %} ```python # New: Directly define the parameters as arguments of your step function. # In case you still want to group your parameters in a separate class, # you can subclass `pydantic.BaseModel` and use that as an argument of your # step function from zenml import pipeline, step @step def my_step(param_1: int, param_2: Optional[float] = None) -> None: ... @pipeline def my_pipeline(): my_step(param_1=17) ``` {% endtab %} {% endtabs %} Check out [this page](https://docs.zenml.io/concepts/steps_and_pipelines#parameters-and-artifacts) for more information on how to parameterize your steps. ## Calling a step outside of a pipeline {% tabs %} {% tab title="Old Syntax" %} ```python from zenml.steps import step @step def my_step() -> None: ... my_step.entrypoint() # Old: Call `step.entrypoint(...)` ``` {% endtab %} {% tab title="New Syntax" %} ```python from zenml import step @step def my_step() -> None: ... my_step() # New: Call the step directly `step(...)` ``` {% endtab %} {% endtabs %} ## Defining pipelines {% tabs %} {% tab title="Old Syntax" %} ```python from zenml.pipelines import pipeline @pipeline def my_pipeline(my_step): # Old: steps are arguments of the pipeline function my_step() ``` {% endtab %} {% tab title="New Syntax" %} ```python from zenml import pipeline, step @step def my_step() -> None: ... @pipeline def my_pipeline(): my_step() # New: The pipeline function calls the step directly ``` {% endtab %} {% endtabs %} ## Configuring pipelines {% tabs %} {% tab title="Old Syntax" %} ```python from zenml.pipelines import pipeline from zenml.steps import step @step def my_step() -> None: ... @pipeline def my_pipeline(my_step): my_step() # Old: Create an instance of the pipeline and then call `pipeline_instance.configure(...)` pipeline_instance = my_pipeline(my_step=my_step()) pipeline_instance.configure(enable_cache=False) ``` {% endtab %} {% tab title="New Syntax" %} ```python from zenml import pipeline, step @step def my_step() -> None: ... @pipeline def my_pipeline(): my_step() # New: Call the `with_options(...)` method on the pipeline my_pipeline = my_pipeline.with_options(enable_cache=False) ``` {% endtab %} {% endtabs %} ## Running pipelines {% tabs %} {% tab title="Old Syntax" %} ```python from zenml.pipelines import pipeline from zenml.steps import step @step def my_step() -> None: ... @pipeline def my_pipeline(my_step): my_step() # Old: Create an instance of the pipeline and then call `pipeline_instance.run(...)` pipeline_instance = my_pipeline(my_step=my_step()) pipeline_instance.run(...) ``` {% endtab %} {% tab title="New Syntax" %} ```python from zenml import pipeline, step @step def my_step() -> None: ... @pipeline def my_pipeline(): my_step() my_pipeline() # New: Call the pipeline ``` {% endtab %} {% endtabs %} ## Scheduling pipelines {% tabs %} {% tab title="Old Syntax" %} ```python from zenml.pipelines import pipeline, Schedule from zenml.steps import step @step def my_step() -> None: ... @pipeline def my_pipeline(my_step): my_step() # Old: Create an instance of the pipeline and then call `pipeline_instance.run(schedule=...)` schedule = Schedule(...) pipeline_instance = my_pipeline(my_step=my_step()) pipeline_instance.run(schedule=schedule) ``` {% endtab %} {% tab title="New Syntax" %} ```python from zenml.pipelines import Schedule from zenml import pipeline, step @step def my_step() -> None: ... @pipeline def my_pipeline(): my_step() # New: Set the schedule using the `pipeline.with_options(...)` method and then run it schedule = Schedule(...) my_pipeline = my_pipeline.with_options(schedule=schedule) my_pipeline() ``` {% endtab %} {% endtabs %} Check out [this page](https://docs.zenml.io/user-guides/tutorial/managing-scheduled-pipelines) for more information on how to schedule your pipelines. ## Fetching pipelines after execution {% tabs %} {% tab title="Old Syntax" %} ```python pipeline: PipelineView = zenml.post_execution.get_pipeline("first_pipeline") last_run: PipelineRunView = pipeline.runs[0] # OR: last_run = my_pipeline.get_runs()[0] model_trainer_step: StepView = last_run.get_step("model_trainer") model: ArtifactView = model_trainer_step.output loaded_model = model.read() ``` {% endtab %} {% tab title="New Syntax" %} ```python pipeline: PipelineResponseModel = zenml.client.Client().get_pipeline("first_pipeline") # OR: pipeline = pipeline_instance.model last_run: PipelineRunResponseModel = pipeline.last_run # OR: last_run = pipeline.runs[0] # OR: last_run = pipeline.get_runs(custom_filters)[0] # OR: last_run = pipeline.last_successful_run model_trainer_step: StepRunResponseModel = last_run.steps["model_trainer"] model: ArtifactResponseModel = model_trainer_step.output loaded_model = model.load() ``` {% endtab %} {% endtabs %} Check out [this page](https://docs.zenml.io/how-to/model-management-metrics/track-metrics-metadata/fetch-metadata-within-steps) for more information on how to programmatically fetch information about previous pipeline runs. ## Controlling the step execution order {% tabs %} {% tab title="Old Syntax" %} ```python from zenml.pipelines import pipeline @pipeline def my_pipeline(step_1, step_2, step_3): step_1() step_2() step_3() step_3.after(step_1) # Old: Use the `step.after(...)` method step_3.after(step_2) ``` {% endtab %} {% tab title="New Syntax" %} ```python from zenml import pipeline @pipeline def my_pipeline(): step_1() step_2() step_3(after=["step_1", "step_2"]) # New: Pass the `after` argument when calling a step ``` {% endtab %} {% endtabs %} Check out [this page](https://docs.zenml.io/concepts/steps_and_pipelines/advanced_features#step-execution-order) for more information on how to control the step execution order. ## Defining steps with multiple outputs {% tabs %} {% tab title="Old Syntax" %} ```python # Old: Use the `Output` class from zenml.steps import step, Output @step def my_step() -> Output(int_output=int, str_output=str): ... ``` {% endtab %} {% tab title="New Syntax" %} ```python # New: Use a `Tuple` annotation and optionally assign custom output names from typing import Annotated from typing import Tuple from zenml import step # Default output names `output_0`, `output_1` @step def my_step() -> Tuple[int, str]: ... # Custom output names @step def my_step() -> Tuple[ Annotated[int, "int_output"], Annotated[str, "str_output"], ]: ... ``` {% endtab %} {% endtabs %} Check out [this page](https://docs.zenml.io/concepts/steps_and_pipelines#type-annotations) for more information on how to annotate your step outputs. ## Accessing run information inside steps {% tabs %} {% tab title="Old Syntax" %} ```python from zenml.steps import StepContext, step from zenml.environment import Environment @step def my_step(context: StepContext) -> Any: # Old: `StepContext` class defined as arg env = Environment().step_environment output_uri = context.get_output_artifact_uri() step_name = env.step_name # Old: Run info accessible via `StepEnvironment` ... ``` {% endtab %} {% tab title="New Syntax" %} ```python from zenml import get_step_context, step @step def my_step() -> Any: # New: StepContext is no longer an argument of the step context = get_step_context() output_uri = context.get_output_artifact_uri() step_name = context.step_name # New: StepContext now has ALL run/step info ... ``` {% endtab %} {% endtabs %} Check out [this page](https://docs.zenml.io/how-to/model-management-metrics/track-metrics-metadata/fetch-metadata-within-steps) for more information on how to fetch run information inside your steps using `get_step_context()`.
ZenML Scarf
--- ### Book/How To/Manage Zenml Server/Migration Guide/Migration Zero Sixty (docs/book/how-to/manage-zenml-server/migration-guide/migration-zero-sixty.md) --- description: How to migrate from ZenML 0.58.2 to 0.60.0 (Pydantic 2 edition). --- # Release Notes ZenML now uses Pydantic v2. 🥳 This upgrade comes with a set of critical updates. While your user experience mostly remains unaffected, you might see unexpected behavior due to the changes in our dependencies. Moreover, since Pydantic v2 provides a slightly stricter validation process, you might end up bumping into some validation errors which was not caught before, but it is all for the better 🙂 If you run into any other errors, please let us know either on [GitHub](https://github.com/zenml-io/zenml) or on our [Slack](https://zenml.io/slack-invite). ## Changes in some of the critical dependencies - SQLModel is one of the core dependencies of ZenML and prior to this upgrade, we were utilizing version `0.0.8`. However, this version is relatively outdated and incompatible with Pydantic v2. Within the scope of this upgrade, we upgraded it to `0.0.18`. - Due to the change in the SQLModel version, we also had to upgrade our SQLAlchemy dependency from V1 to v2. While this does not affect the way that you are using ZenML, if you are using SQLAlchemy in your environment, you might have to migrate your code as well. For a detailed list of changes, feel free to check [their migration guide](https://docs.sqlalchemy.org/en/20/changelog/migration_20.html). ## Changes in `pydantic` Pydantic v2 brings a lot of new and exciting changes to the table. The core logic now uses Rust and it is much faster and more efficient in terms of performance. On top of it, the main concepts like model design, configuration, validation, or serialization now include a lot of new cool features. If you are using `pydantic` in your workflow and are interested in the new changes, you can check [the brilliant migration guide](https://docs.pydantic.dev/2.7/migration/) provided by the `pydantic` team to see the full list of changes. ## Changes in our integrations changes Much like ZenML, `pydantic` is an important dependency in many other Python packages. That’s why conducting this upgrade helped us unlock a new version for several ZenML integration dependencies. Additionally, in some instances, we had to adapt the functionality of the integration to keep it compatible with `pydantic`. So, if you are using any of these integrations, please go through the changes. ### Airflow As mentioned above upgrading our `pydantic` dependency meant we had to upgrade our `sqlmodel` dependency. Upgrading our `sqlmodel` dependency meant we had to upgrade our `sqlalchemy` dependency as well. Unfortunately, `apache-airflow` is still using `sqlalchemy` v1 and is incompatible with pydantic v2. As a solution, we have removed the dependencies of the `airflow` integration. Now, you can use ZenML to create your Airflow pipelines and use a separate environment to run them with Airflow. You can check the updated docs [right here](https://docs.zenml.io/stacks/orchestrators/airflow). ### AWS Some of our integrations now require `protobuf` 4. Since our previous `sagemaker` version (`2.117.0`) did not support `protobof` 4, we could not pair it with these new integrations. Thankfully `sagemaker` started supporting `protobuf` 4 with version `2.172.0` and relaxing its dependency solved the compatibility issue. ### Evidently The old version of our `evidently` integration was not compatible with Pydantic v2. They started supporting it starting from version `0.4.16`. As their latest version is `0.4.22`, the new dependency of the integration is limited between these two versions. ### Feast Our previous implementation of the `feast` integration was not compatible with Pydantic v2 due to the extra `redis` dependency we were using. This extra dependency is now removed and the `feast` integration is working as intended. ### GCP The previous version of the Kubeflow dependency (`kfp==1.8.22`) in our GCP integration required Pydantic V1 to be installed. While we were upgrading our Pydantic dependency, we saw this as an opportunity and wanted to use this chance to upgrade the `kfp` dependency to v2 (which has no dependencies on the Pydantic library). This is why you may see some functional changes in the vertex step operator and orchestrator. If you would like to go through the changes in the `kfp` library, you can find [the migration guide here](https://www.kubeflow.org/docs/components/pipelines/v2/migration/). ### Great Expectations Great Expectations started supporting Pydantic v2 starting from version `0.17.15` and they are closing in on their `1.0` release. Since this release might include a lot of big changes, we adjusted the dependency in our integration to `great-expectations>=0.17.15,<1.0`. We will try to keep it updated in the future once they release the `1.0` version ### Kubeflow Similar to the GCP integration, the previous version of the kubeflow dependency (`kfp==1.8.22`) in our `kubeflow` integration required Pydantic V1 to be installed. While we were upgrading our Pydantic dependency, we saw this as an opportunity and wanted to use this chance to upgrade the `kfp` dependency to v2 (which has no dependencies on the Pydantic library). If you would like to go through the changes in the `kfp` library, you can find [the migration guide here](https://www.kubeflow.org/docs/components/pipelines/v2/migration/). ( We also are considering adding an alternative version of this integration so our users can keep using `kfp` V1 in their environment. Stay tuned for any updates.) ### MLflow `mlflow` is compatible with both Pydantic V1 and v2. However, due to a known issue, if you install `zenml` first and then do `zenml integration install mlflow -y`, it downgrades `pydantic` to V1. This is why we manually added the same duplicated `pydantic` requirement in the integration definition as well. Keep in mind that the `mlflow` library is still using some features of `pydantic` V1 which are deprecated. So, if the integration is installed in your environment, you might run into some deprecation warnings. ### Label Studio While we were working on updating our `pydantic` dependency, the `label-studio-sdk` has released its 1.0 version. In this new version, `pydantic` v2 is also supported. The implementation and documentation of our Label Studio integration have been updated accordingly. ### Skypilot With the switch to `pydantic` v2, the implementation of our `skypilot` integration mostly remained untouched. However, due to an incompatibility between the new version `pydantic` and the `azurecli`, the `skypilot[azure]` flavor can not be installed at the same time, thus our `skypilot_azure` integration is currently deactivated. We are working on fixing this issue and if you are using this integration in your workflows, we recommend staying on the previous version of ZenML until we can solve this issue. ### Tensorflow The new version of `pydantic` creates a drift between `tensorflow` and `typing_extensions` packages and relaxing the dependencies here resolves the issue. At the same time, the upgrade to `kfp` v2 (in integrations like `kubeflow`, `tekton`, or `gcp`) bumps our `protobuf` dependency from `3.X` to `4.X`. To stay compatible with this requirement, the installed version of `tensorflow` needs to be `>=2.12.0`. While this change solves the dependency issues in most settings, we have bumped into some errors while using `tensorflow` 2.12.0 on Python 3.8 on Ubuntu. If you would like to use this integration, please consider using a higher Python version. ### Tekton Similar to the `gcp` and `kubeflow` integrations, the old version of our `tekton` integration was not compatible with `pydantic` V1 due to its `kfp` dependency. With the switch from `kfp` V1 to v2, we have adapted our implementation to use the new version of `kfp` library and updated our documentation accordingly. {% hint style="warning" %} Due to all aforementioned changes, when you upgrade ZenML to 0.60.0, you might run into some dependency issues, especially if you were previously using an integration which was not supporting Pydantic v2 before. In such cases, we highly recommend setting up a fresh Python environment. {% endhint %}
ZenML Scarf
--- ### Book/How To/Manage Zenml Server/Migration Guide/Migration Zero Thirty (docs/book/how-to/manage-zenml-server/migration-guide/migration-zero-thirty.md) --- description: How to migrate from ZenML 0.20.0-0.23.0 to 0.30.0-0.39.1. --- {% hint style="warning" %} Migrating to `0.30.0` performs non-reversible database changes so downgrading to `<=0.23.0` is not possible afterwards. If you are running on an older ZenML version, please follow the [0.20.0 Migration Guide](migration-zero-twenty.md) first to prevent unexpected database migration failures. {% endhint %} The ZenML 0.30.0 release removed the `ml-pipelines-sdk` dependency in favor of natively storing pipeline runs and artifacts in the ZenML database. The corresponding database migration will happen automatically as soon as you run any `zenml ...` CLI command after installing the new ZenML version, e.g.: ```bash pip install zenml==0.30.0 zenml version # 0.30.0 ```
ZenML Scarf
--- ### Book/How To/Manage Zenml Server/Migration Guide/Migration Zero Twenty (docs/book/how-to/manage-zenml-server/migration-guide/migration-zero-twenty.md) --- description: How to migrate from ZenML <=0.13.2 to 0.20.0. --- # Migration guide 0.13.2 → 0.20.0 *Last updated: 2023-07-24* The ZenML 0.20.0 release brings a number of big changes to its architecture and its features, some of which are not backwards compatible with previous versions. This guide walks you through these changes and offers instructions on how to migrate your existing ZenML stacks and pipelines to the new version with minimal effort and disruption to your existing workloads. {% hint style="warning" %} Updating to ZenML 0.20.0 needs to be followed by a migration of your existing ZenML Stacks and you may also need to make changes to your current ZenML pipeline code. Please read this guide carefully and follow the migration instructions to ensure a smooth transition. If you have updated to ZenML 0.20.0 by mistake or are experiencing issues with the new version, you can always go back to the previous version by using `pip install zenml==0.13.2` instead of `pip install zenml` when installing ZenML manually or in your scripts. {% endhint %} High-level overview of the changes: * [ZenML takes over the Metadata Store](migration-zero-twenty.md#zenml-takes-over-the-metadata-store-role) role. All information about your ZenML Stacks, pipelines, and artifacts is tracked by ZenML itself directly. If you are currently using remote Metadata Stores (e.g. deployed in cloud) in your stacks, you will probably need to replace them with a [ZenML server deployment](https://docs.zenml.io/getting-started/deploying-zenml). * the [new ZenML Dashboard](migration-zero-twenty.md#the-zenml-dashboard-is-now-available) is now available with all ZenML deployments. * [ZenML Profiles have been removed](migration-zero-twenty.md#removal-of-profiles-and-the-local-yaml-database) in favor of ZenML Projects. You need to [manually migrate your existing ZenML Profiles](migration-zero-twenty.md#-how-to-migrate-your-profiles) after the update. * the [configuration of Stack Components is now decoupled from their implementation](migration-zero-twenty.md#decoupling-stack-component-configuration-from-implementation). If you extended ZenML with custom stack component implementations, you may need to update the way they are registered in ZenML. * the updated ZenML server provides a new and improved collaborative experience. When connected to a ZenML server, you can now [share your ZenML Stacks and Stack Components](migration-zero-twenty.md#shared-zenml-stacks-and-stack-components) with other users. If you were previously using the ZenML Profiles or the ZenML server to share your ZenML Stacks, you should switch to the new ZenML server and Dashboard and update your existing workflows to reflect the new features. ## ZenML takes over the Metadata Store role ZenML can now run [as a server](https://docs.zenml.io/getting-started/core-concepts#zenml-server-and-dashboard) that can be accessed via a REST API and also comes with a visual user interface (called the ZenML Dashboard). This server can be deployed in arbitrary environments (local, on-prem, via Docker, on AWS, GCP, Azure etc.) and supports user management, workspace scoping, and more. The release introduces a series of commands to facilitate managing the lifecycle of the ZenML server and to access the pipeline and pipeline run information: * `zenml connect / disconnect / down / up / logs / status` can be used to configure your client to connect to a ZenML server, to start a local ZenML Dashboard or to deploy a ZenML server to a cloud environment. For more information on how to use these commands, see [the ZenML deployment documentation](https://docs.zenml.io/getting-started/deploying-zenml). * `zenml pipeline list / runs / delete` can be used to display information and about and manage your pipelines and pipeline runs. In ZenML 0.13.2 and earlier versions, information about pipelines and pipeline runs used to be stored in a separate stack component called the Metadata Store. Starting with 0.20.0, the role of the Metadata Store is now taken over by ZenML itself. This means that the Metadata Store is no longer a separate component in the ZenML architecture, but rather a part of the ZenML core, located wherever ZenML is deployed: locally on your machine or running remotely as a server. All metadata is now stored, tracked, and managed by ZenML itself. The Metadata Store stack component type and all its implementations have been deprecated and removed. It is no longer possible to register them or include them in ZenML stacks. This is a key architectural change in ZenML 0.20.0 that further improves usability, reproducibility and makes it possible to visualize and manage all your pipelines and pipeline runs in the new ZenML Dashboard. The architecture changes for the local case are shown in the diagram below: The architecture changes for the remote case are shown in the diagram below: If you're already using ZenML, aside from the above limitation, this change will impact you differently, depending on the flavor of Metadata Stores you have in your stacks: * if you're using the default `sqlite` Metadata Store flavor in your stacks, you don't need to do anything. ZenML will automatically switch to using its local database instead of your `sqlite` Metadata Stores when you update to 0.20.0 (also see how to [migrate your stacks](migration-zero-twenty.md#-how-to-migrate-your-profiles)). * if you're using the `kubeflow` Metadata Store flavor _only as a way to connect to the local Kubeflow Metadata Service_ (i.e. the one installed by the `kubeflow` Orchestrator in a local k3d Kubernetes cluster), you also don't need to do anything explicitly. When you [migrate your stacks](migration-zero-twenty.md#-how-to-migrate-your-profiles) to ZenML 0.20.0, ZenML will automatically switch to using its local database. * if you're using the `kubeflow` Metadata Store flavor to connect to a remote Kubeflow Metadata Service such as those provided by a Kubeflow installation running in AWS, Google or Azure, there is currently no equivalent in ZenML 0.20.0. You'll need to [deploy a ZenML Server](https://docs.zenml.io/getting-started/deploying-zenml) instance close to where your Kubeflow service is running (e.g. in the same cloud region). * if you're using the `mysql` Metadata Store flavor to connect to a remote MySQL database service (e.g. a managed AWS, GCP or Azure MySQL service), you'll have to [deploy a ZenML Server](https://docs.zenml.io/getting-started/deploying-zenml) instance connected to that same database. * if you deployed a `kubernetes` Metadata Store flavor (i.e. a MySQL database service deployed in Kubernetes), you can [deploy a ZenML Server](https://docs.zenml.io/getting-started/deploying-zenml) in the same Kubernetes cluster and connect it to that same database. However, ZenML will no longer provide the `kubernetes` Metadata Store flavor and you'll have to manage the Kubernetes MySQL database service deployment yourself going forward. {% hint style="info" %} The ZenML Server inherits the same limitations that the Metadata Store had prior to ZenML 0.20.0: * it is not possible to use a local ZenML Server to track pipelines and pipeline runs that are running remotely in the cloud, unless the ZenML server is explicitly configured to be reachable from the cloud (e.g. by using a public IP address or a VPN connection). * using a remote ZenML Server to track pipelines and pipeline runs that are running locally is possible, but can have significant performance issues due to the network latency. It is therefore recommended that you always use a ZenML deployment that is located as close as possible to and reachable from where your pipelines and step operators are running. This will ensure the best possible performance and usability. {% endhint %} ### 👣 How to migrate pipeline runs from your old metadata stores {% hint style="info" %} The `zenml pipeline runs migrate` CLI command is only available under ZenML versions \[0.21.0, 0.21.1, 0.22.0]. If you want to migrate your existing ZenML runs from `zenml<0.20.0` to `zenml>0.22.0`, please first upgrade to `zenml==0.22.0` and migrate your runs as shown below, then upgrade to the newer version. {% endhint %} To migrate the pipeline run information already stored in an existing metadata store to the new ZenML paradigm, you can use the `zenml pipeline runs migrate` CLI command. 1. Before upgrading ZenML, make a backup of all metadata stores you want to migrate, then upgrade ZenML. 2. Decide the ZenML deployment model that you want to follow for your projects. See the [ZenML deployment documentation](https://docs.zenml.io/getting-started/deploying-zenml) for available deployment scenarios. If you decide on using a local or remote ZenML server to manage your pipelines, make sure that you first connect your client to it by running `zenml connect`. 3. Use the `zenml pipeline runs migrate` CLI command to migrate your old pipeline runs: * If you want to migrate from a local SQLite metadata store, you only need to pass the path to the metadata store to the command, e.g.: ```bash zenml pipeline runs migrate PATH/TO/LOCAL/STORE/metadata.db ``` * If you would like to migrate any other store, you will need to set `--database_type=mysql` and provide the MySQL host, username, and password in addition to the database, e.g.: ```bash zenml pipeline runs migrate DATABASE_NAME \ --database_type=mysql \ --mysql_host=URL/TO/MYSQL \ --mysql_username=MYSQL_USERNAME \ --mysql_password=MYSQL_PASSWORD ``` ### 💾 The New Way (CLI Command Cheat Sheet) **Deploy the server** `zenml deploy --aws` (maybe don't do this :) since it spins up infrastructure on AWS…) **Spin up a local ZenML Server** `zenml up` **Connect to a pre-existing server** `zenml connect` (pass in URL / etc, or zenml connect --config + yaml file) **List your deployed server details** `zenml status` ## The ZenML Dashboard is now available The new ZenML Dashboard is now bundled into the ZenML Python package and can be launched directly from Python. The source code lives in the [ZenML Dashboard repository](https://github.com/zenml-io/zenml-dashboard). To launch it locally, simply run `zenml up` on your machine and follow the instructions: ```bash $ zenml up Deploying a local ZenML server with name 'local'. Connecting ZenML to the 'local' local ZenML server (http://127.0.0.1:8237). Updated the global store configuration. Connected ZenML to the 'local' local ZenML server (http://127.0.0.1:8237). The local ZenML dashboard is available at 'http://127.0.0.1:8237'. You can connect to it using the 'default' username and an empty password. ``` The Dashboard will be available at `http://localhost:8237` by default: For more details on other possible deployment options, see the [ZenML deployment documentation](https://docs.zenml.io/getting-started/deploying-zenml), and/or follow the [starter guide](https://docs.zenml.io/user-guides/starter-guide) to learn more. ## Removal of Profiles and the local YAML database Prior to 0.20.0, ZenML used used a set of local YAML files to store information about the Stacks and Stack Components that were registered on your machine. In addition to that, these Stacks could be grouped together and organized under individual Profiles. Profiles and the local YAML database have both been deprecated and removed in ZenML 0.20.0. Stack, Stack Components as well as all other information that ZenML tracks, such as Pipelines and Pipeline Runs, are now stored in a single SQL database. These entities are no longer organized into Profiles, but they can be scoped into different Projects instead. {% hint style="warning" %} Since the local YAML database is no longer used by ZenML 0.20.0, you will lose all the Stacks and Stack Components that you currently have configured when you update to ZenML 0.20.0. If you still want to use these Stacks, you will need to [manually migrate](migration-zero-twenty.md#-how-to-migrate-your-profiles) them after the update. {% endhint %} ### 👣 How to migrate your Profiles If you're already using ZenML, you can migrate your existing Profiles to the new ZenML 0.20.0 paradigm by following these steps: 1. first, update ZenML to 0.20.0. This will automatically invalidate all your existing Profiles. 2. decide the ZenML deployment model that you want to follow for your projects. See the [ZenML deployment documentation](https://docs.zenml.io/getting-started/deploying-zenml) for available deployment scenarios. If you decide on using a local or remote ZenML server to manage your pipelines, make sure that you first connect your client to it by running `zenml connect`. 3. use the `zenml profile list` and `zenml profile migrate` CLI commands to import the Stacks and Stack Components from your Profiles into your new ZenML deployment. If you have multiple Profiles that you would like to migrate, you can either use a prefix for the names of your imported Stacks and Stack Components, or you can use a different ZenML Project for each Profile. {% hint style="warning" %} The ZenML Dashboard is currently limited to showing only information that is available in the `default` Project. If you wish to migrate your Profiles to a different Project, you will not be able to visualize the migrated Stacks and Stack Components in the Dashboard. This will be fixed in a future release. {% endhint %} Once you've migrated all your Profiles, you can delete the old YAML files. Example of migrating a `default` profile into the `default` project: ``` /* Detailed source-code truncated for AI context efficiency. */ ``` Example of migrating a profile into the `default` project using a name prefix: ``` /* Detailed source-code truncated for AI context efficiency. */ ``` Example of migrating a profile into a new project: ``` /* Detailed source-code truncated for AI context efficiency. */ ``` The `zenml profile migrate` CLI command also provides command line flags for cases in which the user wants to overwrite existing components or stacks, or ignore errors. ## Decoupling Stack Component configuration from implementation Stack components can now be registered without having the required integrations installed. As part of this change, we split all existing stack component definitions into three classes: an implementation class that defines the logic of the stack component, a config class that defines the attributes and performs input validations, and a flavor class that links implementation and config classes together. See [**component flavor models #895**](https://github.com/zenml-io/zenml/pull/895) for more details. If you are only using stack component flavors that are shipped with the zenml Python distribution, this change has no impact on the configuration of your existing stacks. However, if you are currently using custom stack component implementations, you will need to update them to the new format. See the [documentation on writing custom stack component flavors](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/implement-a-custom-stack-component) for updated information on how to do this. ## Shared ZenML Stacks and Stack Components With collaboration being the key part of ZenML, the 0.20.0 release puts the concepts of Users in the front and center and introduces the possibility to share stacks and stack components with other users by means of the ZenML server. When your client is connected to a ZenML server, entities such as Stacks, Stack Components, Stack Component Flavors, Pipelines, Pipeline Runs, and artifacts are scoped to a Project and owned by the User that creates them. Only the objects that are owned by the current user used to authenticate to the ZenML server and that are part of the current project are available to the client. Stacks and Stack Components can also be shared within the same project with other users. To share an object, either set it as shared during creation time (e.g. `zenml stack register mystack ... --share`) or afterwards (e.g. through `zenml stack share mystack`). To differentiate between shared and private Stacks and Stack Components, these can now be addressed by name, id or the first few letters of the id in the cli. E.g. for a stack `default` with id `179ebd25-4c5b-480f-a47c-d4f04e0b6185` you can now run `zenml stack describe default` or `zenml stack describe 179` or `zenml stack describe 179ebd25-4c5b-480f-a47c-d4f04e0b6185`. We also introduce the notion of `local` vs `non-local` stack components. Local stack components are stack components that are configured to run locally while non-local stack components are configured to run remotely or in a cloud environment. Consequently: * stacks made up of local stack components should not be shared on a central ZenML Server, even though this is not enforced by the system. * stacks made up of non-local stack components are only functional if they are shared through a remotely deployed ZenML Server. Read more about shared stacks in the [production guide](https://docs.zenml.io/user-guides/production-guide/understand-stacks). ## Other changes ### The `Repository` class is now called `Client` The `Repository` object has been renamed to `Client` to better capture its functionality. You can continue to use the `Repository` object for backwards compatibility, but it will be removed in a future release. **How to migrate**: Rename all references to `Repository` in your code to `Client`. ### The `BaseStepConfig` class is now called `BaseParameters` The `BaseStepConfig` object has been renamed to `BaseParameters` to better capture its functionality. You can NOT continue to use the `BaseStepConfig`. This is part of a broader configuration rehaul which is discussed next. **How to migrate**: Rename all references to `BaseStepConfig` in your code to `BaseParameters`. ### Configuration Rework Alongside the architectural shift, Pipeline configuration has been completely rethought. This video gives an overview of how configuration has changed with ZenML in the post ZenML 0.20.0 world. {% embed url="https://www.youtube.com/embed/hI-UNV7uoNI" %} Configuring pipelines, steps, and stack components in ZenML {% endembed %} **What changed?** ZenML pipelines and steps could previously be configured in many different ways: * On the `@pipeline` and `@step` decorators (e.g. the `requirements` variable) * In the `__init__` method of the pipeline and step class * Using `@enable_xxx` decorators, e.g. `@enable_mlflow`. * Using specialized methods like `pipeline.with_config(...)` or `step.with_return_materializer(...)` Some of the configuration options were quite hidden, difficult to access and not tracked in any way by the ZenML metadata store. With ZenML 0.20.0, we introduce the `BaseSettings` class, a broad class that serves as a central object to represent all runtime configuration of a pipeline run (apart from the `BaseParameters`). Pipelines and steps now allow all configurations on their decorators as well as the `.configure(...)` method. This includes configurations for stack components that are not infrastructure-related which was previously done using the `@enable_xxx` decorators). The same configurations can also be defined in a YAML file. Read more about this paradigm in the [new docs section about settings](https://docs.zenml.io/concepts/steps_and_pipelines/configuration). Here is a list of changes that are the most obvious in consequence of the above code. Please note that this list is not exhaustive, and if we have missed something let us know via [Slack](https://zenml.io/slack). **Deprecating the `enable_xxx` decorators** With the above changes, we are deprecating the much-loved `enable_xxx` decorators, like `enable_mlflow` and `enable_wandb`. **How to migrate**: Simply remove the decorator and pass something like this instead to step directly: ```python @step( experiment_tracker="mlflow_stack_comp_name", # name of registered component settings={ # settings of registered component "experiment_tracker.mlflow": { # this is `category`.`flavor`, so another example is `step_operator.spark` "experiment_name": "name", "nested": False } } ) ``` **Deprecating `pipeline.with_config(...)`** **How to migrate**: Replaced with the new `pipeline.run(config_path=...)`. **Deprecating `step.with_return_materializer(...)`** **How to migrate**: Simply remove the `with_return_materializer` method and pass something like this instead to step directly: ```python @step( output_materializers=materializer_or_dict_of_materializers_mapped_to_outputs ) ``` **`DockerConfiguration` is now renamed to `DockerSettings`** **How to migrate**: Rename `DockerConfiguration` to `DockerSettings` and instead of passing it in the decorator directly with `docker_configuration`, you can use: ```python from zenml.config import DockerSettings @step(settings={"docker": DockerSettings(...)}) def my_step() -> None: ... ``` With this change, all stack components (e.g. Orchestrators and Step Operators) that accepted a `docker_parent_image` as part of its Stack Configuration should now pass it through the `DockerSettings` object. Read more [here](https://docs.zenml.io/how-to/customize-docker-builds/docker-settings-on-a-pipeline). **`ResourceConfiguration` is now renamed to `ResourceSettings`** **How to migrate**: Rename `ResourceConfiguration` to `ResourceSettings` and instead of passing it in the decorator directly with `resource_configuration`, you can use: ```python from zenml.config import ResourceSettings @step(settings={"resources": ResourceSettings(...)}) def my_step() -> None: ... ``` **Deprecating the `requirements` and `required_integrations` parameters** Users used to be able to pass `requirements` and `required_integrations` directly in the `@pipeline` decorator, but now need to pass them through settings: **How to migrate**: Simply remove the parameters and use the `DockerSettings` instead ```python from zenml.config import DockerSettings @step(settings={"docker": DockerSettings(requirements=[...], requirements_integrations=[...])}) def my_step() -> None: ... ``` Read more [here](https://docs.zenml.io/how-to/customize-docker-builds). **A new pipeline intermediate representation** All the aforementioned configurations as well as additional information required to run a ZenML pipelines are now combined into an intermediate representation called `PipelineDeployment`. Instead of the user-facing `BaseStep` and `BasePipeline` classes, all the ZenML orchestrators and step operators now use this intermediate representation to run pipelines and steps. **How to migrate**: If you have written a [custom orchestrator](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/implement-a-custom-stack-component) or [step operator](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/implement-a-custom-stack-component), then you should see the new base abstractions (seen in the links). You can adjust your stack component implementations accordingly. ### `PipelineSpec` now uniquely defines pipelines Once a pipeline has been executed, it is represented by a `PipelineSpec` that uniquely identifies it. Therefore, users are no longer able to edit a pipeline once it has been run once. There are now three options to get around this: * Pipeline runs can be created without being associated with a pipeline explicitly: We call these `unlisted` runs. Read more about unlisted runs [here](https://docs.zenml.io/user-guides/best-practices/keep-your-dashboard-server-clean#unlisted-runs). * Pipelines can be deleted and created again. * Pipelines can be given unique names each time they are run to uniquely identify them. **How to migrate**: No code changes, but rather keep in mind the behavior (e.g. in a notebook setting) when quickly [iterating over pipelines as experiments](https://docs.zenml.io/concepts/steps_and_pipelines#parameters-and-artifacts). ### New post-execution workflow The Post-execution workflow has changed as follows: * The `get_pipelines` and `get_pipeline` methods have been moved out of the `Repository` (i.e. the new `Client` ) class and lie directly in the post\_execution module now. To use the user has to do: ```python from zenml.post_execution import get_pipelines, get_pipeline ``` * New methods to directly get a run have been introduced: `get_run` and `get_unlisted_runs` method has been introduced to get unlisted runs. Usage remains largely similar. Please read the [new docs for post-execution](https://docs.zenml.io/user-guides/tutorial/fetching-pipelines) to inform yourself of what further has changed. **How to migrate**: Replace all post-execution workflows from the paradigm of `Repository.get_pipelines` or `Repository.get_pipeline_run` to the corresponding post\_execution methods. ## 📡Future Changes While this rehaul is big and will break previous releases, we do have some more work left to do. However we also expect this to be the last big rehaul of ZenML before our 1.0.0 release, and no other release will be so hard breaking as this one. Currently planned future breaking changes are: * Following the metadata store, the secrets manager stack component might move out of the stack. * ZenML `StepContext` might be deprecated. ## 🐞 Reporting Bugs While we have tried our best to document everything that has changed, we realize that mistakes can be made and smaller changes overlooked. If this is the case, or you encounter a bug at any time, the ZenML core team and community are available around the clock on the growing [Slack community](https://zenml.io/slack). For bug reports, please also consider submitting a [GitHub Issue](https://github.com/zenml-io/zenml/issues/new/choose). Lastly, if the new changes have left you desiring a feature, then consider adding it to our [public feature voting board](https://zenml.io/discussion). Before doing so, do check what is already on there and consider upvoting the features you desire the most.
ZenML Scarf
--- ### Book/How To/Metadata/Metadata (docs/book/how-to/metadata/metadata.md) --- description: Enrich your ML workflow with contextual information using ZenML metadata. icon: square-info --- # Metadata Metadata in ZenML provides critical context to your ML workflows, allowing you to track additional information about your steps, runs, artifacts, and models. This enhanced traceability helps you better understand, compare, and reproduce your experiments. Metadata is any additional contextual information you want to associate with your ML workflow components. In ZenML, you can attach metadata to: * **Steps**: Log evaluation metrics, execution details, or configuration information * **Pipeline Runs**: Track overall run characteristics like environment variables or git information * **Artifacts**: Document data characteristics, source information, or processing details * **Models**: Capture evaluation results, hyperparameters, or deployment information ZenML makes it easy to log and retrieve this information through a simple interface, and visualizes it in the dashboard for quick analysis. ## Logging Metadata The primary way to log metadata in ZenML is through the `log_metadata` function, which allows you to attach JSON-serializable key-value pairs to various entities. {% hint style="info" %} Metadata supports primitive types (`str`, `int`, `float`, `bool`), collections (`list`, `dict`, `set`, `tuple`), and special ZenML types (`Uri`, `Path`, `DType`, `StorageSize`). Sets and tuples are automatically converted to lists during storage. {% endhint %} ```python from zenml import log_metadata # Basic metadata logging log_metadata( metadata={"accuracy": 0.95, "precision": 0.92}, # Additional parameters to specify where to log the metadata ) ``` The `log_metadata` function is versatile and can target different entities depending on the parameters provided. ### Attaching Metadata to Steps To log metadata for a step, you can either call `log_metadata` within the step (which automatically associates with the current step), or specify a step explicitly: ```python from zenml import step, log_metadata # Method 1: Within a step (automatically associates with current step) @step def train_model_step(data): model = train_model(data) accuracy = evaluate_model(model, data) # Log metrics directly within the step log_metadata( metadata={"evaluation_metrics": {"accuracy": accuracy}} ) return model # Method 2: Targeting a specific step after execution log_metadata( metadata={"post_analysis": {"feature_importance": [0.2, 0.5, 0.3]}}, step_name="train_model_step", run_id_name_or_prefix="my_run_id" ) # Alternative: Using step_id log_metadata( metadata={"post_analysis": {"feature_importance": [0.2, 0.5, 0.3]}}, step_id="step_uuid" ) ``` ### Attaching Metadata to Pipeline Runs You can log metadata for an entire pipeline run, either from within a step during execution or manually after the run: ```python from zenml import get_step_context, pipeline, step, log_metadata # Method 1: Within a step (logs to the current run) @step def log_run_info_step(): context = get_step_context() # Get some runtime information git_commit = get_git_hash() environment = get_env_info() # Log to the current pipeline run log_metadata( metadata={ "git_info": {"commit": git_commit}, "environment": environment }, run_id_name_or_prefix=context.pipeline_run.id, ) # Method 2: Manually targeting a specific run log_metadata( metadata={"post_run_analysis": {"total_training_time": 350}}, run_id_name_or_prefix="my_run_id" ) ``` When logging from within a step to the pipeline run, the metadata key will have the pattern `step_name::metadata_key`, allowing multiple steps to use the same metadata key. ### Attaching Metadata to Artifacts Artifacts are the data objects produced by pipeline steps. You can log metadata for these artifacts to provide more context about the data: ```python from zenml import step, log_metadata from zenml.metadata.metadata_types import StorageSize # Method 1: Within a step for an output artifact @step def process_data_step(raw_data): processed_data = transform(raw_data) # Log metadata for the output artifact (when step has single output) log_metadata( metadata={ "data_stats": { "row_count": len(processed_data), "columns": list(processed_data.columns), "storage_size": StorageSize(processed_data.memory_usage().sum()) } }, infer_artifact=True # Automatically target the output artifact ) return processed_data # Method 2: For a step with multiple outputs @step def split_data_step(data): train, test = split_data(data) # Log metadata for specific output by name log_metadata( metadata={"split_info": {"train_size": len(train)}}, artifact_name="output_0", # Name of the specific output infer_artifact=True ) return train, test # Method 3: Explicitly target an artifact by name and version log_metadata( metadata={"validation_results": {"distribution_shift": 0.03}}, artifact_name="processed_data", artifact_version="20230615" ) # Method 4: Target by artifact version ID log_metadata( metadata={"validation_results": {"distribution_shift": 0.03}}, artifact_version_id="artifact_uuid" ) ``` ### Attaching Metadata to Models Models in ZenML represent a higher-level concept that can encapsulate multiple artifacts and steps. Logging metadata for models helps track performance and other important information: ```python from zenml import step, log_metadata # Method 1: Within a step that produces a model @step def train_model_step(data): model = train_model(data) metrics = evaluate_model(model, data) # Log metadata to the model log_metadata( metadata={ "evaluation_metrics": metrics, "hyperparameters": model.get_params() }, infer_model=True # Automatically target the model associated with this step ) return model # Method 2: Explicitly target a model by name and version log_metadata( metadata={"deployment_info": {"endpoint": "api.example.com/model"}}, model_name="fraud_detector", model_version="1.0.0" ) # Method 3: Target by model version ID log_metadata( metadata={"deployment_info": {"endpoint": "api.example.com/model"}}, model_version_id="model_version_uuid" ) ``` ## Bulk Metadata Logging The `log_metadata` function does not support logging the same metadata for multiple entities simultaneously. To achieve this, you can use the `bulk_log_metadata` function: ~~~python from zenml.models import ( ArtifactVersionIdentifier, ModelVersionIdentifier, PipelineRunIdentifier, StepRunIdentifier, ) from zenml import bulk_log_metadata bulk_log_metadata( metadata={"python_version": "3.11", "environment": "macosx"}, pipeline_runs=[ PipelineRunIdentifier(id=""), PipelineRunIdentifier(name="run name") ], step_runs=[ StepRunIdentifier(id=""), StepRunIdentifier(name="", run=PipelineRunIdentifier(id="")) ], artifact_versions=[ ArtifactVersionIdentifier(id=""), ArtifactVersionIdentifier(name="artifact_name", version="artifact_version") ], model_versions=[ ModelVersionIdentifier(id=""), ModelVersionIdentifier(name="model_name", version="model_version") ] ) ~~~ Note that the `bulk_log_metadata` function has a slightly different signature compared to `log_metadata`. You can use the Identifier class objects to specify any parameter combination that uniquely identifies an object: * VersionedIdentifiers * ArtifactVersionIdentifier & ModelVersionIdentifier * Specify either an id or a combination of name and version. * PipelineRunIdentifier * Specify an id, name, or prefix. * StepRunIdentifier * Specify an id or a combination of name and a pipeline run identifier. Similar to the `log_metadata` function, if you are calling `bulk_log_metadata` from within a step, you can use the infer options to automatically log metadata for the step’s model version or artifacts: ```python from zenml import bulk_log_metadata, step @step() def get_train_test_datasets(): train_dataset, test_dataset = get_datasets() bulk_log_metadata( metadata={"python_version": "3.11", "environment": "macosx"}, infer_models=True, infer_artifacts=True ) return train_dataset, test_dataset ``` Keep in mind that when using the `infer_artifacts` option, the `bulk_log_metadata` function logs metadata to all output artifacts of the step. When logging metadata, you may need the option to use `infer` options in combination with identifier references. For instance, you may want to log metadata to a step's outputs but also to its inputs. The `bulk_log_metadata` function enables you to use both options in one go: ```python from zenml import bulk_log_metadata, get_step_context, step from zenml.models import ArtifactVersionIdentifier def calculate_metrics(model, test_dataset): ... def summarize_metrics(metrics_report): ... @step def model_evaluation(test_dataset, model): metrics_report = calculate_metrics(model, test_dataset) slim_metrics_version = summarize_metrics(metrics_report) bulk_log_metadata( metadata=slim_metrics_version, infer_artifacts=True, # log metadata for outputs artifact_versions=[ ArtifactVersionIdentifier(id=get_step_context().inputs["model"].id) ] # log metadata for the model input ) return metrics_report ``` ### Performance improvements hints Both `log_metadata` and `bulk_log_metadata` internally use parameters such as name and version to resolve the actual IDs of entities. For example, when you provide an artifact's name and version, the function performs an additional lookup to resolve the artifact version ID. To improve performance, prefer using the entity's ID directly instead of its name, version, or other identifiers whenever possible. ### Using the client directly If the `log_metadata` or `bulk_log_metadata` functions are too restrictive for your use case, you can use the ZenML Client directly to create run metadata for resources: ~~~python from zenml.client import Client from zenml.enums import MetadataResourceTypes from zenml.models import RunMetadataResource client = Client() client.create_run_metadata( metadata={"python": "3.11"}, resources=[ RunMetadataResource(id="", type=MetadataResourceTypes.STEP_RUN), RunMetadataResource(id="", type=MetadataResourceTypes.PIPELINE_RUN), RunMetadataResource(id="", type=MetadataResourceTypes.ARTIFACT_VERSION), RunMetadataResource(id="", type=MetadataResourceTypes.MODEL_VERSION) ] ) ~~~ ## Special Metadata Types ZenML includes several special metadata types that provide standardized ways to represent common metadata: ```python from zenml import log_metadata from zenml.metadata.metadata_types import StorageSize, DType, Uri, Path log_metadata( metadata={ "dataset_source": Uri("gs://my-bucket/datasets/source.csv"), # External URI "preprocessing_script": Path("/scripts/preprocess.py"), # File path "column_types": { "age": DType("int"), # Data type "income": DType("float"), "score": DType("int") }, "processed_data_size": StorageSize(2500000) # Size in bytes }, infer_artifact=True ) ``` These special types ensure metadata is logged in a consistent and interpretable manner, and they receive special treatment in the ZenML dashboard. ## Organizing Metadata in the Dashboard To improve visualization in the ZenML dashboard, you can group metadata into logical sections by passing a dictionary of dictionaries: ```python from zenml import log_metadata from zenml.metadata.metadata_types import StorageSize log_metadata( metadata={ "model_metrics": { # First card in the dashboard "accuracy": 0.95, "precision": 0.92, "recall": 0.90 }, "data_details": { # Second card in the dashboard "dataset_size": StorageSize(1500000), "feature_columns": ["age", "income", "score"] } }, artifact_name="my_artifact", artifact_version="version", ) ``` In the ZenML dashboard, "model\_metrics" and "data\_details" will appear as separate cards, each containing their respective key-value pairs, making it easier to navigate and interpret the metadata. ## Visualizing and Comparing Metadata (Pro) Once you've logged metadata in your runs, you can use ZenML's Experiment Comparison tool to analyze and compare metrics across different run. {% hint style="success" %} The metadata comparison tool is a [ZenML Pro](https://zenml.io/pro)-only feature. {% endhint %} [](https://www.loom.com/share/693b2d829600492da7cd429766aeba6a?sid=7182e55b-31e9-4b38-a3be-07c989dbea32) ### Comparison Views The Experiment Comparison tool offers two complementary views for analyzing your pipeline metadata: 1. **Table View**: Compare metadata across runs with automatic change tracking 2. **Parallel Coordinates Plot**: Visualize relationships between different metrics The tool lets you compare up to 20 pipeline runs simultaneously and supports any numerical metadata (`float` or `int`) that you've logged in your pipelines. ## Fetching Metadata ### Retrieving Metadata Programmatically Once metadata has been logged, you can retrieve it using the ZenML Client: ```python from zenml.client import Client client = Client() # Get metadata from a step step = client.get_pipeline_run("pipeline_run_id").steps["step_name"] step_metadata = step.run_metadata["metadata_key"] # Get metadata from a run run = client.get_pipeline_run("pipeline_run_id") run_metadata = run.run_metadata["metadata_key"] # Get metadata from an artifact artifact = client.get_artifact_version("artifact_name", "version") artifact_metadata = artifact.run_metadata["metadata_key"] # Get metadata from a model model = client.get_model_version("model_name", "version") model_metadata = model.run_metadata["metadata_key"] ``` {% hint style="info" %} When fetching metadata using a specific key, the returned value will always reflect the latest entry for that key. {% endhint %} ### Accessing Context Within Steps The `StepContext` object is your handle to the *current* pipeline/step run while a step executes. Use it to read run/step information, inspect upstream input metadata, and work with step outputs: URIs, materializers, run metadata, and tags. It is available: - Inside functions decorated with `@step` (during execution, not composition time). - Inside step hooks like `on_failure` / `on_success` (see [Hooks](../steps-pipelines/hooks.md)). - Inside materializers triggered by a step’s `save` / `load`. - Calling `get_step_context()` elsewhere raises `RuntimeError`. Getting the context is done via `get_step_context()`: ```python from zenml import step, get_step_context @step def trainer(param: int = 1): ctx = get_step_context() print("run:", ctx.pipeline_run.name, ctx.pipeline_run.id) print("step:", ctx.step_run.name, ctx.step_run.id) print("params:", ctx.step_run.config.parameters) ``` This exposes the following properties: * `ctx.pipeline` → the `PipelineResponse` for this run (convenience; may raise if the run has no pipeline object). * `ctx.pipeline_run` → `PipelineRunResponse` (id, name, status, timestamps, etc.). * `ctx.step_run` → `StepRunResponse` (name, parameters via `ctx.step_run.config.parameters`, status). * `ctx.model` → the configured `Model` (resolved from step or pipeline); raises if none configured. * `ctx.inputs` → `{input_name: StepRunInputResponse}`; use `...["x"].run_metadata` to read upstream metadata. * `ctx.step_name` → convenience name string. ### Working with outputs For a single-output step you can omit `output_name`. For multi-output steps you **must** pass it (unnamed outputs are called `output_1`, `output_2`, …). * `get_output_artifact_uri(output_name=None) -> str` – where the output artifact lives (write side files, etc.). * `get_output_materializer(output_name=None, *, custom_materializer_class=None, data_type=None) -> BaseMaterializer` – get an initialized materializer; pass `data_type` to select from `Union[...]` materializers or `custom_materializer_class` to override. * `add_output_metadata(metadata, output_name=None)` / `get_output_metadata(output_name=None)` – set/read run metadata for the output. Values provided via `ArtifactConfig(..., run_metadata=...)` on the return annotation are merged with runtime values. * `add_output_tags(tags, output_name=None)` / `get_output_tags(output_name=None)` / `remove_output_tags(tags, output_name=None)` – manage tags for the produced artifact version. Configured tags via `ArtifactConfig(..., tags=...)` are unioned with runtime tags; duplicates are de‑duplicated in the final artifact. Minimal example: ```python from typing import Annotated, Tuple from zenml import step, get_step_context, log_metadata from zenml.artifacts.artifact_config import ArtifactConfig @step def produce(name: str) -> Tuple[ Annotated[ str, ArtifactConfig( name="custom_name", run_metadata={"config_metadata": "bar"}, tags=["config_tags"], ), ], str, ]: ctx = get_step_context() # Attach metadata and tags to the named (or default) output ctx.add_output_metadata({"m": 1}, output_name=name) ctx.add_output_tags(["t1", "t1"], output_name=name) # duplicates ok return "a", "b" ``` #### Reading upstream metadata via `inputs` ```python from zenml import step, get_step_context, log_metadata @step def upstream() -> int: log_metadata({"quality": "ok"}, infer_artifact=True) return 42 @step def downstream(x: int) -> None: md = get_step_context().inputs["x"].run_metadata assert md["quality"] == "ok" ``` #### Hooks and materializers (advanced) ```python from zenml import step, get_step_context from zenml.materializers.base_materializer import BaseMaterializer def on_failure(exc: BaseException): c = get_step_context() print("Failed step:", c.step_run.name, "-", type(exc).__name__) class ExampleMaterializer(BaseMaterializer): def save(self, data): # Context is available while the step triggers materialization data.meta = get_step_context().pipeline.name super().save(data) @step(on_failure=on_failure) def my_step(): raise ValueError("boom") ``` **Common errors to expect.** * `RuntimeError` if `get_step_context()` is called outside a running step. * `StepContextError` for output helpers when: * The step has no outputs, * You omit `output_name` on a multi‑output step, * You reference an unknown `output_name`. See the [full SDK docs for `StepContext`](https://sdkdocs.zenml.io/latest/core_code_docs/core-steps.html#zenml.steps.StepContext) for a concise reference to this object. ### Accessing Context During Pipeline Composition During pipeline composition, you can access the pipeline configuration using the `PipelineContext`: ```python from zenml import pipeline, get_pipeline_context @pipeline( extra={ "model_configs": [ ("sklearn.tree", "DecisionTreeClassifier"), ("sklearn.ensemble", "RandomForestClassifier"), ] } ) def my_pipeline(): # Get the pipeline context context = get_pipeline_context() # Access the configuration model_configs = context.extra["model_configs"] # Use the configuration to dynamically create steps for i, (model_package, model_class) in enumerate(model_configs): train_model( model_package=model_package, model_class=model_class, id=f"train_model_{i}" ) ``` ## Best Practices To make the most of ZenML's metadata capabilities: 1. **Use consistent keys**: Define standard metadata keys for your organization to ensure consistency 2. **Group related metadata**: Use nested dictionaries to create logical groupings in the dashboard 3. **Leverage special types**: Use ZenML's special metadata types for standardized representation 4. **Log relevant information**: Focus on metadata that aids reproducibility, understanding, and decision-making 5. **Consider automation**: Set up automatic metadata logging for standard metrics and information 6. **Combine with tags**: Use metadata alongside tags for a comprehensive organization system ## Conclusion Metadata in ZenML provides a powerful way to enhance your ML workflows with contextual information. By tracking additional details about your steps, runs, artifacts, and models, you can gain deeper insights into your experiments, make more informed decisions, and ensure reproducibility of your ML pipelines.
ZenML Scarf
--- ### Book/How To/Models/Models (docs/book/how-to/models/models.md) --- description: Managing ML models throughout their lifecycle with ZenML icon: rectangle-history --- # Models Machine learning models and AI agent configurations are at the heart of any ML workflow and AI system. ZenML provides comprehensive model management capabilities through its Model Control Plane, allowing you to track, version, promote, and share both traditional ML models and AI agent systems across your pipelines. {% hint style="info" %} The ZenML Model Control Plane is a [ZenML Pro](https://zenml.io/pro) feature. While the Python functions for creating and interacting with models are available in the open-source version, the visual dashboard for exploring and managing models is only available in ZenML Pro. Please [sign up here](https://zenml.io/pro) to get access to the full model management experience. {% endhint %} This guide covers all aspects of working with models in ZenML, from basic concepts to advanced usage patterns. ## Understanding Models in ZenML ### What is a ZenML Model? A ZenML Model is an entity that groups together related resources: * Pipelines that train, evaluate, or deploy the model or agent system * Artifacts like datasets, model weights, predictions, prompt templates, and agent configurations * Metadata including metrics, parameters, evaluation results, and business information Think of a ZenML Model as a container that organizes all the components related to a specific ML use case, business problem, or AI agent system. This extends beyond just model weights or agent prompts - it represents the entire ML product or intelligent system. {% hint style="info" %} A ZenML Model is different from a "technical model" (the actual ML model files with weights and parameters) or "agent configuration" (prompt templates, tool definitions, etc.). These technical artifacts are just components that can be associated with a ZenML Model, alongside training data, predictions, evaluation results, and other resources. {% endhint %} ### The Model Control Plane The Model Control Plane is ZenML's unified interface for managing models throughout their lifecycle. It allows you to: * Register and version models * Associate pipelines and artifacts with models * Track lineage and dependencies * Manage model promotions through stages (staging, production, etc.) * Exchange data between pipelines using models {% hint style="info" %} While all Model Control Plane functionality is accessible programmatically through the Python SDK in both OSS and Pro versions, the visual dashboard shown below is only available in ZenML Pro. {% endhint %} ## Working with Models ### Registering a Model You can register models in several ways: #### Using the Python SDK ```python from zenml import Model from zenml.client import Client Client().create_model( name="customer_service_agent", license="MIT", description="Multi-agent system for customer service automation", tags=["agent", "customer-service", "llm", "rag"], ) ``` #### Using the CLI ```bash zenml model register customer_service_agent --license="MIT" --description="Multi-agent customer service system" ``` #### Using a Pipeline The most common approach is to register a model implicitly as part of a pipeline: ```python from zenml import pipeline, Model @pipeline( model=Model( name="iris_classifier", description="Classification model for the Iris dataset", tags=["classification", "sklearn"] ) ) def training_pipeline(): # Pipeline implementation... ``` ### Model Versioning Each time you run a pipeline with a model configuration, a new model version is created. You can: #### Explicitly Name Versions ```python from zenml import Model, pipeline @pipeline( model=Model( name="iris_classifier", version="1.0.5" ) ) def training_pipeline(): # Pipeline implementation... ``` #### Use Templated Naming ```python from zenml import Model, pipeline @pipeline( model=Model( name="iris_classifier", version="run-{run.id[:8]}" ) ) def training_pipeline(): # Pipeline implementation... ``` ### Linking Artifacts to Models Artifacts produced during pipeline runs can be linked to models to establish lineage and enable reuse: ```python from zenml import step, Model from zenml.artifacts.utils import save_artifact import pandas as pd from typing import Annotated from zenml.artifacts.artifact_config import ArtifactConfig from sklearn.base import ClassifierMixin from sklearn.ensemble import RandomForestClassifier # Example: Agent configuration step linking artifacts @step(model=Model(name="CustomerServiceAgent", version="2.1.0")) def configure_agent( knowledge_base: pd.DataFrame, evaluation_results: dict ) -> Annotated[dict, ArtifactConfig("agent_config")]: # Create agent configuration based on knowledge base and evaluations agent_config = { "prompt_template": generate_prompt_from_kb(knowledge_base), "tools": ["search", "database_query", "escalation"], "performance_threshold": evaluation_results["min_accuracy"], "model_params": {"temperature": 0.7, "max_tokens": 500} } # Save intermediate prompt variants for variant in ["concise", "detailed", "empathetic"]: prompt_variant = generate_prompt_variant(knowledge_base, variant) save_artifact( f"prompt_template_{variant}", prompt_variant, is_model_artifact=True, ) return agent_config ``` ### Model Promotion Model stages represent the progression of models through their lifecycle. ZenML supports the following stages: * `staging`: Ready for final validation before production * `production`: Currently deployed in a production environment * `latest`: The most recent version (virtual stage) * `archived`: No longer in use You can promote models to different stages: ```python from zenml import Model from zenml.enums import ModelStages # Promote a specific model version to production model = Model(name="iris_classifier", version="1.2.3") model.set_stage(stage=ModelStages.PRODUCTION) # Find latest model and promote to staging latest_model = Model(name="iris_classifier", version=ModelStages.LATEST) latest_model.set_stage(stage=ModelStages.STAGING) ``` ## Using Models Across Pipelines One of the most powerful features of ZenML's Model Control Plane is the ability to share artifacts between pipelines through models. ### Pattern: Model-Mediated Artifact Exchange This pattern allows pipelines to exchange data without knowing the specific artifact IDs: ```python from typing import Annotated from zenml import step, get_pipeline_context, pipeline, Model from zenml.enums import ModelStages import pandas as pd from sklearn.base import ClassifierMixin @step def predict( model: ClassifierMixin, data: pd.DataFrame, ) -> Annotated[pd.Series, "predictions"]: """Make predictions using a trained model.""" predictions = pd.Series(model.predict(data)) return predictions @pipeline( model=Model( name="iris_classifier", # Reference the production version version=ModelStages.PRODUCTION, ), ) def inference_pipeline(): """Run inference using the production model.""" # Get the model from the pipeline context model = get_pipeline_context().model # Load inference data (you'd need to implement this function) inference_data = load_data() # Run prediction using the trained model artifact predict( model=model.get_model_artifact("trained_model"), data=inference_data, ) ``` This pattern enables clean separation between training and inference pipelines while maintaining a clear relationship between them. ## Tracking Metrics and Metadata ZenML allows you to attach metadata to models, which is crucial for tracking performance, understanding training conditions, and making promotion decisions. {% hint style="info" %} While metadata tracking is available in both OSS and Pro versions through the Python SDK, visualizing and exploring model metrics through a dashboard interface is only available in ZenML Pro. {% endhint %} ### Logging Model Metadata ``` /* Detailed source-code truncated for AI context efficiency. */ ``` ### Fetching Model Metadata You can retrieve logged metadata for analysis or decision-making: ```python from zenml.client import Client # Get a specific model version model = Client().get_model_version("iris_classifier", "1.2.3") # Access metadata metrics = model.run_metadata["evaluation_metrics"].value print(f"Model accuracy: {metrics['accuracy']}") ``` ## Deleting Models When a model is no longer needed, you can delete it or specific versions: ### Deleting All Versions of a Model ```python from zenml.client import Client # Using the Python SDK Client().delete_model("iris_classifier") # Or using the CLI # zenml model delete iris_classifier ``` ### Deleting a Specific Version ```python from zenml.client import Client # Using the Python SDK Client().delete_model_version("model_version_id") # Or using the CLI # zenml model version delete ``` ## Best Practices * **Consistent Naming**: Use consistent naming conventions for models and versions * **Rich Metadata**: Log comprehensive metadata to provide context for each model version * **Promotion Strategy**: Develop a clear strategy for promoting models through stages * **Model Association**: Associate pipelines with models to maintain lineage and enable artifact sharing * **Versioning Strategy**: Choose between explicit versioning and template-based versioning based on your needs ## Conclusion The Model Control Plane in ZenML provides a comprehensive solution for managing both traditional ML models and AI agent systems throughout their lifecycle. By properly registering, versioning, linking artifacts, and tracking metadata, you can create a transparent and reproducible workflow for your ML projects and AI agent development. {% hint style="info" %} **OSS vs Pro Feature Summary:** * **ZenML OSS:** Includes all the programmatic (Python SDK) model features described in this guide * **ZenML Pro:** Adds visual model dashboard, advanced model exploration, comprehensive metrics visualization, and integrated model lineage views {% endhint %} Whether you're working on a simple classification model, a complex production ML system, or a sophisticated multi-agent AI application, ZenML's unified model management capabilities help you organize your resources and maintain clarity across your entire AI development lifecycle.
ZenML Scarf
--- ### Book/How To/Popular Integrations/Aws Guide (docs/book/how-to/popular-integrations/aws-guide.md) --- description: A simple guide to create an AWS stack to run your ZenML pipelines icon: aws --- # AWS This page aims to quickly set up a minimal production stack on AWS. With just a few simple steps, you will set up an IAM role with specifically-scoped permissions that ZenML can use to authenticate with the relevant AWS resources. {% hint style="info" %} Would you like to skip ahead and deploy a full AWS ZenML cloud stack already? Check out the [in-browser stack deployment wizard](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/deploy-a-cloud-stack),\ the [stack registration wizard](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/register-a-cloud-stack),\ or [the ZenML AWS Terraform module](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/deploy-a-cloud-stack-with-terraform)\ for a shortcut on how to deploy & register this stack. {% endhint %} ## 1) Set up credentials and local environment To follow this guide, you need: * An active AWS account with necessary permissions for AWS S3, SageMaker, ECR, and ECS. * ZenML [installed](https://docs.zenml.io/getting-started/installation) * AWS CLI installed and configured with your AWS credentials. You can follow the instructions [here](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html). Once ready, navigate to the AWS console: 1. Choose an AWS region: In the AWS console, choose the region where you want to deploy your ZenML stack resources. Make note of the region name (e.g., `us-east-1`, `eu-west-2`, etc.) as you will need it in subsequent steps. 2. Create an IAM role: For this, you'll need to find out your AWS account ID. You can find this by running: ```shell aws sts get-caller-identity --query Account --output text ``` This will output your AWS account ID. Make a note of this as you will need it in the next steps. (If you're doing anything more esoteric with your AWS account and IAM roles, this might not work for you. The account ID here that we're trying to get is the root account ID that you use to log in to the AWS console.) Then create a file named `assume-role-policy.json` with the following content: ```json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam:::root", "Service": "sagemaker.amazonaws.com" }, "Action": "sts:AssumeRole" } ] } ``` Make sure to replace the placeholder `` with your actual AWS account ID that we found earlier. Now create a new IAM role that ZenML will use to access AWS resources. We'll use `zenml-role` as a role name in this example, but you can feel free to choose something else if you prefer. Run the following command to create the role: ```shell aws iam create-role --role-name zenml-role --assume-role-policy-document file://assume-role-policy.json ``` Be sure to take note of the information that is output to the terminal, as you will need it in the next steps, especially the Role ARN. 3. Create and attach least-privilege policies to the role: Instead of using broad managed policies, create custom policies that follow the principle of least privilege. First, create the necessary policy documents: **Create S3 policy document (`s3-policy.json`):** ```json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:ListBucket", "s3:GetObject", "s3:PutObject", "s3:DeleteObject", "s3:GetBucketVersioning", "s3:ListBucketVersions", "s3:DeleteObjectVersion" ], "Resource": [ "arn:aws:s3:::your-bucket-name", "arn:aws:s3:::your-bucket-name/*" ] }, { "Effect": "Allow", "Action": "s3:ListAllMyBuckets", "Resource": "*" } ] } ``` **Create ECR policy document (`ecr-policy.json`):** ```json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "ecr:BatchGetImage", "ecr:BatchCheckLayerAvailability", "ecr:GetDownloadUrlForLayer", "ecr:GetAuthorizationToken", "ecr:InitiateLayerUpload", "ecr:UploadLayerPart", "ecr:CompleteLayerUpload", "ecr:PutImage", "ecr:DescribeRepositories", "ecr:ListRepositories", "ecr:DescribeImages" ], "Resource": "*" } ] } ``` **Create SageMaker policy document (`sagemaker-policy.json`):** ```json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "sagemaker:CreatePipeline", "sagemaker:StartPipelineExecution", "sagemaker:StopPipelineExecution", "sagemaker:DescribePipeline", "sagemaker:DescribePipelineExecution", "sagemaker:ListPipelineExecutions", "sagemaker:ListPipelineExecutionSteps", "sagemaker:UpdatePipeline", "sagemaker:DeletePipeline", "sagemaker:CreateProcessingJob", "sagemaker:DescribeProcessingJob", "sagemaker:StopProcessingJob", "sagemaker:CreateTrainingJob", "sagemaker:DescribeTrainingJob", "sagemaker:StopTrainingJob" ], "Resource": "*" }, { "Effect": "Allow", "Action": "iam:PassRole", "Resource": "arn:aws:iam:::role/zenml-role", "Condition": { "StringEquals": { "iam:PassedToService": "sagemaker.amazonaws.com" } } } ] } ``` Replace `` and `your-bucket-name` with your actual values, then create and attach the policies: ```shell # Create the custom policies aws iam create-policy --policy-name ZenML-S3-Policy --policy-document file://s3-policy.json aws iam create-policy --policy-name ZenML-ECR-Policy --policy-document file://ecr-policy.json aws iam create-policy --policy-name ZenML-SageMaker-Policy --policy-document file://sagemaker-policy.json # Attach the custom policies to the role aws iam attach-role-policy --role-name zenml-role --policy-arn arn:aws:iam:::policy/ZenML-S3-Policy aws iam attach-role-policy --role-name zenml-role --policy-arn arn:aws:iam:::policy/ZenML-ECR-Policy aws iam attach-role-policy --role-name zenml-role --policy-arn arn:aws:iam:::policy/ZenML-SageMaker-Policy ``` 4. If you have not already, install the AWS and S3 ZenML integrations: ```shell zenml integration install aws s3 -y ``` ## 2) Create a Service Connector within ZenML Create an AWS Service Connector within ZenML. The service connector will allow ZenML and other ZenML components to authenticate themselves with AWS using the IAM role. {% tabs %} {% tab title="CLI" %} ```shell zenml service-connector register aws_connector \ --type aws \ --auth-method iam-role \ --role_arn= \ --region= \ --aws_access_key_id= \ --aws_secret_access_key= ``` Replace `` with the ARN of the IAM role you created in the previous step, `` with the respective value and use your AWS access key ID and secret access key that we noted down earlier. {% endtab %} {% endtabs %} ## 3) Create Stack Components ### Artifact Store (S3) An [artifact store](https://docs.zenml.io/user-guides/production-guide/remote-storage) is used for storing and versioning data flowing through your pipelines. 1. Before you run anything within the ZenML CLI, create an AWS S3 bucket. If you already have one, you can skip this step. (Note: the bucket name should be unique, so you might need to try a few times to find a unique name.) ```shell aws s3api create-bucket --bucket your-bucket-name ``` Once this is done, you can create the ZenML stack component as follows: 2. Register an S3 Artifact Store with the connector: ```shell zenml artifact-store register cloud_artifact_store -f s3 --path=s3://bucket-name --connector aws_connector ``` More details [here](https://docs.zenml.io/stacks/artifact-stores/s3). ### Orchestrator (SageMaker Pipelines) An [orchestrator](https://docs.zenml.io/user-guides/production-guide/cloud-orchestration) is the compute backend to run your pipelines. 1. Before you run anything within the ZenML CLI, head on over to AWS and create a SageMaker domain (Skip this if you already have one). The instructions for creating a domain can be found [in the AWS core documentation](https://docs.aws.amazon.com/sagemaker/latest/dg/onboard-quick-start.html). A SageMaker domain is a central management unit for all SageMaker users and resources within a region. It provides a single sign-on (SSO) experience and enables users to create and manage SageMaker resources, such as notebooks, training jobs, and endpoints, within a collaborative environment. When you create a SageMaker domain, you specify the configuration settings, such as the domain name, user profiles, and security settings. Each user within a domain gets their own isolated workspace, which includes a JupyterLab interface, a set of compute resources, and persistent storage. The SageMaker orchestrator in ZenML requires a SageMaker domain to run pipelines because it leverages the SageMaker Pipelines service, which is part of the SageMaker ecosystem. SageMaker Pipelines allows you to define, execute, and manage end-to-end machine learning workflows using a declarative approach. By creating a SageMaker domain, you establish the necessary environment and permissions for the SageMaker orchestrator to interact with SageMaker Pipelines and other SageMaker resources seamlessly. The domain acts as a prerequisite for using the SageMaker orchestrator in ZenML. Once this is done, you can create the ZenML stack component as follows: 2. Register a SageMaker Pipelines orchestrator stack component: You'll need the IAM role ARN that we noted down earlier to register the orchestrator. This is the 'execution role' ARN you need to pass to the orchestrator. ```shell zenml orchestrator register sagemaker-orchestrator --flavor=sagemaker --region= --execution_role= ``` **Note**: The SageMaker orchestrator utilizes the AWS configuration for operation and does not require direct connection via a service connector for authentication, as it relies on your AWS CLI configurations or environment variables. More details [here](https://docs.zenml.io/stacks/orchestrators/sagemaker). ### Container Registry (ECR) A [container registry](https://docs.zenml.io/stacks/container-registries) is used to store Docker images for your pipelines. 1. You'll need to create a repository in ECR. If you already have one, you can skip this step. ```shell aws ecr create-repository --repository-name zenml --region ``` Once this is done, you can create the ZenML stack component as follows: 2. Register an ECR container registry stack component: ```shell zenml container-registry register ecr-registry --flavor=aws --uri=.dkr.ecr..amazonaws.com --connector aws-connector ``` More details [here](https://docs.zenml.io/stacks/container-registries/aws). ## 4) Create stack {% tabs %} {% tab title="CLI" %} ```shell export STACK_NAME=aws_stack zenml stack register ${STACK_NAME} -o ${ORCHESTRATOR_NAME} \ -a ${ARTIFACT_STORE_NAME} -c ${CONTAINER_REGISTRY_NAME} --set ``` {% hint style="info" %} In case you want to also add any other stack components to this stack, feel free to do so. {% endhint %} {% endtab %} {% tab title="Dashboard" %} Combine the three stack components using the stack creation form in the ZenML dashboard. Feel free to add any other component of your choice as well. {% endtab %} {% endtabs %} ## 5) And you're already done! Just like that, you now have a fully working AWS stack ready to go. Feel free to take it for a spin by running a pipeline on it. Define a ZenML pipeline: ```python from zenml import pipeline, step @step def hello_world() -> str: return "Hello from SageMaker!" @pipeline def aws_sagemaker_pipeline(): hello_world() if __name__ == "__main__": aws_sagemaker_pipeline() ``` Save this code to run.py and execute it. The pipeline will use AWS S3 for artifact storage, Amazon SageMaker Pipelines for orchestration, and Amazon ECR for container registry. ```shell python run.py ```

Sequence of events that happen when running a pipeline on a remote stack with a code repository

Read more in the [production guide](https://docs.zenml.io/user-guides/production-guide). ## Cleanup {% hint style="warning" %} Make sure you no longer need the resources before deleting them. The instructions and commands that follow are DESTRUCTIVE. {% endhint %} Delete any AWS resources you no longer use to avoid additional charges. You'll want to do the following: ```shell # delete the S3 bucket aws s3 rm s3://your-bucket-name --recursive aws s3api delete-bucket --bucket your-bucket-name # delete the SageMaker domain aws sagemaker delete-domain --domain-id # delete the ECR repository aws ecr delete-repository --repository-name zenml-repository --force # detach custom policies from the IAM role aws iam detach-role-policy --role-name zenml-role --policy-arn arn:aws:iam:::policy/ZenML-S3-Policy aws iam detach-role-policy --role-name zenml-role --policy-arn arn:aws:iam:::policy/ZenML-ECR-Policy aws iam detach-role-policy --role-name zenml-role --policy-arn arn:aws:iam:::policy/ZenML-SageMaker-Policy # delete the custom policies aws iam delete-policy --policy-arn arn:aws:iam:::policy/ZenML-S3-Policy aws iam delete-policy --policy-arn arn:aws:iam:::policy/ZenML-ECR-Policy aws iam delete-policy --policy-arn arn:aws:iam:::policy/ZenML-SageMaker-Policy # delete the IAM role aws iam delete-role --role-name zenml-role ``` Make sure to run these commands in the same AWS region where you created the resources. By running these cleanup commands, you will delete the S3 bucket, SageMaker domain, ECR repository, and IAM role, along with their associated policies. This will help you avoid any unnecessary charges for resources you no longer need. Remember to be cautious when deleting resources and ensure that you no longer require them before running the deletion commands. ## Conclusion In this guide, we walked through the process of setting up an AWS stack with ZenML to run your machine learning pipelines in a scalable and production-ready environment. The key steps included: 1. Setting up credentials and the local environment by creating an IAM role with the necessary permissions. 2. Creating a ZenML service connector to authenticate with AWS services using the IAM role. 3. Configuring stack components, including an S3 artifact store, a SageMaker Pipelines orchestrator, and an ECR container registry. 4. Registering the stack components and creating a ZenML stack. By following these steps, you can leverage the power of AWS services, such as S3 for artifact storage, SageMaker Pipelines for orchestration, and ECR for container management, all within the ZenML framework. This setup allows you to build, deploy, and manage machine learning pipelines efficiently and scale your workloads based on your requirements. The benefits of using an AWS stack with ZenML include: * Scalability: Leverage the scalability of AWS services to handle large-scale machine learning workloads. * Reproducibility: Ensure reproducibility of your pipelines with versioned artifacts and containerized environments. * Collaboration: Enable collaboration among team members by using a centralized stack and shared resources. * Flexibility: Customize and extend your stack components based on your specific needs and preferences. Now that you have a functional AWS stack set up with ZenML, you can explore more advanced features and capabilities offered by ZenML. Some next steps to consider: * Dive deeper into ZenML's [production guide](https://docs.zenml.io/user-guides/production-guide) to learn best practices for deploying and managing production-ready pipelines. * Explore ZenML's [integrations](https://docs.zenml.io/stacks) with other popular tools and frameworks in the machine learning ecosystem. * Join the [ZenML community](https://zenml.io/slack) to connect with other users, ask questions, and get support. By leveraging the power of AWS and ZenML, you can streamline your machine learning workflows, improve collaboration, and deploy production-ready pipelines with ease. What follows is a set of best practices for using your AWS stack with ZenML. ## Best Practices for Using an AWS Stack with ZenML When working with an AWS stack in ZenML, consider the following best practices to optimize your workflow, enhance security, and improve cost-efficiency. These are all things you might want to do or amend in your own setup once you have tried running some pipelines on your AWS stack. ### Use IAM Roles and Least Privilege Principle Always adhere to the principle of least privilege when setting up IAM roles. The guide above provides specific custom IAM policies with minimal required permissions instead of broad managed policies. This approach significantly reduces security risks by: - Limiting S3 access to only your specific bucket - Restricting SageMaker permissions to pipeline operations only - Scoping ECR access to container operations only - Including proper IAM PassRole conditions Regularly review and audit your [IAM roles](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles.html) to ensure they remain appropriate and secure. Consider using AWS CloudTrail to monitor which permissions are actually being used and remove any unnecessary ones. ### Leverage AWS Resource Tagging Implement a [consistent tagging strategy](https://aws.amazon.com/solutions/guidance/tagging-on-aws/) for all of your AWS resources that you use for your pipelines. For example, if you have S3 as an artifact store in your stack, you should tag it like shown below: ```shell aws s3api put-bucket-tagging --bucket your-bucket-name --tagging 'TagSet=[{Key=Project,Value=ZenML},{Key=Environment,Value=Production}]' ``` These tags will help you with billing and cost allocation tracking and also with any cleanup efforts. ### Implement Cost Management Strategies Use [AWS Cost Explorer](https://aws.amazon.com/aws-cost-management/aws-cost-explorer/) and [AWS Budgets](https://aws.amazon.com/aws-cost-management/aws-budgets/) to monitor and manage your spending. To create a cost budget: 1. Create a JSON file (e.g., `budget-config.json`) defining the budget: ```json { "BudgetLimit": { "Amount": "100", "Unit": "USD" }, "BudgetName": "ZenML Monthly Budget", "BudgetType": "COST", "CostFilters": { "TagKeyValue": [ "user:Project$ZenML" ] }, "CostTypes": { "IncludeTax": true, "IncludeSubscription": true, "UseBlended": false }, "TimeUnit": "MONTHLY" } ``` 2. Create the cost budget: ```shell aws budgets create-budget --account-id your-account-id --budget file://budget-config.json ``` Set up cost allocation tags to track expenses related to your ZenML projects: ```shell aws ce create-cost-category-definition --name ZenML-Projects --rules-version 1 --rules file://rules.json ``` ### Use Warm Pools for your SageMaker Pipelines [Warm Pools in SageMaker](https://docs.zenml.io/stacks/orchestrators/sagemaker#using-warm-pools-for-your-pipelines) can significantly reduce the startup time of your pipeline steps, leading to faster iterations and improved development efficiency. This feature keeps compute instances in a "warm" state, ready to quickly start new jobs. To enable Warm Pools, use the `SagemakerOrchestratorSettings` class: ```python from zenml.integrations.aws.orchestrators.sagemaker import SagemakerOrchestratorSettings sagemaker_orchestrator_settings = SagemakerOrchestratorSettings( keep_alive_period_in_seconds = 300, # 5 minutes, default value ) ``` This configuration keeps instances warm for 5 minutes after each job completes, allowing subsequent jobs to start faster if initiated within this timeframe. The reduced startup time can be particularly beneficial for iterative development processes or frequently run pipelines. ### Implement a Robust Backup Strategy Regularly backup your critical data and configurations. For S3, enable versioning and consider using [cross-region replication](https://docs.aws.amazon.com/AmazonS3/latest/userguide/replication.html) for disaster recovery. By following these best practices and implementing the provided examples, you can create a more secure, efficient, and cost-effective AWS stack for your ZenML projects. Remember to regularly review and update your practices as your projects evolve and as AWS introduces new features and services.
ZenML Scarf
--- ### Book/How To/Popular Integrations/Azure Guide (docs/book/how-to/popular-integrations/azure-guide.md) --- description: A simple guide to create an Azure stack to run your ZenML pipelines icon: microsoft --- # Azure This page aims to quickly set up a minimal production stack on Azure. With just a few simple steps, you will set up a resource group, a service principal with correct permissions, and the relevant ZenML stack and components. {% hint style="info" %} Would you like to skip ahead and deploy a full Azure ZenML cloud stack already? Check out the [in-browser stack deployment wizard](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/deploy-a-cloud-stack), the [stack registration wizard](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/register-a-cloud-stack), or [the ZenML Azure Terraform module](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/deploy-a-cloud-stack-with-terraform) for a shortcut on how to deploy & register this stack. {% endhint %} To follow this guide, you need: * An active Azure account. * ZenML [installed](https://docs.zenml.io/getting-started/installation). * ZenML `azure` integration installed with `zenml integration install azure`. ## 1. Set up proper credentials You can start by [creating a service principal by creating an app registration](https://github.com/Azure/MachineLearningNotebooks/blob/master/how-to-use-azureml/manage-azureml-service/authentication-in-azureml/authentication-in-azureml.ipynb) on Azure: 1. Go to the App Registrations on the Azure portal. 2. Click on `+ New registration`, 3. Give it a name and click register. Once you create the service principal, you will get an Application ID and Tenant ID as they will be needed later. Next, go to your service principal and click on the `Certificates & secrets` in the `Manage` menu. Here, you have to create a client secret. Note down the secret value as it will be needed later. ## 2. Create a resource group and the AzureML instance Now, you have to [create a resource group on Azure](https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/manage-resource-groups-portal). To do this, go to the Azure portal and go to the `Resource Groups` page, and click `+ Create`. Once the resource group is created, go to the overview page of your new resource group and click `+ Create`. This will open up the marketplace where you can select a variety of resources to create. Look for `Azure Machine Learning`. Select it, and you will start the process of creating an AzureML workspace. As you can see from the `Workspace details`, AzureML workspaces come equipped with a storage account, key vault, and application insights. It is highly recommended that you create a container registry as well. ## 3. Create the required role assignments with least privilege Now, that you have your app registration and the resources, you have to create the corresponding role assignments following the principle of least privilege. In order to do this, go to your resource group, open up `Access control (IAM)` on the left side and `+Add` a new role assignment. ### Required Role Assignments for ZenML Components **For AzureML Orchestrator:** - **`AzureML Data Scientist`** - Allows creating and managing AzureML jobs and experiments - **`AzureML Compute Operator`** - Allows managing compute resources (instances, clusters) **For Azure Blob Storage Artifact Store:** - **`Storage Blob Data Contributor`** - Allows read/write access to blob storage containers - **`Reader and Data Access`** - Required for listing containers (if needed) **For Azure Container Registry:** - **`AcrPush`** - Allows pushing container images - **`AcrPull`** - Allows pulling container images - **`Contributor`** (scoped to ACR only) - Allows listing registries for discovery ### Assign the Roles In the role assignment page, search for the specific roles mentioned above: **Step 1:** Assign AzureML roles One by one, select `AzureML Data Scientist` and `AzureML Compute Operator` and click `Next`. **Step 2:** Assign Storage roles Assign `Storage Blob Data Contributor` role to your service principal. **Step 3:** Assign Container Registry roles Assign `AcrPush`, `AcrPull`, and `Contributor` (scoped to ACR resource) roles to your service principal. Finally, click `+Select Members`, search for your registered app by its ID, and assign each role accordingly. {% hint style="info" %} **Security Best Practice:** These role assignments provide the minimum permissions required for ZenML operations. Avoid using broader roles like `Contributor` or `Owner` at the resource group level, as they grant unnecessary permissions. {% endhint %} ## 4. Create a service connector Now you have everything set up, you can go ahead and create [a ZenML Azure Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/azure-service-connector). ```bash zenml service-connector register azure_connector --type azure \ --auth-method service-principal \ --client_secret= \ --tenant_id= \ --client_id= ``` You will use this service connector later on to connect your components with proper authentication. ## 5. Create Stack Components In order to run any workflows on Azure using ZenML, you need an artifact store, an orchestrator, and a container registry. ### Artifact Store (Azure Blob Storage) For the artifact store, we will be using the storage account attached to our AzureML workspace. But before registering the component itself, you have to create a container for blob storage. To do this, go to the corresponding storage account in your workspace and create a new container: Once you create the container, you can go ahead, register your artifact store using its path and connect it to your service connector: ```bash zenml artifact-store register azure_artifact_store -f azure \ --path= \ --connector azure_connector ``` For more information regarding Azure Blob Storage artifact stores, feel free to [check the docs](https://docs.zenml.io/stacks/artifact-stores/azure). ### Orchestrator (AzureML) As for the orchestrator, no additional setup is needed. Simply use the following command to register it and connect it to your service connector: ```bash zenml orchestrator register azure_orchestrator -f azureml \ --subscription_id= \ --resource_group= \ --workspace= \ --connector azure_connector ``` For more information regarding AzureML orchestrator, feel free to [check the docs](https://docs.zenml.io/stacks/orchestrators/azureml). ### Container Registry (Azure Container Registry) Similar to the orchestrator, you can register and connect your container registry using the following command: ```bash zenml container-registry register azure_container_registry -f azure \ --uri= \ --connector azure_connector ``` For more information regarding Azure container registries, feel free to [check the docs](https://docs.zenml.io/stacks/container-registries/azure). ## 6. Create a Stack Now, you can use the registered components to create an Azure ZenML stack: ```shell zenml stack register azure_stack \ -o azure_orchestrator \ -a azure_artifact_store \ -c azure_container_registry \ --set ``` ## 7. ...and you are done. Just like that, you now have a fully working Azure stack ready to go. Feel free to take it for a spin by running a pipeline on it. Define a ZenML pipeline: ```python from zenml import pipeline, step @step def hello_world() -> str: return "Hello from Azure!" @pipeline def azure_pipeline(): hello_world() if __name__ == "__main__": azure_pipeline() ``` Save this code to run.py and execute it. The pipeline will use Azure Blob Storage for artifact storage, AzureML for orchestration, and an Azure container registry. ```shell python run.py ``` Now that you have a functional Azure stack set up with ZenML using least privilege permissions, you can explore more advanced features and capabilities offered by ZenML. Some next steps to consider: * Dive deeper into ZenML's [production guide](https://docs.zenml.io/user-guides/production-guide) to learn best practices for deploying and managing production-ready pipelines. * Explore ZenML's [integrations](https://docs.zenml.io/stacks) with other popular tools and frameworks in the machine learning ecosystem. * Join the [ZenML community](https://zenml.io/slack) to connect with other users, ask questions, and get support. ## Best Practices for Using an Azure Stack with ZenML ### Security and Least Privilege The guide above implements security best practices by: - **Using specific Azure roles** instead of broad permissions like `Owner` or `Contributor` - **Scoping permissions to resources** rather than subscription-wide access - **Separating concerns** with different roles for different components (storage, compute, registry) - **Following Azure's principle of least privilege** for service principal authentication ### Regular Security Maintenance - **Rotate service principal credentials** regularly using Azure Key Vault - **Review role assignments** periodically to ensure they remain necessary - **Use Azure Security Center** to monitor for security recommendations - **Enable Azure AD Conditional Access** for additional security layers when appropriate
ZenML Scarf
--- ### Book/How To/Popular Integrations/Gcp Guide (docs/book/how-to/popular-integrations/gcp-guide.md) --- description: A simple guide to quickly set up a minimal stack on GCP. icon: google --- # GCP This page aims to quickly set up a minimal production stack on GCP. With just a few simple steps you will set up a service account with specifically-scoped permissions that ZenML can use to authenticate with the relevant GCP resources. {% hint style="info" %} Would you like to skip ahead and deploy a full GCP ZenML cloud stack already? Check out the [in-browser stack deployment wizard](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/deploy-a-cloud-stack), the [stack registration wizard](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/register-a-cloud-stack), or [the ZenML GCP Terraform module](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/deploy-a-cloud-stack-with-terraform) for a shortcut on how to deploy & register this stack. {% endhint %} {% hint style="warning" %} While this guide focuses on Google Cloud, we are seeking contributors to create a similar guide for other cloud providers. If you are interested, please create a [pull request over on GitHub](https://github.com/zenml-io/zenml/blob/main/CONTRIBUTING.md). {% endhint %} ### 1) Choose a GCP project In the Google Cloud console, on the project selector page, select or [create a Google Cloud project](https://cloud.google.com/resource-manager/docs/creating-managing-projects). Make sure a billing account is attached to this project to allow the use of some APIs. This is how you would do it from the CLI if this is preferred. ```bash gcloud projects create --billing-project= ``` {% hint style="info" %} If you don't plan to keep the resources that you create in this procedure, create a new project. After you finish these steps, you can delete the project, thereby removing all resources associated with the project. {% endhint %} ### 2) Enable GCloud APIs The [following APIs](https://console.cloud.google.com/flows/enableapi?apiid=cloudfunctions,cloudbuild.googleapis.com,artifactregistry.googleapis.com,run.googleapis.com,logging.googleapis.com&redirect=https://cloud.google.com/functions/docs/create-deploy-gcloud&_ga=2.103703808.1862683951.1694002459-205697788.1651483076&_gac=1.161946062.1694011263.Cj0KCQjwxuCnBhDLARIsAB-cq1ouJZlVKAVPMsXnYrgQVF2t1Q2hUjgiHVpHXi2N0NlJvG3j3y-PPh8aAoSIEALw_wcB) will need to be enabled within your chosen GCP project. * Cloud Functions API # For the vertex orchestrator * Cloud Run Admin API # For the vertex orchestrator * Cloud Build API # For the container registry * Artifact Registry API # For the container registry * Cloud Logging API # Generally needed ### 3) Create a dedicated service account with least privilege permissions Create a custom service account with only the minimum required permissions instead of using broad predefined roles. This follows the principle of least privilege: **For ZenML Client Operations (where pipelines are submitted):** * **Vertex AI User** (`roles/aiplatform.user`) - for creating and managing Vertex AI pipeline jobs * **Storage Object Admin** (`roles/storage.objectAdmin`) - for artifact store operations * **Cloud Functions Developer** (`roles/cloudfunctions.developer`) - for scheduled pipelines (if using scheduling) **For Pipeline Workload Operations (where pipeline steps run):** Create a separate service account for the actual pipeline execution: * **Vertex AI Service Agent** (`roles/aiplatform.serviceAgent`) - for running Vertex AI pipelines * **Storage Object Admin** (`roles/storage.objectAdmin`) - for accessing artifacts during pipeline execution **More Granular Permissions (Alternative):** If you prefer even more granular control, you can create custom roles with these specific permissions: **For GCS Access:** ``` storage.buckets.get storage.buckets.list storage.objects.create storage.objects.delete storage.objects.get storage.objects.list storage.objects.update ``` **For Vertex AI Access:** ``` aiplatform.customJobs.create aiplatform.customJobs.get aiplatform.customJobs.list aiplatform.pipelineJobs.create aiplatform.pipelineJobs.get aiplatform.pipelineJobs.list ``` **For Container Registry Access:** ``` artifactregistry.repositories.uploadArtifacts artifactregistry.repositories.downloadArtifacts artifactregistry.repositories.get artifactregistry.repositories.list ``` This approach significantly reduces security risks by limiting permissions to only what's necessary for ZenML operations. ### 4) Create the service accounts and assign roles Create the service accounts and assign the least privilege roles: ```bash\n# Create client service account\ngcloud iam service-accounts create zenml-client \\\n --display-name=\"ZenML Client Service Account\" \\\n --description=\"Service account for ZenML client operations\"\n\n# Create workload service account\ngcloud iam service-accounts create zenml-workload \\\n --display-name=\"ZenML Workload Service Account\" \\\n --description=\"Service account for ZenML pipeline execution\"\n\n# Assign roles to client service account\ngcloud projects add-iam-policy-binding \\\n --member=\"serviceAccount:zenml-client@.iam.gserviceaccount.com\" \\\n --role=\"roles/aiplatform.user\"\n\ngcloud projects add-iam-policy-binding \\\n --member=\"serviceAccount:zenml-client@.iam.gserviceaccount.com\" \\\n --role=\"roles/storage.objectAdmin\"\n\n# Assign roles to workload service account\ngcloud projects add-iam-policy-binding \\\n --member=\"serviceAccount:zenml-workload@.iam.gserviceaccount.com\" \\\n --role=\"roles/aiplatform.serviceAgent\"\n\ngcloud projects add-iam-policy-binding \\\n --member=\"serviceAccount:zenml-workload@.iam.gserviceaccount.com\" \\\n --role=\"roles/storage.objectAdmin\"\n```\n\n### 5) Create a JSON Key for your client service account This [json file](https://cloud.google.com/iam/docs/keys-create-delete) will allow the service account to assume the identity of this service account. You will need the filepath of the downloaded file in the next step. ```bash export JSON_KEY_FILE_PATH= ``` ### 6) Create a Service Connector within ZenML The service connector will allow ZenML and other ZenML components to authenticate themselves with GCP. {% tabs %} {% tab title="CLI" %} ```bash zenml integration install gcp \ && zenml service-connector register gcp_connector \ --type gcp \ --auth-method service-account \ --service_account_json=@${JSON_KEY_FILE_PATH} \ --project_id= ``` {% endtab %} {% endtabs %} ### 7) Create Stack Components #### Artifact Store Before you run anything within the ZenML CLI, head on over to GCP and create a GCS bucket, in case you don't already have one that you can use. Once this is done, you can create the ZenML stack component as follows: {% tabs %} {% tab title="CLI" %} ```bash export ARTIFACT_STORE_NAME=gcp_artifact_store # Register the GCS artifact-store and reference the target GCS bucket zenml artifact-store register ${ARTIFACT_STORE_NAME} --flavor gcp \ --path=gs:// # Connect the GCS artifact-store to the target bucket via a GCP Service Connector zenml artifact-store connect ${ARTIFACT_STORE_NAME} -i ``` {% hint style="info" %} Head on over to our [docs](https://docs.zenml.io/stacks/artifact-stores/gcp) to learn more about artifact stores and how to configure them. {% endhint %} {% endtab %} {% endtabs %} #### Orchestrator This guide will use Vertex AI as the orchestrator to run the pipelines. As a serverless service Vertex is a great choice for quick prototyping of your MLOps stack. The orchestrator can be switched out at any point in the future for a more use-case- and budget-appropriate solution. {% tabs %} {% tab title="CLI" %} ```bash export ORCHESTRATOR_NAME=gcp_vertex_orchestrator # Register the GCS artifact-store and reference the target GCS bucket zenml orchestrator register ${ORCHESTRATOR_NAME} --flavor=vertex --project= --location=europe-west2 # Connect the GCS orchestrator to the target gcp project via a GCP Service Connector zenml orchestrator connect ${ORCHESTRATOR_NAME} -i ``` {% hint style="info" %} Head on over to our [docs](https://docs.zenml.io/stacks/orchestrators/vertex) to learn more about orchestrators and how to configure them. {% endhint %} {% endtab %} {% endtabs %} #### Container Registry {% tabs %} {% tab title="CLI" %} ```bash export CONTAINER_REGISTRY_NAME=gcp_container_registry zenml container-registry register ${CONTAINER_REGISTRY_NAME} --flavor=gcp --uri= # Connect the GCS orchestrator to the target gcp project via a GCP Service Connector zenml container-registry connect ${CONTAINER_REGISTRY_NAME} -i ``` {% hint style="info" %} Head on over to our [docs](https://docs.zenml.io/stacks/container-registries) to learn more about container registries and how to configure them. {% endhint %} {% endtab %} {% endtabs %} ### 8) Create Stack {% tabs %} {% tab title="CLI" %} ```bash export STACK_NAME=gcp_stack zenml stack register ${STACK_NAME} -o ${ORCHESTRATOR_NAME} \ -a ${ARTIFACT_STORE_NAME} -c ${CONTAINER_REGISTRY_NAME} --set ``` {% hint style="info" %} In case you want to also add any other stack components to this stack, feel free to do so. {% endhint %} {% endtab %} {% endtabs %} ## And you're already done! Just like that, you now have a fully working GCP stack ready to go. Feel free to take it for a spin by running a pipeline on it. ## Cleanup If you do not want to use any of the created resources in the future, simply delete the project you created. ```bash gcloud project delete ``` ## Best Practices for Using a GCP Stack with ZenML When working with a GCP stack in ZenML, consider the following best practices to optimize your workflow, enhance security, and improve cost-efficiency. These are all things you might want to do or amend in your own setup once you have tried running some pipelines on your GCP stack. ### Use IAM and Least Privilege Principle Always adhere to the principle of least privilege when setting up IAM roles. The guide above demonstrates this by using specific roles instead of broad "Editor" or "Owner" permissions: - **Vertex AI User** instead of broad compute permissions - **Storage Object Admin** scoped to specific buckets instead of project-wide storage access - **Separate service accounts** for client operations vs. workload execution - **Custom roles** with granular permissions when predefined roles are too broad Regularly review and audit your IAM roles to ensure they remain appropriate and secure. Use Google Cloud's IAM Recommender to identify and remove unused permissions. ### Leverage GCP Resource Labeling Implement a consistent labeling strategy for your GCP resources. To label a GCS bucket, for example: ```shell gcloud storage buckets update gs://your-bucket-name --update-labels=project=zenml,environment=production ``` This command adds two labels to the bucket: * A label with key "project" and value "zenml" * A label with key "environment" and value "production" You can add or update multiple labels in a single command by separating them with commas. To remove a label, set its value to null: ```shell gcloud storage buckets update gs://your-bucket-name --update-labels=label-to-remove=null ``` These labels will help you with billing and cost allocation tracking and also with any cleanup efforts. To view the labels on a bucket: ```shell gcloud storage buckets describe gs://your-bucket-name --format="default(labels)" ``` This will display all labels currently set on the specified bucket. ### Implement Cost Management Strategies Use Google Cloud's [Cost Management tools](https://cloud.google.com/docs/costs-usage) to monitor and manage your spending. To set up a budget alert: 1. Navigate to the Google Cloud Console 2. Go to Billing > Budgets & Alerts 3. Click "Create Budget" 4. Set your budget amount, scope (project, product, etc.), and alert thresholds You can also use the `gcloud` CLI to create a budget: ```shell gcloud billing budgets create --billing-account=BILLING_ACCOUNT_ID --display-name="ZenML Monthly Budget" --budget-amount=1000 --threshold-rule=percent=90 ``` Set up cost allocation labels to track expenses related to your ZenML projects in the Google Cloud Billing Console. ### Implement a Robust Backup Strategy Regularly backup your critical data and configurations. For GCS, for example, enable versioning and consider using cross-region replication for disaster recovery. To enable versioning on a GCS bucket: ```shell gsutil versioning set on gs://your-bucket-name ``` To set up cross-region replication: ```shell gsutil rewrite -r gs://source-bucket gs://destination-bucket ``` By following these best practices and implementing the provided examples, you can create a more secure, efficient, and cost-effective GCP stack for your ZenML projects. Remember to regularly review and update your practices as your projects evolve and as GCP introduces new features and services.
ZenML Scarf
--- ### Book/How To/Popular Integrations/Kubernetes (docs/book/how-to/popular-integrations/kubernetes.md) --- description: Learn how to deploy ZenML pipelines on a Kubernetes cluster. icon: kubernetes --- # Kubernetes The ZenML Kubernetes Orchestrator allows you to run your ML pipelines on a Kubernetes cluster without writing Kubernetes code. It's a lightweight alternative to more complex orchestrators like Airflow or Kubeflow. {% hint style="info" %} If you only want to run individual steps of your pipeline in Kubernetes, check out our [Kubernetes Step Operator](https://docs.zenml.io/stacks/step-operators/kubernetes). {% endhint %} ## Prerequisites To use the Kubernetes Orchestrator, you'll need: - ZenML `kubernetes` integration installed (`zenml integration install kubernetes`) - Docker installed and running - `kubectl` installed - A remote artifact store and container registry in your ZenML stack - A deployed Kubernetes cluster - A configured `kubectl` context pointing to the cluster (optional, see below) ## Deploying the Orchestrator The Kubernetes orchestrator requires a Kubernetes cluster in order to run. There are many ways to deploy a Kubernetes cluster using different cloud providers or on your custom infrastructure, and we can't possibly cover all of them, but you can check out our [production guide](https://docs.zenml.io/user-guides/production-guide). ## Configuring the Orchestrator There are two ways to configure the orchestrator: 1. Using a [Service Connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/service-connectors-guide) to connect to the remote cluster. This is the recommended approach, especially for cloud-managed clusters. No local `kubectl` context is needed. ```bash zenml orchestrator register --flavor kubernetes zenml service-connector list-resources --resource-type kubernetes-cluster -e zenml orchestrator connect --connector zenml stack register -o ... --set ``` 2. Configuring `kubectl` with a context pointing to the remote cluster and setting the `kubernetes_context` in the orchestrator config: ```bash zenml orchestrator register \ --flavor=kubernetes \ --kubernetes_context= zenml stack register -o ... --set ``` ## Running a Pipeline Once configured, you can run any ZenML pipeline using the Kubernetes Orchestrator: ```bash python your_pipeline.py ``` This will create a Kubernetes pod for each step in your pipeline. You can interact with the pods using `kubectl` commands. For more advanced configuration options and additional details, refer to the [full Kubernetes Orchestrator documentation](https://docs.zenml.io/stacks/orchestrators/kubernetes).
ZenML Scarf
--- ### Book/How To/Secrets/Secrets (docs/book/how-to/secrets/secrets.md) --- description: Registering and using secrets. icon: user-secret --- # Secrets ZenML secrets are groupings of **key-value pairs** which are securely stored in the ZenML secrets store. Additionally, a secret always has a **name** that allows you to fetch or reference them in your pipelines and stacks. Secrets are essential for both traditional ML workflows (database credentials, model registry access) and AI agent development (LLM API keys, third-party service credentials). ## How to create a secret {% tabs %} {% tab title="CLI" %} To create a secret with a name `` and a key-value pair, you can run the following CLI command: ```shell zenml secret create \ --= \ --= # Another option is to use the '--values' option and provide key-value pairs in either JSON or YAML format. zenml secret create \ --values='{"key1":"value2","key2":"value2"}' # Example: Create secrets for LLM API keys zenml secret create openai_secret \ --api_key=sk-proj-... \ --organization_id=org-... zenml secret create anthropic_secret \ --api_key=sk-ant-api03-... # Example: Create secrets for multi-agent system credentials zenml secret create agent_tools_secret \ --google_search_api_key=AIza... \ --weather_api_key=abc123 \ --database_url=postgresql://user:pass@host/db # Create a private secret (only you can access it) zenml secret create my_private_secret --private \ --api_key=secret-value ``` {% hint style="info" %} By default, secrets are public (visible to other users based on RBAC). Use `--private` or `-p` to create a secret only you can access. See [Private and public secrets](#private-and-public-secrets) for more details. {% endhint %} Alternatively, you can create the secret in an interactive session (in which ZenML will query you for the secret keys and values) by passing the `--interactive/-i` parameter: ```shell zenml secret create -i ``` For secret values that are too big to pass as a command line argument, or have special characters, you can also use the special `@` syntax to indicate to ZenML that the value needs to be read from a file: ```bash zenml secret create \ --key=@path/to/file.txt \ ... # Alternatively, you can utilize the '--values' option by specifying a file path containing key-value pairs in either JSON or YAML format. zenml secret create \ --values=@path/to/file.txt ``` The CLI also includes commands that can be used to list, update and delete secrets. A full guide on using the CLI to create, access, update and delete secrets is available [here](https://sdkdocs.zenml.io/latest/cli.html#zenml.cli--secrets-management). **Interactively register missing secrets for your stack** If you're using components with [secret references](secrets.md#reference-secrets-in-stack-component-attributes-and-settings) in your stack, you need to make sure that all the referenced secrets exist. To make this process easier, you can use the following CLI command to interactively register all secrets for a stack: ```shell zenml stack register-secrets [] ``` {% endtab %} {% tab title="Python SDK" %} The ZenML client API offers a programmatic interface to create, e.g.: ```python from zenml.client import Client client = Client() client.create_secret( name="my_secret", values={ "username": "admin", "password": "abc123" } ) # Example: Create LLM API secrets programmatically client.create_secret( name="openai_secret", values={ "api_key": "sk-proj-...", "organization_id": "org-..." } ) # Create a private secret (only you can access it) client.create_secret( name="my_private_secret", values={"api_key": "secret-value"}, private=True, ) ``` {% hint style="info" %} By default, secrets are public (`private=False`). Set `private=True` to create a secret only you can access. See [Private and public secrets](#private-and-public-secrets) for more details. {% endhint %} Other Client methods used for secrets management include `get_secret` to fetch a secret by name or id, `update_secret` to update an existing secret, `list_secrets` to query the secrets store using a variety of filtering and sorting criteria, and `delete_secret` to delete a secret. The full Client API reference is available [here](https://sdkdocs.zenml.io/latest/core_code_docs/core-client.html). {% endtab %} {% endtabs %} ## Size limits A **secret** in ZenML is a name plus a set of key–value pairs. Size is usually the total UTF-8 size of all keys and values. See the table in [Secret value size limits](https://docs.zenml.io/getting-started/deploying-zenml/secret-management#secret-value-size-limits) for the limits associated with the different secrets stores. If you are over the limit, add another named secret, split keys across secrets, or keep large files outside ZenML and only store a reference in a value. ## Private and public secrets ZenML secrets can be either **private** or **public**: - **Private secrets** are only accessible to the user who created them. No other user can view, use, or manage a private secret, regardless of their role or permissions. - **Public secrets** (the default) are accessible to other users based on your RBAC configuration. On ZenML Pro, access to public secrets is governed by your role-based access control settings. {% hint style="info" %} The `private` property takes precedence over RBAC. A private secret is **only** visible to its creator, even if RBAC would otherwise grant access to other users. {% endhint %} ### Creating private secrets By default, secrets are created as public (`private=False`). To create a private secret: {% tabs %} {% tab title="CLI" %} ```shell # Use the --private or -p flag zenml secret create --private \ --= \ --= # Short form zenml secret create -p \ --= ``` {% endtab %} {% tab title="Python SDK" %} ```python from zenml.client import Client client = Client() client.create_secret( name="my_private_secret", values={"api_key": "..."}, private=True, # Makes this secret private ) ``` {% endtab %} {% endtabs %} {% hint style="warning" %} Currently, setting the private status is only available via the CLI and Python SDK. The dashboard UI does not yet support creating or modifying private secrets. {% endhint %} ### Fetching secrets with the same name Since private and public secrets exist in separate namespaces, you can have both a private and a public secret with the same name. When fetching a secret by name without specifying its visibility: - ZenML searches **private secrets first**, then public secrets - The first match is returned To explicitly fetch a secret of a specific visibility: {% tabs %} {% tab title="CLI" %} ```shell # Explicitly fetch a private secret zenml secret get my_secret --private=true # Explicitly fetch a public secret zenml secret get my_secret --private=false ``` {% endtab %} {% tab title="Python SDK" %} ```python from zenml.client import Client client = Client() # Explicitly fetch a private secret private_secret = client.get_secret("my_secret", private=True) # Explicitly fetch a public secret public_secret = client.get_secret("my_secret", private=False) ``` {% endtab %} {% endtabs %} ### Updating secret visibility You can change a secret's visibility after creation: {% tabs %} {% tab title="CLI" %} ```shell # Make a public secret private zenml secret update my_secret --private=true # Make a private secret public zenml secret update my_secret --private=false ``` {% endtab %} {% tab title="Python SDK" %} ```python from zenml.client import Client client = Client() client.update_secret("my_secret", update_private=True) # Make private ``` {% endtab %} {% endtabs %} ## Accessing registered secrets ### Reference secrets in stack component attributes and settings Some of the components in your stack require you to configure them with sensitive information like passwords or tokens, so they can connect to the underlying infrastructure. Secret references allow you to configure these components in a secure way by not specifying the value directly but instead referencing a secret by providing the secret name and key. Referencing a secret for the value of any string attribute of your stack components, simply specify the attribute using the following syntax: `{{.}}` For example: {% tabs %} {% tab title="CLI" %} ```shell # Register a secret called `mlflow_secret` with key-value pairs for the # username and password to authenticate with the MLflow tracking server # Using central secrets management zenml secret create mlflow_secret \ --username=admin \ --password=abc123 # Then reference the username and password in our experiment tracker component zenml experiment-tracker register mlflow \ --flavor=mlflow \ --tracking_username={{mlflow_secret.username}} \ --tracking_password={{mlflow_secret.password}} \ ... ``` {% endtab %} {% endtabs %} When using secret references in your stack, ZenML will validate that all secrets and keys referenced in your stack components exist before running a pipeline. This helps us fail early so your pipeline doesn't fail after running for some time due to some missing secret. This validation by default needs to fetch and read every secret to make sure that both the secret and the specified key-value pair exist. This can take quite some time and might fail if you don't have permission to read secrets. You can use the environment variable `ZENML_SECRET_VALIDATION_LEVEL` to disable or control the degree to which ZenML validates your secrets: * Setting it to `NONE` disables any validation. * Setting it to `SECRET_EXISTS` only validates the existence of secrets. This might be useful if the machine you're running on only has permission to list secrets but not actually read their values. * Setting it to `SECRET_AND_KEY_EXISTS` (the default) validates both the secret existence as well as the existence of the exact key-value pair. ### Fetch secret values in a step If you are using [centralized secrets management](secrets.md), you can access secrets directly from within your steps through the ZenML `Client` API. This allows you to use your secrets for querying APIs from within your step without hard-coding your access keys: ``` /* Detailed source-code truncated for AI context efficiency. */ ```
ZenML Scarf
--- ### Book/How To/Snapshots/Snapshots (docs/book/how-to/snapshots/snapshots.md) --- description: Create and run pipeline snapshots. icon: print --- # Pipeline Snapshots A **Pipeline Snapshot** is an immutable snapshot of your pipeline that includes the pipeline DAG, code, configuration, and container images. Snapshots can be run from the SDK, CLI, ZenML dashboard or via a REST API. Additionally, snapshots can also be [deployed](../deployment/deployment.md). {% hint style="info" %} Snapshots are the successor and replacement of ZenML run templates. {% endhint %} {% hint style="success" %} Running snapshots is a [ZenML Pro](https://zenml.io/pro)-only feature. {% endhint %} {% hint style="info" %} **Looking for a long-running HTTP service?** Snapshots are designed for batch-style pipeline runs triggered on demand. If you need a persistent HTTP endpoint with request/response semantics, custom endpoints, middleware, and concurrent request handling, see [Pipeline Deployments](../deployment/deployment.md) instead. Deployments are available in both OSS and Pro. {% endhint %} ## Real-world Use Case Imagine your team has built a robust training pipeline that needs to be run regularly with different parameters: - **Data Scientists** need to experiment with new datasets and hyperparameters - **MLOps Engineers** need to schedule regular retraining with production data - **Stakeholders** need to trigger model training through a simple UI without coding Without snapshots, each scenario would require: 1. Direct access to the codebase 2. Knowledge of pipeline implementation details 3. Manual pipeline configuration for each run **Pipeline snapshots solve this problem by creating a reusable configuration** that can be executed with different parameters from any interface: - **Through Python**: Data scientists can programmatically trigger snapshots with custom parameters ```python from zenml.client import Client Client().trigger_pipeline( snapshot_name_or_id=, run_configuration={ "steps": { "data_loader": {"parameters": {"data_path": "s3://new-data/"}}, "model_trainer": {"parameters": {"learning_rate": 0.01}} } } ) ``` - **Through REST API**: Your CI/CD system can trigger snapshots via API calls ```bash curl -X POST 'https://your-zenml-server/api/v1/pipeline_snapshots//runs' -H 'Authorization: Bearer ' -d '{"run_configuration": {...}}' ``` - **Through Browser** (Pro feature): Non-technical stakeholders can run snapshots directly from the ZenML dashboard by simply filling in a form with the required parameters - no coding required! This enables your team to standardize execution patterns while maintaining flexibility - perfect for production ML workflows that need to be triggered from various systems. ## Snapshots vs Deployments Snapshots and [Deployments](../deployment/deployment.md) both let you execute pipelines beyond direct Python calls, but they serve different purposes: - **Snapshots** launch a **batch-style pipeline run** each time they are triggered. There is no persistent service — the ZenML server creates a runner job, executes the pipeline, and the job exits. Use snapshots for scheduled retraining, CI/CD-triggered runs, or when non-engineers need to run pipelines from the dashboard. - **Deployments** create a **long-running HTTP service** with a stable URL, request/response semantics, custom endpoints, middleware, and shared state across calls. Use deployments for real-time inference, agents, interactive APIs, or any scenario needing an always-on endpoint. Deployments are available in both OSS and Pro. ## Understanding Pipeline Snapshots While the simplest way to execute a ZenML pipeline is to directly call your pipeline function, pipeline snapshots offer several advantages for more complex workflows: * **Standardization**: Ensure all pipeline runs follow a consistent configuration pattern * **Parameterization**: Easily modify inputs and settings without changing code * **Remote Execution**: Trigger pipelines through the dashboard or API without code access * **Team Collaboration**: Share ready-to-use pipeline configurations with team members * **Automation**: Integrate with CI/CD systems or other automated processes ## Creating Pipeline Snapshots You have several ways to create a snapshot in ZenML: ### Using the Python SDK You can create a snapshot from your local code and configuration like this: ```python from zenml import pipeline @pipeline def my_pipeline(): ... snapshot = my_pipeline.create_snapshot(name="") ``` ### Using the CLI You can create a snapshot using the ZenML CLI, by passing the [source path](../steps-pipelines/sources.md#source-paths) of your pipeline: ```bash zenml pipeline snapshot create --name= ``` {% hint style="warning" %} If you later want to run this snapshot, you need to have an active **remote stack** while running this command or you can specify one with the `--stack` option. {% endhint %} ### Using the Dashboard To create a snapshot through the ZenML dashboard: 1. Navigate to a pipeline run 2. Click on `...` in the top right, and then on `+ New Snapshot` 3. Enter a name for the snapshot 4. Click `Create` ## Running Pipeline Snapshots Once you've created a snapshot, you can run it through various interfaces: ### Using the Python SDK Run a snapshot programmatically: ```python from zenml.client import Client snapshot = Client().get_snapshot("", ...) config = snapshot.config_template # [OPTIONAL] Modify the configuration if needed config.steps["my_step"].parameters["my_param"] = new_value Client().trigger_pipeline( snapshot_name_or_id=snapshot.id, run_configuration=config, ) ``` ### Using the CLI Run a snapshot using the CLI: ```bash zenml pipeline snapshot run # If you want to run the snapshot with a modified configuration, use the `--config=...` parameter ``` ### Using the Dashboard To run a snapshot from the dashboard: 1. Either click `Run a Pipeline` on the main `Pipelines` page, or navigate to a specific snapshot and click `Run Snapshot` 2. On the `Run Details` page, you can: * Modify the configuration using the built-in editor * Upload a `.yaml` configuration file 3. Click `Run` to start the pipeline run Once you run the snapshot, a new run will be executed on the same stack as the original run. ### Using the REST API To run a snapshot through the REST API, you need to make a series of calls: 1. First, get the pipeline ID: ```bash curl -X 'GET' \ '/api/v1/pipelines?hydrate=false&name=' \ -H 'accept: application/json' \ -H 'Authorization: Bearer ' ``` 2. Using the pipeline ID, get the snapshot ID: ```bash curl -X 'GET' \ '/api/v1/pipeline_snapshots?hydrate=false&logical_operator=and&page=1&size=20&pipeline_id=' \ -H 'accept: application/json' \ -H 'Authorization: Bearer ' ``` 3. Finally, trigger the snapshot: ```bash curl -X 'POST' \ '/api/v1/pipeline_snapshots//runs' \ -H 'accept: application/json' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "run_configuration": { "steps": {"model_trainer": {"parameters": {"model_type": "rf"}}}} }' ``` {% hint style="info" %} Learn how to get a bearer token for the curl commands: - For a ZenML OSS API: use [service accounts + API keys](https://docs.zenml.io/how-to/manage-zenml-server/connecting-to-zenml/connect-with-a-service-account). - For a ZenML Pro workspace API: use [ZenML Pro Personal Access Tokens](https://docs.zenml.io/pro/access-management/personal-access-tokens) or [ZenML Pro Organization Service Accounts](https://docs.zenml.io/pro/access-management/service-accounts). {% endhint %} ## Deleting Pipeline Snapshots You can delete a snapshot using the CLI: ```bash zenml pipeline snapshot delete ``` You can also delete a snapshot using the Python SDK: ```python from zenml.client import Client Client().delete_snapshot(name_id_or_prefix=) ``` ## Advanced Usage: Running Snapshots from Other Pipelines You can run snapshots from within other pipelines, enabling complex workflows. There are two ways to do this: ### Method 1: Trigger by Pipeline Name (Uses Latest Snapshot) If you want to run the latest runnable snapshot for a specific pipeline: ```python import pandas as pd from zenml import pipeline, step from zenml.artifacts.unmaterialized_artifact import UnmaterializedArtifact from zenml.artifacts.utils import load_artifact from zenml.client import Client from zenml.config.pipeline_run_configuration import PipelineRunConfiguration @step def trainer(data_artifact_id: str): df = load_artifact(data_artifact_id) @pipeline def training_pipeline(): trainer() @step def load_data() -> pd.DataFrame: # Your data loading logic here return pd.DataFrame() @step def trigger_pipeline(df: UnmaterializedArtifact): # By using UnmaterializedArtifact we can get the ID of the artifact run_config = PipelineRunConfiguration( steps={"trainer": {"parameters": {"data_artifact_id": df.id}}} ) # This triggers the LATEST runnable snapshot for the "training_pipeline" pipeline Client().trigger_pipeline(pipeline_name_or_id="training_pipeline", run_configuration=run_config) @pipeline def loads_data_and_triggers_training(): df = load_data() trigger_pipeline(df) # Will trigger the other pipeline ``` ### Method 2: Trigger by Specific Snapshot ID If you want to run a specific snapshot (not necessarily the latest one): ```python @step def trigger_specific_snapshot(df: UnmaterializedArtifact): run_config = PipelineRunConfiguration( steps={"trainer": {"parameters": {"data_artifact_id": df.id}}} ) Client().trigger_pipeline(snapshot_name_or_id=, run_configuration=run_config) ``` {% hint style="info" %} **Key Difference**: - `Client().trigger_pipeline("pipeline_name", ...)` uses the pipeline name and runs the **latest** snapshot for that pipeline - `Client().trigger_pipeline(snapshot_id=, ...)` runs a **specific** snapshot by its unique ID {% endhint %} The newly created pipeline run will show up in the DAG next to the step that triggered it: This pattern is useful for: * Creating pipeline dependencies * Implementing dynamic workflow orchestration * Building multi-stage ML pipelines where different steps require different resources * Separating data preparation from model training Read more about: * [PipelineRunConfiguration](https://sdkdocs.zenml.io/latest/core_code_docs/core-config.html#zenml.config.pipeline_run_configuration) * [trigger\_pipeline API](https://sdkdocs.zenml.io/latest/core_code_docs/core-client.html#zenml.client.Client) * [Unmaterialized Artifacts](https://docs.zenml.io/concepts/artifacts) ## Best Practices 1. **Use descriptive names** for your snapshots to make them easily identifiable 2. **Document snapshot parameters** so other team members understand how to configure them 3. **Start with a working pipeline run** before creating a snapshot to ensure it's properly configured 4. **Test snapshots with different configurations** to verify they work as expected 5. **Use version control** for your snapshot configurations when storing them as YAML files 6. **Implement access controls** to manage who can run specific snapshots 7. **Monitor snapshot usage** to understand how your team is using them {% hint style="warning" %} **Important:** You need to recreate your snapshots after upgrading your ZenML server. Snapshots are tied to specific server versions and may not work correctly after an upgrade. {% endhint %}
ZenML Scarf
--- ### Book/How To/Stack Components/Service Connectors (docs/book/how-to/stack-components/service_connectors.md) --- description: >- Managing authentication to cloud services and resources with Service Connectors icon: link --- # Service Connectors Service Connectors provide a unified way to handle authentication between ZenML and external services like cloud providers. They are a critical part of working with cloud-based stacks and significantly simplify the authentication challenge in ML workflows. A service connector is an entity that: 1. Stores credentials and authentication configuration 2. Provides secure access to specific resources 3. Can be shared across multiple stack components 4. Manages permissions and access scopes 5. Automatically generates and refreshes short-lived access tokens Think of service connectors as secure bridges between your ZenML stack components and external services that abstract away the complexity of different authentication methods across cloud providers. ## Why Use Service Connectors? ### The Authentication Challenge ML workflows typically interact with multiple cloud services (storage, compute, model registries, etc.), creating complex credential management challenges. Without service connectors, you would need to: * Configure authentication separately for each stack component * Handle different authentication methods for each cloud service * Store and manage credentials manually in code or configuration files * Update credentials in multiple places when they change * Implement proper security practices across all credential usage * Spend engineering time on authentication rather than ML development

Service Connectors abstract away complexity and implement security best practices

Service connectors solve these problems by providing a single point of authentication that can be reused across your stack components, decoupling credentials from code and configuration. ### Key Benefits * **Centralized Authentication**: Manage all your cloud credentials in one place * **Credential Reuse**: Configure authentication once, use it with multiple components * **Security**: Implement security best practices with short-lived tokens, principle of least privilege, and reduced credential exposure * **Authentication Abstraction**: Eliminate credential handling code in pipeline components while supporting multiple auth methods * **Resource Discovery**: Easily find available resources on your cloud accounts * **Simplified Rotation**: Update credentials in one place when they change * **Team Sharing**: Securely share access to resources within your team * **Multi-cloud Support**: Use the same interface across AWS, GCP, Azure and other services with consistent patterns ### Supported Cloud Providers and Services ZenML supports connectors for major cloud providers and services: * **AWS**: For Amazon Web Services (S3, ECR, SageMaker, etc.) * **GCP**: For Google Cloud Platform (GCS, GCR, Vertex AI, etc.) * **Azure**: For Microsoft Azure (Blob Storage, ACR, AzureML, etc.) * **Kubernetes**: For Kubernetes clusters Each connector type supports authentication methods specific to that service. ## Working with Service Connectors ### Creating and Managing Connectors Service connectors can be created with different authentication methods depending on your cloud provider and security requirements. Here is an example of how to register a new connector: ```bash # Register a new connector using AWS profile zenml service-connector register aws-dev \ --type aws \ --auth-method profile \ --profile=dev-account # GCP connector using service account zenml service-connector register gcp-prod \ --type gcp \ --auth-method service-account \ --service-account-json=/path/to/sa.json # List all connectors zenml service-connector list # Verify a connector works zenml service-connector verify aws-dev ``` The authentication happens transparently to your ML code. You don't need to handle credentials in your pipeline steps - the service connector takes care of that for you. ### Discovering Resources A powerful feature of service connectors is resource discovery: ```bash # List available resources through a connector zenml service-connector list-resources aws-dev --resource-type s3-bucket ``` This helps you find existing resources when configuring stack components. ### Using Connectors with Stack Components Connect components to services: ```bash # Register a component with a connector zenml artifact-store register s3-store \ --type s3 \ --bucket my-bucket \ --connector aws-dev ``` ## Best Practices * **Use descriptive names** for connectors indicating their purpose or environment * **Create separate connectors** for development, staging, and production environments * **Apply least privilege** when configuring connector permissions and resource scopes * **Regularly rotate credentials** for enhanced security * **Document your connector configurations** for team knowledge sharing * **Leverage short-lived tokens** where possible instead of long-lived credentials * **Avoid hard-coding credentials** in your code and config files, use service connectors instead ## Code Example When using service connectors, your pipeline code remains clean and focused on ML logic: ```python from zenml import step # Without service connectors @step def upload_model(model): # Need to handle authentication manually import boto3 session = boto3.Session(aws_access_key_id='AKIAXXXXXXXX', aws_secret_access_key='SECRET') s3 = session.client('s3') s3.upload_file(model.path, 'my-bucket', 'models/model.pkl') # With service connectors @step def upload_model_with_connector(model): # Authentication handled by the service connector # No credential handling required from zenml.integrations.s3.artifact_stores import S3ArtifactStore store = S3ArtifactStore() store.copyfile(model.path, 'models/model.pkl') ``` ## Next Steps * Learn how to [deploy stacks](https://docs.zenml.io/stacks/deployment) using service connectors * Explore [authentication methods](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/service-connectors-guide) for different cloud providers * Understand how to [reference secrets in stack configuration](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/reference-secrets-in-stack-configuration) * Read our [blog post](https://www.zenml.io/blog/how-to-simplify-authentication-in-machine-learning-pipelines-for-mlops) on how service connectors simplify authentication in ML pipelines
ZenML Scarf
--- ### Book/How To/Stack Components/Stack Components (docs/book/how-to/stack-components/stack_components.md) --- description: Understanding and working with ZenML Stacks and Stack Components icon: cubes --- # Stack & Components A [ZenML stack](https://docs.zenml.io/stacks) is a collection of components that together form an MLOps infrastructure to run your ML pipelines. While your pipeline code defines what happens in your ML workflow, the stack determines where and how that code runs. Stacks provide several key benefits: 1. **Environment Flexibility**: Run the same pipeline code locally during development and in the cloud for production 2. **Infrastructure Separation**: Change your infrastructure without modifying your pipeline code 3. **Specialized Resources**: Use specialized tools for different aspects of your ML workflow 4. **Team Collaboration**: Share infrastructure configurations across your team 5. **Reproducibility**: Ensure consistent pipeline execution across different environments ### Stack Structure Each ZenML stack must include these core components: * **Orchestrator**: Controls how your pipeline steps are executed * **Artifact Store**: Manages where your pipeline artifacts are stored Stacks may also include these optional components: * **Container Registry**: Stores Docker images for your pipeline steps * **Deployer**: Deploys pipelines as long-running HTTP services * **Step Operator**: Runs specific steps on specialized hardware * **Model Deployer**: Deploys models as prediction services * **Experiment Tracker**: Tracks metrics and parameters * **Feature Store**: Manages ML features * **Alerter**: Sends notifications about pipeline events * **Annotator**: Manages data labeling workflows Most component types appear at most once in a stack. Three component types are repeatable: **step operators**, **experiment trackers**, and **alerters**. If a stack has more than one component of one of these types, the first attached component is the default. You can still choose a non-default component by name in step or pipeline configuration, and you can change the default later with `zenml stack set-default`. ## Working with Stacks ### The Active Stack In ZenML, you always have an active stack that's used when you run a pipeline: ```bash # See your active stack zenml stack describe # Switch to a different stack zenml stack set STACK_NAME ``` ### Managing Stacks You can create and manage stacks through the CLI: ```bash # List all stacks zenml stack list # Register a new stack with minimal components zenml stack register my-stack -a local-store -o local-orchestrator # Register a stack with additional components zenml stack register production-stack \ --artifact-store s3-store \ --orchestrator kubeflow \ --container-registry ecr-registry \ --experiment-tracker mlflow-tracker # Attach multiple repeatable components. The first one becomes the default. zenml stack register training-stack \ --artifact-store s3-store \ --orchestrator kubernetes \ --step_operator gpu-step-operator \ --step_operator cpu-step-operator \ --experiment_tracker mlflow \ --experiment_tracker wandb # Promote a different attached component to be the default. zenml stack set-default training-stack --step_operator cpu-step-operator ``` ### Discovering flavor-specific configuration Stack component flavors have different configuration fields. A local orchestrator needs very little information; a Kubernetes orchestrator needs cluster and namespace details; an S3 artifact store needs bucket information. Once you pass a flavor with `-f` / `--flavor`, the CLI can show the concrete configuration fields for that flavor: ```bash zenml orchestrator register -f kubernetes --help zenml artifact-store register -f s3 --help zenml step-operator update my-runai-step-operator --help ``` The help output includes a **Flavor configuration** section. For register and update commands, pass those fields as `--name=value` arguments. This is often the fastest way to answer, "what exactly does this flavor need from me?" Or through the Python API: ```python from zenml.client import Client client = Client() # List all stacks stacks = client.list_stacks() # Set active stack client.activate_stack("my-stack") ``` ### Local vs. Cloud Stacks ZenML provides two main types of stacks: 1. **Local Stack**: Uses your local machine for orchestration and storage. This is the default and requires no additional setup. 2. **Cloud Stack**: Uses cloud services for orchestration, storage, and other components. These stacks offer more scalability and features but require additional deployment and configuration. When you start with ZenML, you're automatically using a local stack. As your ML projects grow, you'll likely want to deploy cloud stacks to handle larger workloads and collaborate with your team. ## Next Steps Now that you understand what stacks are, you might want to: * Learn about [deploying stacks](https://docs.zenml.io/stacks/deployment) on cloud platforms * Understand [Service Connectors](service_connectors.md) for authenticating with cloud services * Explore how to [register existing cloud resources](https://docs.zenml.io/how-to/infrastructure-deployment/stack-deployment/register-a-cloud-stack) as ZenML stack components
ZenML Scarf
--- ### Book/How To/Steps Pipelines/Advanced Features (docs/book/how-to/steps-pipelines/advanced_features.md) --- description: Advanced features and capabilities of ZenML pipelines and steps --- # Advanced Features This guide covers advanced features and capabilities of ZenML pipelines and steps, allowing you to build more sophisticated machine learning workflows. ## Execution Control ### Caching Steps are automatically cached based on their code, inputs and other factors. When a step runs, ZenML computes a hash of the inputs and checks if a previous run with the same inputs exists. If found, ZenML reuses the outputs instead of re-executing the step. You can control caching behavior at the step level: ```python @step(enable_cache=False) def non_cached_step(): pass ``` You can also configure caching at the pipeline level: ```python @pipeline(enable_cache=False) def my_pipeline(): ... ``` Or modify it after definition: ```python my_step.configure(enable_cache=False) my_pipeline.configure(enable_cache=False) ``` For more information, check out [this page](https://docs.zenml.io/user-guides/starter-guide/cache-previous-executions). ### Running Individual Steps You can run a single step directly: ```python model, accuracy = train_classifier(X_train=X_train, y_train=y_train) ``` This creates a pipeline run with just that step. If you want to bypass ZenML completely and run the underlying function directly: ```python model, accuracy = train_classifier.entrypoint(X_train=X_train, y_train=y_train) ``` You can make this the default behavior by setting the `ZENML_RUN_SINGLE_STEPS_WITHOUT_STACK` environment variable to `True`. ### Step signatures, definitions, and semantic types A ZenML step is usually written as a normal Python function at module level: ```python from zenml import step @step def train_model(dataset_path: str, *, epochs: int = 10) -> None: ... ``` Keyword-only arguments, like `epochs` in the example above, are supported and are treated as normal step inputs. This is useful when you want a call site to be explicit about important values. Variadic signatures such as `*args` and `**kwargs` are not valid step inputs because ZenML needs to know the step interface before the pipeline runs. For production code, define the underlying Python functions for your steps and pipelines at module level. They are easier to import, package, test, and review that way. If you need a factory-style pattern, you can wrap those top-level functions with `@step` or `@pipeline(dynamic=True)` inside another function, but the decorated function itself should still be importable from a module. You can also add semantic metadata to a step with `step_type`: ```python from zenml import step from zenml.enums import StepType @step(step_type=StepType.LLM_CALL) def summarize_prompt(prompt: str) -> str: ... ``` The current step types are `StepType.TOOL_CALL`, `StepType.LLM_CALL`, and `StepType.MEMORY_CALL`. Think of this as a label on the step run: it does not change how your Python function executes, but it gives the dashboard, DAG metadata, and downstream consumers a clearer story about what kind of work the step represents. ### Asynchronous Pipeline Execution By default, pipelines run synchronously, with terminal logs displaying as the pipeline builds and runs. You can change this behavior to run pipelines asynchronously (in the background): ```python from zenml import pipeline @pipeline(settings={"orchestrator": {"synchronous": False}}) def my_pipeline(): ... ``` Alternatively, you can configure this in a YAML config file: ```yaml settings: orchestrator.: synchronous: false ``` You can also configure the orchestrator to always run asynchronously by setting `synchronous=False` in its configuration. ### Async Steps Step functions can be defined with `async def`. ZenML runs the coroutine to completion when the step executes, so whether a step body is async is invisible at the call site: ```python from zenml import step @step async def fetch_data(url: str) -> bytes: async with httpx.AsyncClient() as client: response = await client.get(url) return response.content ``` An async step behaves like any other step: call it inside a pipeline, pass its outputs downstream, or run several concurrently with [`.submit()`](dynamic_pipelines.md#parallel-step-execution). Submitted async steps run on separate threads, so their `await` points overlap and IO-bound work proceeds in parallel. {% hint style="warning" %} A blocking call inside an async body (`time.sleep`, a synchronous HTTP request, heavy CPU work) stalls that step's event loop and erases the concurrency. Use the async equivalents (`asyncio.sleep`, an async HTTP client) inside async steps. {% endhint %} ### Step Execution Order By default, ZenML determines step execution order based on data dependencies. When a step requires output from another step, it automatically creates a dependency. You can explicitly control execution order with the `after` parameter: ```python @pipeline def my_pipeline(): step_a_output = step_a() step_b_output = step_b() # step_c will only run after both step_a and step_b complete, even if # it doesn't use their outputs directly step_c(after=[step_a_output, step_b_output]) # You can also specify dependencies using the step invocation ID step_d(after="step_c") ``` This is particularly useful for steps with side effects (like data loading or model deployment) where the data dependency is not explicit. ### Execution Modes ZenML provides three execution modes that control how your orchestrator behaves when a step fails during pipeline execution. These modes are: - `CONTINUE_ON_FAILURE`: The orchestrator continues executing steps that don't depend on any of the failed steps. - `STOP_ON_FAILURE`: The orchestrator allows the running steps to complete, but prevents new steps from starting. - `FAIL_FAST`: The orchestrator stops the run and any running steps immediately when a failure occurs. You can configure the execution mode of your pipeline in several ways: ```python from zenml import pipeline from zenml.enums import ExecutionMode # Use the decorator @pipeline(execution_mode=ExecutionMode.CONTINUE_ON_FAILURE) def my_pipeline(): ... # Use the `with_options` method my_pipeline_with_fail_fast = my_pipeline.with_options( execution_mode=ExecutionMode.FAIL_FAST ) # Use the `configure` method my_pipeline.configure(execution_mode=ExecutionMode.STOP_ON_FAILURE) ``` {% hint style="warning" %} In the current implementation, if you use the execution mode `STOP_ON_FAILURE`, the token that is associated with your pipeline run stays valid until its leeway runs out (defaults to 1 hour). {% endhint %} As an example, you can consider a pipeline with this dependency structure: ``` ┌─► Step 2 ──► Step 5 ─┐ Step 1 ──┼─► Step 3 ──► Step 6 ─┼──► Step 8 └─► Step 4 ──► Step 7 ─┘ ``` If steps 2, 3, and 4 execute in parallel and step 2 fails: - With `FAIL_FAST`: Step 1 finishes → Steps 2,3,4 start → Step 2 fails → Steps 3, 4 are stopped → No other steps get launched - With `STOP_ON_FAILURE`: Step 1 finishes → Steps 2,3,4 start → Step 2 fails but Steps 3, 4 complete → Steps 5, 6, 7 are skipped - With `CONTINUE_ON_FAILURE`: Step 1 finishes → Steps 2,3,4 start → Step 2 fails, Steps 3, 4 complete → Step 5 skipped (depends on failed Step 2), Steps 6, 7 run normally → Step 8 is skipped as well. {% hint style="info" %} All three execution modes are currently only supported by the `local`, `local_docker`, `kubernetes`, and `modal` orchestrator flavors. For any other orchestrator flavor, the default (and only available) behavior is `CONTINUE_ON_FAILURE`. If you would like to see any of the other orchestrators extended to support the other execution modes, reach out to us in [Slack](https://zenml.io/slack-invite). {% endhint %} #### Execution modes in dynamic pipelines In [dynamic pipelines](dynamic_pipelines.md), execution modes control what happens when a step launched asynchronously with `step.submit(...)` or `step.map(...)` fails. Dynamic pipelines default to `STOP_ON_FAILURE`, so a failing step surfaces as a failed run unless you opt into another mode. With `CONTINUE_ON_FAILURE`, the failure of an asynchronous step no longer fails the run: ```python from zenml import pipeline, step from zenml.enums import ExecutionMode @pipeline(dynamic=True, execution_mode=ExecutionMode.CONTINUE_ON_FAILURE) def my_pipeline() -> None: risky_step.submit() # if this fails, the run still completes independent_step.submit() # keeps running regardless ``` The following rules apply: - A failed asynchronous step does not fail the run. Independent asynchronous steps keep running and the run completes. - Steps that depend on a failed asynchronous step (through its output or `after=...`) are skipped. - Awaiting a failed step through `future.result()` or `future.wait()` still raises the step's exception, and a failure in a step that you call synchronously still fails the run. ### Step Heartbeat Step heartbeat is a background mechanism that runs alongside step executions and performs two core functions: - Periodically pings the ZenML server to refresh the step's heartbeat value. - Retrieves the current pipeline and step status, and terminates the step if the pipeline has entered a stopping state. This enables ZenML to: - Track the liveness of a step execution and assess its health based on incoming heartbeats. - Gracefully interrupt running steps when a pipeline is being stopped. *Scope and current behavior* - Heartbeats are enabled only for steps executed in isolated environments. This excludes: - `Inline` steps in `dynamic` pipelines. - Steps run via the `local` orchestrator. - Heartbeat is enabled by default. - A step that becomes unhealthy automatically triggers a graceful shutdown (currently supported for the `kubernetes` orchestrator). - When using `CONTINUE_ON_FAILURE` execution mode, heartbeat status is also used to decide whether execution tokens should be invalidated. *Configuration* You can configure how long a step may go without sending a heartbeat before it is considered unhealthy using the `heartbeat_healthy_threshold` step parameter. The default value currently applied is 30 minutes. ```python from zenml import step @step(heartbeat_healthy_threshold=30) def my_step(): ... ``` You can disable heartbeat on the pipeline level if you pass the following configuration parameter: ```python from zenml import pipeline @pipeline(enable_heartbeat=False) def my_pipeline(): ... ``` If you want to disable heartbeats for a *running* pipeline you can use the following ZenML store utility: ```python from zenml.client import Client client = Client() client.zen_store.disable_run_heartbeat(run_id="run.id") ``` ## Data & Output Management ## Type annotations Your functions will work as ZenML steps even if you don't provide any type annotations for their inputs and outputs. However, adding type annotations to your step functions gives you lots of additional benefits: * **Type validation of your step inputs**: ZenML makes sure that your step functions receive an object of the correct type from the upstream steps in your pipeline. * **Better serialization**: Without type annotations, ZenML uses [Cloudpickle](https://github.com/cloudpipe/cloudpickle) to serialize your step outputs. When provided with type annotations, ZenML can choose a [materializer](https://docs.zenml.io/getting-started/core-concepts#materializers) that is best suited for the output. In case none of the builtin materializers work, you can even [write a custom materializer](https://docs.zenml.io/how-to/data-artifact-management/handle-data-artifacts/handle-custom-data-types). {% hint style="warning" %} ZenML provides a built-in [CloudpickleMaterializer](https://sdkdocs.zenml.io/latest/core_code_docs/core-materializers.html#zenml.materializers.cloudpickle_materializer) that can handle any object by saving it with [cloudpickle](https://github.com/cloudpipe/cloudpickle). However, this is not production-ready because the resulting artifacts cannot be loaded when running with a different Python version. In such cases, you should consider building a [custom Materializer](https://docs.zenml.io/how-to/data-artifact-management/handle-data-artifacts/handle-custom-data-types#custom-materializers) to save your objects in a more robust and efficient format. Moreover, using the `CloudpickleMaterializer` could allow users to upload of any kind of object. This could be exploited to upload a malicious file, which could execute arbitrary code on the vulnerable system. {% endhint %} ```python from typing import Tuple from zenml import step @step def square_root(number: int) -> float: return number ** 0.5 # To define a step with multiple outputs, use a `Tuple` type annotation @step def divide(a: int, b: int) -> Tuple[int, int]: return a // b, a % b ``` If you want to make sure you get all the benefits of type annotating your steps, you can set the environment variable `ZENML_ENFORCE_TYPE_ANNOTATIONS` to `True`. ZenML will then raise an exception in case one of the steps you're trying to run is missing a type annotation. ### Tuple vs multiple outputs It is impossible for ZenML to detect whether you want your step to have a single output artifact of type `Tuple` or multiple output artifacts just by looking at the type annotation. We use the following convention to differentiate between the two: When the `return` statement is followed by a tuple literal (e.g. `return 1, 2` or `return (value_1, value_2)`) we treat it as a step with multiple outputs. All other cases are treated as a step with a single output of type `Tuple`. ```python from zenml import step from typing import Annotated from typing import Tuple # Single output artifact @step def my_step() -> Tuple[int, int]: output_value = (0, 1) return output_value # Single output artifact with variable length @step def my_step(condition) -> Tuple[int, ...]: if condition: output_value = (0, 1) else: output_value = (0, 1, 2) return output_value # Single output artifact using the `Annotated` annotation @step def my_step() -> Annotated[Tuple[int, ...], "my_output"]: return 0, 1 # Multiple output artifacts @step def my_step() -> Tuple[int, int]: return 0, 1 # Not allowed: Variable length tuple annotation when using # multiple output artifacts @step def my_step() -> Tuple[int, ...]: return 0, 1 ``` ## Step output names By default, ZenML uses the output name `output` for single output steps and `output_0, output_1, ...` for steps with multiple outputs. These output names are used to display your outputs in the dashboard and [fetch them after your pipeline is finished](https://docs.zenml.io/user-guides/tutorial/fetching-pipelines). If you want to use custom output names for your steps, use the `Annotated` type annotation: ```python from typing import Annotated from typing import Tuple from zenml import step @step def square_root(number: int) -> Annotated[float, "custom_output_name"]: return number ** 0.5 @step def divide(a: int, b: int) -> Tuple[ Annotated[int, "quotient"], Annotated[int, "remainder"] ]: return a // b, a % b ``` {% hint style="info" %} If you do not give your outputs custom names, the created artifacts will be named `{pipeline_name}::{step_name}::output` or `{pipeline_name}::{step_name}::output_{i}` in the dashboard. See the [documentation on artifact versioning and configuration](https://docs.zenml.io/user-guides/starter-guide/manage-artifacts) for more information. {% endhint %} ## Workflow Patterns ### Pipeline Composition You can compose pipelines from other pipelines to create modular, reusable workflows: ```python @pipeline def data_pipeline(mode: str): if mode == "train": data = training_data_loader_step() else: data = test_data_loader_step() processed_data = preprocessing_step(data) return processed_data @pipeline def training_pipeline(): # Use another pipeline inside this pipeline training_data = data_pipeline(mode="train") model = train_model(data=training_data) test_data = data_pipeline(mode="test") evaluate_model(model=model, data=test_data) ``` Pipeline composition allows you to build complex workflows from simpler, well-tested components. ### Fan-out and Fan-in The fan-out/fan-in pattern is a common pipeline architecture where a single step splits into multiple parallel operations (fan-out) and then consolidates the results back into a single step (fan-in). This pattern is particularly useful for parallel processing, distributed workloads, or when you need to process data through different transformations and then aggregate the results. For example, you might want to process different chunks of data in parallel and then aggregate the results: ```python from zenml import step, get_step_context, pipeline from zenml.client import Client @step def load_step() -> str: return "Hello from ZenML!" @step def process_step(input_data: str) -> str: return input_data @step def combine_step(step_prefix: str, output_name: str) -> None: run_name = get_step_context().pipeline_run.name run = Client().get_pipeline_run(run_name) # Fetch all results from parallel processing steps processed_results = {} for step_name, step_info in run.steps.items(): if step_name.startswith(step_prefix): output = step_info.outputs[output_name][0] processed_results[step_info.name] = output.load() # Combine all results print(",".join([f"{k}: {v}" for k, v in processed_results.items()])) @pipeline(enable_cache=False) def fan_out_fan_in_pipeline(parallel_count: int) -> None: # Initial step (source) input_data = load_step() # Fan out: Process data in parallel branches after = [] for i in range(parallel_count): artifact = process_step(input_data, id=f"process_{i}") after.append(artifact) # Fan in: Combine results from all parallel branches combine_step(step_prefix="process_", output_name="output", after=after) fan_out_fan_in_pipeline(parallel_count=8) ``` The fan-out pattern allows for parallel processing and better resource utilization, while the fan-in pattern enables aggregation and consolidation of results. This is particularly useful for: - Parallel data processing - Distributed model training - Ensemble methods - Batch processing - Data validation across multiple sources - Hyperparameter tuning Note that when implementing the fan-in step, you'll need to use the ZenML Client to query the results from previous parallel steps, as shown in the example above, and you can't pass in the result directly. {% hint style="warning" %} The fan-in, fan-out method has the following limitations: 1. Steps run sequentially rather than in parallel if the underlying orchestrator does not support parallel step runs (e.g. with the local orchestrator) 2. The number of steps need to be known ahead-of-time, and ZenML does not yet support the ability to dynamically create steps on the fly. {% endhint %} ### Dynamic Fan-out/Fan-in with Snapshots For scenarios where you need to determine the number of parallel operations at runtime (e.g., based on database queries or dynamic data), you can use [snapshots](https://docs.zenml.io/user-guides/tutorial/trigger-pipelines-from-external-systems) to create a more flexible fan-out/fan-in pattern. This approach allows you to trigger multiple pipeline runs dynamically and then aggregate their results. ``` /* Detailed source-code truncated for AI context efficiency. */ ``` This pattern enables dynamic scaling, true parallelism, and database-driven workflows. Key advantages include fault tolerance and separate monitoring for each chunk. Consider resource management and proper error handling when implementing. ### Custom Step Invocation IDs When calling a ZenML step as part of your pipeline, it gets assigned a unique **invocation ID** that you can use to reference this step invocation when defining the execution order of your pipeline steps or use it to fetch information about the invocation after the pipeline has finished running. ```python from zenml import pipeline, step @step def my_step() -> None: ... @pipeline def example_pipeline(): # When calling a step for the first time inside a pipeline, # the invocation ID will be equal to the step name -> `my_step`. my_step() # When calling the same step again, the suffix `_2`, `_3`, ... will # be appended to the step name to generate a unique invocation ID. # For this call, the invocation ID would be `my_step_2`. my_step() # If you want to use a custom invocation ID when calling a step, you can # do so by passing it like this. If you pass a custom ID, it needs to be # unique for all the step invocations that happen as part of this pipeline. my_step(id="my_custom_invocation_id") ``` ### Named Pipeline Runs In the output logs of a pipeline run you will see the name of the run: ```bash Pipeline run training_pipeline-2023_05_24-12_41_04_576473 has finished in 3.742s. ``` This name is automatically generated based on the current date and time. To change the name for a run, pass `run_name` as a parameter to the `with_options()` method: ```python training_pipeline = training_pipeline.with_options( run_name="custom_pipeline_run_name" ) training_pipeline() ``` Pipeline run names must be unique, so if you plan to run your pipelines multiple times or run them on a schedule, make sure to either compute the run name dynamically or include one of the placeholders that ZenML will replace. {% hint style="info" %} The substitutions for the custom placeholders like `experiment_name` can be set in: - `@pipeline` decorator, so they are effective for all steps in this pipeline - `pipeline.with_options` function, so they are effective for all steps in this pipeline run Standard substitutions always available and consistent in all steps of the pipeline are: - `{date}`: current date, e.g. `2024_11_27` - `{time}`: current time in UTC format, e.g. `11_07_09_326492` {% endhint %} ```python training_pipeline = training_pipeline.with_options( run_name="custom_pipeline_run_name_{experiment_name}_{date}_{time}" ) training_pipeline() ``` ## Error Handling & Reliability ### Automatic Step Retries For steps that may encounter transient failures (like network issues or resource limitations), you can configure automatic retries: ```python from zenml.config.retry_config import StepRetryConfig @step( retry=StepRetryConfig( max_retries=3, # Maximum number of retry attempts delay=10, # Initial delay in seconds before first retry backoff=2 # Factor by which delay increases after each retry ) ) def unreliable_step(): # This step might fail due to transient issues ... ``` It's important to note that **retries happen at the step level, not the pipeline level**. This means that ZenML will only retry individual failed steps, not the entire pipeline. With this configuration, if the step fails, ZenML will: 1. Wait 10 seconds before the first retry 2. Wait 20 seconds (10 × 2) before the second retry 3. Wait 40 seconds (20 × 2) before the third retry 4. Fail the pipeline if all retries are exhausted This is particularly useful for steps that interact with external services or resources. ## Monitoring & Notifications ### Pipeline and Step Hooks Run custom code on step and pipeline lifecycle events (`on_start`, `on_end`, `on_success`, `on_failure`) to send notifications, log run details, or trigger external workflows. See the dedicated [Hooks](hooks.md) page for the full hook surface, including signatures, accessing run information, alerter integration, the static-versus-dynamic pipeline behavior, and running custom hooks using `run_hook(...)`. ## Conclusion These advanced features provide powerful capabilities for building sophisticated machine learning workflows in ZenML. By leveraging these features, you can create pipelines that are more robust, maintainable, and flexible. See also: - [Steps & Pipelines](./steps_and_pipelines.md) - Core building blocks - [YAML Configuration](./yaml_configuration.md) - YAML configuration
ZenML Scarf
--- ### Book/How To/Steps Pipelines/Command Steps (docs/book/how-to/steps-pipelines/command_steps.md) --- description: Run arbitrary commands as pipeline steps --- # Command Steps A command step runs an arbitrary command as a step in your pipeline instead of a Python function. Use the `CommandStep` class to wrap any command, and add it to a pipeline like any other step: ```python from zenml import CommandStep, pipeline train = CommandStep(command=["python", "train.py"]) report = CommandStep(command=["bash", "-c", "echo 'done'"]) @pipeline(dynamic=True, enable_cache=False) def my_pipeline() -> None: train() report() if __name__ == "__main__": my_pipeline() ``` The command is whatever you would type in a shell, split into a list. The image just needs to contain the binary you invoke. Shell features like pipes and `&&` need an explicit shell, for example `["bash", "-c", "a | b"]`. ## Running a Python function Instead of a command, you can pass a Python function. ZenML extracts the source code of the function and runs it with `python -c`: ```python from zenml import CommandStep def train() -> None: import json print(json.dumps({"status": "training"})) train_step = CommandStep(command=train) ``` The extracted source code is the entire program, nothing around the function travels with it. This means the function must be self-contained: - All imports must happen inside the function body. Module-level imports are not available. - The function must not reference module-level variables, constants, or other functions. - The function must not take any parameters, be decorated, or be defined inside another function. ZenML rejects parameters, decorators, and references to enclosing functions when the step is created. The execution environment only needs a `python` binary on the `PATH`, ZenML does not have to be installed. ## Running without ZenML in the image (dynamic pipelines only) A regular step runs your Python function inside the container, so the image has to contain `zenml`, your step code, and all of its dependencies. A command step does not. ZenML treats the command as a black box and never imports anything inside the container, so you can run an image that does not have `zenml` installed. Point a command step at such an image with the existing Docker settings: ```python from zenml import CommandStep from zenml.config import DockerSettings train = CommandStep( command=["python", "train.py"], step_operator="sagemaker", settings={ "docker": DockerSettings(skip_build=True, parent_image="my-registry/train:latest") }, ) ``` The image must be pullable by the execution backend and already carry your code and libraries. ## Where command steps run A command step follows the same execution routing as any other step. It can run: - On a **step operator**, in both static and dynamic pipelines. - As an **isolated step** (dynamic pipelines only) - **Locally as a subprocess** as an inline step (dynamic pipelines only) A static pipeline without a step operator is rejected at compile time. Attach a step operator, or use a dynamic pipeline. ## Configuring a command step A command step is a regular step. It takes the same options as any other step (step operator, settings, resources, retry, environment, secrets, and so on), either in the constructor or through `.with_options()` and `.configure()`: ```python train = CommandStep(command=["python", "train.py"], step_operator="sagemaker") @pipeline(dynamic=True) def my_pipeline() -> None: train.with_options(environment={"EPOCHS": "10"}, secrets=["my_api_key"])() ``` Environment variables and secrets are passed to the command as environment variables. The step succeeds when the command exits with status `0` and fails on any non-zero exit. See [Configuration](configuration.md) for the full set of options and the difference between `.with_options()` and `.configure()`. ## Multi-node distributed training For **multi-node** distributed training, a command step is the recommended launcher: point the command at a tool that owns the worker gang (TorchX, Ray) while ZenML owns the run. It's also one option for single-node multi-GPU (for example wrapping `torchrun`), though a native step works there too. See [Train with GPUs and Accelerate](../../user-guide/tutorial/distributed-training.md) for all the patterns and worked examples. ## Limitations - Command steps do not support inputs and outputs. - Your code is not downloaded into the execution environment. - `get_step_context()`, metadata, visualizations, and tags are not available inside the command. - Logs of command steps are not tracked by ZenML. They stay in the backend's native logging (for example CloudWatch or pod logs). - Step hooks are not allowed. - Steps in static pipelines without a step operator are not supported. - Functions passed as commands must be self-contained (see [Running a Python function](#running-a-python-function)). --- ### Book/How To/Steps Pipelines/Configuration (docs/book/how-to/steps-pipelines/configuration.md) --- description: Configuring and customizing your pipeline runs. --- ZenML provides several approaches to configure your pipelines and steps: ### Understanding `.configure()` vs `.with_options()` ZenML provides two primary methods to configure pipelines and steps: `.configure()` and `.with_options()`. While they accept the same parameters, they behave differently: - **`.configure()`**: Modifies the configuration **in-place** and returns the same object. - **`.with_options()`**: Creates a **new copy** with the applied configuration, leaving the original unchanged. When to use each: - Use `.with_options()` in most cases, especially inside pipeline definitions: ```python @pipeline def my_pipeline(): # This creates a new configuration just for this instance my_step.with_options(parameters={"param": "value"})() ``` - Use `.configure()` only when you intentionally want to modify a step globally, and are aware that the change will affect all subsequent invocations of that step. ## Approaches to Configuration ### Pipeline Configuration with `configure` You can configure various aspects of a pipeline using the `configure` method: ```python from zenml import pipeline # Assuming MyPipeline is your pipeline function # @pipeline # def MyPipeline(): # ... # Create a pipeline my_pipeline = MyPipeline() # Configure the pipeline my_pipeline.configure( enable_cache=False, enable_artifact_metadata=True, settings={ "docker": { "parent_image": "zenml-io/zenml-cuda:latest" } } ) # Run the pipeline my_pipeline() ``` ### Runtime Configuration with `with_options` You can configure a pipeline at runtime using the `with_options` method: ```python # Configure specific step parameters my_pipeline.with_options(steps={"trainer": {"parameters": {"learning_rate": 0.01}}})() # Or using a YAML configuration file my_pipeline.with_options(config_file="path_to_yaml_file")() ``` ### Step-Level Configuration You can configure individual steps with the `@step` decorator: ```python import tensorflow as tf from zenml import step @step( settings={ # Custom materializer for handling output serialization "output_materializers": { "output": "zenml.materializers.tensorflow_materializer.TensorflowModelMaterializer" }, # Step-specific experiment tracker settings "experiment_tracker.mlflow": { "experiment_name": "custom_experiment" } } ) def train_model() -> tf.keras.Model: model = build_and_train_model() return model ``` ### Direct Component Assignment If you have an experiment tracker or step operator in your active stack, you can enable them for specific steps like this: ```python from zenml import step @step(experiment_tracker=True, step_operator=True) def train_model(): # This step will use the experiment tracker and step operator of the active stack ... ``` If you want to make sure a step can only run with a specific experiment tracker/step operator, you can also specify the component names like this: ```python from zenml import step @step(experiment_tracker="mlflow_tracker", step_operator="vertex_ai") def train_model(): # This step will use MLflow for tracking and run on Vertex AI ... ``` You can combine both approaches with settings to configure the specific behavior of those components: ```python from zenml import step @step(step_operator=True, settings={"step_operator": {"estimator_args": {"instance_type": "m7g.medium"}}}) def my_step(): # This step will use the step operator of the active stack with custom instance type ... # Alternatively, using the step operator name and appropriate settings class: @step(step_operator="nameofstepoperator", settings={"step_operator": SagemakerStepOperatorSettings(instance_type="m7g.medium")}) def my_step(): # Same configuration using the settings class ... ``` This approach allows you to use different components for different steps in your pipeline while also customizing their runtime behavior. {% hint style="info" %} If your stack contains multiple step operators, `@step(step_operator=True)` uses the default step operator, which is the first attached step operator in the stack. If your stack contains multiple experiment trackers, `@step(experiment_tracker=True)` activates all attached experiment trackers for that step. {% endhint %} ## Types of Settings Settings in ZenML are categorized into three main types: * **General settings** that can be used on all ZenML pipelines: * `DockerSettings` for container configuration * `ResourceSettings` for CPU, memory, and GPU allocation (on ZenML Pro, the same fields drive [resource pools](https://docs.zenml.io/pro/core-concepts/resource-pools) for workspace quotas, queuing, and preemption) * `DeploymentSettings` for pipeline deployment configuration - can only be set at the pipeline level * **Stack-component-specific settings** for configuring behaviors of components in your stack: * These use the pattern ``, `.`, or `:` as keys * Use `` to target the default attached component of that type * Use `.` to select by flavor: `.` only works if exactly one attached component of that flavor exists in the stack * Use `:` to select an exact named instance: `:` targets a specific attached component by name * Examples include `experiment_tracker`, `experiment_tracker.wandb`, or `step_operator:vertex` ## Configuration Hierarchy There are a few general rules when it comes to settings and configurations that are applied in multiple places. Generally the following is true: * Configurations in code override configurations made inside of the yaml file * Configurations at the step level override those made at the pipeline level * In case of attributes the dictionaries are merged ```python from zenml import pipeline, step from zenml.config import ResourceSettings @step def load_data(parameter: int) -> dict: ... @step(settings={"resources": ResourceSettings(gpu_count=1, memory="2GB")}) def train_model(data: dict) -> None: ... @pipeline(settings={"resources": ResourceSettings(cpu_count=2, memory="1GB")}) def simple_ml_pipeline(parameter: int): ... # ZenMl merges the two configurations and uses the step configuration to override # values defined on the pipeline level train_model.configuration.settings["resources"] # -> cpu_count: 2, gpu_count=1, memory="2GB" simple_ml_pipeline.configuration.settings["resources"] # -> cpu_count: 2, memory="1GB" ``` ## Common Setting Types ### Resource Settings Resource settings allow you to specify the CPU, memory, and GPU requirements for your steps. On **ZenML Pro**, those declarations are also what the **resource pool** feature uses: for eligible dynamic pipelines, the server builds resource requests from your merged `ResourceSettings` (including `pool_resources` and `preemptible`), matches them against workspace pools and policies on your stack’s orchestrator or step operator, and may queue or preempt work accordingly. For more information on how this feature works, see [ZenML Pro Resource Pools](https://docs.zenml.io/pro/core-concepts/resource-pools). ```python from zenml.config import ResourceSettings @step(settings={"resources": ResourceSettings(gpu_count=1, memory="2GB")}) def train_model(data: dict) -> None: ... @pipeline(settings={"resources": ResourceSettings(cpu_count=2, memory="1GB")}) def simple_ml_pipeline(parameter: int): ... ``` When both pipeline and step resource settings are specified, they are merged with step settings taking precedence: ```python # Result of merging the above configurations: # train_model.configuration.settings["resources"] # -> cpu_count: 2, gpu_count=1, memory="2GB" ``` {% hint style="info" %} Note that `ResourceSettings` are not always applied by all orchestrators. The ability to enforce resource constraints depends on the specific orchestrator being used. Some orchestrators like Kubernetes fully support these settings, while others may ignore them. In order to learn more, read the [individual pages](https://docs.zenml.io/stacks/stack-components/orchestrators) of the orchestrator you are using. {% endhint %} Resource settings also allow you to configure scaling options - including minimum and maximum number of instances, and scaling policy - for your pipeline deployments, when used at the pipeline level: ```python from zenml.config import ResourceSettings @pipeline(settings={"resources": ResourceSettings( cpu_count=2, memory="4GB", min_replicas=0, max_replicas=10, max_concurrency=10 )}) def simple_llm_pipeline(parameter: int): ... ``` {% hint style="info" %} Note that `ResourceSettings` are not always applied exactly as specified by all deployers. Some deployers fully support these settings, while others may adjust them automatically to match a set of predefined static values or simply ignore them. In order to learn more, read the [individual pages](https://docs.zenml.io/stacks/stack-components/deployers) of the deployer you are using. {% endhint %} ### Docker Settings Docker settings allow you to customize the containerization process: ```python @pipeline(settings={ "docker": { "parent_image": "zenml-io/zenml-cuda:latest" } }) def my_pipeline(): ... ``` For more detailed information on containerization options, see the [containerization guide](../containerization/containerization.md). ### Deployment Settings Deployment settings allow you to customize the web server and ASGI application used to run your pipeline deployments. You can specify a range of options, including custom endpoints, middleware, extensions and even custom files used to serve an entire single-page application alongside your pipeline: ```python from typing import Dict, Any import psutil from zenml.config import DeploymentSettings, EndpointSpec, EndpointMethod, SecureHeadersConfig from zenml import pipeline async def health_detailed() -> Dict[str, Any]: return { "status": "healthy", "cpu_percent": psutil.cpu_percent(), "memory_percent": psutil.virtual_memory().percent, "disk_percent": psutil.disk_usage("/").percent, } @pipeline(settings={ "deployment": DeploymentSettings( custom_endpoints=[ EndpointSpec( path="/health", method=EndpointMethod.GET, handler=health_detailed, auth_required=False, ), ], secure_headers=SecureHeadersConfig( csp=( "default-src 'none'; " "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; " "connect-src 'self' https://cdn.jsdelivr.net; " "style-src 'self' 'unsafe-inline'" ), ), dashboard_files_path="my/custom/ui", }) def my_pipeline(): ... ``` For more detailed information on deployment options, see the [pipeline deployment guide](../deployment/deployment.md), particularly the [deployment settings](../deployment/deployment_settings.md) section. ## Stack Component Configuration ### Registration-time vs Runtime Stack Component Settings Stack components have two types of configuration: 1. **Registration-time configuration**: Static settings defined when registering a component ```bash # Example: Setting a fixed tracking URL for MLflow zenml experiment-tracker register mlflow_tracker --flavor=mlflow --tracking_url=http://localhost:5000 ``` 2. **Runtime settings**: Dynamic settings that can change between pipeline runs ```python # Example: Setting experiment name that changes for each run @step(settings={"experiment_tracker.mlflow": {"experiment_name": "custom_experiment"}}) def my_step(): ... ``` Even for runtime settings, you can set default values during registration: ```bash # Setting a default value for "nested" setting zenml experiment-tracker register --flavor=mlflow --nested=True ``` ### Using the Right Key for Stack Component Settings When specifying stack-component-specific settings, the key follows this pattern: ```python # Using just the component category @step(settings={"step_operator": {"estimator_args": {"instance_type": "m7g.medium"}}}) # Or using the component category and flavor @step(settings={"experiment_tracker.mlflow": {"experiment_name": "custom_experiment"}}) ``` If you specify just the category (e.g., `step_operator`), ZenML applies these settings to whatever flavor of component is in your stack. If the settings don't apply to that flavor, they are ignored. ## Making Configurations Flexible with Environment Variables You can make your configurations more flexible by referencing environment variables using the placeholder syntax `${ENV_VARIABLE_NAME}`: **In code:** ```python from zenml import step @step(extra={"value_from_environment": "${ENV_VAR}"}) def my_step() -> None: ... ``` **In configuration files:** ```yaml extra: value_from_environment: ${ENV_VAR} combined_value: prefix_${ENV_VAR}_suffix ``` This allows you to easily adapt your pipelines to different environments without changing code. ## Using External Configuration Tools (Hydra) For more advanced configuration management, you can use tools like [Hydra](https://hydra.cc/) to manage your pipeline parameters through composable YAML configs and CLI overrides, then pass the resolved values into ZenML. The key idea: **Hydra decides _what_ to run** (hyperparameters, model settings, data config) while **ZenML decides _where_ and _when_** (orchestration, caching, artifact versioning). Neither needs to know about the other's internals. For a complete working example, see the [Hydra + ZenML configuration management example](https://github.com/zenml-io/zenml/tree/main/examples/hydra_config_management). ## Autogenerate a template yaml file If you want to generate a template yaml file of your specific pipeline, you can do so by using the `.write_run_configuration_template()` method. This will generate a yaml file with all options commented out. This way you can pick and choose the settings that are relevant to you. ```python from zenml import pipeline ... @pipeline(enable_cache=True) # set cache behavior at step level def simple_ml_pipeline(parameter: int): dataset = load_data(parameter=parameter) train_model(dataset) simple_ml_pipeline.write_run_configuration_template(path="") ```
An example of a generated YAML configuration template ``` /* Detailed source-code truncated for AI context efficiency. */ ```
{% hint style="info" %} When you want to configure your pipeline with a certain stack in mind, you can do so as well: `...write_run_configuration_template(stack=)` {% endhint %}
ZenML Scarf
--- ### Book/How To/Steps Pipelines/Dynamic Pipelines (docs/book/how-to/steps-pipelines/dynamic_pipelines.md) --- description: Write dynamic pipelines --- # Dynamic Pipelines ## Why Dynamic Pipelines? Traditional ZenML pipelines require you to define the entire DAG structure at pipeline definition time. While this works well for many use cases, there are scenarios where you need more flexibility: - **Runtime-dependent workflows**: When the number of steps or their configuration depends on data computed during pipeline execution - **Dynamic parallelization**: When you need to spawn multiple parallel step executions based on runtime conditions - **Conditional execution**: When the workflow structure needs to adapt based on intermediate results Dynamic pipelines allow you to write pipelines that generate their DAG structure dynamically at runtime, giving you the power of Python's control flow (loops, conditionals) combined with ZenML's orchestration capabilities. {% hint style="info" %} Dynamic pipelines are powerful but easy to get wrong (e.g., `.load()` vs `.chunk()`, mapping vs submit). If you use an AI coding agent, the `zenml-pipeline-authoring` skill can guide implementation step-by-step. See [LLM tooling](../../reference/llms-txt.md). {% endhint %} ## Basic Example The simplest dynamic pipeline uses regular Python control flow to determine step execution: ```python from zenml import step, pipeline @step def generate_int() -> int: return 3 @step def do_something(index: int) -> None: print(f"Processing index {index}") @pipeline(dynamic=True) def dynamic_pipeline() -> None: count = generate_int() # `count` is an artifact, we now load the data count_data = count.load() for idx in range(count_data): # This will run sequentially, like regular Python code would. do_something(idx) if __name__ == "__main__": dynamic_pipeline() ``` In this example, the number of `do_something` steps executed depends on the value returned by `generate_int()`, which is only known at runtime. ## Key Features ### Dynamic Step Configuration You can configure steps dynamically within your pipeline using `with_options()`: ```python @pipeline(dynamic=True) def dynamic_pipeline(): some_step.with_options(enable_cache=False)() ``` This allows you to modify step behavior based on runtime conditions or data. ### Artifact name substitutions in dynamic pipelines Dynamic pipelines support the same artifact name substitutions as regular pipelines. This matters when a dynamically generated step has outputs whose names include runtime-friendly placeholders. The substituted artifact name is still a real output that you can pass to downstream steps. ```python from typing import Annotated from zenml import ArtifactConfig, pipeline, step @step(substitutions={"suffix": "validated"}) def produce() -> Annotated[int, ArtifactConfig(name="score_{suffix}")]: return 1 @step def consume(score: int) -> None: print(score) @pipeline(dynamic=True) def dynamic_pipeline() -> None: score = produce() consume(score) ``` One caveat: when you use `child_pipeline.embed(...)`, the child pipeline's own configuration is not applied. That includes child-level `substitutions`; the parent run's configuration controls the steps that execute inline. ### Step inputs as parameters Any value you pass to a step that is not the output of another step is uploaded to the artifact store as an external artifact, even a small `int`. Each upload costs a write to the artifact store and a request to the server, which adds up when a pipeline calls steps in a loop. Set the `ZENML_PARAMETER_SIZE_THRESHOLD` environment variable to pass JSON-serializable inputs as step parameters instead, which skips the upload. The variable must be set in the environment in which the pipeline executes, not on the client that starts the run. See [this page](../environment-variables/environment-variables.md) for how to configure environment variables for pipeline execution. | Value | Behavior | | --- | --- | | unset or `0` | Every raw input is uploaded. This is the default. | | a positive number | JSON-serializable inputs up to that many bytes become step parameters. Larger inputs are uploaded. | | `-1` | Every JSON-serializable input becomes a step parameter. | Inputs that cannot be parameters are still uploaded, so raising the threshold is safe. Wrap an input with `ExternalArtifact` to keep uploading it. ### Step Runtime Configuration You can control where a step executes by specifying its runtime: - **`runtime="inline"`**: The step runs in the orchestration environment (same process/container as the orchestrator) - **`runtime="isolated"`**: The orchestrator spins up a separate step execution environment (new container/process) ```python @step(runtime="isolated") def some_step() -> None: # This step will run in its own isolated environment ... @step(runtime="inline") def another_step() -> None: # This step will run in the orchestration environment ... ``` Use `runtime="isolated"` when you need: - Better resource isolation - Different environment requirements - Parallel execution (see below) Use `runtime="inline"` when you need: - Faster execution (no container startup overhead) - Shared resources with the orchestrator - Sequential execution ### Map/Reduce over collections Dynamic pipelines support a high-level map/reduce pattern over sequence-like step outputs. This lets you fan out a step across items of a collection and then reduce the results without manually writing loops or loading data in the orchestration environment. ```python from zenml import pipeline, step @step def producer() -> list[int]: return [1, 2, 3] @step def worker(value: int) -> int: return value * 2 @step def reducer(values: list[int]) -> int: return sum(values) @pipeline(dynamic=True, enable_cache=False) def map_reduce(): values = producer() results = worker.map(values) # fan out over collection reducer(results) # pass list of artifacts directly ``` For a complete agentic workflow that combines dynamic mapping, reduction, and a human approval gate, see the [`agentic_hitl_pipeline` example](https://github.com/zenml-io/zenml/tree/main/examples/agentic_hitl_pipeline). Key points: - `step.map(...)` fans out a step over sequence-like inputs. These inputs can be either - a single list-like output artifact (see the code sample above) - a list of output artifacts. - the output of a `.map(...)` or `.product(...)` call if the respective step only returns a single output artifact - Steps can accept lists of artifacts directly as inputs (useful for reducers). - You can pass the mapped output directly to a downstream step without loading in the orchestration environment. #### Mapping semantics: map vs product - `step.map(...)`: If multiple sequence-like inputs are provided, all must have the same length `n`. ZenML creates `n` mapped steps where the i-th step receives the i-th element from each input. - `step.product(...)`: Creates a mapped step for each combination of elements across all input sequences (cartesian product). Example (cartesian product): ```python from zenml import pipeline, step @step def int_values() -> list[int]: return [1, 2] @step def str_values() -> list[str]: return ["a", "b", "c"] @step def do_something(a: int, b: str) -> int: ... @pipeline(dynamic=True) def cartesian_example(): a = int_values() b = str_values() # Produces 2 * 3 = 6 mapped steps do_something.product(a=a, b=b) ``` #### Broadcasting inputs with unmapped(...) If you want to pass a sequence-like artifact as a whole to each mapped invocation (i.e., avoid splitting), wrap it with `unmapped(...)`: ```python from zenml import pipeline, step, unmapped @step def producer(length: int) -> list[int]: return [1] * length @step def consumer(a: int, b: list[int]) -> None: # `b` is the full list for every mapped call ... @pipeline(dynamic=True) def unmapped_example(): a = producer(length=3) # list of 3 ints b = producer(length=4) # list of 4 ints consumer.map(a=a, b=unmapped(b)) ``` #### Unpacking mapped outputs If a mapped step returns multiple outputs, you can split them into separate lists (one per output) using `unpack()`. This returns a tuple of lists of artifact futures, aligned by mapped invocation. ```python from zenml import pipeline, step @step def create_int_list() -> list[int]: return [1, 2] @step def compute(a: int) -> tuple[int, int]: return a * 2, a * 3 @pipeline(dynamic=True) def map_pipeline(): ints = create_int_list() results = compute.map(a=ints) # Map over [1, 2] # Unpack per-output across all mapped invocations double, triple = results.unpack() # Each element is an ArtifactFuture; load to get concrete values doubles = [f.load() for f in double] # [2, 4] triples = [f.load() for f in triple] # [3, 6] ``` Notes: - `results` is a future that refers to all outputs of all steps, and `unpack()` works for both `.map(...)` and `.product(...)`. - Each list contains future objects that refer to a single artifact. #### Manual Looping: `.chunk()` vs `.load()` When looping over artifacts manually, you need two different operations: | Method | Purpose | When to Use | |--------|---------|-------------| | `.load()` | Gets the **actual data** | Making decisions, filtering, control flow | | `.chunk(idx)` | Creates a **DAG edge** | Passing to downstream steps | {% hint style="info" %} **Mental model**: `.chunk()` is for wiring (tells the orchestrator "this step depends on item X from upstream"), `.load()` is for decisions (gets values for your Python logic). You typically need both: load to iterate and decide, chunk to wire up the DAG. {% endhint %} ```python from zenml import pipeline, step @step def create_int_list() -> list[int]: return [1, 2, 3, 4] @step def compute(a: int) -> int: return a * 2 @pipeline(dynamic=True) def custom_loop(): ints = create_int_list() # .load() to get values for Python control flow (iteration + filtering) for index, value in enumerate(ints.load()): if value % 2 == 0: # .chunk() to create DAG edge (wiring to downstream step) chunk = ints.chunk(index=index) compute(chunk) ``` ### Parallel Step Execution Dynamic pipelines support true parallel execution using `step.submit()`. This method returns a `StepFuture` that you can use to wait for results or pass to downstream steps: ```python from zenml import step, pipeline @step def some_step(arg: int) -> int: return arg * 2 @pipeline(dynamic=True) def dynamic_pipeline(): # Submit a step for parallel execution future = some_step.submit(arg=1) # Wait and get artifact response(s) artifact = future.result() # Wait and load artifact data data = future.load() # Pass the output to another step downstream_step(future) # Run multiple steps in parallel for idx in range(3): some_step.submit(arg=idx) ``` The `StepFuture` object provides several methods: - **`result()`**: Wait for the step to complete and return the artifact response(s) - **`load()`**: Wait for the step to complete and load the actual artifact data - **Pass directly**: You can pass a `StepFuture` directly to downstream steps, and ZenML will automatically wait for it {% hint style="info" %} When using `step.submit()`, steps with `runtime="isolated"` will execute in separate containers/processes, while steps with `runtime="inline"` will execute in separate threads within the orchestration environment. {% endhint %} ### Ordering submitted steps A submitted step starts as soon as its inputs are available. To impose an order between steps that have no data dependency, use `after` or `start_after`: - `after=` waits for the upstream step to **finish** before starting. - `start_after=` waits for the upstream step to **start** before starting. `start_after` is useful when an upstream step is long-running and you want a dependent to run alongside it rather than after it. A common case is a step that brings up a service and a second step that uses it: ```python @pipeline(dynamic=True) def serve_and_query(): server = serve_model.submit() # long-running # Starts once the server step is running, not when it finishes. query = run_inference.submit(start_after=server) query.wait() ``` Using `after=server` here would deadlock, since the dependent would wait for the long-running server step to finish. Both parameters accept a single future or a list, and you can combine them: `run_inference.submit(after=preprocess, start_after=server)`. `start_after` is available on `step.submit(...)`, `step.map(...)`, `step.product(...)`, and on a direct synchronous `step(...)` call, where the entrypoint blocks until the upstream has started. The upstream can be another step or a submitted child pipeline, for example `run_inference(start_after=serve_pipeline.submit())`. `start_after` orders execution, it does not probe readiness. The dependent starts once the upstream step has launched (for isolated steps, once it is submitted to the infrastructure), which does not guarantee that whatever the upstream sets up is ready to serve. Add your own connection retries if the dependent needs to reach a service the upstream starts. A failed upstream counts as started, so a `start_after` dependent is released rather than blocked when the upstream fails. Circular `start_after` dependencies are not detected and will stall the involved steps. ### Child pipelines inside dynamic pipelines Dynamic pipelines can call other dynamic pipelines from their `@pipeline` body. This is useful for composing larger workflows out of reusable dynamic building blocks. Key behavior: - Only dynamic pipelines can be called as child pipelines. - Child pipelines run on the same stack as the parent run. - Child pipelines can run synchronously (`child(...)`) or concurrently (`child.submit(...)`). - Child pipeline calls are only allowed in pipeline bodies, not inside step functions. - Child pipelines reuse the parent run's Docker image — they don't trigger a new build. The child snapshot inherits the parent's build, code reference, and code path so the child runs against the exact same image and source bundle as the parent. Child pipeline outputs are returned as artifact references: - `None` - A single output artifact - A tuple of output artifacts These outputs can be passed directly to downstream steps. ```python from zenml import pipeline, step @step def produce_number() -> int: return 42 @pipeline(dynamic=True) def child_pipeline(): return produce_number() @step def consume_number(value: int) -> None: print(value) @pipeline(dynamic=True) def parent_pipeline(): child_output = child_pipeline() consume_number(child_output) ``` For concurrent execution, use `submit()` and wait on the future: ```python @pipeline(dynamic=True) def parent_pipeline_concurrent(): future = child_pipeline.submit() child_output = future.result() consume_number(child_output) ``` ### Inline child pipelines with `embed(...)` Use `child_pipeline.embed(...)` if you want to reuse another dynamic pipeline's body without creating a child pipeline run. ```python @pipeline(dynamic=True) def parent_pipeline_inline(): # Executes child steps in the parent run context child_output = child_pipeline.embed() consume_number(child_output) ``` `embed(...)` behavior: - It executes the child pipeline entrypoint inline as part of the parent run. - It does not create a separate child run in the dashboard. - It is only valid inside a dynamic pipeline body. - It is not allowed inside `@step` functions. {% hint style="warning" %} **Limitations of `embed(...)`.** Unlike `child_pipeline(...)` and `child_pipeline.submit(...)`, the inline form does not apply the child pipeline's own configuration. The parent run's configuration governs every step that runs inline: - Child-level `settings`, `retry`, `enable_cache`, `enable_step_logs`, `environment`, `secrets`, `tags`, `substitutions`, `model`, and `on_init` / `on_success` / `on_failure` / `on_cleanup` hooks are ignored. - Per-step Docker overrides on the child pipeline are also ignored — the parent's image is used for any inline isolated step. - `depends_on` config templates declared on the child pipeline are not picked up. - There is no failure isolation: an exception inside the inline body aborts the parent run. If any of these matter to your use case, call the child as `child_pipeline(...)` (sync) or `child_pipeline.submit(...)` (concurrent) instead. Both create a real child run with its own configuration applied. {% endhint %} In short, use: - `child_pipeline(...)` for a synchronous child run - `child_pipeline.submit(...)` for a concurrent child run - `child_pipeline.embed(...)` for embedded execution in the parent run {% hint style="warning" %} **Resume idempotency depends on submit order.** Child pipeline child runs are identified by the order of `child_pipeline(...)` / `child_pipeline.submit(...)` calls in the parent body: the first call to `my_pipeline` becomes `pipeline:my_pipeline`, the second becomes `pipeline:my_pipeline_2`, and so on. On resume, ZenML reuses an existing child run only if the same call appears in the same position. If you reorder, insert, or remove child pipeline calls before existing ones, every subsequent ID shifts and previously completed children are re-executed. Same caveat applies to step invocation IDs. {% endhint %} ### Build, code, and Docker settings inheritance Child runs share the parent's orchestration environment, image, and code bundle. This has two consequences worth knowing: - **No new Docker build.** The child snapshot inherits the parent's `build`, `code_reference`, and `code_path`. The child runs against the exact same image and source bundle as the parent — there is no separate build step, and the child's code/dependencies must already be installed in the parent's image. - **Pipeline-level Docker settings on the child are ignored.** When a child pipeline (or a child step) declares non-default `docker_settings`, those settings are silently overridden by the parent's. If you need a different image for a step inside a child pipeline, configure that step with a `step_operator` or use `runtime="isolated"` together with stack-level resource configuration on the parent. This applies to all three call modes (`child(...)`, `child.submit(...)`, and `child.embed(...)`). ### Permissions and authentication for nested runs Nested runs orchestrate from the parent's environment, so they share the parent's API token. The token must be scoped to the **root** run of the nesting tree — the root orchestrator can mint per-child-run tokens for any descendant. Child runs cannot mint tokens for their siblings; only descendants of the same root tree are reachable from a given parent token. This is transparent for the default flow (the root orchestrator launches everything in the same environment). It matters if you build automation on top of `ZENML_PIPELINE_RUN_ID` tokens — those tokens give you read/update access to the run they were minted for and any of its descendants, but not to siblings or unrelated runs. ### Config Templates with `depends_on` You can use YAML configuration files to provide default parameters for steps using the `depends_on` parameter: ```yaml # config.yaml steps: some_step: parameters: arg: 3 ``` ```python # run.py from zenml import step, pipeline @step def some_step(arg: int) -> None: print(f"arg is {arg}") @pipeline(dynamic=True, depends_on=[some_step]) def dynamic_pipeline(): some_step() if __name__ == "__main__": dynamic_pipeline.with_options(config_path="config.yaml")() ``` The `depends_on` parameter tells ZenML which steps can be configured via the YAML file. This is particularly useful when you want to allow users to configure pipeline behavior without modifying code. ### Pass pipeline parameters when running snapshots from the server When running a snapshot from the server (either via the UI or the SDK/Rest API), you can now pass pipeline parameters for your dynamic pipelines. For example: ```python from zenml.client import Client Client().trigger_pipeline(snapshot_id=, run_configuration={"parameters": {"my_param": 3}}) ``` ## Limitations and Known Issues ### Execution modes When using the `FAIL_FAST` execution mode, failure of a step does not immediately cancel other **inline** steps. Instead, they continue executing until finished. **Isolated** steps on the other hand will be shut down immediately. ### Orchestrator Support Dynamic pipelines are currently only supported by: | Orchestrator | Isolated steps | Handles orchestration environment failures | | ------------------------------------------- | :------------: | :----------------------------------------: | | [LocalOrchestrator](https://docs.zenml.io/stacks/stack-components/orchestrators/local) | ❌ | ❌ | | [LocalDockerOrchestrator](https://docs.zenml.io/stacks/stack-components/orchestrators/local-docker) | ❌ | ❌ | | [KubernetesOrchestrator](https://docs.zenml.io/stacks/stack-components/orchestrators/kubernetes) | ✅ | ✅ | | [VertexOrchestrator](https://docs.zenml.io/stacks/stack-components/orchestrators/vertex) | ✅ | ❌ | | [SagemakerOrchestrator](https://docs.zenml.io/stacks/stack-components/orchestrators/sagemaker) | ✅ | ❌ | | [AzureMLOrchestrator](https://docs.zenml.io/stacks/stack-components/orchestrators/azureml) | ✅ | ❌ | ### Artifact Loading When you call `.load()` on an artifact in a dynamic pipeline, it synchronously loads the data. For large artifacts or when you want to maintain parallelism, consider passing the step outputs (future or artifact) directly to downstream steps instead of loading them. ### Mapping Limitations - Mapping is currently supported only over artifacts produced within the same pipeline run (mapping over raw data or external artifacts is not supported). - Chunk size for mapped collection loading defaults to 1 and is not yet configurable. ## Best Practices 1. **Use `runtime="isolated"` for parallel steps**: This ensures better resource isolation and prevents interference between concurrent step executions. 2. **Handle step outputs appropriately**: If you need the data immediately, use `.load()`. If you're just passing to another step, pass the output directly. 3. **Be mindful of resource usage**: Running many steps in parallel can consume significant resources. Monitor your orchestrator's resource limits. 4. **Test incrementally**: Start with simple dynamic pipelines and gradually add complexity. Dynamic pipelines can be harder to debug than static ones. 5. **Use config templates for flexibility**: The `depends_on` feature allows you to make pipelines configurable without code changes. ## When to Use Dynamic Pipelines Dynamic pipelines are ideal for: - **AI agent orchestration**: Coordinating multiple autonomous agents (e.g., retrieval or reasoning agents) whose interactions or number of invocations are determined at runtime - **Hyperparameter tuning**: Spawning multiple training runs with different configurations - **Data processing**: Processing variable numbers of data chunks in parallel - **Conditional workflows**: Adapting pipeline structure based on runtime data - **Dynamic batching**: Creating batches based on available data - **Multi-agent and collaborative AI workflows**: Building flexible, adaptive workflows where agents or LLM-driven components can be dynamically spawned, routed, or looped based on outputs, results, or user input For most standard ML workflows, traditional static pipelines are simpler and more maintainable. Use dynamic pipelines when you specifically need runtime flexibility that static pipelines cannot provide. ## Real-World Example: Hierarchical Document Search The [`examples/hierarchical_doc_search_agent`](https://github.com/zenml-io/zenml/tree/main/examples/hierarchical_doc_search_agent) example combines dynamic pipelines with Pydantic AI agents for intelligent document traversal. It demonstrates: - Using `.with_options()` to pass parameters vs artifacts - The `.chunk()` vs `.load()` pattern: chunks for wiring the DAG, loads for making traversal decisions - Spawning steps dynamically based on AI agent decisions Each `traverse_node` call appears as a separate step in the DAG, created at runtime based on what the agent decides to explore. Two other examples are useful when you want to see dynamic pipelines in more specialized settings: - [`examples/rlm_document_analysis`](https://github.com/zenml-io/zenml/tree/main/examples/rlm_document_analysis) shows a Recursive Language Model style document-analysis workflow. ZenML decides how many chunk-processing steps to create at runtime, while the LLM loop inside each chunk decides which typed search tools to use. - [`examples/optuna_hyperparameter_tuning`](https://github.com/zenml-io/zenml/tree/main/examples/optuna_hyperparameter_tuning) combines Optuna's ask API with ZenML dynamic pipelines. Optuna decides which hyperparameters to try next; ZenML runs the trials, tracks their artifacts and metadata, and can fan the work out in parallel.
ZenML Scarf
--- ### Book/How To/Steps Pipelines/Execution (docs/book/how-to/steps-pipelines/execution.md) --- description: Step and pipeline execution. --- # How ZenML executes steps This page explains what happens under the hood when ZenML executes steps in static and dynamic pipelines. Regardless of where or how a step executes (inline or in an isolated environment, synchronous or concurrent), ZenML applies the same core semantics: inputs are loaded via materializers, outputs are materialized as versioned artifacts, lineage/metadata and logs are recorded, caching policies are respected, and step/run status is published consistently. ## Static pipelines In static pipelines, ZenML executes the pipeline function before running the pipeline to compile a DAG of steps, which the orchestrator then schedules according to their upstream dependencies. This pre-compilation allows ZenML to optimize execution order and validate the DAG structure before any steps run. ### Execution scenarios ## Dynamic pipelines [Dynamic pipelines](./dynamic_pipelines.md) execute the pipeline function at runtime. Each step executed inside the pipeline function can be: - **Inline** (runs inside the orchestration environment) - **Isolated** (runs in a separate environment via the orchestrator or a step operator) And each step call can be: - **Synchronous** (via `my_step(...)`): blocks until completion and returns the step output artifacts. - **Concurrent** (via `my_step.submit(...)`): starts step execution in a separate thread and returns a future. The pipeline function resumes execution immediately. ### Execution scenarios #### Synchronous inline The step runs in-process inside the orchestration environment. The pipeline function blocks until the step completes. #### Concurrent inline The step runs in-process in a separate thread. The pipeline function continues immediately and only waits when results are consumed. #### Synchronous isolated The step runs in a separate environment (via the orchestrator or step operator). The pipeline function blocks until the job completes. #### Concurrent isolated The step runs in a separate environment (via the orchestrator or step operator). The pipeline function continues immediately and only waits when results are consumed.
ZenML Scarf
--- ### Book/How To/Steps Pipelines/Hooks (docs/book/how-to/steps-pipelines/hooks.md) --- description: Run custom code on pipeline and step lifecycle events and record hook invocations. --- # Hooks Hooks let you run custom code at lifecycle points of a run or a step, and record those invocations as queryable `HookInvocation` records. The same machinery powers the built-in lifecycle hooks ZenML fires for you and the public API you can call from your own code. Common uses include sending notifications on success or failure, logging run details, and triggering external workflows. ## Lifecycle hooks Lifecycle hooks fire automatically and each fire creates one `HookInvocation` row. | Hook | Step scope | Pipeline scope (dynamic) | |---|---|---| | `on_start` | Each execution attempt, before the step body | Once before the run starts | | `on_end` | Each execution attempt, regardless of outcome | Once when the run reaches a terminal state | | `on_success` | Once when the step completes successfully | Once when the run completes successfully | | `on_failure` | Once when the step fails terminally | Once when the run fails | | `on_pause` | — | Once when the run pauses | | `on_resume` | — | Once when a paused run resumes | Step-level hooks fire for both static and dynamic pipelines, uniformly. ### Static pipelines propagate, dynamic pipelines fire The same `@pipeline(on_*=...)` kwarg has two different runtime meanings depending on whether the pipeline is dynamic. | `@pipeline(on_*=X)` | Static pipeline | Dynamic pipeline | |---|---|---| | `on_start` | Propagates to each step's `on_start` default | Fires once at the pipeline level | | `on_success` | Propagates to each step's `on_success` default | Fires once at the pipeline level | | `on_failure` | Propagates to each step's `on_failure` default | Fires once at the pipeline level | | `on_end` | Propagates to each step's `on_end` default | Fires once at the pipeline level | | `on_pause` | Ignored | Fires once at the pipeline level | | `on_resume` | Ignored | Fires once at the pipeline level | For a static pipeline, a pipeline-level hook is a default that every step inherits where it has not set its own. No pipeline-level hooks are run. For a dynamic pipeline, the hook fires once at the run level and produces `RUN_*` entries. It fires on every run, including each invocation of a deployed pipeline. Dynamic pipeline users who want per-step defaults wire each `@step` directly. ## Registering hooks Pass a callable or a source string to the decorator, `.configure(...)`, or `.with_options(...)`. ```python from zenml import step, pipeline def notify_start(): print("starting") def notify_end(exception=None): print("finished") @step(on_start=notify_start, on_end=notify_end) def my_step() -> int: return 42 @pipeline(on_start=notify_start, on_end=notify_end) def my_pipeline(): my_step() # Override at configuration time my_step = my_step.with_options(on_failure="my_module.alert_on_failure") ``` ### Hook signatures `on_start`, `on_success`, `on_pause` and `on_resume` take no arguments. `on_failure` and `on_end` optionally take a single `BaseException` argument. ```python from typing import Optional def on_end(): ... def on_end(exception: Optional[BaseException] = None): ... ``` `exception` is set only when the attempt or run failed. Details about the current step or run are available through the step or run context (see below). ### Accessing step/run information in hooks Step-scope hooks read the current step run and pipeline run through the step context. Run-scope hooks on a dynamic pipeline fire outside any step and read the run from the run context instead, via `DynamicPipelineRunContext.get().run`. ```python from zenml import get_step_context, step def on_failure(exception: BaseException): context = get_step_context() print(f"Failed step: {context.step_run.name}") @step(on_failure=on_failure) def my_step(some_parameter: int = 1): raise ValueError("My exception") ``` ### Sending alerts from hooks Use the [Alerter stack component](https://docs.zenml.io/component-guide/alerters) to send notifications when a step or run fails or succeeds. ```python from zenml import get_step_context from zenml.client import Client def on_failure(): step_name = get_step_context().step_run.name Client().active_stack.alerter.post(f"{step_name} just failed!") ``` ZenML ships built-in alerter hooks for the common case. ```python from zenml.hooks import alerter_success_hook, alerter_failure_hook @step(on_failure=alerter_failure_hook, on_success=alerter_success_hook) def my_step(): ... ``` ## Behavior notes * **Retries.** A retried step fires one `on_start` / `on_end` pair per attempt. `on_success` and `on_failure` fire exactly once, at the terminal outcome. * **Cache hits.** A cached step fires no step-level hooks. Pipeline-level hooks on a dynamic run still fire even when every step was cached. * **Hook failures are swallowed.** When a lifecycle hook raises, the run or step is not aborted. The exception is captured into the `HookInvocation` record with `status=FAILED` and execution proceeds. * **Async hooks.** Hook functions can be defined with `async def`. When the hook fires, ZenML runs the coroutine to completion and blocks until it finishes. * **Return values are discarded.** Set `ZENML_TRACK_LIFECYCLE_HOOK_OUTPUTS=true` in the execution environment to instead materialize lifecycle hook return values as output artifacts of the invocation, following the same rules as `store_return=True` on `run_hook` (see below). ## Init and cleanup hooks `on_init` and `on_cleanup` are pipeline setup and teardown hooks. They initialize and tear down shared run state rather than reacting to a single run or step outcome, so they follow different rules from the lifecycle hooks above. They are **not** recorded as `HookInvocation` records. When `on_init` fails, the run still records `RUN_START`, `RUN_END`, and `RUN_FAILURE`, but never a row for `on_init` itself. Find the root cause on `pipeline_run.exception_info`. ZenML runs `on_init` **once per execution environment**, before any step body runs in that environment, and `on_cleanup` once when that environment is torn down. Where that lands depends on how the pipeline runs. ### Deployments `on_init` runs once per deployment replica when the replica starts, and `on_cleanup` once when it shuts down. Individual invocations of the deployed pipeline reuse the initialized state and do not re-run either hook. Lifecycle hooks like `on_start` and `on_end` still fire on every invocation. ### Regular runs For a run that is not a deployment, `on_init` runs once per execution environment: * **Dynamic pipeline:** once in the orchestrator environment, where the pipeline function executes. * **Any step that runs outside the orchestration environment (static or dynamic):** once ahead of that step body, the first time its environment is used. A step that shares the orchestration environment skips the hook, because the run context is already initialized there. ## Recording custom invocations Beyond the built-in lifecycle hooks, you can record arbitrary invocations from inside a step or a dynamic pipeline function. This is useful for instrumenting third-party callbacks such as the tool and model calls of an agent framework. ### `run_hook` Call `run_hook(func, ...)` to run a function and record the invocation in one call. The return value flows through to you. ```python from zenml import run_hook, step def call_tool(name: str) -> str: return f"result of {name}" @step def agent_step(): # Records one CUSTOM HookInvocation, returns the function's result. result = run_hook(call_tool, "search") ``` Pass `store_return=True` to materialize the return value as an output artifact. A single unannotated return becomes one artifact named `output`. An annotated tuple return unpacks into one artifact per element. ```python result = run_hook(call_tool, "search", store_return=True) ``` ## Querying hook invocations List the invocations recorded for a run through the client. ```python from zenml.client import Client from zenml.enums import HookType invocations = Client().list_hook_invocations( pipeline_run_id=run.id, hook_type=HookType.CUSTOM, ) for invocation in invocations.items: print(invocation.name, invocation.status) ``` --- ### Book/How To/Steps Pipelines/Logging (docs/book/how-to/steps-pipelines/logging.md) --- description: >- Learn how to control and customize logging behavior in ZenML pipelines. --- # Logging By default, ZenML uses a logging handler to capture two types of logs: * **Pipeline run logs**: Logs collected from your ZenML client while triggering and waiting for a pipeline to run. These logs cover everything that happens client-side: building and pushing container images, triggering the pipeline, waiting for it to start, and waiting for it to finish. These logs are now stored in the artifact store, making them accessible even after the client session ends. * **Step logs**: Logs collected from the execution of individual steps. These logs only cover what happens during the execution of a single step and originate mostly from the user-provided step code and the libraries it calls. For step logs, users are free to use the default python logging module or print statements, and ZenML's logging handler will catch these logs and store them. ```python import logging from zenml import step @step def my_step() -> None: logging.warning("`Hello`") # You can use the regular `logging` module. print("World.") # You can utilize `print` statements as well. ``` All these logs are stored within the respective artifact store of your stack. You can visualize the pipeline run logs and step logs in the dashboard as follows: * Local ZenML server (`zenml login --local`): Both local and remote artifact stores may be accessible * Deployed ZenML server: Local artifact store logs won't be accessible; remote artifact store logs require [service connector](https://docs.zenml.io/how-to/infrastructure-deployment/auth-management/service-connectors-guide) configuration (see [remote storage guide](https://docs.zenml.io/user-guides/production-guide/remote-storage)) {% hint style="warning" %} In order for logs to be visible in the dashboard with a deployed ZenML server, you must configure both a remote artifact store and the appropriate service connector to access it. Without this configuration, your logs won't be accessible through the dashboard. {% endhint %} ## Logging Configuration ### Environment Variables and Remote Execution For all logging configurations below, note: - Setting environment variables on your local machine only affects local pipeline runs - For remote pipeline runs, you must set these variables in the pipeline's execution environment using Docker settings: ```python from zenml import pipeline from zenml.config import DockerSettings docker_settings = DockerSettings(environment={"ENVIRONMENT_VARIABLE": "value"}) # Either add it to the decorator @pipeline(settings={"docker": docker_settings}) def my_pipeline() -> None: my_step() # Or configure the pipelines options my_pipeline = my_pipeline.with_options( settings={"docker": docker_settings} ) ``` ### Enabling or Disabling Logs Storage You can control log storage for both pipeline runs and steps: #### Step Logs To disable storing step logs in your artifact store: 1. Using the `enable_step_logs` parameter with step decorator: ```python from zenml import step @step(enable_step_logs=False) # disables logging for this step def my_step() -> None: ... ``` 2. Setting the `ZENML_DISABLE_STEP_LOGS_STORAGE=true` environment variable in the execution environment: ```python from zenml import pipeline from zenml.config import DockerSettings docker_settings = DockerSettings(environment={"ZENML_DISABLE_STEP_LOGS_STORAGE": "true"}) # Either add it to the decorator @pipeline(settings={"docker": docker_settings}) def my_pipeline() -> None: my_step() # Or configure the pipelines options my_pipeline = my_pipeline.with_options( settings={"docker": docker_settings} ) ``` This environment variable takes precedence over the parameter mentioned above. #### Pipeline Run Logs To disable storing client-side pipeline run logs in your artifact store: 1. Using the `enable_pipeline_logs` parameter with pipeline decorator: ```python from zenml import pipeline @pipeline(enable_pipeline_logs=False) # disables client-side logging for this pipeline def my_pipeline(): ... ``` 2. Using the runtime configuration: ```python # Disable pipeline logs at runtime my_pipeline.with_options(enable_pipeline_logs=False) ``` 3. Setting the `ZENML_DISABLE_PIPELINE_LOGS_STORAGE=true` environment variable: ```python from zenml import pipeline from zenml.config import DockerSettings docker_settings = DockerSettings(environment={"ZENML_DISABLE_PIPELINE_LOGS_STORAGE": "true"}) # Either add it to the decorator @pipeline(settings={"docker": docker_settings}) def my_pipeline() -> None: my_step() # Or configure the pipelines options my_pipeline = my_pipeline.with_options( settings={"docker": docker_settings} ) ``` The environment variable takes precedence over parameters set in the decorator or runtime configuration. ### Setting Logging Verbosity Change the default logging level (`INFO`) with: ```bash export ZENML_LOGGING_VERBOSITY=INFO ``` Options: `INFO`, `WARN`, `ERROR`, `CRITICAL`, `DEBUG` For remote pipeline runs: ```python from zenml import pipeline from zenml.config import DockerSettings docker_settings = DockerSettings(environment={"ZENML_LOGGING_VERBOSITY": "DEBUG"}) # Either add it to the decorator @pipeline(settings={"docker": docker_settings}) def my_pipeline() -> None: my_step() # Or configure the pipelines options my_pipeline = my_pipeline.with_options( settings={"docker": docker_settings} ) ``` ### Setting Console Logging Format Change the console/stdout logging format with: ```bash export ZENML_CONSOLE_LOGGING_FORMAT=console ``` Options: - `console` (default): Human-readable console output. Client-side `INFO` logs use a compact layout, while `DEBUG` logs and server logs use a full structured text layout. - `json`: JSON formatted console/stdout logs. - Any other valid Python `%`-style logging format string, such as `%(asctime)s - %(message)s`, for custom console output. ```bash export ZENML_CONSOLE_LOGGING_FORMAT='%(asctime)s %(message)s' ``` The format must use `%`-string formatting style. See the [available LogRecord attributes](https://docs.python.org/3/library/logging.html#logrecord-attributes). This only changes terminal output; stored logs keep their raw message and structured metadata. {% hint style="warning" %} The older `ZENML_LOGGING_FORMAT` environment variable is deprecated and will be removed in a future version. Use `ZENML_CONSOLE_LOGGING_FORMAT` instead. Existing configurations such as `ZENML_LOGGING_FORMAT='%(asctime)s %(message)s'` continue to work during the deprecation period. {% endhint %} The compact client console layout is: ```text | [traceback and stack_info if any] ``` The full structured console layout for `DEBUG` logs is: ```text