Skip to content
Open
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
100 changes: 90 additions & 10 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,96 @@
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,
)
from zenml.config.resource_settings import ResourceSettings
from zenml.config.source import SourceOrObject

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=SourceOrObject(health_detailed),
auth_required=False,
),
],
custom_middlewares=[
MiddlewareSpec(
middleware=SourceOrObject(RequestTimingMiddleware),
order=10,
),
],
)

environment = {}
Expand All @@ -34,6 +116,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 @@ -55,13 +141,7 @@
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
"""Weather agent pipeline.

Args:
city: City name to analyze weather for
Expand Down
4 changes: 0 additions & 4 deletions helm/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1004,10 +1004,6 @@ zenml:
hsts: enabled
# The `X-Frame-Options` HTTP header value. The default value is `SAMEORIGIN`.
xfo: enabled
# 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.
xxp: enabled
# The `X-Content-Type-Options` HTTP header value. The default value is
# `nosniff`.
content: enabled
Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ zenml = "zenml.cli.cli:cli"
[project.optional-dependencies]
local = [
"alembic>=1.8.1,<=1.15.2",
"asgiref~=3.10.0",
"bcrypt==4.0.1",
"passlib[bcrypt]~=1.7.4",
"pymysql>=1.1.1,~=1.1.0",
Expand All @@ -72,7 +73,7 @@ server = [
"orjson~=3.10.0",
"Jinja2",
"ipinfo>=4.4.3",
"secure~=0.3.0",
"secure~=1.0.1",
"tldextract~=5.1.0",
"itsdangerous~=2.2.0",
]
Expand Down
2 changes: 2 additions & 0 deletions src/zenml/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# or implied. See the License for the specific language governing
# permissions and limitations under the License.
"""Config classes."""
from zenml.config.deployment_settings import DeploymentSettings
from zenml.config.docker_settings import (
DockerSettings,
PythonPackageInstaller,
Expand All @@ -24,6 +25,7 @@
from zenml.config.cache_policy import CachePolicy

__all__ = [
"DeploymentSettings",
"DockerSettings",
"PythonPackageInstaller",
"PythonEnvironmentExportMethod",
Expand Down
5 changes: 5 additions & 0 deletions src/zenml/config/build_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

from pydantic import BaseModel

from zenml.config.deployment_settings import DeploymentSettings
from zenml.config.docker_settings import DockerSettings
from zenml.logger import get_logger
from zenml.utils import json_utils
Expand All @@ -36,13 +37,15 @@ class BuildConfiguration(BaseModel):
Attributes:
key: The key to store the build.
settings: Settings for the build.
deployment_settings: Deployment settings for the build.
step_name: Name of the step for which this image will be built.
entrypoint: Optional entrypoint for the image.
extra_files: Extra files to include in the Docker image.
"""

key: str
settings: DockerSettings
deployment_settings: Optional[DeploymentSettings] = None
step_name: Optional[str] = None
entrypoint: Optional[str] = None
extra_files: Dict[str, str] = {}
Expand Down Expand Up @@ -73,6 +76,7 @@ def compute_settings_checksum(
default=json_utils.pydantic_encoder,
)
hash_.update(settings_json.encode())

if self.entrypoint:
hash_.update(self.entrypoint.encode())

Expand All @@ -93,6 +97,7 @@ def compute_settings_checksum(
stack=stack,
code_repository=code_repository if pass_code_repo else None,
log=False,
deployment_settings=self.deployment_settings,
)
)
for _, requirements, _ in requirements_files:
Expand Down
1 change: 1 addition & 0 deletions src/zenml/config/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@

DOCKER_SETTINGS_KEY = "docker"
RESOURCE_SETTINGS_KEY = "resources"
DEPLOYMENT_SETTINGS_KEY = "deployment"
Loading
Loading