Skip to content

islamborghini/clowdy

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

61 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Clowdy

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.

Table of Contents

How It Works

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

Features

  • 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_versions table.

  • Per-Function Network Toggle: Functions run with network_disabled=True by 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 exec into 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/:id and 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.txt support. 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_URL into every function container in the project. No configuration needed -- just import 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.

Comparison to Vercel and AWS

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.

Tech Stack

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

Project Structure

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

Setup Guide

Prerequisites

  • 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)

Backend

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 --reload

The 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.

Frontend

cd frontend

# Install dependencies
npm install

# Start the development server
npm run dev

The frontend runs at http://localhost:5173.

Verify

# Health check
curl http://localhost:8000/api/health
# Expected: {"status":"ok"}

Environment Variables Reference

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

API Endpoints

Health and Stats

Method Path Auth Description
GET /api/health No Health check
GET /api/stats Yes Dashboard statistics

Projects

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

Functions

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)

Invocation

Method Path Auth Description
POST /api/invoke/:id No Invoke a function
GET /api/functions/:id/invocations No List invocation logs

AI Chat

Method Path Auth Description
POST /api/chat Yes Chat with the AI assistant

Environment Variables

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

Routes

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

Dependencies

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

Database

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

API Gateway

Method Path Auth Description
ANY /api/gateway/:slug No Route to project root
ANY /api/gateway/:slug/*path No Route with path matching

Security Model

Container Isolation

Every function runs in a Docker container with strict limits:

  • Network off by default -- network_disabled=True unless 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.

Authentication

  • 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 sub claim
  • Each user can only see and manage their own projects and functions

Public Endpoints

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

Secret Handling

  • Environment variables marked as secret display as **** in the UI
  • Database connection strings have passwords replaced with *** in API responses
  • Real values are always injected into function containers at runtime

Contributing

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/your-feature)
  3. Make your changes
  4. Run the backend (./venv/bin/uvicorn app.main:app --reload) and frontend (npm run dev) to verify
  5. Commit and push
  6. Open a pull request

Code Style

  • No emojis in code, comments, or commit messages
  • Python: type hints, async/await, Black formatting
  • TypeScript: strict mode, explicit types for API responses

Adding a New Router

  1. Create backend/app/routers/your_router.py with an APIRouter
  2. Import and register it in backend/app/main.py
  3. Add Pydantic schemas in backend/app/schemas.py
  4. Add TypeScript types and API methods in frontend/src/lib/api.ts

Adding a Migration

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.

About

Simplified serverless platform for Python. Editor + deploy + invoke + gateway + versions + managed DB, all from one dashboard

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors