Skip to main content
Deploy your elizaOS agents as serverless functions for automatic scaling, pay-per-use pricing, and global distribution. This example covers AWS Lambda, Vercel Edge, and Cloudflare Workers.

Overview

Serverless platforms let you run agents without managing servers, scaling automatically with demand and charging only for actual usage. What you’ll learn:
  • Deploy to AWS Lambda
  • Deploy to Vercel Edge Functions
  • Deploy to Cloudflare Workers
  • Handle cold starts
  • Optimize for performance

Quick Comparison

PlatformCold StartRuntimeFree TierBest For
AWS Lambda1-3sNode, Python, Rust1M requests/monthComplex workflows
Vercel Edge<50msNode, Edge100K requests/monthWeb apps
Cloudflare Workers<10msEdge Runtime100K requests/dayGlobal distribution

AWS Lambda

Setup

1

Install AWS SAM CLI

brew install aws-sam-cli  # macOS
# or follow: https://docs.aws.amazon.com/serverless-application-model/
2

Create Project

mkdir eliza-lambda && cd eliza-lambda
bun init
bun add @elizaos/core @elizaos/plugin-openai @elizaos/plugin-sql
3

Create Handler

Create index.ts with Lambda handler
4

Deploy

sam build && sam deploy --guided

Lambda Handler

index.ts
import {
  APIGatewayProxyEvent,
  APIGatewayProxyResult,
  Context,
} from "aws-lambda";
import {
  AgentRuntime,
  createMessageMemory,
  stringToUuid,
} from "@elizaos/core";
import { openaiPlugin } from "@elizaos/plugin-openai";
import { plugin as sqlPlugin } from "@elizaos/plugin-sql";
import { v4 as uuidv4 } from "uuid";

// Global runtime instance (survives across invocations)
let runtime: AgentRuntime | null = null;

async function getRuntime(): Promise<AgentRuntime> {
  if (runtime) {
    console.log("Reusing existing runtime (warm start)");
    return runtime;
  }

  console.log("Initializing new runtime (cold start)");

  runtime = new AgentRuntime({
    character: {
      name: process.env.CHARACTER_NAME || "Eliza",
      bio: process.env.CHARACTER_BIO || "A helpful AI assistant.",
    },
    plugins: [sqlPlugin, openaiPlugin],
  });

  await runtime.initialize();
  return runtime;
}

export async function handler(
  event: APIGatewayProxyEvent,
  context: Context
): Promise<APIGatewayProxyResult> {
  const headers = {
    "Content-Type": "application/json",
    "Access-Control-Allow-Origin": "*",
    "Access-Control-Allow-Methods": "POST, GET, OPTIONS",
  };

  try {
    // Health check
    if (event.path === "/health") {
      return {
        statusCode: 200,
        headers,
        body: JSON.stringify({
          status: "healthy",
          requestId: context.requestId,
          runtime: "aws-lambda",
        }),
      };
    }

    // Handle OPTIONS for CORS
    if (event.httpMethod === "OPTIONS") {
      return { statusCode: 200, headers, body: "" };
    }

    // Chat endpoint
    if (event.path === "/chat" && event.httpMethod === "POST") {
      const runtime = await getRuntime();
      const body = JSON.parse(event.body || "{}");
      const { message, userId, conversationId } = body;

      if (!message) {
        return {
          statusCode: 400,
          headers,
          body: JSON.stringify({ error: "Message is required" }),
        };
      }

      // Create message
      const messageMemory = createMessageMemory({
        id: uuidv4(),
        entityId: stringToUuid(userId || uuidv4()),
        roomId: stringToUuid(conversationId || "default"),
        content: { text: message },
      });

      // Get response
      let responseText = "";
      await runtime.messageService!.handleMessage(
        runtime,
        messageMemory,
        async (content) => {
          if (content?.text) {
            responseText += content.text;
          }
          return [];
        }
      );

      return {
        statusCode: 200,
        headers,
        body: JSON.stringify({
          response: responseText,
          userId,
          conversationId,
          timestamp: new Date().toISOString(),
        }),
      };
    }

    return {
      statusCode: 404,
      headers,
      body: JSON.stringify({ error: "Not found" }),
    };
  } catch (error) {
    console.error("Error:", error);
    return {
      statusCode: 500,
      headers,
      body: JSON.stringify({
        error: "Internal server error",
        message: error instanceof Error ? error.message : "Unknown error",
      }),
    };
  }
}

SAM Template

template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: elizaOS Lambda Function

Globals:
  Function:
    Timeout: 30
    MemorySize: 512
    Environment:
      Variables:
        OPENAI_API_KEY: !Ref OpenAIApiKey

Parameters:
  OpenAIApiKey:
    Type: String
    Description: OpenAI API Key
    NoEcho: true

Resources:
  ElizaFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: ./
      Handler: index.handler
      Runtime: nodejs20.x
      Architectures:
        - arm64
      Events:
        Chat:
          Type: Api
          Properties:
            Path: /chat
            Method: post
        Health:
          Type: Api
          Properties:
            Path: /health
            Method: get

Outputs:
  ApiUrl:
    Description: "API Gateway endpoint URL"
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/"

Deploy

# Build
sam build

# Deploy
sam deploy --guided \
  --parameter-overrides OpenAIApiKey=$OPENAI_API_KEY

# Test
curl -X POST https://your-api-id.execute-api.us-east-1.amazonaws.com/Prod/chat \
  -H "Content-Type: application/json" \
  -d '{"message": "Hello!"}'

Vercel Edge Functions

Setup

1

Install Vercel CLI

bun add -g vercel
2

Create Project

mkdir eliza-vercel && cd eliza-vercel
bun init
bun add @elizaos/core @elizaos/plugin-openai
3

Create API Route

Create api/chat.ts
4

Deploy

vercel

Edge Function

api/chat.ts
import { AgentRuntime, ModelType } from "@elizaos/core";
import { openaiPlugin } from "@elizaos/plugin-openai";

// Edge runtime configuration
export const config = {
  runtime: "edge",
};

// Global runtime (persists across requests)
let runtime: AgentRuntime | null = null;

async function getRuntime(): Promise<AgentRuntime> {
  if (runtime) return runtime;

  runtime = new AgentRuntime({
    character: {
      name: "Eliza",
      bio: "A helpful AI assistant.",
    },
    plugins: [openaiPlugin],
  });

  await runtime.initialize();
  return runtime;
}

export default async function handler(request: Request) {
  // CORS headers
  const headers = {
    "Content-Type": "application/json",
    "Access-Control-Allow-Origin": "*",
    "Access-Control-Allow-Methods": "POST, OPTIONS",
  };

  if (request.method === "OPTIONS") {
    return new Response(null, { status: 200, headers });
  }

  if (request.method !== "POST") {
    return new Response(
      JSON.stringify({ error: "Method not allowed" }),
      { status: 405, headers }
    );
  }

  try {
    const runtime = await getRuntime();
    const { message } = await request.json();

    if (!message) {
      return new Response(
        JSON.stringify({ error: "Message is required" }),
        { status: 400, headers }
      );
    }

    const response = await runtime.useModel(ModelType.TEXT_LARGE, {
      prompt: message,
    });

    return new Response(
      JSON.stringify({
        response: String(response),
        timestamp: new Date().toISOString(),
      }),
      { status: 200, headers }
    );
  } catch (error) {
    console.error("Error:", error);
    return new Response(
      JSON.stringify({ error: "Internal server error" }),
      { status: 500, headers }
    );
  }
}

Configuration

vercel.json
{
  "functions": {
    "api/**/*.ts": {
      "memory": 1024,
      "maxDuration": 10
    }
  },
  "env": {
    "OPENAI_API_KEY": "@openai-api-key"
  }
}

Deploy

# Set environment variable
vercel env add OPENAI_API_KEY

# Deploy
vercel --prod

# Test
curl -X POST https://your-project.vercel.app/api/chat \
  -H "Content-Type: application/json" \
  -d '{"message": "Hello!"}'

Cloudflare Workers

Setup

1

Install Wrangler

bun add -g wrangler
2

Create Project

wrangler init eliza-worker
cd eliza-worker
bun add @elizaos/core @elizaos/plugin-openai
3

Create Worker

Create src/index.ts
4

Deploy

wrangler deploy

Worker Code

src/index.ts
import { AgentRuntime, ModelType } from "@elizaos/core";
import { openaiPlugin } from "@elizaos/plugin-openai";

export interface Env {
  OPENAI_API_KEY: string;
}

let runtime: AgentRuntime | null = null;

async function getRuntime(env: Env): Promise<AgentRuntime> {
  if (runtime) return runtime;

  runtime = new AgentRuntime({
    character: {
      name: "Eliza",
      bio: "A helpful AI assistant.",
      secrets: {
        OPENAI_API_KEY: env.OPENAI_API_KEY,
      },
    },
    plugins: [openaiPlugin],
  });

  await runtime.initialize();
  return runtime;
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const headers = {
      "Content-Type": "application/json",
      "Access-Control-Allow-Origin": "*",
      "Access-Control-Allow-Methods": "POST, OPTIONS",
    };

    if (request.method === "OPTIONS") {
      return new Response(null, { status: 200, headers });
    }

    if (request.method !== "POST") {
      return new Response(
        JSON.stringify({ error: "Method not allowed" }),
        { status: 405, headers }
      );
    }

    try {
      const runtime = await getRuntime(env);
      const { message } = await request.json();

      if (!message) {
        return new Response(
          JSON.stringify({ error: "Message is required" }),
          { status: 400, headers }
        );
      }

      const response = await runtime.useModel(ModelType.TEXT_LARGE, {
        prompt: message,
      });

      return new Response(
        JSON.stringify({
          response: String(response),
          timestamp: new Date().toISOString(),
          edge: request.cf?.colo, // Cloudflare edge location
        }),
        { status: 200, headers }
      );
    } catch (error) {
      console.error("Error:", error);
      return new Response(
        JSON.stringify({ error: "Internal server error" }),
        { status: 500, headers }
      );
    }
  },
};

Configuration

wrangler.toml
name = "eliza-worker"
main = "src/index.ts"
compatibility_date = "2024-01-01"

[env.production]
vars = { ENVIRONMENT = "production" }

Deploy

# Set secret
wrangler secret put OPENAI_API_KEY

# Deploy
wrangler deploy

# Test
curl -X POST https://eliza-worker.your-subdomain.workers.dev \
  -H "Content-Type: application/json" \
  -d '{"message": "Hello!"}'

Performance Optimization

Minimize Cold Starts

// Pre-initialize heavy dependencies
const pluginsPromise = Promise.all([
  import("@elizaos/plugin-openai"),
  import("@elizaos/plugin-sql"),
]);

async function getRuntime() {
  const [openaiPlugin, sqlPlugin] = await pluginsPromise;
  // ... create runtime
}

Use Smaller Models

// For serverless, prefer faster models
const response = await runtime.useModel(ModelType.TEXT_SMALL, {
  prompt: message,
  maxTokens: 150, // Limit response length
  temperature: 0.7,
});

Connection Pooling

// Reuse database connections
let dbConnection: any = null;

async function getConnection() {
  if (dbConnection) return dbConnection;
  dbConnection = await createConnection();
  return dbConnection;
}

Response Streaming

For long responses, use streaming:
export default async function handler(request: Request) {
  const encoder = new TextEncoder();
  const stream = new TransformStream();
  const writer = stream.writable.getWriter();

  // Stream response
  runtime.messageService!.handleMessage(
    runtime,
    message,
    async (content) => {
      if (content?.text) {
        await writer.write(encoder.encode(content.text));
      }
      return [];
    }
  ).then(() => writer.close());

  return new Response(stream.readable, {
    headers: { "Content-Type": "text/plain; charset=utf-8" },
  });
}

Cost Estimation

AWS Lambda (1M requests/month, 512MB, 2s avg)

  • Requests: $0.20
  • Compute: $16.67
  • Total: ~$17/month

Vercel (1M requests/month)

  • Requests: Covered in Pro plan ($20/month)
  • Compute: Additional based on execution time
  • Total: ~$20-40/month

Cloudflare Workers (1M requests/month)

  • Requests: $0.50 per million
  • Compute: Included
  • Total: ~$0.50/month (incredible value!)

Monitoring

AWS CloudWatch

# View logs
aws logs tail /aws/lambda/eliza-function --follow

# View metrics
aws cloudwatch get-metric-statistics \
  --namespace AWS/Lambda \
  --metric-name Duration \
  --dimensions Name=FunctionName,Value=eliza-function \
  --start-time 2026-03-03T00:00:00Z \
  --end-time 2026-03-03T23:59:59Z \
  --period 3600 \
  --statistics Average

Vercel Analytics

View in Vercel Dashboard:
  • Function execution times
  • Error rates
  • Geographic distribution

Cloudflare Analytics

# View logs
wrangler tail

# View analytics in dashboard
open https://dash.cloudflare.com

Best Practices

Minimize Bundle Size: Use tree-shaking and remove unused dependencies.
Environment Variables: Never hardcode secrets - use platform secret management.
Timeout Handling: Set appropriate timeouts and handle them gracefully.
Error Logging: Implement proper error logging and monitoring.
Region Selection: Deploy close to your users for lower latency.

Next Steps

REST API Server

Learn about traditional server deployment

Browser Integration

Run agents client-side

Deploy Guide

Complete deployment documentation

Multi-Agent

Deploy multi-agent systems serverlessly

Build docs developers (and LLMs) love