Container Images
The container image system in this SDK provides a fluent, high-level API for defining and building environment specifications. Instead of writing Dockerfiles manually, you use the Image class to programmatically define base images and additional layers such as system packages, Python dependencies, and source code.
The Fluent Image API
The Image class (found in src/flyte/_image.py) follows a fluent, two-step pattern:
- Initialization: Create a base image using a
from_*constructor. - Customization: Add layers using
with_*methods.
A critical design aspect of the Image class is immutability. Every with_* method calls the internal clone() method, returning a new Image instance with the additional layer. This allows you to reuse base image definitions without side effects.
Direct instantiation of Image is disallowed via a guard in __post_init__. You must use the provided class-level constructors.
from flyte import Image
# Defining a reusable base
base = Image.from_debian_base(python_version=(3, 12))
# Creating specialized images from the base
training_image = base.with_pip_packages("torch", "numpy")
serving_image = base.with_pip_packages("fastapi", "uvicorn")
Base Image Constructors
The SDK provides several ways to start an image definition depending on your requirements:
Debian Base
Image.from_debian_base() is the standard way to start. It uses a slim Debian-based image optimized for Flyte. It automatically detects the local Python version if not specified and can optionally install the Flyte library itself.
Custom Base URI
If you have an existing image in a registry, use Image.from_base(image_uri="..."). This allows you to layer Flyte-specific configurations on top of corporate or specialized base images.
Dockerfile Support
For complex setups that cannot be expressed through the fluent API, Image.from_dockerfile() allows you to point to an existing Dockerfile.
- Note: Images created from a Dockerfile are marked as
extendable=False. You cannot add further layers usingwith_*methods to a Dockerfile-based image because the SDK does not parse the Dockerfile to understand its internal state (like the OS or Python environment).
UV Scripts
Image.from_uv_script() is a specialized constructor that creates an image from a uv-compatible script. It parses the script's header for inline dependencies and Python version requirements, making it ideal for self-contained scripts.
# Example from examples/image/uv_image.py
image = Image.from_uv_script(__file__, name="hello", registry="ghcr.io/flyteorg")
Layering and Customization
Once a base is established, you can add various layers. Each layer is encapsulated in a specific class (e.g., PipPackages, AptPackages, Env) and added to the _layers tuple of the Image object.
Dependency Management
with_pip_packages(*packages): Adds a layer that runspip install.with_apt_packages(*packages): Adds a layer for system-level dependencies viaapt-get.with_requirements(file): Installs dependencies from a standardrequirements.txt.
Environment and Commands
with_env_vars(env_vars): Sets environment variables in the container.with_commands(commands): Runs arbitrary shell commands during the build process.with_workdir(workdir): Sets theWORKDIRfor subsequent layers and the runtime.
Source Code Integration
The SDK provides multiple ways to include your code:
with_source_folder(src, dst): Copies a local directory into the image.with_source_file(src, dst): Copies specific files.with_code_bundle(): A specialized layer that automatically handles copying source code based on the runner's configuration.
UV and Poetry Integration
The SDK treats uv and poetry as first-class citizens for dependency management.
- UV Projects:
with_uv_project()looks forpyproject.tomlanduv.lock. It can be configured to install only dependencies or the entire project as a package using theproject_install_modeparameter. - Poetry Projects:
with_poetry_project()provides similar functionality for Poetry-managed environments, handlingpoetry.lockand project installation.
Building Images
The ImageBuildEngine (in src/flyte/_internal/imagebuild/image_builder.py) is responsible for transforming an Image specification into a real container image.
The Build Process
When you call flyte.build(image), the engine performs the following steps:
- Validation: Calls
image.validate()to ensure layers are consistent. - Existence Check: It calculates a hash of the image (based on base image and all layers). If an image with that hash already exists in the registry, it skips the build unless
force=Trueor the tag islatest. - Builder Selection: It selects a builder based on configuration (
FLYTE_IMAGE_BUILDER).- Local Builder: Uses
DockerImageBuilder, which requiresdocker buildx. It creates a builder instance namedflytexto handle multi-platform builds. - Remote Builder: Uses
RemoteImageBuilderto offload the build process to a remote service.
- Local Builder: Uses
Build Results
The build() function returns an ImageBuild object:
@dataclass
class ImageBuild:
uri: str | None # The final URI (e.g., registry/name:hash)
remote_run: Optional[Run] # The Run object if a remote builder was used
Hashing and Caching
To ensure efficient builds, the SDK implements a robust hashing mechanism in Image._get_hash_digest(). The hash is derived from:
- The
base_imageURI. - The contents of the
dockerfile(if applicable). - Every layer in the
_layerstuple.
Each layer class implements update_hash(hasher, ignore). For example, a PipPackages layer hashes the list of packages and installation arguments, while a CopyConfig layer (used by with_source_folder) hashes the actual contents of the files being copied, respecting .dockerignore files.
If the resulting hash matches an image already present in the target registry, the SDK avoids a redundant build, significantly speeding up deployment cycles.
Secret Mounts
For security-sensitive builds (e.g., installing from private PyPI indexes or Git repositories), many with_* methods support secret_mounts. These allow you to safely pass credentials to the build engine without baking them into the image layers.
# Example of using secrets for private packages
image = (
Image.from_debian_base()
.with_pip_packages(
"private-pkg",
secret_mounts=[Secret(key="PYPI_TOKEN")]
)
)
During the build, these secrets are mounted as environment variables or files, depending on the builder's implementation.