DEV Community

Umut Tan
Umut Tan

Posted on

ComfyDeploy Local Development Setup Guide

Getting ComfyDeploy running locally is a bunch of work; but is a dream, and a good helpful one; in which you gotta spend all your weekend just to get things running...

What You'll See

By completing this guide, you'll gain hands-on experience with:

Full-Stack Development:

  • 🚀 Modern Web Stack: React/Vite frontend + Python FastAPI backend
  • Database Management: PostgreSQL with Drizzle ORM, schema design, and migrations
  • Authentication: Clerk integration with JWT validation
  • Billing & Subscriptions: Autumn API for SaaS monetization and feature limits
  • Containerization: Docker Compose for local development infrastructure

Cloud & Serverless Architecture:

  • Serverless Computing: Deploy GPU-accelerated Python functions with Modal
  • Persistent Storage: Modal volumes for model storage and file management
  • Cloud Storage: AWS S3 integration for file uploads and assets
  • Webhooks & Tunneling: ngrok for local development with cloud callbacks

DevOps & Infrastructure:

  • Monorepo Management: Turborepo for managing multiple applications
  • Environment Configuration: Managing secrets and API keys across services
  • CI/CD Concepts: Understanding deployment pipelines and environments
  • Debugging: Reading logs, troubleshooting errors, and fixing common issues

AI/ML Deployment:

  • ComfyUI Integration: Running AI image generation workflows in production
  • GPU Management: Serverless GPU allocation and concurrency limits
  • Usage Tracking: Monitoring GPU credits and resource consumption

Free Credits & Resources:

  • 💰 $5 USD in Modal Credits: Sign up at https://modal.com/ to get free credits for GPU compute
  • 🎯 Run Real Workflows: Deploy and execute ComfyUI workflows on production-grade infrastructure
  • 🧪 Experiment with AI Models: Test different models, custom nodes, and workflow configurations
  • 📚 Production-Ready Codebase: Learn from a real SaaS application architecture

Skills You'll Develop:

  • Setting up complex development environments with multiple services
  • Integrating third-party APIs (Clerk, Autumn, AWS, Modal, GitHub)
  • Managing database schemas and foreign key relationships
  • Debugging full-stack applications across frontend, backend, and cloud services
  • Understanding SaaS billing models and feature gating
  • Working with serverless architectures and GPU compute

By the end of this guide, you'll have a fully functional local development environment for ComfyDeploy, ready to build features, fix bugs, and deploy AI workflows! 🚀

Prerequisites

Required Software & Tools

Install these dependencies before starting:

Core Development Tools:

  • Node.js (v18 or higher) - JavaScript runtime
  • Bun (v1.0+) - Fast JavaScript package manager and runtime
    • macOS: brew install oven-sh/bun/bun
    • Or: curl -fsSL https://bun.sh/install | bash
  • Python (3.12.0 exactly) - Required for API server
    • macOS: brew install python@3.12
    • Or use pyenv: pyenv install 3.12.0
  • uv - Fast Python package manager
    • macOS: brew install uv
    • Or: curl -LsSf https://astral.sh/uv/install.sh | sh

Infrastructure:

  • Docker Desktop - For PostgreSQL and Redis
  • PostgreSQL Client (psql) - For database management
    • macOS: brew install postgresql@16
    • Or use the client included with Docker

Cloud & Networking:

  • AWS CLI - For S3 bucket management
    • macOS: brew install awscli
    • Or: curl "https://awscli.amazonaws.com/AWSCLIV2.pkg" -o "AWSCLIV2.pkg" && sudo installer -pkg AWSCLIV2.pkg -target /
  • Modal CLI - For serverless deployment
    • Installed via Python: pip install modal (or included in uv sync)
  • ngrok - For local tunneling

Optional but Recommended:

  • Git (latest version) - Version control
    • macOS: brew install git
  • jq - JSON processor for debugging API responses
    • macOS: brew install jq

Required Service Accounts & API Keys

You'll need accounts and API keys for:

  • Clerk (authentication) - get your publishable key, secret key, and JWT public key
  • Autumn (billing/subscriptions) - get your secret key
  • AWS S3 (file storage) - create a bucket and get your credentials
  • Modal (serverless compute) - create an account and get your token
  • ngrok (for local development) - get your authtoken
  • GitHub (API access) - create a personal access token for fetching ComfyUI version info

Step 1: Clone and Install Dependencies

git clone https://github.com/comfy-deploy/comfydeploy.git
cd comfydeploy

# IMPORTANT: Initialize git submodules to pull in the actual code
# The apps/api and apps/app directories are git submodules
git submodule init
git submodule update

# Install API dependencies (Node.js packages)
cd apps/api
bun install

# Install Python dependencies using uv (faster than pip)
# This creates a virtual environment at apps/api/.venv
uv sync

# Install frontend dependencies
cd ../app
bun install
Enter fullscreen mode Exit fullscreen mode

Note: The repository uses git submodules for apps/api and apps/app. You must run git submodule init && git submodule update to pull in the actual application code. Without this step, the directories will be empty.

Python Dependencies: The API uses pyproject.toml instead of requirements.txt. Use uv sync to install Python dependencies - it will automatically create a virtual environment at apps/api/.venv with Python 3.12.0.

Step 2: Configure Environment Variables

Required Service Accounts & API Keys

Before configuring, sign up for these services and get your API keys:

  1. Clerk (Authentication) - https://clerk.com/

    • Dashboard: https://dashboard.clerk.com/
    • Get: CLERK_PUBLISHABLE_KEY, CLERK_SECRET_KEY, CLERK_PUBLIC_JWT_KEY
    • Go to: Dashboard → Your App → API Keys → Find "JWKS Public Key" section
  2. Autumn (Billing) - https://useautumn.com/

    • Get: AUTUMN_SECRET_KEY
    • Authenticate CLI: npx atmn auth
    • Push config: npx atmn push (after configuring autumn.config.ts)
  3. AWS S3 (File Storage) - https://aws.amazon.com/s3/

    • Create bucket: aws s3 mb s3://comfydeploy-dev-storage --region us-east-1
    • Get credentials from AWS IAM or use existing AWS CLI credentials
    • Get: Access Key ID, Secret Access Key

Example S3 Bucket Configuration:

Here's one way to configure your S3 bucket for ComfyDeploy. This is a simple approach that works, but you may have better security methods (e.g., using presigned URLs, CloudFront, etc.):

1. Access Control List (ACL):

  • Public READ access - Grants READ permission to "AllUsers" for public file access
  • Owner has FULL_CONTROL

2. CORS Configuration:

  • Allows all origins (*)
  • Allows all HTTP methods (GET, PUT, POST, DELETE, HEAD)
  • Allows all headers (*)
  • Exposes ETag header
  • Max age: 3000 seconds

3. Bucket Policy:

  • No specific bucket policy required (can be left empty)

4. Public Access Block:

  • All public access blocks disabled for public file access
  • BlockPublicAcls: false
  • IgnorePublicAcls: false
  • BlockPublicPolicy: false
  • RestrictPublicBuckets: false

5. Bucket Structure:

  • assets/upload/ - For uploaded assets
  • outputs/runs/ - For workflow output files

⚠️ Security Note: This configuration makes the bucket publicly readable by anyone on the internet. This is a simple approach that works for development, but you may want to implement more secure methods like presigned URLs, CloudFront distributions, or bucket policies for production use.

To configure CORS via AWS CLI:

   # Create a cors.json file
   cat > cors.json << 'EOF'
   {
     "CORSRules": [
       {
         "AllowedHeaders": ["*"],
         "AllowedMethods": ["GET", "PUT", "POST", "DELETE", "HEAD"],
         "AllowedOrigins": ["*"],
         "ExposeHeaders": ["ETag"],
         "MaxAgeSeconds": 3000
       }
     ]
   }
   EOF

   # Apply CORS configuration
   aws s3api put-bucket-cors --bucket YOUR-BUCKET-NAME --cors-configuration file://cors.json --region YOUR-REGION

   # Set public read ACL
   aws s3api put-bucket-acl --bucket YOUR-BUCKET-NAME --acl public-read --region YOUR-REGION

   # Disable public access blocks
   aws s3api delete-public-access-block --bucket YOUR-BUCKET-NAME --region YOUR-REGION
Enter fullscreen mode Exit fullscreen mode

To verify your bucket configuration:

   # Check CORS
   aws s3api get-bucket-cors --bucket YOUR-BUCKET-NAME --region YOUR-REGION

   # Check ACL
   aws s3api get-bucket-acl --bucket YOUR-BUCKET-NAME --region YOUR-REGION

   # Check public access block
   aws s3api get-public-access-block --bucket YOUR-BUCKET-NAME --region YOUR-REGION
Enter fullscreen mode Exit fullscreen mode
  1. Modal (Serverless Compute) - https://modal.com/

  2. ngrok (Local Tunneling) - https://ngrok.com/

  3. GitHub (API Access for ComfyUI Version Info) - https://github.com/

    • Settings → Developer settings → Personal access tokens → Tokens (classic)
    • Generate new token (classic) with public_repo scope
    • Get: GITHUB_TOKEN (starts with ghp_)

Configure Environment Files

Copy the example env files and fill in your credentials:

cp apps/api/.env.example apps/api/.env
cp apps/app/.env.example apps/app/.env
Enter fullscreen mode Exit fullscreen mode

Generate Encryption Key

Generate a secret encryption key for the API:

cd apps/api
source .venv/bin/activate
python3 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode('utf-8'))"
Enter fullscreen mode Exit fullscreen mode

Copy the output and add it to apps/api/.env as SECRET_ENCRYPTION_KEY.

Get Clerk JWT Public Key

  1. Go to Clerk Dashboard → Your App → API Keys
  2. Scroll down to the "JWKS Public Key" section
  3. Copy the entire key (starts with -----BEGIN PUBLIC KEY-----)
  4. Add it to apps/api/.env as CLERK_PUBLIC_JWT_KEY

Example Configuration

apps/api/.env (key variables):

ENV=development
DATABASE_URL="postgresql://postgres:postgres@localhost:5480/verceldb"
CLERK_PUBLIC_JWT_KEY="-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----"
CLERK_SECRET_KEY="sk_test_..."
AUTUMN_SECRET_KEY="am_sk_test_..."
SPACES_BUCKET_V2="comfydeploy-dev-storage"
SPACES_ENDPOINT_V2="https://s3.amazonaws.com"
SPACES_REGION_V2="us-east-1"
SPACES_KEY_V2="AKIA..."
SPACES_SECRET_V2="..."
MODAL_ENVIRONMENT="dev"
MODAL_TOKEN_ID="ak-..."
MODAL_TOKEN_SECRET="as-..."
SHARED_MODEL_VOLUME_NAME="comfydeploy-dev-public-models"  # Check with: modal volume list
PUBLIC_MODEL_VOLUME_NAME="comfydeploy-dev-public-models"  # Should match your Modal volume name
NGROK_AUTHTOKEN="..."
GITHUB_TOKEN="ghp_..."
SECRET_ENCRYPTION_KEY="..."
CURRENT_API_URL="https://your-ngrok-url.ngrok-free.dev"  # ⚠️ MUST be ngrok URL, not localhost!
Enter fullscreen mode Exit fullscreen mode

⚠️ CRITICAL: CURRENT_API_URL must be set to your ngrok URL (from Step 5.5), NOT http://localhost:3011. Modal functions run in the cloud and cannot reach localhost. See Step 5.5 for how to get your ngrok URL.

Note: To verify your Modal volume name, run modal volume list and use the exact name that appears in the output. See Step 5.6 for details.

apps/app/.env:

NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY="pk_test_..."
NEXT_PUBLIC_CD_API_URL="http://localhost:3011"
Enter fullscreen mode Exit fullscreen mode

Step 2.5: Set Up Autumn Billing Integration

What is Autumn?

Autumn is a billing and subscription management API for SaaS applications. It handles product definitions, pricing plans, feature limits, and usage tracking. ComfyDeploy uses Autumn to manage different subscription tiers (free, business, enterprise) and enforce feature limits like machine count, workflow limits, and GPU concurrency.

Understanding autumn.config.ts

The file apps/api/autumn.config.ts defines your entire billing structure. Here's what you need to know:

  • Features: Define what users can do (e.g., machineLimit, gpuConcurrencyLimit, workflowLimit)
  • Products: Combine features into subscription tiers (e.g., free, business, enterprise)
  • Pricing: Set monthly/yearly prices and included usage amounts

Example Configuration

Here's a simplified version of what's in the config:

// Define features
export const machineLimit = feature({
  id: "machine_limit",
  name: "Machine Limit",
  type: "continuous_use",
});

export const gpuConcurrencyLimit = feature({
  id: "gpu_concurrency_limit",
  name: "GPU Concurrency Limit",
  type: "continuous_use",
});

// Define products (subscription tiers)
export const free = product({
  id: "free",
  name: "Free",
  is_default: true,
  items: [
    featureItem({
      feature_id: machineLimit.id,
      included_usage: 3,  // Free tier gets 3 machines
    }),
    featureItem({
      feature_id: gpuConcurrencyLimit.id,
      included_usage: 1,  // Can run 1 GPU job at a time
    }),
  ],
});

export const business = product({
  id: "business",
  name: "Business",
  items: [
    pricedFeatureItem({
      feature_id: gpuCredit.id,
      price: 0.01,
      included_usage: 0,
      usage_model: "pay_per_use",
    }),
    featureItem({
      feature_id: machineLimit.id,
      included_usage: 100,  // Business tier gets 100 machines
    }),
    featureItem({
      feature_id: gpuConcurrencyLimit.id,
      included_usage: 10,  // Can run 10 GPU jobs at a time
    }),
  ],
});
Enter fullscreen mode Exit fullscreen mode

Pushing Configuration to Autumn

After updating autumn.config.ts, push your changes to Autumn:

cd apps/api
npx atmn push
Enter fullscreen mode Exit fullscreen mode

This command validates your config and syncs it with the Autumn API. You'll see output confirming which products and features were created/updated.

Verifying Your Setup

Check that plans are active in the Autumn dashboard:

  1. Go to https://dashboard.autumn.dev (or your Autumn instance)
  2. Navigate to Products → verify your products are listed
  3. Check that features have the correct limits
  4. Test by creating a subscription in the dashboard

Common Gotcha: Missing Feature Definitions

If you see "Max Machines Exceeded" errors even though users have paid plans, it's usually because:

  1. The machineLimit feature isn't defined in the product
  2. The included_usage is set too low
  3. The config wasn't pushed to Autumn after changes

Fix: Add the missing feature to your product and run npx atmn push again.

Push Configuration to Autumn:

After setting up your .env file with AUTUMN_SECRET_KEY, push the billing configuration:

cd apps/api
npx atmn push
Enter fullscreen mode Exit fullscreen mode

This will push all features and products defined in autumn.config.ts to your Autumn sandbox environment. You should see:

  • ✅ 20 features pushed (GPU types, credits, limits, etc.)
  • ✅ 10 products pushed (Free, Creator, Deployment, Business plans + add-ons)

View your products at: http://app.useautumn.com/sandbox/products

🔑 GitHub Token Setup

The API uses GitHub's API to fetch the latest ComfyUI and ComfyUI-Deploy version information. Without a GitHub token, the /api/latest-hashes endpoint will fail.

Create a GitHub Classic Token:

  1. Go to https://github.com/settings/tokens
  2. Click "Generate new token" → "Generate new token (classic)"
  3. Give it a descriptive name (e.g., "ComfyDeploy Local Dev")
  4. Select scopes:
    • public_repo (Access public repositories)
  5. Click "Generate token"
  6. Copy the token (starts with ghp_)
  7. Add to apps/api/.env:
   GITHUB_TOKEN="ghp_your_token_here"
Enter fullscreen mode Exit fullscreen mode

Why is this needed?

The API fetches:

  • Latest ComfyUI commit hash from comfyanonymous/ComfyUI
  • Latest ComfyUI release information
  • Latest ComfyUI-Deploy commit hash from bennykok/comfyui-deploy

Without the token, you'll see errors like:

ERROR:api.routes.comfy_node:Error fetching repo info: Illegal header value b'Bearer '
INFO:     127.0.0.1:xxxxx - "GET /api/latest-hashes HTTP/1.1" 404 Not Found
Enter fullscreen mode Exit fullscreen mode

📦 Modal Volume Configuration

What are Modal Volumes?

Modal volumes are persistent storage for your serverless ComfyUI deployments. They store:

  • Public models (shared across all users)
  • Private models (user-specific or organization-specific)
  • Workflow files and custom nodes

Configure Volume Names:

For local development, you need to set the public model volume name in apps/api/.env:

SHARED_MODEL_VOLUME_NAME="comfydeploy-dev-public-models"
PUBLIC_MODEL_VOLUME_NAME="comfydeploy-dev-public-models"
Enter fullscreen mode Exit fullscreen mode

Why is this needed?

Without these environment variables, Modal will fail to create volumes with the error:

InvalidError: Invalid Volume name: ''.
Names may contain only alphanumeric characters, dashes, periods, and underscores,
must be shorter than 64 characters, and cannot conflict with App ID strings.
Enter fullscreen mode Exit fullscreen mode

Volume Naming Convention:

  • Local Development: Use a simple name like comfydeploy-dev-public-models
  • Production: Use organization-specific names like models_org_<org_id>
  • Private Volumes: Automatically named as models_<user_id> or models_org_<org_id>

How Volumes Work:

  1. When you deploy a serverless machine, Modal creates volumes with create_if_missing=True
  2. The public volume is shared across all deployments for common models
  3. Private volumes are created per-user or per-organization for custom models
  4. Volumes persist between deployments and are mounted at runtime

Verifying Volume Creation:

After starting the API server, check the Modal dashboard:

  1. Go to https://modal.com/home
  2. Navigate to "Volumes" in the sidebar
  3. You should see comfydeploy-dev-public-models listed
  4. The volume will be created automatically on first use

Step 3: Start Docker Services

cd apps/api
docker compose up -d
Enter fullscreen mode Exit fullscreen mode

This starts PostgreSQL, Redis, pg_proxy, and serverless-redis-http containers.

Verify: Check that all services are running:

docker compose ps

# Expected output should show:
# - api-postgres-1 (port 5480)
# - api-redis-1 (port 6379)
# - api-pg_proxy-1 (port 5481)
# - api-serverless-redis-http-1 (port 8079)
Enter fullscreen mode Exit fullscreen mode

If a service failed to start, check logs:

docker compose logs postgres  # or redis, etc.
Enter fullscreen mode Exit fullscreen mode

Step 4: Run Database Migrations

cd apps/api
bun run migrate-local
Enter fullscreen mode Exit fullscreen mode

⚠️ Troubleshooting Connection Timeout:

If migrations fail with CONNECT_TIMEOUT localhost:5480, VS Code's port forwarding may be conflicting:

# Check what's listening on port 5480
lsof -i :5480

# If you see "Code Helper" process, kill it:
kill -9 <PID>

# Then retry migrations
bun run migrate-local
Enter fullscreen mode Exit fullscreen mode

Verify: Check that migrations completed without errors. You should see output like:

Database is live
Migrating... DB 1
Done!
Enter fullscreen mode Exit fullscreen mode

Step 4.5: Understanding the Database Architecture

Schema Structure

All ComfyDeploy tables live in the comfyui_deploy schema (not the default public schema). This keeps your data organized and separate from other PostgreSQL objects.

Key Tables and Relationships

Here's the core data model:

users (root table)
├── machines (user_id → users.id)
│   ├── machine_versions (machine_id → machines.id)
│   └── workflow_runs (machine_id → machines.id)
├── workflows (user_id → users.id)
│   ├── workflow_versions (workflow_id → workflows.id)
│   └── workflow_runs (workflow_id → workflows.id)
├── user_volume (user_id → users.id)
│   └── models (user_volume_id → user_volume.id)
└── deployments (user_id → users.id)
Enter fullscreen mode Exit fullscreen mode

Critical Foreign Key Relationships

  • machines.user_idusers.id (cascade delete)
  • user_volume.user_idusers.id (no cascade)
  • workflow_runs.machine_idmachines.id (set null on delete)
  • models.user_volume_iduser_volume.id (cascade delete)

Verify Your Schema

Check that tables exist and have the right structure:

# Connect to the database
psql postgresql://postgres:postgres@localhost:5480/verceldb

# List all tables in the comfyui_deploy schema
\dt comfyui_deploy.*

# Check the users table structure
\d comfyui_deploy.users

# Count rows in key tables
SELECT COUNT(*) FROM comfyui_deploy.users;
SELECT COUNT(*) FROM comfyui_deploy.machines;
SELECT COUNT(*) FROM comfyui_deploy.workflows;
Enter fullscreen mode Exit fullscreen mode

Why Fresh Docker Restarts Wipe Data

When you run docker-compose down, the PostgreSQL container is removed along with its data volume. This is why you need to:

  1. Re-run migrations (bun run migrate-local)
  2. Recreate user records (see Step 6)

In production, you'd use persistent volumes to prevent data loss.

Step 5: Start the Development Servers

Start both servers (they will run in the background):

# Start API server (runs on port 3011)
cd apps/api
bun run dev

# Start Frontend server (runs on port 3001)
cd apps/app
bun run dev
Enter fullscreen mode Exit fullscreen mode

Verify API Server:

curl http://localhost:3011/
# Expected: {"detail":"Not Found"} (this is normal - root path returns 404)

# Check if server is responding
curl -I http://localhost:3011/
# Expected: HTTP/1.1 404 Not Found (server is running)
Enter fullscreen mode Exit fullscreen mode

Verify Frontend: Open http://localhost:3001 in your browser

  • You should see the Comfy Deploy login page
  • Try logging in with your Clerk credentials

Note: The API server may show some SyntaxWarning messages about invalid escape sequences - these are harmless and don't affect functionality.

Step 5.5: Set Up ngrok Tunnel (CRITICAL - Required for Local Development)

⚠️ CRITICAL STEP: ngrok is required for local development with Modal. Without it, Modal functions running in the cloud cannot send status updates back to your local API server, and workflows will get stuck in "not-started" status.

Install ngrok

If you haven't installed ngrok yet:

# macOS
brew install ngrok

# Or download from https://ngrok.com/download
Enter fullscreen mode Exit fullscreen mode

Configure ngrok Authentication

Get your authtoken from the ngrok dashboard:

Then configure ngrok:

ngrok config add-authtoken YOUR_AUTHTOKEN
Enter fullscreen mode Exit fullscreen mode

Example:

ngrok config add-authtoken YOUR_AUTHTOKEN
Enter fullscreen mode Exit fullscreen mode

Start ngrok Tunnel

Launch ngrok to create a public tunnel to your local API:

ngrok http 3011
Enter fullscreen mode Exit fullscreen mode

You'll see output like this:

Session Status                online
Account                       Your Name (Plan: Free)
Version                       3.x.x
Region                        United States (us)
Latency                       -
Web Interface                 http://127.0.0.1:4040
Forwarding                    https://ngrok_generated_url.dev -> http://localhost:3011
Enter fullscreen mode Exit fullscreen mode

Copy the Forwarding URL (the https one, e.g., https://shelia-nondedicative-kaidence.ngrok-free.dev)

Update Environment Variables

Add the ngrok URL to your apps/api/.env file:

NGROK_DOMAIN="https://your-ngrok-url.ngrok-free.dev"
Enter fullscreen mode Exit fullscreen mode

Example:

NGROK_DOMAIN="https://ngrok_generated_url.dev"
Enter fullscreen mode Exit fullscreen mode

Important: Keep ngrok running in a separate terminal window. If ngrok stops, Modal functions won't be able to communicate with your API.

Verify ngrok is working:

# Test that your ngrok URL reaches your local API
curl https://your-ngrok-url.ngrok-free.dev/
# Expected: {"detail":"Not Found"} (this is normal - root path returns 404)
Enter fullscreen mode Exit fullscreen mode

Step 5.6: Set Up Modal for Serverless Compute

What is Modal?

Modal is a serverless platform for running Python code with GPU access. ComfyDeploy uses Modal to run ComfyUI workflows in the cloud without needing to manage servers. When you create a "serverless machine" in ComfyDeploy, it's actually a Modal app that gets deployed and runs your workflows.

Creating a Modal Environment

Modal uses "environments" to organize deployments (dev, staging, production). You've already configured this in your .env:

MODAL_ENVIRONMENT="dev"
MODAL_TOKEN_ID="your-modal-token-id"
MODAL_TOKEN_SECRET="your-modal-token-secret"
Enter fullscreen mode Exit fullscreen mode

⚠️ CRITICAL: Deploying the volume-operations App

This step is REQUIRED! Without deploying the volume-operations app, model downloads will fail and workflows won't be able to access models from Modal volumes.

The volume-operations Modal app handles downloading models to Modal volumes. Deploy it:

cd apps/api
modal deploy src/modal_apps/modal_downloader.py::modal_downloader_app
Enter fullscreen mode Exit fullscreen mode

What this does:

  • Creates a Modal app called "volume-operations" in your Modal workspace
  • Enables downloading models from HuggingFace, CivitAI, and arbitrary URLs
  • Uploads models to Modal volumes for use in workflows
  • Required for model management functionality to work

Verify Modal Deployment:

# List all Modal apps in your workspace
modal app list

# Expected output should include "volume-operations"
Enter fullscreen mode Exit fullscreen mode

Check the Modal dashboard at https://modal.com/apps to see your deployed apps.

Common Issues:

  • If you see "Function not found" errors when downloading models, you likely forgot this step
  • The app must be deployed to the same Modal environment specified in MODAL_ENVIRONMENT
  • Re-run the deploy command if you change Modal environments

Configuring Modal Volumes

Modal volumes are persistent storage that Modal functions can access. ComfyDeploy creates volumes for:

  • comfydeploy-dev-public-models: Shared models available to all workflows
  • models_org_<org_id>: Organization-specific models
  • models_<user_id>: User-specific models

Check your existing Modal volumes:

modal volume list
Enter fullscreen mode Exit fullscreen mode

You should see output like:

Volumes in environment 'dev'
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┓
┃ Name                                    ┃ Created at           ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━┩
│ comfydeploy-dev-public-models           │ 2025-11-15 22:23 +03 │
│ models_org_35Wmd12wjmWmYICCWpxZykn4YVq  │ 2025-11-15 22:18 +03 │
│ public-models                           │ 2025-10-18 19:05 +03 │
└─────────────────────────────────────────┴──────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Update your .env with the correct volume name:

Look for the volume that matches your environment (e.g., comfydeploy-dev-public-models for dev environment), then update apps/api/.env:

# Modal Volume Configuration
SHARED_MODEL_VOLUME_NAME=comfydeploy-dev-public-models
PUBLIC_MODEL_VOLUME_NAME=comfydeploy-dev-public-models
Enter fullscreen mode Exit fullscreen mode

Important Notes:

  • The volume name must match exactly what appears in modal volume list
  • If the volume doesn't exist, Modal will create it automatically on first use
  • Use environment-specific names (e.g., comfydeploy-dev-* for dev, comfydeploy-prod-* for production)
  • Both SHARED_MODEL_VOLUME_NAME and PUBLIC_MODEL_VOLUME_NAME can point to the same volume for local development

When you download a model through the UI, it gets uploaded to the appropriate volume, and then your workflows can access it.

The ngrok Part (Important!)

Here's the thing: Modal functions run in the cloud and need to send status updates back to your local API. They can't reach http://localhost:3011 directly. That's where ngrok comes in.

ngrok creates a public tunnel to your local API. When you start it, you'll get a URL like https://abc123.ngrok-free.app. Update your .env:

CURRENT_API_URL="https://ngrok_generated_url.dev"
Enter fullscreen mode Exit fullscreen mode

Then restart your API server. Now Modal functions can reach your API and send status updates properly.

Step 6: Create a User Record in the Database (CRITICAL - Must Do After First Login)

⚠️ CRITICAL STEP: After logging in with Clerk for the first time, you must create a user record in your local database. Without this, you'll get ForeignKeyViolationError errors when trying to use the application.

Why is this needed?

Clerk handles authentication and creates a user in their system, but your local PostgreSQL database doesn't know about this user yet. Most API endpoints require a user record to exist in the comfyui_deploy.users table due to foreign key constraints.

When to do this:

  1. After your first login via the frontend (http://localhost:3001)
  2. After restarting Docker (which wipes the database)
  3. Whenever you see errors like: Key (user_id)=(user_xxx) is not present in table "users"

Get Your Clerk User ID

First, log in to the frontend at http://localhost:3001. Then get your Clerk user ID:

Option 1: From Browser DevTools

  1. Open DevTools (F12)
  2. Go to Console tab
  3. Type: window.Clerk.user.id
  4. Copy the user ID (starts with user_)

Option 2: From API Logs
Check your API server logs - you'll see the user ID in authentication errors:

ERROR: Key (user_id)=(user_123456789) is not present in table "users"
Enter fullscreen mode Exit fullscreen mode

Create the User Record

Replace your-clerk-user-id, your-username, and Your Name with your actual values:

cd apps/api
source .venv/bin/activate
python3 << 'EOF'
import asyncio
from sqlalchemy import text
from sqlalchemy.ext.asyncio import create_async_engine

DATABASE_URL = "postgresql+asyncpg://postgres:postgres@localhost:5480/verceldb"
engine = create_async_engine(DATABASE_URL)

async def main():
    async with engine.begin() as conn:
        await conn.execute(text("""
            INSERT INTO comfyui_deploy.users (id, username, name, created_at, updated_at)
            VALUES (:id, :username, :name, now(), now())
            ON CONFLICT (id) DO NOTHING
        """), {
            "id": "your-clerk-user-id",  # e.g., "user_35WjS9hWy1iZFJ6WbeThk92qcgf"
            "username": "your-username",  # e.g., "umut"
            "name": "Your Name"           # e.g., "Umut"
        })
    await engine.dispose()
    print("✅ User created successfully!")

asyncio.run(main())
EOF
Enter fullscreen mode Exit fullscreen mode

Example with actual values:

python3 << 'EOF'
import asyncio
from sqlalchemy import text
from sqlalchemy.ext.asyncio import create_async_engine

DATABASE_URL = "postgresql+asyncpg://postgres:postgres@localhost:5480/verceldb"
engine = create_async_engine(DATABASE_URL)

async def main():
    async with engine.begin() as conn:
        await conn.execute(text("""
            INSERT INTO comfyui_deploy.users (id, username, name, created_at, updated_at)
            VALUES (:id, :username, :name, now(), now())
            ON CONFLICT (id) DO NOTHING
        """), {
            "id": "user_12345679",
            "username": "umut",
            "name": "Umut"
        })
    await engine.dispose()
    print("✅ User created successfully!")

asyncio.run(main())
EOF
Enter fullscreen mode Exit fullscreen mode

Verify User Creation:

psql postgresql://postgres:postgres@localhost:5480/verceldb

# In psql:
SELECT id, username, name FROM comfyui_deploy.users;

# You should see your user record
# Exit psql with: \q
Enter fullscreen mode Exit fullscreen mode

After creating the user, refresh your browser at http://localhost:3001. The foreign key errors should be gone and you should be able to use the application normally.

Step 6.5: Attach an Autumn Plan to Your User or Organization (CRITICAL)

⚠️ CRITICAL STEP: After creating your user record, you must attach a subscription plan in the Autumn dashboard. Without a plan, you'll hit feature limits and won't be able to create machines or run workflows.

Why is this needed?

ComfyDeploy uses Autumn for billing and feature limits. Even in local development, the API checks Autumn to determine:

  • How many machines you can create (machine_limit)
  • How many concurrent GPU jobs you can run (gpu_concurrency_limit)
  • How many workflows you can have (workflow_limit)
  • How many GPU credits you have available (gpu-credit)

Without a plan attached, you'll be on the default "Free" tier with very limited features.

Access the Autumn Dashboard

  1. Go to https://dashboard.autumn.dev/ (or https://app.useautumn.com/)
  2. Log in with your Autumn account
  3. Navigate to Customers in the sidebar

Attach a Plan to Your Organization

For Organization-based Development (Recommended):

  1. In the Autumn dashboard, go to Customers
  2. Find or create a customer for your Clerk organization ID (e.g., org_123456789)
    • If it doesn't exist, click "Create Customer"
    • Enter your Clerk organization ID as the customer ID
  3. Click on the customer to view details
  4. Click "Attach Plan" or "Add Subscription"
  5. Select "Business (Monthly)" plan
  6. Verify the plan includes:
    • GPU Concurrency Limit: 10
    • Machine Limit: 25
    • Workflow Limit: 300
    • Seats: 10
    • GPU Credit: $10,000 per month (100,000 credits)
    • Price: $998 per month
  7. Click "Create" or "Attach"

For User-based Development:

  1. In the Autumn dashboard, go to Customers
  2. Find or create a customer for your Clerk user ID (e.g., user_123456789)
  3. Follow the same steps as above to attach the Business plan

Example Plan Configuration

Here's what the Business (Monthly) plan should look like in Autumn:

Business (Monthly)
├── GPU Concurrency Limit: 10
├── Machine Limit: 25
├── Workflow Limit: 300
├── Seats: 10
├── GPU Credit: $10,000 per month (100,000 credits)
└── Price: $998 per month
Enter fullscreen mode Exit fullscreen mode

Feature Items Breakdown:

Feature Included Usage Price
gpu_concurrency_limit 10 Feature
machine_limit 25 Feature
workflow_limit 300 Feature
seats 10 Feature
gpu-credit $10,000 per GPU Credit (cents) per month Feature
Total $998 per month

Verify the Plan is Active

  1. Refresh your browser at http://localhost:3001
  2. Navigate to SettingsBilling (or Usage)
  3. You should see:
    • Current plan: Business
    • Machine limit: 25
    • GPU concurrency: 10
    • Available credits: 100,000 credits ($10,000)

Check via API:

curl -H "Authorization: Bearer YOUR_CLERK_TOKEN" \
  http://localhost:3011/api/platform/plan
Enter fullscreen mode Exit fullscreen mode

You should see a response with:

{
  "plan": "business",
  "features": {
    "machine_limit": 25,
    "gpu_concurrency_limit": 10,
    "workflow_limit": 300,
    "gpu_credit": 100000
  }
}
Enter fullscreen mode Exit fullscreen mode

Common Issues

Error: "Max Machines Exceeded" despite attaching a plan

This usually means:

  1. The plan wasn't attached to the correct customer ID (check if you're using user ID vs organization ID)
  2. The Autumn cache needs to be refreshed - wait 30 seconds and try again
  3. The autumn.config.ts wasn't pushed - run npx atmn push again

Error: "Insufficient GPU credits"

The Business plan includes $10,000 in GPU credits (100,000 credits). If you see this error:

  1. Check the Autumn dashboard to verify credits were added
  2. Make sure the gpu-credit feature is included in the plan
  3. Try refreshing the page or logging out and back in

Plan not showing in the frontend

  1. Clear browser cache and hard refresh (Cmd+Shift+R or Ctrl+Shift+R)
  2. Check that AUTUMN_SECRET_KEY is correct in apps/api/.env
  3. Restart the API server to pick up the latest Autumn data

Step 7: Access the App

Open http://localhost:3001 in your browser. You should be able to log in with Clerk and start creating workflows.

Verify Full Stack:

  1. Log in with your Clerk credentials
  2. Navigate to "Machines" → you should see an empty list
  3. Try creating a new workflow
  4. Check that the API is receiving requests (watch the API server logs)

Troubleshooting Guide

Database Issues

Error: relation "comfyui_deploy.machines" does not exist

This means migrations didn't run or failed. Fix it:

cd apps/api
bun run migrate-local
Enter fullscreen mode Exit fullscreen mode

Check the output for any errors. If migrations are stuck, you may need to reset:

# WARNING: This deletes all data!
docker-compose down -v
docker-compose up -d
bun run migrate-local
Enter fullscreen mode Exit fullscreen mode

Error: ForeignKeyViolationError: Key (user_id)=(...) is not present in table "users"

This is the most common error in local development. The user record doesn't exist in the database. This happens when:

  • You restarted Docker (which wipes the database)
  • You're using a new Clerk user ID that hasn't been added to the database
  • You skipped Step 6 after first login

Fix: Run Step 6 to create the user record. This is a required step after every Docker restart or first login.

Error: 500 Internal Server Error on /api/volume/private-models

Usually caused by missing user record. Check:

# Get your Clerk user ID from the browser console:
# Open DevTools → Application → Cookies → find __clerk_db_jwt
# Decode it to see your user_id

# Then verify the user exists:
psql postgresql://postgres:postgres@localhost:5480/verceldb
SELECT * FROM comfyui_deploy.users WHERE id = 'your-user-id';
Enter fullscreen mode Exit fullscreen mode

If the user doesn't exist, create it (Step 6).

Port and Process Issues

Error: Address already in use on port 3011

Something is already running on that port. Kill it:

lsof -ti:3011 | xargs kill -9
Enter fullscreen mode Exit fullscreen mode

Or find what's using it:

lsof -i :3011
Enter fullscreen mode Exit fullscreen mode

Error: Address already in use on port 3001 (frontend)

lsof -ti:3001 | xargs kill -9
Enter fullscreen mode Exit fullscreen mode

Error: ngrok tunnel keeps disconnecting

ngrok free tier has connection limits. If you're testing heavily:

  • Upgrade to ngrok pro
  • Or restart ngrok: ngrok http 3011

Modal and Workflow Issues

Runs stuck in "not-started" status

This means Modal functions can't send status updates back to your API. Check:

  1. Is ngrok running?
   ps aux | grep ngrok
Enter fullscreen mode Exit fullscreen mode
  1. Is the ngrok URL in your .env?
   grep CURRENT_API_URL apps/api/.env
Enter fullscreen mode Exit fullscreen mode
  1. Did you restart the API server after updating .env?
   # Kill the old server and restart it
   lsof -ti:3011 | xargs kill -9
   PORT=3011 uv run src/api/server.py
Enter fullscreen mode Exit fullscreen mode
  1. Test the ngrok tunnel:
   curl https://your-ngrok-url.ngrok-free.app/api/health
Enter fullscreen mode Exit fullscreen mode

Error: "Max Machines Exceeded" despite having a paid plan

This is an Autumn billing configuration issue. The most common cause is not attaching a plan to your user or organization in the Autumn dashboard.

Fix:

  1. 🔴 MOST COMMON: Did you attach a plan in the Autumn dashboard? (See Step 6.5)

    • Go to https://dashboard.autumn.dev/
    • Navigate to Customers
    • Find your user ID or organization ID
    • Click "Attach Plan" → Select "Business (Monthly)"
    • Verify the plan includes machine_limit: 25
  2. Did you push your config to Autumn?

   cd apps/api
   npx atmn push
Enter fullscreen mode Exit fullscreen mode
  1. Is the machineLimit feature defined in your product?
   grep -A 5 "machineLimit" autumn.config.ts
Enter fullscreen mode Exit fullscreen mode
  1. Are you using the correct customer ID?

    • Check if you're logged in as a user or organization
    • The plan must be attached to the correct customer ID (user ID vs org ID)
    • Check browser console: window.Clerk.user.id or window.Clerk.organization.id
  2. Wait 30 seconds for Autumn cache to refresh, then try again

Error: "GET /api/latest-hashes 404 Not Found" or "Illegal header value b'Bearer '"

This means the GITHUB_TOKEN is missing or empty in your .env file. The API needs this token to fetch ComfyUI version information from GitHub.

Fix:

  1. Create a GitHub classic token at https://github.com/settings/tokens
  2. Select public_repo scope
  3. Add to apps/api/.env:
   GITHUB_TOKEN="ghp_your_token_here"
Enter fullscreen mode Exit fullscreen mode
  1. Restart the API server

Error: "Invalid Volume name: ''" or "Can't find Volume: Invalid Volume name: ''"

This means the Modal volume environment variables are missing or empty. Modal requires valid volume names to create persistent storage for models.

Symptoms:

ERROR:api.middleware.authMiddleware:Can't find Volume: Invalid Volume name: ''.
Names may contain only alphanumeric characters, dashes, periods, and underscores,
must be shorter than 64 characters, and cannot conflict with App ID strings.
Enter fullscreen mode Exit fullscreen mode

Fix:

  1. Add volume names to apps/api/.env:
   SHARED_MODEL_VOLUME_NAME="comfydeploy-dev-public-models"
   PUBLIC_MODEL_VOLUME_NAME="comfydeploy-dev-public-models"
Enter fullscreen mode Exit fullscreen mode
  1. Restart the API server
  2. The volume will be automatically created in Modal on first use

Why this happens:

  • The code looks for PUBLIC_MODEL_VOLUME_NAME or SHARED_MODEL_VOLUME_NAME environment variables
  • If both are empty, it returns an empty string ""
  • Modal rejects empty volume names with a validation error
  • This affects both the /api/volume/public-models endpoint and serverless deployments

Error: Model downloads fail or "Function not found" errors

This usually means you forgot to deploy the volume-operations Modal app, which is required for model management.

Symptoms:

  • Model downloads fail silently or with errors
  • "Function not found" errors when trying to download models
  • Models don't appear in Modal volumes

Fix:

  1. Deploy the volume-operations app:
   cd apps/api
   modal deploy src/modal_apps/modal_downloader.py::modal_downloader_app
Enter fullscreen mode Exit fullscreen mode
  1. Verify deployment:
   modal app list
   # Should show "volume-operations" in the list
Enter fullscreen mode Exit fullscreen mode
  1. Check the Modal dashboard at https://modal.com/apps

Why this is required:

  • The volume-operations app contains Modal functions for downloading models
  • Without it, the API can't download models to Modal volumes
  • This is a one-time deployment per Modal environment
  • See Step 5.4 in the setup guide for details

Service Health Checks

Check if PostgreSQL is running:

psql postgresql://postgres:postgres@localhost:5480/verceldb -c "SELECT 1"
# Expected: 1
Enter fullscreen mode Exit fullscreen mode

Check if Redis is running:

redis-cli -p 6379 ping
# Expected: PONG
Enter fullscreen mode Exit fullscreen mode

Check if API server is responding:

curl http://localhost:3011/api/health
# Expected: 200 OK
Enter fullscreen mode Exit fullscreen mode

Check if frontend is running:

curl http://localhost:3001
# Expected: HTML response
Enter fullscreen mode Exit fullscreen mode

Check API server logs for errors:

# The API server logs to stdout, so check your terminal
# Look for ERROR or EXCEPTION messages
Enter fullscreen mode Exit fullscreen mode

Check Docker container logs:

docker-compose logs postgres    # PostgreSQL logs
docker-compose logs redis       # Redis logs
docker-compose logs             # All services
Enter fullscreen mode Exit fullscreen mode

React Query Caching Issues

Settings page shows "disabled for free tier" despite having a paid plan

This is a React Query cache issue. The frontend cached stale plan data. Fix:

  1. Clear browser cache: DevTools → Application → Clear Storage
  2. Or hard refresh: Ctrl+Shift+R (Windows/Linux) or Cmd+Shift+R (Mac)
  3. Or log out and log back in

If this keeps happening, check that your .env has the correct AUTUMN_SECRET_KEY.

That's It!

You're now ready to develop. The key insights:

  1. 🔴 CRITICAL: Create user record after first login (Step 6) - Without this, you'll get foreign key errors on every API call. This must be done after every Docker restart.

  2. 🔴 CRITICAL: Attach Autumn plan (Step 6.5) - Without a subscription plan attached in the Autumn dashboard, you'll hit feature limits and won't be able to create machines or run workflows. Attach the "Business (Monthly)" plan to your user or organization.

  3. 🔴 CRITICAL: ngrok must be running (Step 5.5) - Modal functions need to communicate back to your local API. Without ngrok, workflows will get stuck in "not-started" status.

  4. Fresh Docker restarts wipe data - You'll need to:

    • Re-run migrations (bun run migrate-local)
    • Recreate user records (Step 6)
    • Verify Autumn plan is still attached (Step 6.5)
  5. Autumn config must be pushed - Changes to autumn.config.ts don't take effect until you run npx atmn push

  6. Check logs first - Most issues are visible in the API server logs or Docker logs

Quick Start Checklist

After cloning the repo, here's the minimal path to get running:

Prerequisites:

  • [ ] Install all required software (Node.js, Bun, Python 3.12, uv, Docker, AWS CLI, ngrok, psql)
  • [ ] Create accounts and get API keys (Clerk, Autumn, AWS S3, Modal, ngrok, GitHub)

Setup:

  • [ ] Initialize git submodules: git submodule init && git submodule update
  • [ ] Install dependencies (API: bun install + uv sync, Frontend: bun install)
  • [ ] Configure .env files with all API keys (Clerk, Autumn, AWS S3, Modal, ngrok, GitHub, Modal Volumes)
  • [ ] Push Autumn config: npx atmn push
  • [ ] Start Docker: docker compose up -d
  • [ ] Run migrations: bun run migrate-local
  • [ ] 🔴 Install and configure ngrok: brew install ngrok/ngrok/ngrok + ngrok config add-authtoken YOUR_TOKEN
  • [ ] 🔴 Start ngrok: ngrok http 3011 (keep running)
  • [ ] 🔴 Update .env with ngrok URL and Modal volume names
  • [ ] Start API server: bun run dev
  • [ ] Start frontend: bun run dev
  • [ ] 🔴 Log in via frontend, then create user record in database (Step 6)
  • [ ] 🔴 Attach Business plan to your user/org in Autumn dashboard (Step 6.5)
  • [ ] Verify everything works by creating a test workflow

Happy coding! 🚀

Top comments (0)