Image, resources, caching, secrets, and scheduling move from the task decorator to the environment.

Task configuration

In Flyte 1, image, resources, caching, secrets, and scheduling were configured per-task on the @task decorator or per-workflow on a LaunchPlan. In Flyte 2 most of this moves to the flyte.TaskEnvironment, so it’s declared once and shared. See Migration for the overall approach.

Image, resources, and caching

Image, resources, and caching move from the @task decorator to the TaskEnvironment. Per-task settings like retries and timeout stay on @env.task. Note that mem is renamed to memory, and there are no separate requests/limits — a single Resources value serves as both.

Flyte 1Flyte 2
task_config_v1.py
from datetime import timedelta

import flytekit
from flytekit import Resources

image = flytekit.ImageSpec(
    name="training-image",
    packages=["scikit-learn", "pandas"],
)

@flytekit.task(
    container_image=image,
    requests=Resources(cpu="2", mem="4Gi"),
    limits=Resources(cpu="4", mem="8Gi"),
    cache=True,
    cache_version="1.0",
    retries=3,
    timeout=timedelta(minutes=30),
)
def train_epoch(step: int) -> float:
    # A stand-in for a training step that returns the current loss.
    return 1.0 / (step + 1)

@flytekit.workflow
def main(step: int) -> float:
    return train_epoch(step=step)
task_config_v2.py
from datetime import timedelta

import flyte

# Image, resources, and caching move to the TaskEnvironment, so they are declared
# once and shared by every task in the environment.
env = flyte.TaskEnvironment(
    name="training",
    image=flyte.Image.from_debian_base().with_pip_packages("scikit-learn", "pandas"),
    resources=flyte.Resources(cpu="2", memory="4Gi"),  # "memory", not "mem"
    cache="auto",
)

# retries and timeout stay on the task decorator.
@env.task(retries=3, timeout=timedelta(minutes=30))
def train_epoch(step: int) -> float:
    # A stand-in for a training step that returns the current loss.
    return 1.0 / (step + 1)

@env.task
def main(step: int) -> float:
    return train_epoch(step)

For the full @task → TaskEnvironment + @env.task parameter mapping, see Tasks and workflows.

Container images

Flyte 1’s ImageSpec is replaced by Flyte 2’s flyte.Image with a fluent builder API.

from flyte import Image

image = (
    Image.from_debian_base(name="my-image", registry="ghcr.io/myorg", python_version=(3, 11))
    .with_pip_packages("pandas", "numpy")
    .with_apt_packages("curl", "git")
    .with_env_vars({"MY_VAR": "value"})
)

Instead of one constructor with many arguments, you start from a base and chain builder methods:

Constructor Use case
Image.from_debian_base() Most common; includes the Flyte SDK
Image.from_base(image_uri) Start from any existing image
Image.from_dockerfile(path) Complex custom builds
Image.from_uv_script(path) UV-based projects

Common chainable builder methods: .with_pip_packages(...), .with_requirements(path), .with_uv_project(path), .with_apt_packages(...), .with_commands([...]), .with_source_file(path, dst=...), .with_source_folder(path, dst=...), .with_env_vars({...}), and .with_workdir(...).

Flyte 1 ImageSpec Flyte 2 Image Notes
name name (constructor) Same
registry registry (constructor) Same
python_version python_version (tuple) "3.11" becomes (3, 11)
packages .with_pip_packages() Method instead of param
apt_packages .with_apt_packages() Method instead of param
requirements .with_requirements() Supports txt, poetry.lock, uv.lock
env .with_env_vars() Method instead of param
commands .with_commands() Method instead of param
copy / source_root .with_source_file() / .with_source_folder() More explicit methods
base_image Image.from_base() Different constructor
builder Config file or flyte.init() Global setting
platform platform (constructor) Tuple: ("linux/amd64", "linux/arm64")

For a private registry, create an image-pull secret and reference it:

flyte create secret --type image_pull my-registry-secret --from-file ~/.docker/config.json
image = Image.from_debian_base(
    registry="private.registry.com",
    name="my-image",
    registry_secret="my-registry-secret",
)

See Container images for more.

Resources

A single flyte.Resources value serves as both request and limit — there are no separate requests/limits. Several parameters were renamed.

Flyte 1 Flyte 2 Notes
cpu="1" cpu="1" Same
mem="2Gi" memory="2Gi" Renamed
gpu="1" gpu="A100:1" Type:count format
ephemeral_storage="10Gi" disk="10Gi" Renamed
N/A shm="auto" New: shared memory

GPU type and count are combined into one string, replacing the separate Flyte 1 accelerator= argument:

env = flyte.TaskEnvironment(
    name="gpu_env",
    resources=flyte.Resources(
        cpu="4",
        memory="32Gi",
        gpu="A100:2",              # Type:count
        # gpu="A100_80G:1"         # 80GB variant (flyte >= 2.11.3; earlier: "A100 80G:1")
        # gpu=flyte.GPU("A100", quantity=1, partition="1g.5gb")   # MIG partition
    ),
)

Supported GPU types include A10, A10G, A100, A100_80G, B200, H100, H200, L4, L40s, T4, V100, RTX_PRO_4500, RTX_PRO_6000, and GB10. The underscore names need flyte 2.11.3 or later; earlier releases use A100 80G and RTX PRO 6000. See Resources for more.

Caching

Caching is enabled at the env level with cache="auto" (or per-task on @env.task). The explicit cache_version string moves into a flyte.Cache object.

Behavior Description
"auto" Cache results and reuse if available
"override" Always execute and overwrite the cache
"disable" No caching (default for a TaskEnvironment)
# Flyte 1: @task(cache=True, cache_version="1.0")
# Flyte 2:
@env.task(cache="auto")
def cached_task(x: int) -> int:
    return x * 2

# Advanced control (replaces cache_version, serialize, ignored_inputs, ...)
@env.task(cache=flyte.Cache(
    behavior="auto",
    version_override="v1.0",
    serialize=True,
    ignored_inputs=("debug",),
))
def advanced(x: int, debug: bool = False) -> int:
    return x * 2

See Caching for more.

Secrets

Secrets move from secret_requests on the task to secrets on the TaskEnvironment, and you read them from environment variables instead of current_context().secrets — for example, an API key for a model registry or hosted LLM.

Flyte 1Flyte 2
secrets_v1.py
from flytekit import task, workflow, Secret, current_context

@task(secret_requests=[Secret(group="openai", key="api_key")])
def call_api() -> str:
    token = current_context().secrets.get(group="openai", key="api_key")
    return f"token has {len(token)} chars"

@workflow
def main() -> str:
    return call_api()
secrets_v2.py
import os

import flyte

# Secrets are declared on the TaskEnvironment and injected as environment
# variables (instead of read through current_context().secrets).
env = flyte.TaskEnvironment(
    name="secrets",
    secrets=[flyte.Secret(key="openai_api_key", as_env_var="OPENAI_API_KEY")],
)

@env.task
def call_api() -> str:
    token = os.getenv("OPENAI_API_KEY", "")
    return f"token has {len(token)} chars"

@env.task
def main() -> str:
    return call_api()

A flyte.Secret can be mounted as an environment variable or as a file, and the access convention changes:

flyte.Secret(key="openai-key", as_env_var="OPENAI_API_KEY")   # mount as env var
flyte.Secret(key="access-key", group="aws")                    # env var: AWS_ACCESS_KEY
flyte.Secret(key="ssl-cert", mount="/etc/flyte/secrets")       # mount as a file
Flyte 1 pattern Flyte 2 pattern
ctx.secrets.get(key="mykey", group="mygroup") os.environ["MYGROUP_MYKEY"] (auto-named)
ctx.secrets.get(key="mykey") os.environ["MY_SECRET"] (with as_env_var="MY_SECRET")

Create and manage secrets from the CLI:

flyte create secret MY_SECRET_KEY --value my_secret_value
flyte create secret MY_SECRET_KEY --from-file /path/to/secret
flyte get secret
flyte delete secret MY_SECRET_KEY

See Secrets for more.

Scheduling

A LaunchPlan with a CronSchedule (say, a nightly retraining job) becomes a flyte.Trigger attached directly to the task. Use flyte.TriggerTime to bind the scheduled fire time to an input, and deploy the trigger with flyte deploy.

Flyte 1Flyte 2
scheduling_v1.py
from flytekit import task, workflow, LaunchPlan, CronSchedule

@task
def retrain(kickoff_time: str) -> str:
    return f"retrained model at {kickoff_time}"

@workflow
def main(kickoff_time: str) -> str:
    return retrain(kickoff_time=kickoff_time)

# A LaunchPlan attaches a schedule (and default inputs) to a workflow.
nightly_retrain = LaunchPlan.get_or_create(
    workflow=main,
    name="nightly_retrain",
    schedule=CronSchedule(
        schedule="0 2 * * *",  # 2 AM daily
        kickoff_time_input_arg="kickoff_time",
    ),
)
scheduling_v2.py
from datetime import datetime

import flyte

env = flyte.TaskEnvironment(name="scheduling")

# A Trigger replaces LaunchPlan + CronSchedule. It is attached directly to the
# task and deployed with it (flyte deploy). flyte.TriggerTime binds the
# scheduled fire time to a task input.
nightly_retrain = flyte.Trigger.minutely(
    name="nightly_retrain",
    inputs={"trigger_time": flyte.TriggerTime},
    auto_activate=True,
)

@env.task(triggers=nightly_retrain)
def main(trigger_time: datetime = datetime(2024, 1, 1, 2, 0)) -> str:
    return f"retrained model at {trigger_time.isoformat()}"

Triggers support flyte.Cron("0 9 * * *", timezone="America/New_York") and flyte.FixedRate(timedelta(hours=1)) as automations, plus convenience constructors like flyte.Trigger.hourly() and flyte.Trigger.daily(). See Triggers for more.

Next