Concept Mapping
| Modal | fal | Notes |
|---|---|---|
@app.function() | @fal.function() | Standalone serverless functions |
@app.cls() | class MyApp(fal.App) | Class-based apps (recommended) |
@modal.method() | @fal.endpoint("/") | fal uses HTTP endpoints, not RPC |
@modal.enter() | def setup(self) | Container startup hook |
@modal.exit() | def teardown(self) | Container shutdown hook |
modal.Image.debian_slim().pip_install(...) | requirements = [...] | Or use ContainerImage for Dockerfiles |
modal.Image.from_dockerfile(...) | ContainerImage.from_dockerfile(...) | Custom container support |
gpu="A100" | machine_type = "GPU-A100" | GPU selection |
modal.Volume | /data persistent storage | Mounted automatically on all runners |
modal.Secret | fal secrets set | Secrets exposed as env vars |
modal deploy | fal deploy | CLI deployment |
Cls().method.remote(x=123) | fal_client.subscribe(...) | fal uses HTTP + queue, not RPC |
Migration Path 1: Standalone Functions
If you use@app.function() in Modal, the closest fal equivalent is @fal.function().
- Modal
- fal
Migration Path 2: Class-Based Apps (Recommended)
If you use@app.cls() with @modal.enter() and @modal.method(), convert to a fal.App class.
- Modal
- fal
Deploying
Calling Your App
Key Differences
HTTP Endpoints vs RPC
Modal uses.remote() to invoke functions as Python-to-Python RPC calls. fal uses HTTP endpoints that any language or tool can call. Your fal App exposes a REST API automatically.
This means:
- No
.remote()calls - clients usefal_client.subscribe()or raw HTTP - Your endpoints accept and return JSON (use Pydantic models for validation)
- The same endpoint works from Python, JavaScript, cURL, or any HTTP client
Queue-Based by Default
fal provides a persistent queue that handles retries, scaling, and load balancing automatically. When callers usesubscribe or submit, requests go through this queue by default. Direct calls via run bypass the queue for lower overhead.
Container Images
Modal chains image methods (Image.debian_slim().pip_install(...).apt_install(...)). fal offers two approaches:
requirementslist (simpler): Just list your pip packagesContainerImage(full control): Bring your own Dockerfile
Volumes and Storage
Modal uses namedVolume objects mounted at specific paths. fal provides /data — a persistent filesystem automatically mounted on every runner, shared across all your apps.
Secrets
Modal usesmodal.Secret.from_name(...) and attaches secrets to functions. fal exposes secrets as environment variables:
os.environ["HF_TOKEN"] in your code.