Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
8599e96
Implement deployment settings
stefannica Oct 13, 2025
958bbce
Separated code into base abstraction and implementation classes
stefannica Oct 14, 2025
cf4d43d
Finalize implementation of the app runner and associated extensions
stefannica Oct 15, 2025
ddb368a
Use constants for URL paths
stefannica Oct 15, 2025
65c4de8
Small fixes and better docstrings
stefannica Oct 15, 2025
87e58f8
Reorganize the build logic to allow the app to be built last (for e.g…
stefannica Oct 16, 2025
b111ae9
Apply code review suggestions and fix docstrings
stefannica Oct 17, 2025
209752f
Implement deployment app runner flavor
stefannica Oct 17, 2025
bb149e7
Upgrade secure, remove deprecated XSSP and rework middleware adapter
stefannica Oct 17, 2025
35d7363
Fix linter errors
stefannica Oct 19, 2025
9094827
Fix secure headers and dashboard static files support
stefannica Oct 20, 2025
226ea0e
Reimplemented source or object class
stefannica Oct 20, 2025
f92f48d
Fix circular dependency
stefannica Oct 20, 2025
7a89cf1
Remove outdated unit tests
stefannica Oct 20, 2025
8dafdbf
Add custom UI to the weather agent example
stefannica Oct 21, 2025
79c9aa0
Fix unit tests remove outdated unit tests
stefannica Oct 21, 2025
8e9d78b
Refactor builder config to store extra requirements instead of the fu…
stefannica Oct 21, 2025
fd4ba49
Merge remote-tracking branch 'origin/develop' into feature/customizab…
stefannica Oct 21, 2025
2afe6cc
Fix new linter errors after merge with develop
stefannica Oct 21, 2025
2dd7173
Add documentation and a few minor tweaks
stefannica Oct 21, 2025
f9a0cd3
Linter fixes
stefannica Oct 21, 2025
05652fa
Doc updates
stefannica Oct 22, 2025
3345864
More doc updates
stefannica Oct 22, 2025
b014442
Merge branch 'develop' into feature/customizable-deployment-servers
stefannica Oct 22, 2025
a5a523b
More doc updates
stefannica Oct 22, 2025
1657bd6
Small fixes and documentation updates
stefannica Oct 23, 2025
73f9de3
Final doc updates and code modifications
stefannica Oct 23, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,6 @@ 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\_XXP**: The `X-XSS-Protection` HTTP header value. The default value is `0`. NOTE: this header is deprecated and should not be customized anymore. The `Content-Security-Policy` header should be used instead.
* **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`.
Expand Down
11 changes: 10 additions & 1 deletion docs/book/how-to/containerization/containerization.md
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,16 @@ you already want this automatic detection in current versions of ZenML, set `dis
docker_settings = DockerSettings(install_stack_requirements=False)
```

7. **Install Local Projects**:
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
Expand Down
113 changes: 101 additions & 12 deletions examples/weather_agent/pipelines/weather_agent.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"""Weather Agent Pipeline."""

import os
import time
from typing import Any, Dict

from pipelines.hooks import (
InitConfig,
Expand All @@ -10,16 +12,105 @@
from steps import analyze_weather_with_llm, get_weather

from zenml import pipeline
from zenml.config import DockerSettings
from zenml.config import DeploymentSettings, DockerSettings

# Import enums for type-safe capture mode configuration
from zenml.config.docker_settings import PythonPackageInstaller
from zenml.config.deployment_settings import (
EndpointMethod,
EndpointSpec,
MiddlewareSpec,
SecureHeadersConfig,
)
from zenml.config.resource_settings import ResourceSettings

docker_settings = DockerSettings(
requirements=["openai"],
prevent_build_reuse=True,
python_package_installer=PythonPackageInstaller.UV,
)


async def health_detailed() -> Dict[str, Any]:
"""Detailed health check with system metrics."""
import psutil

return {
"status": "healthy",
"cpu_percent": psutil.cpu_percent(),
"memory_percent": psutil.virtual_memory().percent,
"disk_percent": psutil.disk_usage("/").percent,
}


class RequestTimingMiddleware:
"""ASGI middleware to measure request processing time.

Uses the standard ASGI interface (scope, receive, send) which works
across all ASGI frameworks: FastAPI, Django, Starlette, Quart, etc.
"""

def __init__(self, app):
"""Initialize the middleware.

Args:
app: The ASGI application to wrap.
"""
self.app = app

async def __call__(self, scope, receive, send):
"""Process ASGI request with timing measurement.

Args:
scope: ASGI connection scope (contains request info).
receive: Async callable to receive ASGI events.
send: Async callable to send ASGI events.
"""
if scope["type"] != "http":
return await self.app(scope, receive, send)

start_time = time.time()

async def send_wrapper(message):
"""Intercept response to add timing header."""
if message["type"] == "http.response.start":
process_time = (time.time() - start_time) * 1000
headers = list(message.get("headers", []))
headers.append(
(
b"x-process-time-ms",
str(process_time).encode(),
)
)
message = {**message, "headers": headers}

await send(message)

await self.app(scope, receive, send_wrapper)


deployment_settings = DeploymentSettings(
custom_endpoints=[
EndpointSpec(
path="/health/detailed",
method=EndpointMethod.GET,
handler=health_detailed,
auth_required=False,
),
],
custom_middlewares=[
MiddlewareSpec(
middleware=RequestTimingMiddleware,
order=10,
),
],
dashboard_files_path="ui",
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'"
),
),
)

environment = {}
Expand All @@ -34,6 +125,10 @@
on_cleanup=cleanup_hook,
settings={
"docker": docker_settings,
"deployment": deployment_settings,
"deployer": {
"generate_auth_key": True,
},
"deployer.gcp": {
"allow_unauthenticated": True,
# "location": "us-central1",
Expand All @@ -54,14 +149,8 @@
)
def weather_agent(
city: str = "London",
) -> str:
"""Weather agent pipeline optimized for run-only serving.

Automatically uses run-only architecture for millisecond-class latency:
- Zero database writes
- Zero filesystem operations
- In-memory step output handoff
- Perfect for real-time inference
) -> tuple[Dict[str, float], str]:
"""Weather agent pipeline.

Args:
city: City name to analyze weather for
Expand All @@ -71,4 +160,4 @@ def weather_agent(
"""
weather_data = get_weather(city=city)
result = analyze_weather_with_llm(weather_data=weather_data, city=city)
return result
return weather_data, result
Loading
Loading