Where Lambda's 15-minute limit, payload caps and per-invocation model start to hurt, and how Encore, ECS Express Mode, Cloud Run, Cloudflare Workers, Azure Functions and Fly.io compare.
AWS Lambda runs a function per event and bills per request and per GB-second, which suits spiky, short work such as resizing an image or answering a webhook. The limits show up as the work grows: an invocation stops at 15 minutes, a synchronous request or response is capped at 6 MB, and a zip package cannot exceed 250 MB unzipped, per the Lambda quotas page.
AWS itself describes Lambda as designed for short-lived compute tasks that do not keep state between invocations. Backends with steady traffic, long reports, connection pools to Postgres or workers that drain a queue all fit better on something that keeps a process running.
Of the alternatives below, Encore, ECS Express Mode, Cloud Run and Fly.io run containers or long-lived processes, while Cloudflare Workers and Azure Functions stay in the function model with different limits.
Encore combines an open source infrastructure SDK for TypeScript and Go with a platform that provisions and deploys what the code declares into your own AWS or GCP account. Instead of one function per trigger, you write a service whose endpoints, subscribers, cron jobs, buckets and database are declarations in the code, and encore run starts Postgres locally next to them.

The usual Lambda triggers map onto Encore declarations one to one:
A report service that used to be three Lambda functions looks like this in one file:
import { api } from "encore.dev/api";
import { SQLDatabase } from "encore.dev/storage/sqldb";
import { Bucket } from "encore.dev/storage/objects";
import { Topic, Subscription } from "encore.dev/pubsub";
import { CronJob } from "encore.dev/cron";
import { randomUUID } from "node:crypto";
const db = new SQLDatabase("reports", { migrations: "./migrations" });
const files = new Bucket("report-files");
interface ReportRequested { jobId: string }
const requested = new Topic<ReportRequested>("report-requested", {
deliveryGuarantee: "at-least-once",
});
// Replaces API Gateway plus a Lambda handler.
export const request = api(
{ expose: true, method: "POST", path: "/reports" },
async (): Promise<{ jobId: string }> => {
const jobId = randomUUID();
await db.exec`INSERT INTO report_jobs (id) VALUES (${jobId})`;
await requested.publish({ jobId });
return { jobId };
},
);
// Replaces an SQS-triggered Lambda. No 15-minute ceiling on the work.
new Subscription(requested, "build-report", {
handler: async ({ jobId }) => {
const job = await db.queryRow<{ status: string }>`
SELECT status FROM report_jobs WHERE id = ${jobId}`;
if (job?.status === "done") return; // redelivery: already built
const csv = Buffer.from("day,orders\n2026-10-27,42\n");
await files.upload(`${jobId}.csv`, csv, { contentType: "text/csv" });
await db.exec`UPDATE report_jobs SET status = 'done' WHERE id = ${jobId}`;
},
});
export const status = api(
{ expose: true, method: "GET", path: "/reports/:jobId" },
async ({ jobId }: { jobId: string }): Promise<{ status: string; stored: boolean }> => {
const job = await db.queryRow<{ status: string }>`
SELECT status FROM report_jobs WHERE id = ${jobId}`;
return { status: job?.status ?? "missing", stored: await files.exists(`${jobId}.csv`) };
},
);
// Replaces an EventBridge schedule that invokes a Lambda.
export const prune = api({}, async (): Promise<{ deleted: number }> => {
const res = await db.query<{ id: string }>`
DELETE FROM report_jobs WHERE created_at < now() - interval '30 days' RETURNING id`;
let deleted = 0;
for await (const row of res) {
await files.remove(`${row.id}.csv`).catch(() => {});
deleted++;
}
return { deleted };
});
new CronJob("prune-reports", { title: "Delete reports older than 30 days", every: "24h", endpoint: prune });
The subscriber checks the job status before building, which keeps a redelivered message from producing the report twice. Locally, the request, the background build and the bucket write all happen under encore run:
Subscriptions are documented in the Pub/Sub guide, including retry policies and the dead-letter queue, and buckets in the object storage guide. Cron jobs do not run under encore run, so the prune endpoint is tested by calling it directly.
On deploy, Encore creates the resources in the table above in your AWS account, inside a VPC with the compute in private subnets, and provisions an IAM role for the service. The compute runs as Fargate tasks that stay up between requests, which keeps database connections warm and removes cold starts from the request path.
encore run. The same bucket declaration is used in preview environments and production.Encore does not deploy to Lambda, so very spiky workloads that sit idle most of the day can cost more on always-running Fargate tasks than on per-invocation billing. Encore also needs an AWS or GCP account to deploy into. It supports TypeScript and Go today, and you can sign up for early access to Python or Rust.
Run in your terminal to get started locally.
ECS Express Mode takes a container image and creates a Fargate service with its own URL, a load balancer with TLS, auto scaling, monitoring and networking. There is no charge for Express Mode itself; you pay for Fargate, the load balancer, CloudWatch and data transfer. AWS now points new customers to Express Mode because App Runner is closed to new customers.
Express Mode solves the compute side of leaving Lambda. AWS positions it for stateless web applications and APIs, so the queue that replaced your SQS trigger, the schedule and the database are still separate resources with their own IAM grants, usually written in Terraform or CloudFormation. For a single API container that already talks to an existing RDS instance, it is the shortest path off Lambda that stays entirely inside AWS. It is a better fit than Encore for a team that wants standard AWS resources it fully controls, already has Terraform modules for the database and queues, and only needs the compute to change. The service keeps running between requests, so a long report or a slow upstream call is no longer cut off at 15 minutes.
Cloud Run runs a container per service and scales the number of instances with incoming requests, including down to zero. The request timeout defaults to 5 minutes and can be raised to 60 minutes, according to the Cloud Run timeout docs, and Google recommends retries and reconnect handling for anything longer than 15 minutes.
Cloud Run keeps much of what people like about Lambda, such as per-request scaling, while removing the package-size and runtime restrictions, since any container works. It also moves the workload from AWS to Google Cloud, which fits best when the rest of the stack already runs on GCP. For work that runs to completion instead of serving requests, Cloud Run jobs run a container until it exits, which replaces a Lambda used as a batch step. Cloud Run is a better fit than Encore when a team wants scale to zero for spiky HTTP traffic and pays only while requests are handled. The Cloud Run alternatives guide compares it with other container platforms.
Workers run JavaScript, TypeScript and WebAssembly in V8 isolates across Cloudflare's network, so a request is handled close to the user with a very small startup cost. Each request gets a capped amount of CPU time and memory stays at 128 MB per isolate, while Cron Triggers run under separate CPU and wall time limits, per the Cloudflare Workers limits page on October 28, 2026.
Workers replace Lambda well for authentication checks, redirects, API gateways and other short work where latency matters. They are a poor fit for memory-heavy jobs or code that depends on Node.js libraries Workers do not support, and the data products around them, such as D1 and Queues, live in Cloudflare rather than your AWS account. Workers are a better fit than Encore or any container platform for code that must answer within milliseconds for users around the world.
Azure Functions on the Flex Consumption plan, which Microsoft now recommends for new serverless apps, has a default function timeout of 30 minutes and no fixed maximum, per Microsoft's Azure Functions hosting docs. HTTP-triggered functions must still respond within 230 seconds because of the Azure Load Balancer idle timeout, and Microsoft points longer work to Durable Functions.
For a team that wants to keep the function-per-trigger model and only needs more than 15 minutes per run, Flex Consumption removes Lambda's main ceiling. The trade is a move to Azure and its bindings model, which fits when the rest of the workload is moving to Azure as well. Triggers and bindings connect a function to Service Bus, Storage queues, Cosmos DB and timers with little code, and Durable Functions add orchestrations that wait, retry and fan out across many steps. It is a better fit than Encore for a team on Azure that wants to keep writing small functions and let the platform handle scaling per event.
Fly.io runs containers as Machines in the regions you pick, billed by the second while they run. A shared-cpu-1x Machine with 256 MB costs $2.19 a month running continuously and $3.69 with 512 MB, and a stopped Machine costs $0.15 per GB of storage per month, per Fly.io's pricing page on October 28, 2026. Machines can stop when idle and start on the next request, which brings back some of Lambda's scale-to-zero economics.
Fly.io fits small workers, bots and APIs that should run continuously in a specific region for little money. Managed Postgres and the rest of the stack live on Fly.io, so it replaces Lambda by moving off AWS, which matters if your data and other services stay there. It is a better fit than Encore for small, always-running processes where a few dollars a month matters more than running in your own AWS account. The Fly.io alternatives guide compares it with similar platforms.
The right alternative depends on which Lambda limit you hit: long-running work fits containers on ECS Fargate or Google Cloud Run, which have no 15-minute ceiling per task. Azure Functions on Flex Consumption keeps the function model with a longer timeout, Cloudflare Workers fits short requests that should run close to users, and Fly.io suits always-on processes in regions you pick.
Move the handler into a Pub/Sub subscription on a container service. In Encore you declare a Topic and a Subscription in TypeScript, publish from your API endpoint, and the handler runs inside a long-lived Fargate task with no 15-minute ceiling. Encore creates the SNS topic, the SQS queue and a dead-letter queue in your AWS account on deploy, and encore run delivers messages locally.
A standard Lambda invocation can run for at most 900 seconds, which is 15 minutes, according to the Lambda quotas page. Synchronous request and response payloads are capped at 6 MB, and a zip deployment package at 250 MB unzipped.
Deploy to your cloud on AWS and Google Cloud
without managing infrastructure
Encore lets developers and agents define application infrastructure directly in code, then automatically provisions it from local development to production in your AWS or GCP account.