A Modal App is a group of functions and classes that are deployed together. The App serves as the fundamental unit of deployment and organization for your serverless code.
Creating an app
Create an App by instantiating the modal.App class:
import modal
app = modal.App("my-app")
You can optionally configure defaults that apply to all functions and classes in the App:
image = modal.Image.debian_slim().pip_install("numpy", "pandas")
secret = modal.Secret.from_name("my-secret")
volume = modal.Volume.from_name("my-data")
app = modal.App(
"my-app",
image=image,
secrets=[secret],
volumes={"/mnt/data": volume}
)
App configuration options
When creating an App, you can specify:
image: Default container image for all functions
secrets: Secrets to inject as environment variables
volumes: Volume mounts to attach to all functions
tags: Additional metadata for the App
include_source: Whether to automatically include function source files (default: True)
Registering functions
The most common way to register functions with an App is using the @app.function() decorator:
import modal
app = modal.App()
@app.function()
def hello(name: str):
return f"Hello, {name}!"
When you decorate a function with @app.function(), both the function and any passed objects (like schedules and secrets) are registered with the App.
Functions and classes are the main objects you register with an App. Learn more in the Functions and Classes documentation.
Running an app
Use the app.run() context manager to run an ephemeral app and execute functions:
import modal
app = modal.App()
@app.function()
def my_function():
print("Running in Modal!")
return 42
if __name__ == "__main__":
with app.run():
result = my_function.remote()
print(f"Result: {result}")
To see output logs, use modal.enable_output():
if __name__ == "__main__":
with modal.enable_output():
with app.run():
my_function.remote()
Do not invoke app.run() in the global scope of a file where you define Modal Functions or Classes, as this would run the code when the function is imported in your containers. Always protect it with if __name__ == "__main__": or use it in a separate script.
Deploying an app
Deploy your App to make it persistently available:
import modal
app = modal.App("my-app")
@app.function()
@modal.web_endpoint()
def web():
return {"message": "Hello, world!"}
if __name__ == "__main__":
with modal.enable_output():
app.deploy()
Deployed Apps remain available for lookups and web-based invocations until they are stopped.
Deployment options
The deploy() method accepts several parameters:
name: Override the App name for this deployment
environment_name: Deploy to a specific environment
tag: Add metadata specific to this deployment
client: Use an alternate client for communication
app.deploy(
name="my-deployment",
environment_name="staging",
tag="v1.2.3"
)
You can also deploy apps using the modal deploy CLI command. The deploy() method is a programmatic alternative.
Looking up apps
Reference a deployed App by name:
import modal
app = modal.App.lookup("my-app", create_if_missing=True)
This is useful when creating Sandboxes or accessing deployed functions:
app = modal.App.lookup("my-app", create_if_missing=True)
sandbox = modal.Sandbox.create("echo", "hi", app=app)
Local entrypoints
Use @app.local_entrypoint() to define CLI entrypoints that run locally:
import modal
app = modal.App()
@app.function()
def process_data(x: int):
return x * 2
@app.local_entrypoint()
def main(value: int):
result = process_data.remote(value)
print(f"Result: {result}")
Run it with:
modal run app_module.py --value 42
Multiple entrypoints
If you have multiple entrypoints, qualify the function name:
modal run app_module.py::app.other_entrypoint
Argument parsing
Entrypoint functions with primitive type arguments are automatically parsed as CLI options:
@app.local_entrypoint()
def main(foo: int, bar: str, enabled: bool = False):
# Use --foo 1 --bar "hello" --enabled
pass
Supported types: str, int, float, bool, and datetime.datetime.
Best practices
Group related functions and classes in a single App for cohesive deployment:
app = modal.App("data-pipeline")
@app.function()
def extract():
pass
@app.function()
def transform():
pass
@app.function()
def load():
pass
Use environment-specific apps
Deploy to different environments for testing and production:
app.deploy(environment_name="staging")
app.deploy(environment_name="production")
Keep apps focused
Create separate Apps for distinct services or workflows rather than bundling everything into one large App. This makes deployments faster and more manageable.