Developing and debugging

Modal makes it easy to run apps in the cloud, try code changes in the cloud, and debug remotely executing code as if it were right there on your laptop. To speed boost your inner dev loop, this guide provides a rundown of tools and techniques for developing and debugging software in Modal.

Interactivity

You can launch a Modal app interactively and have it drop you right into the middle of the action, at an interesting callsite or the site of a runtime detonation.

Interactive functions

It is possible to start the interactive Python debugger or start an IPython REPL right in the middle of your Modal app.

To do so, you first need to run your app in “interactive” mode by using the --interactive / -i flag. In interactive mode, you can establish a connection to the calling terminal by calling interact() from within your function.

For a simple example, you can accept user input with the built-in Python input function:

@app.function()
def my_fn(x):
    modal.interact()

    print("Enter a number:", end=" ")
    x = input()
    print(f"Your number is {x}")

Now when you run your app with the --interactive flag, you’re able to send inputs to your app, even though it’s running in a remote container!

modal run -i guess_number.py
Enter a number: 5
Your number is 5

For a more interesting example, you can start an IPython REPL dynamically anywhere in your code:

@app.function()
def f():
    model = expensive_function()
    # play around with model
    modal.interact()
    import IPython
    IPython.embed()

The Python debugger can be initiated with the language built-in breakpoint() function. For convenience, breakpoints call interact automatically.

@app.function()
def f():
    x = "10point3"
    breakpoint()
    answer = float(x)

Interactive shell

Modal lets you run interactive commands on your running containers from the terminal.

To run a command inside a running container, you first need to get the container ID. You can view all running containers and their container IDs with modal container list.

After you obtain the container ID, you can run commands with modal container exec [container-id] [command...]. For example, to run a bash shell, you can run modal container exec [container-id] /bin/bash.

Note that your executed command will terminate immediately once your container has finished running.

By default, commands will be run within a pseudoterminal (PTY), but this can be disabled with the --no-pty flag.

You can also launch an interactive shell in a new container with the same environment as your function. This is handy for debugging issues with your image, interactively refining build commands, and exploring the contents of Volumes, NetworkFileSystems, and Mounts.

The primary interface for accessing this feature is the modal shell CLI command, which accepts a function name in your app (or prompts you to select one, if none is provided), and runs an interactive command on the same image as the function, with the same Secrets, NetworkFileSystems and Mounts attached as the selected function.

The default command is /bin/bash, but you can override this with any other command of your choice using the --cmd flag.

Note that this command does not attach a shell to an existing instance of the function, but instead creates a fresh instance of the underlying image. We might support the former soon - please reach out to us if that would be useful to you.

Live updating

Hot reloading with modal serve

Modal has the command modal serve <filename.py>, which creates a loop that live updates an app when any of the supporting files change.

Live updating works with web endpoints, syncing your changes as you make them, and it also works well with cron schedules and job queues.

from modal import App, web_endpoint

app = App()  # Note: prior to April 2024, "app" was called "stub"

@app.function()
@web_endpoint()
def f():
    return "I update on file edit!"

@app.function(schedule=modal.Period(seconds=5))
def run_me():
    print("I also update on file edit!")

If you edit this file, the modal serve command will detect the change and update the code, without having to restart the command.

Observability

Each running Modal app, including all ephemeral apps, streams logs and resource metrics back to you for viewing.

On start, an app will log a dashboard link that will take you its app page.

$ python3 main.py
✓ Initialized. View app page at https://modal.com/apps/ap-XYZ1234.
...

From this page you can access the following:

  • logs, both from your application and system-level logs from Modal
  • compute resource metrics (CPU, RAM, GPU)
  • function call history, including historical success/failure counts