Skip to main content

Configuring Container Arguments for Remote Execution

When a task is scheduled for execution on a remote Flyte cluster, the control plane needs an exact specification of the container runtime: the entrypoint binary, input/output paths, code distribution bundles, image identifiers, and task loader directives. In flyte-sdk, task templates translate high-level Python functions or container commands into these CLI tokens and protobuf definitions during serialization.

Serialization Architecture and SerializationContext

During compilation and deployment, flyte-sdk converts task instances into protobuf tasks_pb2.Container specifications using SerializationContext (defined in flyte.models). Rather than hardcoding absolute remote storage locations or run identifiers at compilation time, flyte-sdk inserts Flyte Propeller template variables (such as {{.input}} and {{.outputPrefix}}). When Kubernetes pods or execution engines launch on the Flyte backend, Propeller substitutes these template tokens with concrete execution URIs.

from flyte.models import CodeBundle, ImageCache, SerializationContext

# SerializationContext supplies runtime placeholders and bundle metadata
sctx = SerializationContext(
version="v1.0.0",
project="my-project",
domain="development",
input_path="{{.input}}",
output_path="{{.outputPrefix}}",
code_bundle=CodeBundle(tgz="s3://my-bucket/code.tar.gz", destination="."),
)

Inside flyte._internal.runtime.task_serde, the serialization pipeline builds the protobuf container definition:

# flyte-sdk sets an empty command list and relies on container args for execution directives
tasks_pb2.Container(
image=img_uri,
command=[],
args=task_template.container_args(serialize_context),
resources=resources,
env=env,
data_config=task_template.data_loading_config(serialize_context),
config=task_template.config(serialize_context),
)

Standard Python Task Argument Generation

For tasks defined with @task or by subclassing AsyncFunctionTaskTemplate (in flyte.extend), remote execution is managed through the runtime CLI entrypoint a0. The task template's container_args(serialize_context) method generates the full CLI invocation passed to the primary container.

from flyte.extend import AsyncFunctionTaskTemplate
from flyte.models import SerializationContext

# AsyncFunctionTaskTemplate constructs a structured CLI invocation for the `a0` runtime entrypoint
template: AsyncFunctionTaskTemplate = ...
args = template.container_args(sctx)

Generated CLI Structure

AsyncFunctionTaskTemplate.container_args generates the command tokens as follows:

def container_args(self, serialize_context: SerializationContext) -> List[str]:
args = [
"a0",
"--inputs",
serialize_context.input_path,
"--outputs-path",
serialize_context.output_path,
"--version",
serialize_context.version,
"--raw-data-path",
"{{.rawOutputDataPrefix}}",
"--checkpoint-path",
"{{.checkpointOutputPrefix}}",
"--prev-checkpoint",
"{{.prevCheckpointPrefix}}",
"--run-name",
"{{.runName}}",
"--name",
"{{.actionName}}",
]
# Image cache resolution
if serialize_context.image_cache and serialize_context.image_cache.serialized_form:
args = [*args, "--image-cache", serialize_context.image_cache.serialized_form]
elif serialize_context.image_cache:
args = [*args, "--image-cache", serialize_context.image_cache.to_transport]

# Code distribution bundles
if serialize_context.code_bundle:
if serialize_context.code_bundle.tgz:
args = [*args, *["--tgz", f"{serialize_context.code_bundle.tgz}"]]
elif serialize_context.code_bundle.pkl:
args = [*args, *["--pkl", f"{serialize_context.code_bundle.pkl}"]]
args = [*args, *["--dest", f"{serialize_context.code_bundle.destination or '.'}"]]

# Task resolution loader
if not serialize_context.code_bundle or not serialize_context.code_bundle.pkl:
from flyte._internal.resolvers.default import DefaultTaskResolver

_task_resolver = DefaultTaskResolver()
args = [
*args,
*[
"--resolver",
_task_resolver.import_path,
*_task_resolver.loader_args(task=self, root_dir=serialize_context.root_dir),
],
]

assert all(isinstance(item, str) for item in args), f"All args should be strings, non string item = {args}"
return args

Argument Categories and Flags

Argument / FlagValue SourcePurpose
a0Static binary nameRuntime CLI entrypoint in the Flyte container.
--inputsserialize_context.input_pathInput literal protobuf location (default: {{.input}}).
--outputs-pathserialize_context.output_pathOutput directory destination (default: {{.outputPrefix}}).
--versionserialize_context.versionTask serialization version string.
--raw-data-path{{.rawOutputDataPrefix}}Destination prefix for offloaded blobs and files.
--checkpoint-path{{.checkpointOutputPrefix}}Destination path for task state checkpoints.
--prev-checkpoint{{.prevCheckpointPrefix}}Source path of previous checkpoint for state recovery.
--run-name{{.runName}}Current workflow execution run name.
--name{{.actionName}}Action/node identifier in the workflow graph.
--image-cacheserialize_context.image_cacheSerialized image cache mapping for fast container image lookup.
--tgz / --pklserialize_context.code_bundlePath to downloaded archive bundle or pickled function payload.
--destcode_bundle.destinationCode unpacking directory destination (defaults to .).
--resolverDefaultTaskResolver.import_pathResolver module path (flyte._internal.resolvers.default.DefaultTaskResolver).
mod <M> instance <T>DefaultTaskResolver.loader_argsPython module and task object attribute names to import dynamically.

When a task uses a pickled code bundle (--pkl), flyte-sdk omits the --resolver arguments because the unpickling process re-inflates the task instance directly without importing from source modules.

Raw Container Tasks and CoPilot Data Loading

When running tasks in pre-built images without the Flyte Python SDK (such as C++, Rust, or Bash executables), flyte-sdk provides ContainerTask in flyte.extras (implemented in flyte.extras._container).

ContainerTask sets task_type="raw-container". Rather than invoking the a0 entrypoint, ContainerTask.container_args(sctx) returns the user-provided command and argument tokens unmodified:

from flyte.extras import ContainerTask

task = ContainerTask(
name="process_data",
image="ubuntu:22.04",
command=["python3", "main.py"],
arguments=["--threshold", "{{.inputs.threshold}}", "/var/inputs/data_file"],
inputs={"threshold": float, "data_file": File},
outputs={"result": int},
input_data_dir="/var/inputs",
output_data_dir="/var/outputs",
metadata_format="JSON",
)

CoPilot Data Loading Configuration

Flyte CoPilot handles side-loading input data to the container filesystem and reading outputs when the process exits. ContainerTask.data_loading_config produces the required tasks_pb2.DataLoadingConfig:

def data_loading_config(self, sctx: SerializationContext) -> tasks_pb2.DataLoadingConfig:
literal_to_protobuf = {
"JSON": tasks_pb2.DataLoadingConfig.JSON,
"YAML": tasks_pb2.DataLoadingConfig.YAML,
"PROTO": tasks_pb2.DataLoadingConfig.PROTO,
}

return tasks_pb2.DataLoadingConfig(
input_path=str(self._input_data_dir) if self._input_data_dir else None,
output_path=str(self._output_data_dir) if self._output_data_dir else None,
enabled=True,
format=literal_to_protobuf.get(self._metadata_format, "JSON"),
)

Primitive Templates vs. File/Directory Path Syntax

In ContainerTask, input values are referenced differently depending on whether they are primitive values or file/directory references:

  1. Primitive Inputs: Reference primitive types (e.g., int, str, float) using template notation {{.inputs.key}}.
  2. File and Directory Inputs (File, Dir): Reference file or directory inputs using path-like syntax rooted at input_data_dir (e.g., /var/inputs/data_file).

Using template syntax {{.inputs.infile}} for File or Dir inputs raises an AssertionError during local volume preparation in _render_command_and_volume_binding:

if input_val and type(input_val) in [File, Dir]:
if not path_k:
raise AssertionError(
"File and Directory commands should not use the template syntax "
"like this: {{.inputs.infile}}\n"
"Please use a path-like syntax, such as: /var/inputs/infile.\n"
"This requirement is due to how Flyte Propeller processes template syntax inputs."
)

Path-like arguments ensure Flyte CoPilot and local Docker executors can bind volume mounts directly to /var/inputs/<input_name>.

Extending and Customizing Container Execution

Custom execution environments can override how container commands, resource specifications, and runtime plugins are built by subclassing task templates and using the extension primitives exported in flyte.extend.

Creating a Custom Task Template

Subclass AsyncFunctionTaskTemplate to intercept argument generation, attach additional flags, or customize execution behavior:

from typing import Any, List
from flyte.extend import AsyncFunctionTaskTemplate
from flyte.models import SerializationContext


class CustomEngineTaskTemplate(AsyncFunctionTaskTemplate):
def container_args(self, serialize_context: SerializationContext) -> List[str]:
# Generate baseline standard CLI arguments
base_args = super().container_args(serialize_context)

# Append custom runtime engine configuration
return base_args + ["--custom-flag", "custom-value"]

Registering Plugins via TaskPluginRegistry

When tasks accept custom plugin configurations (plugin_config), TaskEnvironment resolves the matching template class using TaskPluginRegistry (exported in flyte.extend):

from dataclasses import dataclass
from flyte.extend import TaskPluginRegistry


@dataclass
class CustomEngineConfig:
concurrency_limit: int = 4


# Register the template class for the custom config type
TaskPluginRegistry.register(CustomEngineConfig, CustomEngineTaskTemplate)

# TaskEnvironment automatically selects CustomEngineTaskTemplate
# whenever plugin_config=CustomEngineConfig(...) is used

Pod Spec Helpers

When building pod specifications or custom backend templates, flyte.extend provides pod_spec_from_resources and PRIMARY_CONTAINER_DEFAULT_NAME ("primary") to construct Kubernetes Pod templates that align with Flyte backend expectations:

from flyte._pod import PodSpec
from flyte._resources import Resources
from flyte.extend import PRIMARY_CONTAINER_DEFAULT_NAME, pod_spec_from_resources

resources = Resources(cpu="2", mem="4Gi")
pod_spec: PodSpec = pod_spec_from_resources(
resources=resources,
primary_container_name=PRIMARY_CONTAINER_DEFAULT_NAME,
)

This ensures container resource requests, limits, and runtime flags are structured uniformly across both standard Python tasks and specialized container extensions.