Skip to main content

Managing Data I/O in Container Tasks

In flyte-sdk, the ContainerTask class provides a mechanism for executing arbitrary containerized workloads. It manages the mapping between Flyte task inputs/outputs and the container's filesystem, allowing you to run existing binaries or scripts within a Flyte workflow.

Defining a Container Task

To create a container task, instantiate flyte.extras.ContainerTask. You must specify the container image, the command to run, and the expected inputs and outputs.

from flyte.extras import ContainerTask
from flyte.image import Image
import pathlib

# Define a task that runs a shell script inside a container
my_container_task = ContainerTask(
name="bash-task",
image=Image.from_base("ubuntu:latest"),
command=["/bin/bash", "-c"],
arguments=["echo 'hello world' > /var/outputs/res"],
inputs={"val": int},
outputs={"res": str},
input_data_dir="/var/inputs",
output_data_dir="/var/outputs",
metadata_format="JSON"
)

Configuring Input and Output Directories

ContainerTask uses specific directories inside the container to exchange data with the Flyte engine. By default, these are:

  • input_data_dir: /var/inputs
  • output_data_dir: /var/outputs

When the task executes, Flyte mounts the input data into the input_data_dir. The task is expected to write its results into the output_data_dir.

Data Loading Formats

The metadata_format parameter determines how Flyte serializes and deserializes the data. Supported formats include:

  • JSON (Default)
  • YAML
  • PROTO

This configuration is captured in the DataLoadingConfig during task serialization:

# Internal representation of data loading configuration
config = my_container_task.data_loading_config(serialization_context)
# Result: tasks_pb2.DataLoadingConfig(input_path="/var/inputs", output_path="/var/outputs", format=JSON)

Handling File and Directory Inputs

When working with flyte.io.File or flyte.io.Dir types, flyte-sdk handles the volume mounting automatically. However, you must use a specific path-like syntax in your command to reference these inputs.

Correct Path Syntax

Instead of using template syntax like {{.inputs.my_file}}, you must reference the file by its expected path within the input_data_dir.

from flyte.io import File

file_task = ContainerTask(
name="file-processor",
image=Image.from_base("alpine"),
command=["cat"],
# Use the path where the file will be mounted: input_data_dir / input_name
arguments=["/var/inputs/data_file"],
inputs={"data_file": File},
input_data_dir="/var/inputs"
)

If you use template syntax for File or Dir types, flyte-sdk will raise an AssertionError during command rendering to prevent execution failures in the Flyte backend.

Local Execution with Docker

For local testing, ContainerTask.execute uses the local Docker daemon to run the container.

  1. Volume Binding: flyte-sdk creates a temporary local directory and binds it to the container's output_data_dir.
  2. Image Management: It automatically pulls the required image if it is not present locally using client.images.pull(uri).
  3. Log Streaming: If local_logs=True (default), container logs are printed to the console with the prefix [Local Container].
# Local execution requires Docker to be installed
# result = await my_container_task.execute(val=10)

Troubleshooting and Constraints

  • Docker Dependency: Local execution will fail with an ImportError if the docker Python package is not installed, or an error if the Docker daemon is not reachable.
  • Template Syntax: Template syntax {{.inputs.key}} is only supported for primitive types (strings, integers, etc.). For File and Dir types, you must use the explicit path /var/inputs/<key>.
  • Execution Timeout: The current implementation of execute waits indefinitely for the container to finish (container.wait()). Ensure your containerized process has its own internal timeouts if necessary.
  • Output Parsing: flyte-sdk parses outputs by reading files from the output_data_dir named after the output keys. For example, if outputs={"res": str}, the task must write to /var/outputs/res.