| name | modal |
| description | Run, serve, and deploy serverless Python on Modal's cloud with the `modal` CLI. Use when running or deploying Modal apps/functions; iterating with hot-reload (`modal serve`); debugging in a cloud container (`modal shell`, `modal container exec`); tailing or stopping app runs (`modal app logs/stop/history/rollback`); managing Volumes, Secrets, Dicts, or Queues; authenticating (`modal setup`, `modal token`); switching workspaces/environments/profiles; or requesting GPUs (A100/H100/L4) for cloud jobs. Trigger on 'modal', 'modal run', 'modal deploy', 'modal.com', '@app.function', 'modal.Image', 'serverless GPU', or a repo with a `modal_*.py` / `.modal.toml`. |
Modal CLI
Overview
Modal runs ordinary Python functions in the cloud as serverless containers — no Dockerfiles, no YAML, no Kubernetes. Everything is code: the container image, GPU request, secrets, and schedule are all declared in the Python file with decorators (@app.function(...), modal.Image, modal.Volume). The modal CLI is the thin lifecycle layer on top: it takes a local .py file and runs / serves / deploys it remotely, then lets you observe and manage what's running.
Core mental model — one file, three verbs:
| Verb | Command | What it does |
|---|
| Run once | modal run app.py | Execute now, stream logs, tear down when done. For batch jobs & scripts. |
| Serve (dev) | modal serve app.py | Bring web endpoints up with hot-reload on file save. URLs get a -dev suffix. |
| Deploy | modal deploy app.py | Persist the app — endpoints, scheduled functions, and .lookup() targets stay live. |
Auth lives in ~/.modal.toml (profiles). The CLI resolves which environment to act on from -e/--env → MODAL_ENVIRONMENT → active profile default → workspace default.
Function references (FUNC_REF)
run, serve, deploy, and shell take a reference to code:
modal run path/to/app.py # runs the file's local entrypoint / sole function
modal run path/to/app.py::my_function # target a specific function or @app.local_entrypoint
modal run -m my_pkg.my_module # -m treats the arg as a Python module path, not a file
Pass arguments to the target function as CLI flags — Modal builds them from the function signature:
modal run app.py::train --epochs 10 --lr 0.001 # def train(epochs: int, lr: float)
Quick reference
Lifecycle
modal run app.py
modal run app.py -w out.json
modal run -q app.py
modal serve app.py
modal deploy app.py --name my-app
modal deploy app.py --stream-logs
modal deploy app.py --strategy rolling
Observe & control running apps
modal app list
modal app logs my-app
modal app stop my-app
modal app history my-app
modal app rollback my-app [VERSION]
Debug inside a container
modal shell
modal shell app.py::my_function
modal shell app.py -c 'pip list'
modal shell --gpu a100:2 --image python:3.12 --volume my-vol
modal container list
modal container exec CONTAINER_ID bash -lc 'ps aux'
modal container stop CONTAINER_ID
modal shell resource flags: --gpu — bare type (any, a10g, a100, h100, l4) or type:N for a count (a100:4, h100:2); --cpu N, --memory MiB, --cloud aws|gcp|oci|auto, --region, --secret NAME (repeatable), --volume NAME (mounts at /mnt/{name}), --add-local PATH.
Auth, profiles, environments
modal setup
modal token new
modal token set --token-id ID --token-secret SECRET --profile NAME
modal token info
modal profile list
modal profile activate NAME
modal profile current
modal config show
modal config set-environment NAME
modal environment list
modal environment create NAME
modal environment delete NAME -y
Data primitives
modal volume create my-vol
modal volume ls my-vol /path
modal volume put my-vol ./local.bin /remote.bin
modal volume get my-vol /remote.bin ./local.bin
modal volume rm my-vol /remote.bin -r
modal volume cp my-vol SRC... DST -r
modal volume delete my-vol -y
modal secret create my-secret KEY1=val1 KEY2=val2
modal secret create db --from-dotenv .env
modal secret list
modal dict create d ; modal dict get d KEY ; modal dict items d -a ; modal dict clear d -y
modal queue create q ; modal queue len q -t ; modal queue peek q 5 ; modal queue clear q -a -y
Common workflows
Iterate on a web endpoint: modal serve app.py → edit & save → it hot-reloads → hit the -dev URL → modal deploy app.py when happy.
Debug a failing function: reproduce its exact environment with modal shell app.py::broken_fn (same image, GPU, mounts, secrets), poke around, then fix the code.
One-off GPU box: modal shell --gpu h100 --image pytorch/pytorch — no app file needed; you get a shell on a GPU machine billed per second.
Promote dev → prod safely: keep separate environments (modal environment create prod), deploy with -e prod, and modal app rollback if a release misbehaves.
Common mistakes
| Mistake | Fix |
|---|
Putting -e/--env after the subcommand inconsistently | It's a per-command option — modal run -e prod app.py and modal app logs -e prod my-app both work; the env always resolves --env → MODAL_ENVIRONMENT → profile default → workspace default. |
Expecting modal serve URLs to be stable/shareable | Serve URLs carry a -dev suffix and only live while the command runs. Use modal deploy for a permanent URL. |
Editing while modal run (not serve) | run is one-shot — it does not hot-reload. Use modal serve for live iteration. |
Closing the terminal kills a long modal run | Add -d/--detach so the run survives local disconnects; manage it later with modal app stop. |
| Passing function args as positional | Modal maps args to --flag value from the signature: modal run app.py::f --x 1, not modal run app.py::f 1. |
modal volume get to a path when you wanted stdout | Use - as the destination to stream contents to stdout. |
Guessing a command exists (nfs, launch) | The CLI evolves. Run modal --help and modal <cmd> --help, or check https://modal.com/docs/reference/cli/run for the current tree. |
Discovery
modal --help lists the full command set (also: billing, image, dashboard, changelog). Every command and subcommand supports --help for exact, version-correct flags. CLI reference: https://modal.com/docs/reference/cli/run — Guide: https://modal.com/docs/guide