Runtime Environments¶
Ray RuntimeEnv lets one Ray cluster execute tasks with different Python packages, uploaded project code, environment variables, or container images. django-ray adds named profiles and a durable environment identity on top of Ray's native feature.
Cold-start warning: The first task using a new profile can be substantially slower while Ray downloads code and creates the environment on the selected node. RuntimeEnv caches are node-local, so the first use on every node is cold; later executions of the identical environment on a warm node are usually much faster.
RuntimeEnv Is More Than pip¶
django-ray passes the resolved profile to Ray as a native RuntimeEnv mapping. Use the environment mechanism that fits the workload:
| Field | Best suited to |
|---|---|
working_dir |
Shipping an application directory or remote ZIP archive |
py_modules |
Adding reusable modules, wheels, or remote archives to PYTHONPATH |
pip |
Installing Python packages with pip |
uv |
Installing Python packages with uv |
conda |
Selecting a named Conda environment or defining one inline |
env_vars |
Setting non-secret worker environment variables |
image_uri / container |
Running workers in a prebuilt container image |
py_executable |
Selecting an alternate Python executable or launcher |
config |
Controlling setup timeout and other RuntimeEnv behavior |
See Ray's complete RuntimeEnv API reference for supported fields, value formats, and version-specific constraints.
For example, profiles can use uv or Conda without changing django-ray's task API:
DJANGO_RAY = {
"RUNTIME_ENV_PROFILES": {
"analytics-uv": {
"working_dir": "s3://deployments/analytics/7f3a2c1.zip",
"uv": ["numpy==2.3.5", "polars==1.31.0"],
"env_vars": {"DJANGO_SETTINGS_MODULE": "config.settings"},
},
"analytics-conda": {
"working_dir": "s3://deployments/analytics/7f3a2c1.zip",
"conda": {
"channels": ["conda-forge"],
"dependencies": ["python=3.12", "numpy=2.3"],
},
"env_vars": {"DJANGO_SETTINGS_MODULE": "config.settings"},
},
},
}
Ray does not allow top-level pip and conda in the same RuntimeEnv. Its
container-based fields also have stricter combination rules: image_uri must
generally be used alone except for env_vars and config, so application code
and dependencies should already be present in that image. Consult the linked Ray
reference when mixing fields.
Define Profiles¶
Profiles live in DJANGO_RAY. A profile can be a direct Ray RuntimeEnv mapping:
# settings.py
import os
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
DJANGO_RAY = {
"RAY_ADDRESS": "ray://ray-head-svc:10001",
"RUNTIME_ENV_PROFILES": {
"project": {
"working_dir": os.environ.get(
"DJANGO_RAY_WORKING_DIR_URI",
str(BASE_DIR),
),
"excludes": [".git", ".venv"],
"pip": ["django>=6.0", "psycopg[binary]>=3.1"],
"env_vars": {
"DJANGO_SETTINGS_MODULE": "config.settings",
"PYTHONPATH": "src",
},
},
},
"DEFAULT_RUNTIME_ENV_PROFILE": "project",
}
Profiles may extend another profile. Dictionary fields such as env_vars are
merged; list fields pip, uv, py_modules, and excludes are appended.
Other fields are replaced:
DJANGO_RAY = {
"RAY_ADDRESS": "ray://ray-head-svc:10001",
"RUNTIME_ENV_PROFILES": {
"project": {
"working_dir": "s3://deployments/myapp/7f3a2c1.zip",
"pip": ["django>=6.0"],
"env_vars": {"DJANGO_SETTINGS_MODULE": "config.settings"},
},
"numpy-2-2": {
"extends": "project",
"runtime_env": {
"pip": ["numpy==2.2.6"],
"env_vars": {"APP_VARIANT": "numpy-2-2"},
},
},
"numpy-2-3": {
"extends": "project",
"runtime_env": {
"pip": ["numpy==2.3.5"],
"env_vars": {"APP_VARIANT": "numpy-2-3"},
},
},
},
}
Pin production dependencies and use immutable archive URIs for working_dir or
py_modules when reproducibility matters.
- Local mode can content-address and upload a local directory.
- Ray Job submission can upload its job-level local working directory.
- Ray Client (
ray://...) cannot turn a task-level local path from the Django pod into a remote RuntimeEnv. Usehttps://,s3://, orgs://, or afile://ZIP on storage mounted at the same path on every Ray node.
Local py_modules directories and files remain available to dynamic Ray tasks, but
version 1 rejects them for reusable strategies. Their import basename changes Python
semantics, while Ray 2.56's package URI is not a strong identity for every such archive.
The plan fingerprints the basename and content so retry pinning remains conservative;
prefer an independently verified immutable artifact before enabling future reuse.
Do not point a standalone Django pod at the head node's GCS port as a substitute for a
Ray node. A direct Ray Core driver also expects a local raylet. The repository's
KubeRay overlay demonstrates the shared file:///runtime-env/django-ray-source.zip
pattern for local testing.
Select a Profile for a Django Task¶
Django Tasks supports backend aliases. Bind each alias to one trusted profile:
TASKS = {
"default": {
"BACKEND": "django_ray.backends.RayTaskBackend",
"QUEUES": ["default"],
"OPTIONS": {"RUNTIME_ENV_PROFILE": "project"},
},
"numpy-2-3": {
"BACKEND": "django_ray.backends.RayTaskBackend",
"QUEUES": ["default"],
"OPTIONS": {"RUNTIME_ENV_PROFILE": "numpy-2-3"},
},
}
Select it with the standard Django API:
# myapp/tasks.py
from django.tasks import task
@task(queue_name="default")
def average(values: list[float]) -> float:
import numpy
return float(numpy.mean(values))
result = average.using(backend="numpy-2-3").enqueue([1.0, 2.0, 3.0])
The backend resolves the profile during enqueue and stores its canonical JSON and
SHA-256 identity on RayTaskExecution. Retries use that immutable snapshot even
if Django settings change after enqueue.
RAY_RUNTIME_ENV remains supported as the unnamed default. A backend may also
provide an inline RAY_RUNTIME_ENV, but it cannot combine that option with
RUNTIME_ENV_PROFILE.
Select a Profile for a Workflow Step¶
Workflow leaves inherit the outer task environment unless they request another:
from django_ray.workflows import chain, step
def load_rows(values: list[float]) -> list[float]:
return values
def run_numpy_model(values: list[float]) -> dict[str, float]:
import numpy
return {"mean": float(numpy.mean(values))}
def store_summary(summary: dict[str, float]) -> dict[str, float]:
return summary
pipeline = chain(
step(load_rows),
step(run_numpy_model, runtime_env="numpy-2-3"),
step(store_summary),
)
Inline environments are also accepted:
def column_names(rows: list[dict[str, object]]) -> list[str]:
import pandas
return [str(name) for name in pandas.DataFrame(rows).columns]
step(
column_names,
runtime_env={"pip": ["pandas==2.3.0"]},
)
Use signature.with_runtime_env("profile-name") when constructing a workflow
dynamically. The older ray_options={"runtime_env": ...} form remains compatible.
RuntimeEnv identity in effective workflow plans¶
Named and inline step environments are resolved when WorkflowSignature.run() creates
its effective plan, before the first workflow leaf is submitted. Changing a package,
archive, image, working-directory snapshot, or named profile content therefore changes
the plan identity or produces an explicit reusable-strategy rejection. Mutating Django
settings after materialization cannot change the environment submitted by that run.
Credential variable values and URI credentials never enter the plan and are never
hashed into its fingerprint. Credential-looking names remain visible as schema
metadata and are covered by a non-secret provider revision. Ordinary environment
values such as MODE are also kept out of the fingerprint because a variable name is
not proof that its value is non-secret. Reusable strategies require one declared
environment_revision that covers the full non-secret configuration contract:
DJANGO_RAY = {
"WORKFLOW_PLAN_CODE_REVISION": "container:sha256:0123456789abcdef",
"WORKFLOW_PLAN_TRUST_IDENTITY": {
"trust_domain": "cluster:production",
"credential_provider": "kubernetes-service-account",
"credential_revision": "provider-v3",
"environment_revision": "namespace-sync-v8",
"scheduling_revision": "placement-v2",
"service_account_audience": "kubernetes.default.svc",
},
}
credential_revision identifies the provider contract, not a token. Rotating a token
inside the same declared provider revision does not churn a plan or leak a secret-derived
digest. environment_revision is an operator promise to change the revision whenever
any covered ordinary runtime value changes. Changing either revision, the provider,
trust domain, or audience invalidates the plan.
If an environment has secret-dependent behavior but no safe provider revision, dynamic
Ray tasks remain available and reusable strategies receive UNRESOLVED_RUNTIME_ENV.
Ray label selectors use the separate scheduling_revision; dashboard names and task
labels are execution-only annotations and never enter the plan fingerprint.
The identity transported to a Ray worker is a strict versioned envelope containing only
bounded diagnostics and digests of the safe projection. The full RuntimeEnv and its
secret-bearing execution values travel only through Ray's execution channel. Local
per-step code paths are snapshotted with Ray's packaging semantics and rebound before
the first leaf is submitted; fenced durable plans are pinned before package upload so
stale attempts cannot create artifacts. A source change during packaging fails before
leaf submission. Ray's current local-package cache key is not a strong end-to-end
verification of django-ray's SHA-256 snapshot, so local working_dir and py_modules
inputs remain dynamic-only and receive UNRESOLVED_RUNTIME_ENV until a worker-verifiable
artifact identity is available.
Reusable-strategy eligibility and durable retry safety are separate decisions. A local file or tree snapshot is not reusable because Ray does not yet verify django-ray's content digest end to end, but it is safe to retry after django-ray rechecks the same content before packaging. Conversely, a mutable package pin or opaque URI remains valid for dynamic execution yet is retry-unsafe: its raw value is intentionally absent from the secret-free plan, so a later attempt cannot prove that the execution binding stayed the same. The effective plan persists bounded retry-safety paths and the attempt that first pinned it; later attempts fail before submission when any binding is unsafe. Provider and environment revisions restore retry safety only for the values their documented contracts cover, without hashing raw credentials.
Caching and Performance¶
Ray caches RuntimeEnv artifacts on each node. Reusing identical canonical specs allows later tasks to avoid most environment setup work. django-ray's environment hash makes this identity visible in the database and admin.
Keep the number of environment variants bounded:
- prefer named, stable profiles over a unique inline environment per task;
- group related work under one outer task or actor when it can reuse an environment;
- compare the first run with a repeated run before drawing latency conclusions;
- prebuild system libraries and very large common dependencies into the base image.
RuntimeEnv removes application-image rebuilds; it does not make dependency installation free.
The cache is node-local. The first fan-out across four cold nodes may install the same environment four times in parallel. Warm each node before a latency-sensitive rollout, or keep large, common dependencies in the base image. See Performance.
Generic KubeRay Images¶
The KubeRay example uses the upstream rayproject/ray image for Ray head and
worker containers, plus a stock Python image for its dashboard-import helper.
The project profile uploads source and installs Python dependencies. The Django
web and task-manager images remain application-specific.
For Ray Client submissions, django-ray serializes only its small outer bootstrap executor by value. The generic head can therefore deserialize the submission before the task-level RuntimeEnv installs and exposes the full project package.
This separation works well for a shared cluster within one trust boundary:
- The Django task manager resolves a named profile at enqueue time.
- The task row stores the resolved profile snapshot and its content hash.
- The generic Ray cluster creates that environment on the node selected for work.
- That node can reuse its cached copy for later tasks with the same environment.
Mount credentials, certificates, and shared data through the cluster deployment.
Do not put secrets in profile URIs or env_vars: the resolved RuntimeEnv is stored
on the task row and is visible to operators.
RuntimeEnv is packaging and dependency isolation, not a security boundary. Use separate Ray clusters for mutually untrusted teams or workloads.
Test Project¶
The sample project defines project, thin, numpy-2-2, and numpy-2-3
profiles and exposes:
POST /api/cluster/runtime-env/probe?profile=thin
POST /api/cluster/runtime-env/probe?profile=numpy-2-3&package=numpy
POST /api/cluster/runtime-env/benchmark?profile=numpy-2-3&package=numpy&repeats=3
GET /api/cluster/runtime-env/{task_id}
The benchmark runs repeated workflow leaves with the same profile and reports per-run elapsed time so cold setup and cache reuse are easy to compare.