Modal backend
Deploy Faheem Code on Modal as a remote backend for Faheem Code. Canvas runs locally on your machine while the Faheem Code Backend runs on Modal and executes code inside the container—the same execution model as the backend started by npx @faheem-code/app.
When to use it
A Modal backend is a good fit when you want to:
- Offload agent execution to the cloud without managing your own VM or Docker host
- Take advantage of Modal's per-second billing and free-tier credits
- Get a persistent, always-warm backend with minimal setup — or scale to zero when idle to reduce costs
Prerequisites
- A Modal account (free tier includes $30/month credit)
- Python 3.12+
- Faheem Code running locally — see Setup
- An LLM API key (OpenAI, Anthropic, etc.)
1. Install the Modal CLI
pip install modal
modal setup
modal setup opens a browser to authenticate. Your credentials are saved to \~/.modal.toml.
2. Create a Modal secret
Generate an API key and encryption key, then store them as a Modal secret:
export API_KEY=$(openssl rand -base64 32)
modal secret create faheem-code-server-keys \
FC_SESSION_API_KEYS_0="$API_KEY" \
FC_SECRET_KEY="$(openssl rand -base64 32)"
echo "Save this — you'll need it to connect Canvas:"
echo " API Key: $API_KEY"
This secret persists in your Modal account. You only need to create it once.
3. Deploy
Save the following as deploy.py:
"""
Deploy Faheem Code Agent Server on Modal.
Prerequisites:
- Modal account + CLI: pip install modal && modal setup
- Create a Modal secret named "faheem-code-server-keys" with:
modal secret create faheem-code-server-keys \
FC_SESSION_API_KEYS_0="$(openssl rand -base64 32)" \
FC_SECRET_KEY="$(openssl rand -base64 32)"
Usage:
modal deploy deploy.py
# Dry run (validate config without deploying):
modal run deploy.py
"""
import os
import subprocess
import modal
# --- Configuration ---
# Agent-server image tag — must match a published ghcr.io/alsairy/faheem-code-agent-server tag.
# CI publishes the `binary` target with variant suffix: {version}-python.
# Includes Python, Node.js 22, tmux, git, uv, and the PyInstaller-built
# agent-server binary at /usr/local/bin/faheemcode-agent-server.
AGENT_SERVER_IMAGE_TAG = "1.24.0-python"
AGENT_SERVER_PORT = 8000
SCALEDOWN_WINDOW = 600 # seconds before an idle container is eligible for shutdown
CONTAINER_CPU = 2.0
CONTAINER_MEMORY_MB = 4096 # 4 GB
# Always-on mode (default): keeps one container warm at all times for zero
# cold-start latency. Costs ~$102/month (2 vCPU / 4 GB, 24/7).
# Set MODAL_ALWAYS_ON=0 to scale to zero when idle. You only pay while
# actively coding, but the first request after idle has a ~10-30s cold start.
ALWAYS_ON = os.environ.get("MODAL_ALWAYS_ON", "1").lower() in ("1", "true")
MIN_CONTAINERS = 1 if ALWAYS_ON else 0
# --- Modal App ---
app = modal.App("faheemcode-agent-server")
# Persistent volume for ~/.faheem-code (conversations, settings, secrets, DB).
# Survives container restarts and redeploys.
volume = modal.Volume.from_name("faheem-code-data", create_if_missing=True)
VOLUME_MOUNT = "/home/faheemcode/.faheem-code"
# Secrets: FC_SESSION_API_KEYS_0 (auth) and FC_SECRET_KEY (encryption at rest).
# Create once with: modal secret create faheem-code-server-keys ...
secrets = modal.Secret.from_name("faheem-code-server-keys")
# --- Image ---
# canvas_ui_tool.py is required by the agent-server but ships with faheem-code,
# not the standalone server image. Fetch it from GitHub during image build.
TOOLS_REMOTE_DIR = "/opt/canvas-tools"
CANVAS_UI_TOOL_URL = "https://raw.githubusercontent.com/alsairy/faheem-code-app/main/tools/canvas_ui_tool.py"
agent_server_image = (
modal.Image.from_registry(
f"ghcr.io/alsairy/faheem-code-agent-server:{AGENT_SERVER_IMAGE_TAG}",
add_python="3.13",
)
.dockerfile_commands(
# Clear the image's ENTRYPOINT so Modal manages the process lifecycle.
["ENTRYPOINT []"],
)
.run_commands(
f"mkdir -p {TOOLS_REMOTE_DIR} && curl -fsSL -o {TOOLS_REMOTE_DIR}/canvas_ui_tool.py {CANVAS_UI_TOOL_URL}",
)
.env({"FC_EXTRA_PYTHON_PATH": TOOLS_REMOTE_DIR})
)
# --- Agent Server ---
@app.cls(
image=agent_server_image,
secrets=[secrets],
volumes={VOLUME_MOUNT: volume},
cpu=CONTAINER_CPU,
memory=CONTAINER_MEMORY_MB,
scaledown_window=SCALEDOWN_WINDOW,
timeout=3600,
# The agent-server is stateful (SQLite DB, tmux sessions, in-memory
# conversation state) — multiple containers would diverge.
# min_containers is controlled by MODAL_ALWAYS_ON (default: 1, always warm).
min_containers=MIN_CONTAINERS,
max_containers=1,
)
@modal.concurrent(max_inputs=10)
class AgentServer:
@modal.web_server(port=AGENT_SERVER_PORT, startup_timeout=300)
def serve(self):
cmd = [
"/usr/local/bin/faheemcode-agent-server",
"--host", "0.0.0.0",
"--port", str(AGENT_SERVER_PORT),
]
print(f"Starting agent-server on port {AGENT_SERVER_PORT}...")
subprocess.Popen(cmd)
# --- Dry-run entrypoint: modal run deploy.py ---
@app.local_entrypoint()
def main():
mode = "always-on" if ALWAYS_ON else "scale-to-zero"
print("Faheem Code Agent Server — Modal deployment")
print(f" Image: ghcr.io/alsairy/faheem-code-agent-server:{AGENT_SERVER_IMAGE_TAG}")
print(f" Volume: faheem-code-data → {VOLUME_MOUNT}")
print(f" Mode: {mode} (min_containers={MIN_CONTAINERS})")
print(f" Scaledown: {SCALEDOWN_WINDOW}s")
print()
print("To deploy:")
print(" modal deploy deploy.py")
if ALWAYS_ON:
print()
print(" # Or, to scale to zero when idle (saves cost, adds cold starts):")
print(" MODAL_ALWAYS_ON=0 modal deploy deploy.py")
print()
print("After deploying, add the backend in Faheem Code:")
print(" 1. Open Faheem Code")
print(" 2. Go to Manage backends → Add a backend")
print(" 3. Enter:")
print(" Name: Modal Agent Server")
print(" Host: https://faheemcode-agent-server--agentserver-serve.modal.run")
print(" API Key: <your FC_SESSION_API_KEYS_0 value>")
Then deploy:
modal deploy deploy.py
Modal builds the container image on first deploy (takes a few minutes), then prints the serving URL:
https://faheemcode-agent-server--agentserver-serve.modal.run
The agent server runs on 2 vCPU / 4 GB RAM with a persistent volume for conversations and settings. By default, the container is always warm (min_containers=1) so there's no cold-start latency. To scale to zero when idle instead (lower cost, but ~10-30s cold start on first request):
MODAL_ALWAYS_ON=0 modal deploy deploy.py
See Cost for a comparison of the two modes.
4. Connect Faheem Code
- Open Faheem Code locally (
npx @faheem-code/app). - Click the backend switcher → Manage Backends → Add Backend.
- Fill in:
- Name — e.g.
Modal - Host / Base URL — the URL from step 3 (e.g.
https://faheemcode-agent-server--agentserver-serve.modal.run) - API Key — the
API_KEYvalue from step 2
- Name — e.g.
- Save and select it as the active backend.
5. Configure your LLM
The agent server doesn't come with LLM credentials — you provide them once through the Canvas UI:
- With the Modal backend selected, open Settings.
- Choose a provider (e.g. OpenAI, Anthropic).
- Enter your API key and select a model.
- Save.
Settings are stored server-side on the Modal volume (encrypted with FC_SECRET_KEY) and persist across redeploys.
Cost
Modal charges per-second for CPU and memory. The MODAL_ALWAYS_ON setting controls whether the container stays warm between requests:
| Always-on (default) | Scale-to-zero (MODAL_ALWAYS_ON=0) | |
|---|---|---|
| Cold starts | None | ~10-30s after idle period |
| Idle behavior | Container stays warm 24/7 | Scales down after 10 min idle |
| Best for | Daily driver, fast iteration | Occasional use, cost-sensitive |
| Monthly cost | ~$102 (24/7) | Pay only for active hours |
Hourly rate breakdown (2 vCPU / 4 GB):
| Resource | Rate |
|---|---|
| 2 vCPU (1 physical core) | ~$0.096/hr |
| 4 GB RAM | ~$0.046/hr |
| Total | ~$0.14/hr |
Always-on costs ~$3.40/day (~$102/month). Modal's $30/month free credit covers about 9 days.
Scale-to-zero costs only for the hours the container is running. At 8 hours/day on workdays, that's roughly ~$1.12/day (~$25/month). The first request after an idle period takes ~10-30s while the container cold-starts; after that, the scaledown_window (10 min) keeps it warm between interactions.
To stop the deployment entirely and avoid all charges: modal app stop faheemcode-agent-server. Your data on the Modal volume persists.
Limitations
- No Docker-in-Docker. Modal containers don't support nested Docker. The agent executes code directly on the container filesystem (same model as running
npx @faheem-code/applocally). Tools that require Docker won't work. - Single-user only. Pinned to one container (
max_containers=1) because the agent server uses SQLite and in-memory state that can't be shared across containers. - Public URL. The
*.modal.runendpoint is internet-reachable. All API endpoints require the API key, but the URL itself is public.
Security
The agent server is protected by the API key you created in step 2. Every REST and WebSocket request is rejected without it. Modal provides TLS on all *.modal.run endpoints automatically.
The *.modal.run URL is not indexed or easily guessable, but treat it as sensitive — it appears in terminal output, browser history, and Canvas localStorage.
Rotating the API key
If you suspect the API key has been leaked:
export API_KEY=$(openssl rand -base64 32)
modal secret create faheem-code-server-keys --force \
FC_SESSION_API_KEYS_0="$API_KEY" \
FC_SECRET_KEY="$(openssl rand -base64 32)"
modal deploy deploy.py
echo "New API Key: $API_KEY"
Then update the API key in Faheem Code — click the backend switcher → Manage Backends → edit the Modal backend → paste the new key.
Upgrading
To update to a newer agent-server version, change AGENT_SERVER_IMAGE_TAG in deploy.py to the desired tag (e.g. 1.25.0-python) and redeploy:
modal deploy deploy.py
Modal rebuilds the container image with the new version. Your data on the Modal volume (conversations, settings, LLM credentials) is preserved.
Available tags are listed at ghcr.io/alsairy/faheem-code-agent-server. Use the -python variant.
Troubleshooting
Check the server logs:
modal app logs faheemcode-agent-server
List running apps to confirm the deployment is active:
modal app list
If the container is crashing or unresponsive, redeploy to force a fresh start:
modal deploy deploy.py
Your data on the Modal volume persists across redeploys.
Tearing down
To stop the deployment and stop incurring costs:
modal app stop faheemcode-agent-server
Your data on the Modal volume (faheem-code-data) is preserved. Redeploy later with modal deploy deploy.py and everything picks up where you left off. To permanently delete the volume:
modal volume delete faheem-code-data