Choosing a Base Image
Choose the starting point
Choose a starting image for a Flyte task or app with a flyte.Image.from_* constructor, then add SDK-managed layers only when that base supports them. Image is a frozen dataclass and is intentionally not directly instantiable; its __post_init__ rejects Image(...) unless the internal constructor guard is present. The public import is flyte.Image.
The four constructors in this guide have different ownership and build behavior:
| Constructor | Use it when | Initial image state | Platform default |
|---|---|---|---|
from_debian_base() | You want the Flyte SDK's Debian/Python default | SDK-managed base; it can be customized | ("linux/amd64", "linux/arm64") unless overridden |
from_base(image_uri) | You already have an image in a registry | Untouched, non-cloned image whose URI is returned unchanged | Uses the Image default |
from_dockerfile(file, registry, name, platform) | The Dockerfile owns OS, Python, and dependency setup | Dockerfile-based, already marked cloned and non-extendable | ("linux/amd64",) unless overridden |
from_ref_name(name="default") | Deployment configuration should provide the concrete URI | Symbolic reference; resolved during deployment | Uses the Image default |
An image is a specification, not an immediate Docker build. An untouched base can be used as-is; an image that is cloned or receives layers is sent through the configured image-building path.
Use the SDK-managed Debian base
Start with from_debian_base() when the SDK should select the Debian/Python base and manage the generated setup. The normal customization pattern is a fluent chain:
import flyte
image = (
flyte.Image.from_debian_base(python_version=(3, 12))
.with_pip_packages("pandas", "scikit-learn")
.with_apt_packages("curl", "git")
)
python_version is a (major, minor) tuple. If it is omitted, from_debian_base() calls the SDK's Python-version detector. The default image targets both linux/amd64 and linux/arm64; pass platform=("linux/amd64",) or another tuple to choose the target platforms.
The constructor accepts flyte_version, install_flyte, registry, registry_secret, name, and platform:
import flyte
image = flyte.Image.from_debian_base(
python_version=(3, 12),
install_flyte=False,
registry="ghcr.io/flyteorg",
name="my-python-image",
platform=("linux/amd64", "linux/arm64"),
)
For released SDK versions, the default can resolve to an existing Flyte-managed URI such as ghcr.io/flyteorg/flyte:py3.12-v<tag>. In development mode, or when install_flyte=False, the SDK constructs the image from a python:<major>.<minor>-slim-bookworm base and adds its generated layers. Supplying registry or name clones the selected base with those output properties, making it suitable for a derived build.
Attach the image to a task environment in the same way as the examples in the SDK:
import flyte
build_env = flyte.TaskEnvironment(
name="build_env",
image=(
flyte.Image.from_debian_base(
name="base-image",
python_version=(3, 12),
).with_pip_packages("flyte")
),
)
Each with_* call returns a new Image; it does not mutate the preceding object. with_pip_packages() records a PipPackages layer, and with_apt_packages() records an AptPackages layer. Other supported layering methods include with_requirements(), with_env_vars(), with_source_file(), with_source_folder(), with_code_bundle(), and with_workdir().
Start from an existing image URI
Use from_base() when the image has already been built and is expected to exist in a registry:
import flyte
env = flyte.TaskEnvironment(
name="image_from_uri",
image=flyte.Image.from_base("python:3.12-slim"),
)
from_base(image_uri: str) stores the URI as base_image. Because the returned image is not cloned, its uri is the exact string passed to the constructor. from_base() does not check that the image exists and does not build it.
This is also useful when another process supplies the URI. The repository's environment-based example reads BASE_IMAGE and passes it to from_base():
import os
import flyte
image_env_var = "BASE_IMAGE"
image_uri = os.getenv(image_env_var)
env = flyte.TaskEnvironment(
name="image_from_env",
env_vars={image_env_var: image_uri},
image=flyte.Image.from_base(image_uri),
)
Clone before adding SDK layers
A raw from_base() image has no output name, so adding a layer immediately is rejected. Give the derived image a name and explicitly make it extendable first:
from flyte import Image
image = (
Image.from_base("ghcr.io/flyteorg/flyte:py3.12-v2.0.0b56")
.clone(
name="my-extendable-image",
extendable=True,
)
.with_pip_packages("requests", "pandas")
)
clone() returns another immutable Image and can also set registry, registry_secret, base_image, python_version, or extendable. A clone is marked as derived (_is_cloned=True). Its URI is consequently generated from the output name and registry plus a tag derived from the image contents and layers, rather than simply passing through the original base URI.
The extendable=True requirement applies to SDK-generated layers: clone(addl_layer=...) raises ValueError when the current image is not extendable. A clone without that flag is still useful when you want the builder to produce a named image but do not want to append layers:
import flyte
from flyte import Image
image = Image.from_base("ghcr.io/flyteorg/flyte:py3.12-v2.0.0b56").clone(
name="my-flyte-image",
)
aux_env = flyte.TaskEnvironment(
name="aux_env",
image=image,
)
Put all setup in a custom Dockerfile
Use from_dockerfile() when generated with_* layers cannot express the required image setup. The constructor requires file, registry, and name; it converts a string path to Path, normalizes a single platform to a one-item tuple, and marks the image non-extendable.
from pathlib import Path
import flyte
from flyte.extras import ContainerTask
version_task = ContainerTask(
image=flyte.Image.from_dockerfile(
file=Path(__file__).parent / "Dockerfile.example",
name="flyte-temp-dockerfile-example",
registry="ghcr.io/flyteorg",
platform=("linux/amd64", "linux/arm64"),
),
name="example_task",
input_data_dir="/var/inputs",
output_data_dir="/var/outputs",
inputs={},
outputs={"greeting": str},
command=["/bin/sh", "-c", "python --version | tee -a /var/outputs/version"],
)
env = flyte.TaskEnvironment.from_task("dockerfile_env", version_task)
The Dockerfile's parent directory is the build context. Use a path anchored to the source file, such as Path(__file__).parent / "Dockerfile.example", rather than relying on the caller's working directory. The Docker builder passes the normalized platforms to buildx and pushes when a registry is present.
For the example above, examples/basics/Dockerfile.example owns the setup directly:
# syntax=docker/dockerfile:1.4
# Declare build arguments
ARG TARGETPLATFORM
ARG BUILDPLATFORM
# Use an official base image depending on platform
FROM python:3.11-slim AS base
# Redefine args inside this stage
ARG TARGETPLATFORM
ARG BUILDPLATFORM
# Print the platforms
RUN echo "Building for TARGETPLATFORM=$TARGETPLATFORM from BUILDPLATFORM=$BUILDPLATFORM"
# Example: set up architecture-specific env var
RUN case "$TARGETPLATFORM" in \
"linux/amd64") export ARCH_ALIAS="x86_64" ;; \
"linux/arm64") export ARCH_ALIAS="aarch64" ;; \
*) export ARCH_ALIAS="unknown" ;; \
esac && echo "ARCH_ALIAS=$ARCH_ALIAS" > /arch_info.txt
# Set working dir
WORKDIR /app
# Copy source code
COPY . .
CMD ["python3", "--version"]
Do not call with_pip_packages() or another with_* method on a Dockerfile image. clone() refuses to append an additional layer when dockerfile is set because the SDK does not parse the Dockerfile to determine its operating system, Python version, or package manager. Put all such logic in the Dockerfile itself.
Resolve a pre-built image by reference name
Use from_ref_name() when source code should name an image symbolically and deployment configuration should provide the URI:
import flyte
noop_env = flyte.TaskEnvironment(
name="env_from_image_ref_name",
image=flyte.Image.from_ref_name("custom-image"),
)
@noop_env.task
async def main(x: int = 1) -> int:
return x
The constructor stores the supplied name in _ref_name; it does not make a concrete URI at construction time. During deployment, _build_images() looks up that name in configured image references. The example supplies the mapping through init_from_config():
import flyte
flyte.init_from_config(
images=("custom-image=ghcr.io/flyteorg/flyte:py3.13-v2.0.0b52",)
)
run = flyte.run(main)
The equivalent YAML shape is:
image:
image_refs:
custom-image: ghcr.io/flyteorg/flyte:py3.13-v2.0.0b52
The no-argument form uses the reserved reference name default:
import flyte
image = flyte.Image.from_ref_name()
A reference with no additional layers is used directly after resolution, and deployment skips building it. If layers have been added, deployment first clones it with the configured URI as base_image and then sends the derived image to the image builder. If the reference is absent from configuration, deployment raises a ValueError identifying the missing image name and listing available references.
Check the resulting URI and build behavior
Image.uri is a cached property with two relevant behaviors:
- An untouched image returns its
base_imagedirectly. This is the behavior offrom_base()and of a resolved reference with no layers. - A cloned image requires a
nameand formats eitherregistry/name:tagorname:tag. The tag is an internal preset where applicable, otherwise an MD5 digest based on the base image, Dockerfile contents, and layer data/options.
Thus changing a package layer, requirements file, source input, or other hashed layer can change the derived URI. Deployment resolves reference names, uses direct reference URIs without layers, and builds derived images through the configured image builder.
For a private base, distinguish build-time and runtime credentials. registry_secret is an Image/clone() option used for pulling or pushing the private image. The private-base workflow also passes the needed registry secret to the TaskEnvironment so the runtime can pull the resulting private image; a build-time secret alone is not sufficient for task execution. The current private-base flow supports one registry secret for an image.
Troubleshoot base-image selection
TypeErrorfromImage(...): construct the image withfrom_debian_base(),from_base(),from_ref_name(),from_dockerfile(), or anotherfrom_*constructor.ValueError: Cannot add additional layer ... without name: a rawfrom_base()image has no output name. Callclone(name=..., extendable=True)before using awith_*method.ValueErrorabout a non-extendable image: setextendable=Trueon the clone. Dockerfile images cannot be made SDK-layerable; amend the Dockerfile instead.- Dockerfile cannot be found or copies the wrong files: use an absolute or source-anchored
Path; the Dockerfile's containing directory is the build context. - Unexpected architecture coverage: Debian defaults to
("linux/amd64", "linux/arm64"), whilefrom_dockerfile()defaults to("linux/amd64",). Passplatformexplicitly when the target matters. - A pre-built image is not found:
from_base()assumes the URI already exists, andfrom_ref_name()requires a matchingimage.image_refsentry at initialization/deployment. - A previously computed URI does not change after edits:
uriis cached. TreatImageas immutable and retain/use the new object returned byclone()or awith_*method rather than mutating fields.