一键导入
policyengine-modal-deployment
Deploying PolicyEngine backend APIs to Modal — workspace setup, authentication, deployment commands, environments, and troubleshooting
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Deploying PolicyEngine backend APIs to Modal — workspace setup, authentication, deployment commands, environments, and troubleshooting
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Recharts chart patterns, formatting, and styling for PolicyEngine apps
PolicyEngine code writing style guide - formula optimization, direct returns, eliminating unnecessary variables
PolicyEngine parameter patterns - YAML structure, naming conventions, metadata requirements, federal/state separation
PolicyEngine code review patterns - validation checklist, common issues, review standards
PolicyEngine testing patterns - YAML test structure, naming conventions, period handling, and quality standards
PolicyEngine variable patterns - variable creation, no hard-coding principle, federal/state separation, metadata standards
| name | policyengine-modal-deployment |
| description | Deploying PolicyEngine backend APIs to Modal — workspace setup, authentication, deployment commands, environments, and troubleshooting |
How to deploy PolicyEngine backend APIs (custom Modal backends for dashboards and interactive tools) to Modal under the PolicyEngine organizational workspace.
This skill applies only when a dashboard or tool uses the custom-backend data pattern. If the project uses api-v2-alpha (stub data or direct API calls), no Modal deployment is needed.
PolicyEngine uses a shared Modal workspace called policyengine. All backend deployments MUST target this workspace — never a personal workspace.
The policyengine workspace has three environments:
| Environment | Web suffix | URL pattern | Purpose |
|---|---|---|---|
main | (empty) | policyengine--<app>-<func>.modal.run | Production |
staging | staging | policyengine-staging--<app>-<func>.modal.run | Pre-production testing |
testing | testing | policyengine-testing--<app>-<func>.modal.run | Development/CI |
Default to main for production deployments.
pip install modalpolicyengine workspace stored in a local profile# Create a token for the policyengine workspace (opens browser)
modal token new --profile policyengine
# Activate the profile
modal profile activate policyengine
# Verify — must show "Workspace: policyengine"
modal token info
HUMAN GATE: Before any deployment, verify the active workspace:
modal token info
modal profile list
Expected output from modal profile list:
┏━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┓
┃ ┃ Profile ┃ Workspace ┃
┡━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━┩
│ • │ policyengine │ policyengine │
└───┴──────────────┴──────────────┘
The • indicates the active profile. If policyengine is not active or not present:
Authentication required. Your Modal CLI is not configured for the
policyengineworkspace.Run:
modal token new --profile policyengine modal profile activate policyengineIf you don't have access to the PolicyEngine workspace, ask a workspace owner to invite you at https://modal.com/settings/policyengine
Do NOT proceed with deployment until modal token info shows Workspace: policyengine.
Modal CLI respects MODAL_TOKEN_ID and MODAL_TOKEN_SECRET environment variables, which override the profile. If these are set (e.g., from a CI environment), the CLI will deploy to whatever workspace those tokens belong to — potentially a personal workspace.
Always unset before deploying:
unset MODAL_TOKEN_ID MODAL_TOKEN_SECRET
The Modal app name MUST match the dashboard/tool repo name in kebab-case:
app = modal.App("my-dashboard-name")
This keeps the app name consistent with the GitHub repo and Vercel project.
Build a container image with the required Python packages:
image = (
modal.Image.debian_slim(python_version="3.12")
.pip_install(
"policyengine-us>=1.155.0", # Pin minimum version
"fastapi[standard]",
"numpy",
"pandas",
)
.env({"NUMEXPR_MAX_THREADS": "4"})
.add_local_dir("api", "/root/api") # Local source code
.add_local_file("config.yaml", "/root/config.yaml") # Config files
)
Guidelines:
debian_slim with Python 3.12 or 3.13policyengine-us / policyengine-uk.add_local_dir() / .add_local_file() for project source code.env() for non-secret environment variablesFor tools with one calculation endpoint:
@app.function(image=image, timeout=300, memory=2048)
@modal.web_endpoint(method="POST")
def calculate(data: dict) -> dict:
from policyengine_us import Simulation
# Build simulation from data, return results
return {"result": value}
URL: https://policyengine--<app-name>-calculate.modal.run
For dashboards with multiple API routes:
@app.function(image=image, timeout=300, memory=2048)
@modal.concurrent(max_inputs=100)
@modal.asgi_app()
def fastapi_app():
from api.main import app as api
return api
URL: https://policyengine--<app-name>-fastapi-app.modal.run
Every Modal backend SHOULD include a health check:
@app.function(image=image)
@modal.web_endpoint(method="GET")
def health():
return {"status": "ok"}
| Parameter | Default | Recommended for PE | Purpose |
|---|---|---|---|
timeout | 60s | 300 | PolicyEngine simulations can take minutes |
memory | 128MB | 2048 | PE models are memory-intensive |
@modal.concurrent(max_inputs=N) | 1 | 100 | Handle concurrent requests without cold starts |
Store sensitive values as Modal Secrets (not in code or .env files):
# Create a secret
modal secret create my-secret API_KEY=abc123
# List secrets
modal secret list
Reference in code:
@app.function(
image=image,
secrets=[modal.Secret.from_name("my-secret")],
)
def my_function():
import os
api_key = os.environ["API_KEY"] # Injected by Modal
Existing secrets in the policyengine workspace:
policyengine-logfire — logging/observabilitygcp-credentials — Google Cloud accesshuggingface-token — HuggingFace model accessanthropic-api-key — Anthropic API access# 1. Ensure correct workspace
unset MODAL_TOKEN_ID MODAL_TOKEN_SECRET
modal token info # Verify "Workspace: policyengine"
# 2. Deploy to production
modal deploy modal_app.py --env main
# 3. Deploy to staging (for testing)
modal deploy modal_app.py --env staging
Flags:
--env main / --env staging / --env testing — target environment--name TEXT — override the deployment name (rarely needed)--tag TEXT — tag the deployment with a version string--stream-logs — stream container logs during deployment# List deployed apps
modal app list --env main
# Health check
curl -s -w "\n%{http_code}" https://policyengine--DASHBOARD_NAME-health.modal.run
# Test the endpoint
curl -X POST https://policyengine--DASHBOARD_NAME-calculate.modal.run \
-H "Content-Type: application/json" \
-d '{"test": true}'
After Modal deployment, set the API URL as an environment variable in the Vercel project:
vercel env add NEXT_PUBLIC_API_URL production
# Enter: https://policyengine--DASHBOARD_NAME-calculate.modal.run
vercel --prod --force --yes --scope policy-engine
The --force flag is required to rebuild with the new environment variable.
Redeploying an existing app is the same command — Modal handles zero-downtime transitions:
modal deploy modal_app.py --env main
WARNING: This is destructive and irreversible. A stopped app cannot be restarted; you must redeploy.
modal app stop <app-name>
# View logs for a deployed app
modal app logs <app-name>
# List all apps and their status
modal app list --env main
App states:
deployed — running and accepting requestsephemeral — temporary (from modal serve)stopped — permanently stopped| Issue | Cause | Fix |
|---|---|---|
modal token info shows wrong workspace | Wrong profile active | modal profile activate policyengine |
| Deploy goes to personal workspace | MODAL_TOKEN_ID env var set | unset MODAL_TOKEN_ID MODAL_TOKEN_SECRET |
| 404 on endpoint URL | App stopped or never deployed | modal deploy modal_app.py --env main |
| Cold start latency (5-15s) | No warm containers | Add @modal.concurrent(max_inputs=100) |
MemoryError during simulation | Container memory too low | Increase memory= (try 4096) |
| Timeout error | Simulation exceeds limit | Increase timeout= (try 600) |
| Python dependency conflict | Version mismatch | Pin exact versions in .pip_install() |
| Missing local files in container | Forgot .add_local_dir() | Add source dirs to image definition |
| Modal app silently disappeared | Unknown — can happen | curl the URL; if 404, redeploy |
policyengine workspaceMODAL_TOKEN_ID / MODAL_TOKEN_SECRET env vars set when deploying via profilemodal run for production deployments — use modal deploymodal serve for production — it creates ephemeral apps that stop when you close your terminalmodal token info