Skip to main content

Creating Raw Container Tasks

When you need to execute non-Python binaries, pre-packaged scripts, or custom container images directly in Flyte without wrapping them inside standard Python @task functions, ContainerTask in flyte-sdk allows you to specify the container image, entrypoint command, arguments, inputs, and outputs explicitly.

Defining a ContainerTask

ContainerTask is exported from flyte.extras (implemented in extras._container.py) and subclasses TaskTemplate with task_type="raw-container". It allows you to run any container image and map task inputs and outputs to container arguments and filesystem paths.

from flyte.extras import ContainerTask

simple_shell_task = ContainerTask(
name="simple-shell-task",
image="alpine:latest",
command=["/bin/sh", "-c"],
arguments=["echo 'Hello from Flyte ContainerTask'"],
)

Constructor Parameters

The ContainerTask class accepts the following parameters:

ParameterTypeDefaultDescription
namestrRequiredName of the task template.
imageUnion[str, Image]RequiredContainer image reference string (e.g., "alpine:latest" or "auto" which resolves to Image.from_debian_base()) or a flyte.Image instance.
commandList[str]RequiredThe entrypoint command to run inside the container.
argumentsOptional[List[str]]NoneArguments passed to the command.
inputsOptional[Dict[str, Type]]NoneMapping of input argument names to their expected Python types.
outputsOptional[Dict[str, Type]]NoneMapping of output names to their expected Python types.
input_data_dirstr | pathlib.Path"/var/inputs"Target filesystem directory inside the container where input files/directories are staged.
output_data_dirstr | pathlib.Path"/var/outputs"Directory inside the container where the task command must write its output files.
metadata_formatLiteral["JSON", "YAML", "PROTO"]"JSON"Format enum for Flyte data loading metadata.
local_logsboolTrueWhether to stream container stdout/stderr to the console during local execution.

Handling Scalar Inputs with Template Syntax

For primitive scalar inputs (such as str, int, or float), ContainerTask supports templated substitution inside command and arguments using the {{.inputs.<name>}} placeholder syntax.

from flyte.extras import ContainerTask

calculate_task = ContainerTask(
name="calculate-sum",
image="alpine:latest",
command=["/bin/sh", "-c"],
arguments=[
"echo $(( {{.inputs.a}} + {{.inputs.b}} )) > /var/outputs/result"
],
inputs={"a": int, "b": int},
outputs={"result": int},
)

During execution, _render_command_and_volume_binding identifies placeholders matching the regular expression \{\{\.inputs\.([a-zA-Z0-9_]+)\}\} and replaces each key with the string representation of its corresponding input value.

Handling File and Directory Inputs

When working with flyte.io.File or flyte.io.Dir, do not use the {{.inputs.<name>}} template syntax. Instead, reference the absolute path under input_data_dir (default: /var/inputs/).

from flyte.extras import ContainerTask
from flyte.io import File

line_counter = ContainerTask(
name="count-lines",
image="alpine:latest",
command=["/bin/sh", "-c"],
arguments=[
"wc -l < /var/inputs/input_file > /var/outputs/line_count"
],
inputs={"input_file": File},
outputs={"line_count": int},
input_data_dir="/var/inputs",
output_data_dir="/var/outputs",
)

Path Matching and Volume Binding

When executing locally:

  1. _extract_path_command_key matches arguments starting with input_data_dir/<name>.
  2. ContainerTask extracts the key name (input_file) from /var/inputs/input_file.
  3. The local path of the File or Dir (input_val.path) is bound to /var/inputs/input_file inside the container in read-write mode ({"bind": "/var/inputs/input_file", "mode": "rw"}).

If a File or Dir is passed using the template syntax {{.inputs.input_file}}, ContainerTask raises an AssertionError:

AssertionError: File and Directory commands should not use the template syntax like this: {{.inputs.infile}}
Please use a path-like syntax, such as: /var/inputs/infile.
This requirement is due to how Flyte Propeller processes template syntax inputs.

Producing and Parsing Outputs

ContainerTask expects the container process to write task outputs as individual files directly into output_data_dir (default: /var/outputs), where each filename corresponds to the key defined in the outputs dictionary.

import datetime
from flyte.extras import ContainerTask
from flyte.io import File

process_data = ContainerTask(
name="generate-metadata",
image="ubuntu:22.04",
command=["/bin/bash", "-c"],
arguments=[
"echo '2026-03-30T12:00:00' > /var/outputs/timestamp && "
"echo 'true' > /var/outputs/is_valid && "
"echo '100' > /var/outputs/count && "
"echo 'task payload' > /var/outputs/artifact"
],
outputs={
"timestamp": datetime.datetime,
"is_valid": bool,
"count": int,
"artifact": File,
},
)

When reading output files via _convert_output_val_to_correct_type, the values are converted based on their target Python type:

  • bool: Evaluates output_val.lower() != "false".
  • datetime.datetime: Parsed via datetime.datetime.fromisoformat(output_val).
  • datetime.timedelta: Parsed using regex supporting formats such as 1 days, 02:30:00.500000 or 01:15:30.
  • File: Loaded via await File.from_local(output_path).
  • Dir: Loaded via await Dir.from_local(output_path).
  • Other types (e.g., int, float, str): Instantiated directly using the type constructor output_type(output_val).

If an expected output file does not exist when the container finishes, output_val defaults to None.

Local Execution and Cluster Serialization

Local Execution with Docker

Calling await task.execute(**kwargs) runs the task inside your local Docker daemon:

import asyncio
from flyte.extras import ContainerTask

task = ContainerTask(
name="local-echo",
image="alpine:latest",
command=["echo"],
arguments=["Local container execution test"],
)

asyncio.run(task.execute())

During local execution:

  1. docker.from_env() connects to the local Docker engine (requires the docker Python package).
  2. The image is pulled if not present locally via _pull_image_if_not_exists.
  3. Input volumes and a temporary local output directory (storage.get_random_local_directory()) are mounted to the container.
  4. The container is executed (remove=True, detach=True), container logs are streamed to stdout if local_logs=True, and output files are collected upon completion.

Serialization for Remote Cluster Execution

When serialized for Flyte Propeller, ContainerTask implements two serialization hooks:

  • container_args(sctx): Returns the concatenated command and arguments list (self._cmd + (self._args or [])).
  • data_loading_config(sctx): Returns a flyteidl.core.tasks_pb2.DataLoadingConfig protobuf object containing:
    • input_path: str(self._input_data_dir)
    • output_path: str(self._output_data_dir)
    • enabled: True
    • format: Protobuf enum mapping for "JSON", "YAML", or "PROTO".

Troubleshooting and Common Gotchas

Missing Docker Package for Local Runs

If you call execute() locally without having the Docker SDK installed, an ImportError is raised:

ImportError: Docker is not installed. Please install Docker by running `pip install docker`.

Fix: Install the docker client in your Python environment:

pip install docker

Template Substitution on Files/Directories

Using {{.inputs.myfile}} when myfile is a flyte.io.File or flyte.io.Dir results in an explicit AssertionError. Fix: Change the argument to /var/inputs/myfile (or <input_data_dir>/myfile).

Missing Output Files

If the container command fails to create a file matching a key in outputs under /var/outputs, the SDK receives None for that output and passes it to the type converter, which may raise a TypeError or ValueError (e.g., int(None)). Fix: Ensure every branch in your container command creates all files specified in the outputs dictionary.