Skip to main content

Use Secrets and Pod Templates

Inject runtime secrets into task environments​

Use a TaskEnvironment to apply one secret request to every task it decorates. The request is a reference to a secret in the configured Flyte/Union secret backend; it is not the secret value itself.

import flyte

env = flyte.TaskEnvironment(
"wandb-example",
image=flyte.Image.from_debian_base().with_pip_packages("wandb"),
secrets="WANDB_API_KEY",
)

@env.task
async def log_result() -> str:
# The backend-provided value is available to the task under WANDB_API_KEY.
return "logged"

This is the pattern used in examples/context/wandb_context.py. Environment.secrets accepts a string, a Secret, or a list of strings and Secret objects. Runtime serialization calls secrets_from_request() and places the resulting entries in the task's FlyteIDL SecurityContext (src/flyte/_internal/runtime/task_serde.py, get_security_context).

Choose the environment-variable name explicitly​

A bare string is normalized to a Secret and, when no mount or explicit variable name is supplied, the key becomes an uppercase environment-variable name with hyphens replaced by underscores. A group is included in the derived name:

import flyte
from flyte.io import File

env = flyte.TaskEnvironment(
name="hf-raw-data-example",
image=flyte.Image.from_debian_base(name="hf-raw-data-example").with_pip_packages("huggingface_hub"),
secrets=[flyte.Secret(**{"key": "hf-token", "as_env_var": "HF_TOKEN"})],
)

This is the secret configuration from examples/advanced/hf_storage.py; HF_TOKEN is the name expected by the Hugging Face client. The equivalent defaulting behavior is visible in Secret.__post_init__():

  • Secret("my-secret") produces MY_SECRET.
  • Secret("api-key", "openai") produces OPENAI_API_KEY.
  • An explicit as_env_var must match ^[A-Z_][A-Z0-9_]*$. For example, as_env_var="invalid-name" raises ValueError; explicit lowercase names are also outside the accepted pattern.

The SDK sends group, key, the mount type, and as_env_var to the runtime. It does not store a credential value in Secret. Keep secret values in the configured backend rather than in Python source, ordinary env_vars, labels, or annotations.

Use the supported file mount​

For file-based access, provide exactly /etc/flyte/secrets as a pathlib.Path mount:

import pathlib

import flyte

GROUP = "arn:aws:secretsmanager:us-east-2:XXXXXXXXXX:secret"
KEY = "V2_SECRET_TEST"
SECRET_PATH = "/etc/flyte/secrets"

env = flyte.TaskEnvironment(
"secret-fun",
secrets=flyte.Secret(**{"group": GROUP, "key": KEY, "mount": pathlib.Path(SECRET_PATH)}),
)

@env.task
def main() -> str:
return pathlib.Path(f"{SECRET_PATH}/{GROUP}/{KEY.lower()}").read_text()

This is examples/basics/secret_file_group.py. When mount is supplied and as_env_var is omitted, Secret.__post_init__() does not auto-create an environment variable, so get_security_context() emits a file mount. The example's group/key path reflects the layout used by that example and backend/runtime behavior; the Secret class itself only records the mount request.

Only pathlib.Path("/etc/flyte/secrets") is accepted today. For example, pathlib.Path("/tmp/secrets") raises ValueError. The source contains TODOs for arbitrary mount paths, secret versioning, and broader secret-group support, so those are not portable Secret features in the current SDK.

Customize the Kubernetes task pod​

Construct a Kubernetes V1PodSpec, ensure it contains the primary container, and wrap it in flyte.PodTemplate. The following complete pattern is from examples/advanced/pod_template.py:

from kubernetes.client import (
V1Container,
V1EnvVar,
V1LocalObjectReference,
V1PodSpec,
)

import flyte

pod_template = flyte.PodTemplate(
primary_container_name="primary",
labels={"lKeyA": "lValA"},
annotations={"aKeyA": "aValA"},
pod_spec=V1PodSpec(
containers=[V1Container(name="primary", env=[V1EnvVar(name="hello", value="world")])],
image_pull_secrets=[V1LocalObjectReference(name="regcred-test")],
),
)

env = flyte.TaskEnvironment(
name="hello_world",
pod_template=pod_template,
image=flyte.Image.from_uv_script(__file__, name="flyte"),
)

@env.task
async def say_hello(data: str) -> str:
return f"Hello {data}"

PodTemplate has four fields: pod_spec, primary_container_name (defaulting to "primary"), and optional labels and annotations. During task serialization, labels and annotations become FlyteIDL K8sObjectMetadata. The Kubernetes client is needed for real V1PodSpec objects; PodTemplate.to_k8s_pod() serializes the model with ApiClient().sanitize_for_serialization().

Name a different primary container​

If the pod uses a non-default primary container name, set the same name in both places:

from kubernetes.client import V1Container, V1PodSpec

import flyte

pod_template = flyte.PodTemplate(
primary_container_name="worker",
pod_spec=V1PodSpec(containers=[V1Container(name="worker")]),
)

env = flyte.TaskEnvironment(name="worker_env", pod_template=pod_template)

Serialization searches pod_spec.containers for a container whose name equals primary_container_name. If no such container exists, it raises ValueError with a message requiring that primary-container name. A template containing only a container named foo therefore fails with the default primary setting.

Flyte merges task execution settings into a deep copy of the template rather than using the primary container completely verbatim:

  • If the template primary container has no image, Flyte copies in the task image; an existing template image is retained.
  • Flyte always replaces the primary container's command and arguments with the task command and arguments.
  • Non-empty task resource limits and requests replace the primary container's resource requirements. Empty task requirements do not replace them.
  • Task environment variables are prepended to the primary container's existing environment list.
  • Other containers and pod-spec fields, such as sidecars, volumes, volume mounts, and image-pull secrets, remain part of the serialized pod.

PodTemplate.to_k8s_pod() itself lazily replaces a None pod_spec with an empty Kubernetes V1PodSpec; it is a mutable dataclass. Runtime task serialization deep-copies the template before applying the merge.

Select the template at the right level​

Environment.pod_template accepts either an inline PodTemplate or a string reference to a named server-side template:

import flyte

env = flyte.TaskEnvironment(
name="hello_world",
pod_template="shared-kubernetes-template",
)

A PodTemplate object is serialized inline as TaskTemplate.k8s_pod. A string is not parsed as Kubernetes YAML; it is serialized as RuntimeMetadata.pod_template_name.

TaskEnvironment.task() also accepts pod_template=.... The task value takes precedence over the environment value: the task template construction uses the task argument first and falls back to self.pod_template. The task-template API and task.override() support task-specific pod-template configuration in addition to the environment default.

There is one explicit restriction: if the environment has reusable set, passing a pod template to @env.task(...) raises ValueError ("Cannot set pod_template when environment is reusable."). Reusable execution also restricts overriding environment resources, env_vars, and secrets unless the same task.override() call disables reuse with reusable="off".

Validate the environment before deployment​

TaskEnvironment runs Environment.__post_init__() and then its own validation. Common failures are:

SymptomCurrent SDK rule
Secret(..., as_env_var="invalid-name") raises ValueErrorExplicit names must match ^[A-Z_][A-Z0-9_]*$.
Secret(..., mount=pathlib.Path("/tmp/secrets")) raises ValueErrorThe only supported mount path is /etc/flyte/secrets.
Pod serialization raises No primary container defined...Add a V1Container whose name matches primary_container_name.
An environment name such as "My Env", "bad!", or "" failsNames must match the lowercase snake_case/kebab-case form ^[a-z0-9]+([_-][a-z0-9]+)*$.
A task cannot retrieve a requested secretSecret contains only backend lookup metadata; the corresponding value must exist in the configured secret backend.
An image build cannot resolve a build secretImage-build handling looks up an uppercased group/key environment variable and then /etc/secrets/{group}_{key}; neither source is supplied by Secret itself.

Do not confuse image_pull_secrets in a V1PodSpec with flyte.Secret: the former is a Kubernetes pod reference such as V1LocalObjectReference(name="regcred-test"), while the latter becomes a task security-context request for a runtime secret.

How the settings reach FlyteIDL​

The runtime boundary is src/flyte/_internal/runtime/task_serde.py. get_security_context() normalizes a string, one Secret, or a list through secrets_from_request() and emits security_pb2.Secret entries. Task serialization attaches that result to TaskTemplate.security_context.

For a non-string PodTemplate, get_proto_task() calls _get_k8s_pod(), which validates and merges the named primary container before serializing the Kubernetes model. The resulting labels and annotations populate the FlyteIDL pod metadata. For a string template, serialization instead records the named template reference in runtime metadata. This distinction determines whether the Kubernetes specification travels inline with the task or is resolved as a server-side template.