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")producesMY_SECRET.Secret("api-key", "openai")producesOPENAI_API_KEY.- An explicit
as_env_varmust match^[A-Z_][A-Z0-9_]*$. For example,as_env_var="invalid-name"raisesValueError; 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:
| Symptom | Current SDK rule |
|---|---|
Secret(..., as_env_var="invalid-name") raises ValueError | Explicit names must match ^[A-Z_][A-Z0-9_]*$. |
Secret(..., mount=pathlib.Path("/tmp/secrets")) raises ValueError | The 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 "" fails | Names must match the lowercase snake_case/kebab-case form ^[a-z0-9]+([_-][a-z0-9]+)*$. |
| A task cannot retrieve a requested secret | Secret contains only backend lookup metadata; the corresponding value must exist in the configured secret backend. |
| An image build cannot resolve a build secret | Image-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.