A simplified serverless function platform inspired by AWS Lambda. Write Python functions in a web editor, deploy them instantly, and get HTTP endpoints to invoke them. Organize functions into projects with environment variables, pip dependencies, managed databases, and an API gateway, all from a single dashboard.
Built to learn how serverless cloud infrastructure works under the hood.
- How It Works
- Features
- Comparison to Vercel and AWS
- Tech Stack
- Project Structure
- Setup Guide
- API Endpoints
- Security Model
- Contributing
Write Python code in Monaco editor (browser)
|
v
Save to SQLite via FastAPI
(each save creates a new FunctionVersion row;
the function points at its active_version)
|
+---------+---------+
| |
Direct invoke Gateway invoke
POST /api/invoke/:id ANY /api/gateway/:slug/:path
| |
| Match route (method + path pattern)
| Extract path params (:id -> {id: "123"})
| |
+---------+---------+
|
v
Resolve execution context
(env vars, custom image, DATABASE_URL)
|
v
Invoke Service orchestrates the call
|
+---------+---------+
| |
Assignment Service Placement Service
(warm container (cold start: build image
pool, keyed by if needed, create new
image+network) container)
| |
+---------+---------+
|
v
Worker Service
(docker exec into the container)
|
v
Inject into container:
- Active version code as /app/function.py
- Input data as INPUT_JSON env var
- Project env vars (KEY=value)
- DATABASE_URL (if Neon database provisioned)
|
v
Run with resource limits:
- 128MB RAM, 0.5 CPU, 30s timeout
- Network: disabled by default,
opt-in per function via toggle
|
v
Capture stdout (JSON result)
Release container back to warm pool
(or destroy if pool is full)
|
v
Log invocation (input, output, status, duration)
Return result to caller
-
Projects: Organize functions into projects with auto-generated URL slugs. Each project gets its own environment variables, pip dependencies, database, and API routes.
-
Functions: Create, edit, and delete Python functions through a Monaco code editor (same editor as VS Code). Test functions with JSON input directly from the browser and see results immediately.
-
Function Versions: Every code save creates a new immutable version. A version dropdown in the editor lets you browse previous versions and roll back instantly by setting an older version as active -- no redeploy step. Each version stores its full code snapshot in the
function_versionstable. -
Per-Function Network Toggle: Functions run with
network_disabled=Trueby default. Flip the toggle in the function detail page to allow outbound network access for that specific function (for calling third-party APIs, fetching data, etc.). The warm container pool keys on network setting so toggling does not bleed network state between functions. -
Warm Container Pool: An AWS Lambda-style invocation pipeline split into Assignment Service (warm container pool with LRU eviction and idle reaper), Placement Service (cold start container creation), and Worker Service (
docker execinto a container to run user code). Warm hits skip container creation entirely, dramatically reducing per-invocation latency. -
API Gateway: Map HTTP routes to functions using method + path patterns with parameter extraction. Define routes like
GET /users/:idand Clowdy matches incoming requests, extracts parameters, and invokes the right function with a structured event object containing method, path, params, query, headers, and body. -
Environment Variables: Per-project key-value pairs injected into function containers at runtime. Mark variables as secret to hide values in the UI. Access them via
os.environ["KEY"]in your functions. -
pip Dependencies: Per-project
requirements.txtsupport. When you save dependencies, Clowdy builds a custom Docker image extending the base runtime with your packages installed. Images are cached by content hash -- unchanged requirements skip the build. -
Managed Databases: One-click PostgreSQL database provisioning via Neon. The connection string is automatically injected as
DATABASE_URLinto every function container in the project. No configuration needed -- justimport psycopg2; conn = psycopg2.connect(os.environ["DATABASE_URL"]). -
AI Assistant: Chat panel powered by Groq (Llama 4 Scout) with tool calling. The AI can create, invoke, update, delete, and inspect functions through natural language. Ask it to "create a function that reverses a string" and it writes the code, deploys it, and gives you the invoke URL.
-
Invocation Logs: Every function execution is logged with input data, output data, status (success/error/timeout), duration in milliseconds, source (direct or gateway), and HTTP method/path for gateway calls. View the 50 most recent logs per function.
-
Dashboard: Overview stats showing total functions, total invocations, success rate percentage, and average execution duration. Quick access to recent projects.
| Dimension | Clowdy | Vercel Serverless Functions | AWS Lambda |
|---|---|---|---|
| Runtimes | Python | Node.js, Python, Go, Ruby | 7+ languages |
| Isolation | Docker containers | V8 isolates / microVMs | Firecracker microVMs |
| API Gateway | Built-in route matching with path params | Filesystem routing (Next.js) | Separate API Gateway service |
| Versioning | Auto-versioned, one-click rollback | Git commit per deploy | Versions + aliases |
| Warm starts | Container pool (Assignment Service) | V8 isolate reuse | Firecracker microVM reuse |
| Network egress | Per-function toggle (default off) | Always on | VPC config / always on |
| Env vars | Per-project UI with secret masking | Dashboard per-environment | Console / SSM / Secrets Manager |
| Database | One-click Neon PostgreSQL | Neon/Supabase integration (separate) | RDS/DynamoDB (separate services) |
| AI assistant | Built-in (create/invoke/manage functions) | None | Amazon Q (separate service) |
| Code editor | Built-in Monaco editor | None (deploy from repo) | AWS Cloud9 (separate service) |
| Auth | Clerk JWT | Vercel Auth | IAM / Cognito |
| Pricing | Free (self-hosted) | Free tier, then per-invocation | Free tier, then per-invocation |
| Use case | Learning, prototyping | Production web apps | Production at scale |
Clowdy is not a production competitor to Vercel or AWS. It is a learning project that implements the core concepts of serverless platforms in a simple, understandable codebase.
| Layer | Technology | Role |
|---|---|---|
| Frontend | React 19, TypeScript, Vite | SPA framework and build tool |
| Styling | Tailwind CSS v4, shadcn/ui | Utility-first CSS and component library |
| Routing | React Router v7 | Client-side page routing |
| Code Editor | Monaco Editor (React) | In-browser Python code editing |
| Auth (Frontend) | Clerk React SDK | Sign-in/sign-up UI and JWT tokens |
| Backend | FastAPI, Python 3.10+ | Async API framework |
| ORM | SQLAlchemy 2.x (async) | Database models and queries |
| Database | SQLite (aiosqlite) | Application data storage |
| Migrations | Alembic | Schema versioning (auto-runs on startup) |
| Validation | Pydantic | Request/response schema validation |
| Auth (Backend) | PyJWT + Clerk JWKS | JWT signature verification |
| Execution | Docker SDK for Python | Container lifecycle management |
| Runtime Image | python:3.12-slim | Base image for function containers |
| AI | Groq API (Llama 4 Scout) | LLM with tool calling for the AI assistant |
| Managed DB | Neon REST API v2 | PostgreSQL database provisioning |
| HTTP Client | httpx | Async HTTP calls to Neon API |
clowdy/
backend/
app/
main.py # FastAPI app, lifespan, CORS, router registration
auth.py # Clerk JWT verification (get_current_user)
config.py # Environment variables (GROQ, CLERK, NEON keys)
database.py # SQLAlchemy async engine and session
models.py # ORM models (Project, Function, FunctionVersion, Invocation, EnvVar, Route)
schemas.py # Pydantic request/response schemas
routers/
projects.py # Project CRUD
functions.py # Function CRUD + version listing and rollback
invoke.py # Function execution and invocation logs
chat.py # AI agent chat endpoint
env_vars.py # Environment variable management
routes.py # HTTP route definitions
requirements.py # pip dependency management
database.py # Neon database provisioning
gateway.py # HTTP API gateway with route matching
services/ # AWS Lambda-style execution pipeline
invoke_service.py # Orchestrator: warm-or-cold path, exec, release
assignment_service.py # Warm container pool, LRU eviction, idle reaper
placement_service.py # Cold start container creation
worker_service.py # docker exec runner (no container lifecycle)
context.py # Resolves env vars, image, DATABASE_URL per invoke
image_builder.py # Custom Docker image building for pip deps
ai_agent.py # Groq integration and tool definitions
neon_service.py # Neon PostgreSQL API client
docker/
runtimes/
python/
Dockerfile # Base runtime image (python:3.12-slim)
runner.py # Wrapper that imports and calls handler()
alembic/
versions/ # 9 migration files (001-009)
requirements.txt # Python dependencies
.env.local # API keys (git-ignored)
frontend/
src/
pages/
Dashboard.tsx # Home screen with stats
Projects.tsx # Project list
CreateProject.tsx # New project form
ProjectDetail.tsx # Project management (6 tabs)
Functions.tsx # Function list
CreateFunction.tsx # New function form
FunctionDetail.tsx # Function editor, test panel, logs
components/
ui/ # shadcn/ui (button, card, input, label, badge)
layout/ # Layout, Sidebar
functions/ # FunctionCard, CodeEditor (Monaco wrapper)
projects/ # ProjectCard
chat/ # ChatPanel, ChatMessage
auth/ # AuthProvider (Clerk)
lib/
api.ts # Typed API client with Clerk JWT injection
package.json
vite.config.ts
tsconfig.json
- Python 3.10+
- Node.js 18+
- Docker (Docker Desktop, Colima, or similar)
- Clerk account (for authentication)
- Groq account (free, for AI assistant)
- Neon account (free, optional, for managed databases)
cd backend
# Create virtual environment and install dependencies
python -m venv venv
./venv/bin/pip install -r requirements.txt
# Build the base Docker runtime image
cd docker/runtimes/python
docker build -t clowdy-python-runtime .
cd ../../..
# Create .env.local with your API keys
cat > .env.local << 'EOF'
GROQ_API_KEY=gsk_your_key_here
CLERK_JWKS_URL=https://your-instance.clerk.accounts.dev/.well-known/jwks.json
NEON_API_KEY=your_neon_api_key_here
EOF
# Start the development server
./venv/bin/uvicorn app.main:app --reloadThe backend runs at http://localhost:8000. Interactive API docs at http://localhost:8000/docs.
Alembic migrations run automatically on startup -- the database schema is always up to date.
cd frontend
# Install dependencies
npm install
# Start the development server
npm run devThe frontend runs at http://localhost:5173.
# Health check
curl http://localhost:8000/api/health
# Expected: {"status":"ok"}Backend (backend/.env.local):
| Variable | Required | Default | Source |
|---|---|---|---|
DATABASE_URL |
No | sqlite+aiosqlite:///./clowdy.db |
-- |
FRONTEND_URL |
No | http://localhost:5173 |
-- |
GROQ_API_KEY |
Yes | -- | https://console.groq.com/keys |
CLERK_JWKS_URL |
Yes | -- | Clerk dashboard > API Keys |
NEON_API_KEY |
No | -- | https://console.neon.tech/account/api-keys |
Frontend (frontend/.env.local):
| Variable | Required | Default |
|---|---|---|
VITE_API_URL |
No | http://localhost:8000 |
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /api/health |
No | Health check |
| GET | /api/stats |
Yes | Dashboard statistics |
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /api/projects |
Yes | Create a project |
| GET | /api/projects |
Yes | List all projects |
| GET | /api/projects/:id |
Yes | Get a project |
| PUT | /api/projects/:id |
Yes | Update a project |
| DELETE | /api/projects/:id |
Yes | Delete a project |
| GET | /api/projects/:id/functions |
Yes | List functions in a project |
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /api/functions |
Yes | Create a function |
| GET | /api/functions |
Yes | List all functions |
| GET | /api/functions/:id |
Yes | Get a function (returns active version code) |
| PUT | /api/functions/:id |
Yes | Update a function (new code creates a new version) |
| DELETE | /api/functions/:id |
Yes | Delete a function |
| GET | /api/functions/:id/versions |
Yes | List all versions, newest first |
| PUT | /api/functions/:id/versions/:version |
Yes | Set a specific version as active (rollback) |
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /api/invoke/:id |
No | Invoke a function |
| GET | /api/functions/:id/invocations |
No | List invocation logs |
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /api/chat |
Yes | Chat with the AI assistant |
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /api/projects/:id/env |
Yes | List env vars |
| POST | /api/projects/:id/env |
Yes | Set an env var (upsert) |
| DELETE | /api/projects/:id/env/:key |
Yes | Delete an env var |
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /api/projects/:id/routes |
Yes | List routes |
| POST | /api/projects/:id/routes |
Yes | Create a route |
| PUT | /api/projects/:id/routes/:routeId |
Yes | Update a route |
| DELETE | /api/projects/:id/routes/:routeId |
Yes | Delete a route |
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /api/projects/:id/requirements |
Yes | Get requirements and build status |
| PUT | /api/projects/:id/requirements |
Yes | Update requirements and rebuild image |
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /api/projects/:id/database |
Yes | Get database status |
| POST | /api/projects/:id/database/provision |
Yes | Provision a Neon database |
| DELETE | /api/projects/:id/database/deprovision |
Yes | Delete the Neon database |
| Method | Path | Auth | Description |
|---|---|---|---|
| ANY | /api/gateway/:slug |
No | Route to project root |
| ANY | /api/gateway/:slug/*path |
No | Route with path matching |
Every function runs in a Docker container with strict limits:
- Network off by default --
network_disabled=Trueunless the function explicitly opts in via the per-function network toggle. The warm pool keys on this setting so a network-enabled function can never reuse a network-disabled container or vice versa. - Memory cap -- 128MB limit prevents memory exhaustion
- CPU cap -- 0.5 cores prevents CPU monopolization
- Timeout -- 30 seconds maximum, container killed after
- No volume mounts -- code copied via
put_archive, no host filesystem access - Warm pool reuse -- containers are kept warm for fast invocations but capped in size (LRU eviction) and reaped after idle timeout. Code is copied in fresh on every invocation so versions cannot leak between calls.
- Protected endpoints require a valid Clerk JWT in the
Authorization: Bearer <token>header - Backend verifies token signatures using Clerk's JWKS endpoint (RS256)
- User ID extracted from the token's
subclaim - Each user can only see and manage their own projects and functions
These endpoints are intentionally public (no auth required):
/api/health-- health check/api/invoke/:id-- function invocation (anyone with the function ID can invoke)/api/gateway/:slug/*-- API gateway (anyone with the project slug can call routes)/api/functions/:id/invocations-- invocation logs
- Environment variables marked as
secretdisplay as****in the UI - Database connection strings have passwords replaced with
***in API responses - Real values are always injected into function containers at runtime
- Fork the repository
- Create a feature branch (
git checkout -b feature/your-feature) - Make your changes
- Run the backend (
./venv/bin/uvicorn app.main:app --reload) and frontend (npm run dev) to verify - Commit and push
- Open a pull request
- No emojis in code, comments, or commit messages
- Python: type hints, async/await, Black formatting
- TypeScript: strict mode, explicit types for API responses
- Create
backend/app/routers/your_router.pywith anAPIRouter - Import and register it in
backend/app/main.py - Add Pydantic schemas in
backend/app/schemas.py - Add TypeScript types and API methods in
frontend/src/lib/api.ts
cd backend
./venv/bin/alembic revision -m "description_of_change"Edit the generated file in alembic/versions/, then restart the server -- migrations run automatically on startup.