Modal 1.0 migration guide
We are planning to release version 1.0 of the modal
Python SDK in Q1 of 2025.
This release will signify an increased commitment to API stability and will
imply some changes to our development workflow.
Preceding the 1.0 release, we are making a number of breaking changes based on feedback that we have received from early users. These changes are intended to address pain points and reduce confusion about some aspects of the Modal API. While they will require some changes to existing user code, we believe that they’ll make it easier to use Modal going forward.
Our plan is to gradually roll out changes — with deprecation warnings — across the final sequence of 0.X releases. Once we release 1.0, code that does not issue deprecation warnings can be considered stable API. We will eventually expire the deprecations introduced prior to 1.0 and remove support for the old APIs.
This page outlines the major changes that we’re making as part of the v1.0 releases.
Deprecating Image.copy_*
methods
Introduced in: v0.72.11
We recently introduced new Image
methods — Image.add_local_dir
and
Image.add_local_file
— to replace the existing Image.copy_local_dir
and
Image.copy_local_file
.
The new methods subsume the functionality of the old ones, but their default
behavior is different and more performant. By default, files will be mounted to
the container at runtime rather than copied into a new Image
layer. This can
speed up development substantially when iterating on the contents of the files.
Building a new Image
layer should be necessary only when subsequent build
steps will use the added files. In that case, you can pass copy=True
in
Image.add_local_file
or Image.add_local_dir
.
The Image.add_local_dir
method also has an ignore=
parameter, which you can
use to pass file-matching patterns (using dockerignore rules) or predicate
functions to exclude files.
Deprecating Mount
as part of the public API
Introduced in: v0.72.4
Currently, local files can be mounted to the container filesystem either by
including them in the Image
definition or by passing a modal.Mount
object
directly to the App.function
or App.cls
decorators. As part of the 1.0
release, we are simplifying the container filesystem configuration to be defined
only by the Image
used for each Function. This implies deprecation of the
following:
- The
mount=
parameter ofApp.function
andApp.cls
- The
context_mount=
parameter of severalmodal.Image
methods - The
Image.copy_mount
method - The
Mount
object
Code that uses the mount=
parameter of App.function
and App.cls
should be
migrated to pass those files / directories to the Image
used by that Function
or Cls, i.e. using the Image.add_local_file
, Image.add_local_dir
, or
Image.add_local_python_source
methods:
# Mounting local files
# Old way (deprecated)
mount = modal.Mount.from_local_dir("data").add_local_file("config.yaml")
@app.function(image=image, mount=mount)
def f():
...
# New way
image = image.add_local_dir("data").add_local_file("config.yaml")
@app.function(image=image)
def f():
...
## Mounting local Python source code
# Old way (deprecated)
mount = modal.Mount.from_local_python_packages("my-lib"))
@app.function(image=image, mount=mount)
def f()
...
# New way
image = image.add_local_python_source("my-lib")
@app.function(image=image)
def f(...):
...
## Using Image.copy_mount
# Old way (deprecated)
mount = modal.Mount.from_local_dir("data").add_local_file("config.yaml")
image.copy_mount(mount)
# New way
image.add_local_dir("data").add_local_file("config.yaml")
Code that uses the context_mount=
parameter of Image.from_dockerfile
and
Image.dockerfile_commands
methods can delete that parameter; we now
automatically infer the files that need to be included in the context.
Deprecating the @modal.build
decorator
Introduced in: v0.72.17
As part of consolidating the filesystem configuration API, we are also
deprecating the modal.build
decorator.
For use cases where modal.build
would previously have been the suggested
approach (e.g., downloading model weights or other large assets to the
container filesystem), we now recommend using a modal.Volume
instead. The
main advantage of storing weights in a Volume
instead of an Image
is that
the weights do not need to be re-downloaded every time you change something else
about the Image
definition.
Many frameworks, such as Hugging Face, automatically cache downloaded model
weights. When using these frameworks, you just need to ensure that you mount a
modal.Volume
to the expected location of the framework’s cache:
cache_vol = modal.Volume.from_name("hf-hub-cache")
@app.cls(
image=image.env({"HF_HUB_CACHE": "/cache"}),
volumes={"/cache": cache_vol},
...
)
class Model:
@modal.enter()
def load_model(self):
self.model = ModelClass.from_pretrained(...)
For frameworks that don’t support automatic caching, you could write a separate
function to download the weights and write them directly to the Volume, then
modal run
against this function before you deploy.
In some cases (e.g., if the step runs very quickly), you may wish for the logic
currently decorated with @modal.build
to continue modifying the Image
filesystem. In that case, you can extract the method as a standalone function
and pass it to Image.run_function
:
def download_weights():
...
image = image.run_function(download_weights)
Deprecating the .lookup
method on Modal objects
Introduced in: Pending
Most Modal objects can be instantiated through two distinct methods:
.from_name
and .lookup
. The redundancy between these methods is a persistent
source of confusion.
The .from_name
method is lazy: it operates entirely locally and instantiates
only a shell for the object. The local object won’t be associated with its
identity on the Modal server until you interact with it. In contrast, the
.lookup
method is eager; calling it triggers an RPC, and it returns a
fully-hydrated object.
Because Modal objects can now be hydrated on-demand, when they are first
used,there is rarely a need to eagerly hydrate. Therefore, we’re deprecating
.lookup
so that there’s only one obvious way to instantiate objects.
In most cases, the migration is a simple find-and-replace of .lookup
→
.from_name
.
One exception is when your code needs to access object metadata, such as its ID,
or a web endpoint’s URL. In that case, you can explicitly force hydration of the
object by calling its .hydrate()
method.