Choice 03. By Nicolas Dao · Last reviewed

Longer than 15 minutes: Fargate, durable functions or Managed Instances?

My job runs past Lambda's 15 minutes, maybe for hours, and I don't know when it ends. Where does it run, and how does it stop billing once done?

A Fargate task for hours of continuous work: it runs until the job's process exits, then stops billing by itself, about $15 a month for a 2.5-hour job twice a day. Lambda durable functions for long jobs made of short steps and waits: an execution lasts up to a year and waiting is free. Both when a durable function waits and a Fargate task does the heavy step. Managed Instances' 90 minutes rarely fits, and Trigger.dev does it all outside the client's AWS account at about six times Fargate's price.

When to pick which

Pick A Fargate scheduled task when

  • Hours of continuous work in one process: an import, a report, a migration, a batch of AI calls.
  • The end time isn't known: the task stops, and stops billing, when the job's process exits.
  • The job needs a container: Chromium, ffmpeg, a vendor CLI, more than 10 GB of memory, up to 200 GB of disk.

Pick Lambda durable functions when

  • The long job is a chain of short steps, each finishing well inside 15 minutes, with retries per step.
  • It waits for a person or an outside system for hours or days: no compute is charged while it waits, and an execution lasts up to a year.

Pick Lambda Managed Instances when

  • Only a large, steady, event-driven Lambda that already runs 15 to 90 minutes and must not be rewritten as a container: at least three instances run around the clock.

Pick Trigger.dev when

  • Outside AWS: TypeScript jobs started from an app, mostly waiting or showing live progress, for a team that doesn't want to build retries, a dashboard and a progress channel.

Both, when

  • A durable function runs the workflow and its waits, and starts a Fargate task for the heavy step; the task reports back through the function's callback when it's done.
  • Not Fargate and Trigger.dev for the same job: they're two homes for the same work, and one of them keeps the data in the client's account.

Questions that change the answer

  • Does the job run continuously, or is it mostly waiting between short steps? Continuous points to Fargate, waiting to durable functions.

  • Must the code and the data stay in the client's AWS account, or in Australia? Then it's Fargate or durable functions, not a service outside AWS.

  • Can the job be restarted or resumed from a checkpoint? Only then is Fargate Spot worth it.

  • Does anyone wait on the result in an app? A 30 to 90 second container start is fine at 2 am and wrong behind a button.

Doesn't cover

  • Jobs that need a GPU: Fargate has none. A Runpod pod for experiments, SageMaker training jobs or EC2 on a client's data: Runpod or AWS for a GPU?

The short version

Fargate scheduled task Lambda durable functions Lambda Managed Instances Trigger.dev
My verdict Use when it fits: my answer for long jobs Watching: what I'd pilot Skip Skip
Longest run No fixed limit 1 year in all (asynchronous); 15 minutes per invocation 90 minutes, asynchronous and event-source calls only No limit
Continuous compute in one go Hours 15 minutes 90 minutes Hours
Billed while waiting Yes, every second the container is up No The instances run anyway No, on its own waits
Billed between runs Nothing Nothing At least three instances, around the clock Nothing
Where it runs The client's AWS account, Sydney The client's AWS account, Sydney The client's AWS account, Sydney Trigger.dev's machines, no region published
Biggest machine 16 vCPU, 120 GB, 200 GB of disk 10 GB, about 6 vCPU Your EC2 instance type 8 vCPU, 16 GB
Pulumi Official Official Official No provider

Running for a week isn't staying alive for a week

Two different things hide behind "long-running". A durable job is logically running for days but only uses compute in short bursts: it saves its progress after each step, stops, and resumes later, so a pause of a week costs nothing. A long process stays alive the whole time: a migration copying millions of rows, a video render, a batch of model calls holding state in memory.

Durable functions are built for the first. Fargate is built for the second. Most confusion comes from forcing one into the other: chopping a three-hour import into 15-minute Lambda pieces with their own checkpoints, or keeping a container up for a day to wait on someone's approval.

The lean Fargate setup, when the end time isn't known

Nothing has to stop Fargate when the job ends. EventBridge Scheduler starts one standalone ECS task on a schedule, the container runs the job, the process exits, and the task stops: Fargate bills per second from the image pull to the stop, with a one-minute minimum. No Lambda, no state machine, no "stopper".

Seven rules make that safe:

  1. A standalone task, never an ECS service. A service keeps a set number of copies running: when your job finishes, it starts another.
  2. One essential container, nothing else that runs forever. Send logs straight to CloudWatch with the awslogs driver, without a sidecar. An essential sidecar that never exits keeps the task, and the bill, alive.
  3. The process must exit. Node.js stays alive while anything is open: a database pool, a timer, a socket. End the job with a clean shutdown and process.exit(code), 0 on success and anything else on failure.
  4. A kill switch, because ECS has no maximum run time. Start the job through timeout: after 10 hours it sends SIGTERM, two minutes later a kill. Under the 12-hour gap between runs, two runs can never overlap. A run killed this way exits with code 124.
  5. No NAT gateway. A public subnet, a public IP and a security group with no inbound rules. The address bills only while the task runs, about $0.005 an hour; a NAT gateway bills every hour of the month.
  6. An alert when it goes wrong. The schedule only knows that RunTask was accepted. An EventBridge rule on ECS task state changes (STOPPED with a non-zero exit code) catches a crash, the timeout kill and a task that never started.
  7. Regular Fargate, not Spot, for a job whose end you don't know. Spot can stop it with two minutes' notice, so it's only for jobs that save their progress and resume.

In Pulumi, everything is in @pulumi/aws:

const cluster = new aws.ecs.Cluster("jobs");
const logs = new aws.cloudwatch.LogGroup("job", { retentionInDays: 30 });

const task = new aws.ecs.TaskDefinition("job", {
  family: "long-job",
  requiresCompatibilities: ["FARGATE"],
  networkMode: "awsvpc",
  cpu: "2048",
  memory: "4096",
  runtimePlatform: { cpuArchitecture: "ARM64", operatingSystemFamily: "LINUX" },
  executionRoleArn: executionRole.arn, // pulls the image, writes the logs
  taskRoleArn: jobRole.arn,            // what the job itself may touch
  containerDefinitions: pulumi.jsonStringify([{
    name: "job",
    image: imageUri,
    essential: true,
    // Kill switch: SIGTERM after 10 h, SIGKILL 2 min later. Under the 12 h gap, so runs never overlap.
    command: ["timeout", "-k", "120", "36000", "node", "dist/job.js"],
    stopTimeout: 120,
    logConfiguration: {
      logDriver: "awslogs",
      options: { "awslogs-group": logs.name, "awslogs-region": region, "awslogs-stream-prefix": "job" },
    },
  }]),
});

new aws.scheduler.Schedule("job-twice-daily", {
  scheduleExpression: "cron(0 6,18 * * ? *)",
  scheduleExpressionTimezone: "Australia/Sydney",
  flexibleTimeWindow: { mode: "OFF" },
  target: {
    arn: cluster.arn,
    roleArn: schedulerRole.arn, // ecs:RunTask on the task definition, iam:PassRole on both roles
    retryPolicy: { maximumRetryAttempts: 3 },
    ecsParameters: {
      taskDefinitionArn: task.arn,
      launchType: "FARGATE",
      networkConfiguration: { subnets: publicSubnetIds, securityGroups: [noInboundSg.id], assignPublicIp: true },
    },
  },
});

// A crash, the timeout kill (exit 124) or a task that failed to start: send it to an SNS topic you're subscribed to.
new aws.cloudwatch.EventRule("job-failed", {
  eventPattern: pulumi.jsonStringify({
    source: ["aws.ecs"],
    "detail-type": ["ECS Task State Change"],
    detail: { clusterArn: [cluster.arn], lastStatus: ["STOPPED"], containers: { exitCode: [{ "anything-but": 0 }] } },
  }),
});

The timeout form with seconds works with coreutils (Debian-based node:*-slim images) and recent BusyBox (Alpine). The schedule avoids 2 to 3 am, the hour Sydney skips the night daylight saving starts.

What it costs: 2 vCPU and 4 GB on Arm in Sydney is $0.09474 an hour. A job of about 2.5 hours twice a day is about 150 hours a month: about $15, plus about $0.75 for the public address and a few cents of logs and image storage. Nothing between runs.

If you'd rather have a timeout built in than the timeout wrapper, AWS Batch on Fargate has job timeouts and retries. It costs three more resources (a compute environment, a job queue, a job definition) for what one word in the command does, so I'd only reach for it once there are many such jobs.

Durable functions, when the job mostly waits

When the long part is waiting (an approval, a partner's callback, a rate limit to wait out), a container that stays up to wait is the wrong tool. A durable function saves each completed step, suspends during context.wait() or waitForCallback() without compute charges, and resumes on the next line days later. Each invocation still stops at 15 minutes, so any one step must fit inside that. The rules of replay, and the comparison with Step Functions, are in Durable functions or Step Functions?.

The two combine well. The durable function runs the workflow, starts a Fargate task for the step that needs an hour of CPU, and waits on a callback; the task's last act is to call that callback with its result.

Why not Managed Instances and their 90 minutes

Since 9 September 2026, Lambda Managed Instances allow a 90-minute timeout. Read the small print first: only asynchronous and event-source calls get it (synchronous calls and the init phase stay at 15 minutes), and the instances behind it don't scale to zero, three by default, around the clock. For a job twice a day, that's paying for machines all day to run them for three hours. Behind SQS it also means a visibility timeout of at least six times the function timeout: nine hours for a 90-minute function.

It only makes sense for a large, steady, event-driven Lambda that already runs that long and that a team won't rewrite as a container. For anything new, a Fargate task has no limit at all and bills nothing between runs.

Fargate or Trigger.dev?

Trigger.dev runs long TypeScript tasks on its own machines, with retries, free pauses and live progress built in. Here is the same work on both: Fargate at Sydney Arm rates plus the public address, Trigger.dev at its published prices.

Machine, per hour Fargate Trigger.dev Ratio
0.5 vCPU (Fargate 1 GB, Trigger.dev 0.5 GB) $0.029 $0.122 4.2×
1 vCPU, 2 GB $0.052 $0.306 5.9×
2 vCPU, 4 GB $0.100 $0.612 6.1×
4 vCPU, 8 GB $0.195 $1.224 6.3×
8 vCPU, 16 GB $0.384 $2.448 6.4×
Job, per month Fargate Trigger.dev
2.5 hours twice a day, 2 vCPU and 4 GB (150 hours) about $15 about $92
A 2-hour nightly import, 2 vCPU and 4 GB (60 hours) about $6 about $37
A daily fan-out to 500 two-minute pieces, 0.5 vCPU about $15, but RunTask starts 10 tasks per call and you build the "wait for all 500" about $61, with one batchTriggerAndWait call; the parent isn't billed while it waits
3,000 runs of 10 minutes' compute plus a 4-hour wait for a person's approval, 1 vCPU and 2 GB about $655 if the task stays up; about $26 split into two tasks with the state stored in between about $153: the run is saved during the wait

On raw compute Fargate always wins. Trigger.dev only wins when Fargate is left up through a wait, and on AWS a durable function does that waiting for free.

What Trigger.dev does that Fargate doesn't, without building it:

  • Pauses mid-job with the process saved, resuming on the next line with its variables. Only its own waits are free; an ordinary await on an API is still billed.
  • Live progress in the app: status, metadata and even an LLM's output streamed from the task to the browser.
  • Starts in a fraction of a second (100 to 300 ms warm, its own figure) against 30 to 90 seconds for a Fargate task: what matters behind a button.
  • Retries, history and a dashboard per run, with reruns from the dashboard, queues with limits per customer, and preview environments per branch.

What Fargate does that Trigger.dev doesn't: the code and the data stay in the client's AWS account, in Sydney, with an IAM role and direct access to the private database. It goes up to 16 vCPU, 120 GB and 200 GB of disk, runs any language or binary as it is, is in Pulumi officially, and has no plan cap on concurrent runs.

That's why it's a skip for me. For a startup that isn't on AWS and starts its jobs from an app, Trigger.dev's plumbing is worth its price.

And Inngest?

Inngest isn't compute: it orchestrates steps in code that runs on your own hosting, with the same replay model as durable functions. On Lambda, each step still stops at 15 minutes, so it doesn't solve a long process either. Its strengths are flow control per tenant and its dashboard, and it fits apps that aren't on AWS.

Sources, checked on 30 October 2026: Fargate pricing, scheduling ECS tasks with EventBridge Scheduler, the 90-minute timeout on Lambda Managed Instances, AWS Compute Blog on the 90-minute timeout, Lambda durable functions, Trigger.dev pricing, Trigger.dev v4, Inngest pricing.

More choices

  1. 01 Lambda, Web Functions or Workers? Where should this piece of logic run?
  2. 02 Lambda, Fargate or EC2? My Lambda is busy all day, or its reserved concurrency keeps going up. When is it time to move to a container, and where is the line?
  3. 04 Runpod or AWS for a GPU? I need an NVIDIA GPU for models and experiments. Do I rent it on Runpod, or on AWS with SageMaker or EC2, and when do I use both?
  4. 05 Cloudflare Sandbox or Lambda MicroVMs? My product runs code nobody reviewed, written by an LLM or a customer. Where does it run safely, and which fits which business?
  5. 06 SQS, EventBridge or SNS? Something happens in my app and other code must act on it. Do I put it in a queue, on an event bus, or on a topic?
  6. 07 Queue, bus or stream? SQS, EventBridge, Kinesis or Kafka Events move between my services. Are SQS, SNS and EventBridge enough, or do I need a stream like Kinesis or Kafka, and at what scale does the answer change?
  7. 08 Kinesis or Kafka on AWS? I need a real stream on AWS. Kinesis Data Streams, or Kafka on Amazon MSK, and is running Kafka myself ever worth it?
  8. 09 SQS retries or Durable Functions? A job calls something flaky (a third-party API, an LLM, a slow Postgres). How do I retry it reliably, and when do I reach for Lambda durable functions instead of tuning SQS?
  9. 10 Durable functions or Step Functions? A job has several steps, retries and waits. Do I write it as a Lambda durable function, or as a Step Functions state machine?
  10. 11 pgvector or S3 Vectors? Where should my embeddings live?
  11. 12 Pulumi or Terraform? We're putting our infrastructure in code. Pulumi or Terraform, and what if we already run one of them?
  12. 13 Cloudflare or S3 and CloudFront for a website? Where should a website, SPA or PWA live, Cloudflare static hosting or S3 behind CloudFront, and where do its heavy files go?
  13. 14 CloudWatch and Firehose, side by side How do I keep chatty logs without a CloudWatch-sized bill?
  14. 15 Athena or DuckDB? How should I query my data lake, and what changed when AWS bought DuckDB's company?
  15. 16 Neon or Aurora? I need Postgres for an app on AWS. When is Neon the right call, and when does it have to be Aurora?
  16. 17 Better Auth or Cognito? Which sign-in should my app use, and what do I actually get for free?
  17. 18 Why I ended up on AWS Which cloud should my company run on, and is AWS just the one you know?
  18. 19 Better Auth, run yourself or managed by Neon? My app is on Neon. Should I run Better Auth myself, or switch on Neon Auth, which is Better Auth managed by Neon?
  19. 20 Stripe or PayPal Should we take payments with Stripe or PayPal, and when is PayPal worth the pain?
  20. 21 Tailscale or Cloudflare Tunnel? I need to reach a private box or VPC, or put a service from it online. Is that Tailscale or Cloudflare Tunnel?
  21. 22 Resend or SES? Our app sends email, and maybe a newsletter. Resend, or Amazon SES in our own AWS account?
  22. 23 WhatsApp or Telegram Our bot should reach users in a messaging app. WhatsApp or Telegram, and why did yours get blocked?
  23. 24 Which model, at what price Claude, GPT or an open model? Which is the cheapest model that's good enough, tier by tier?
  24. 25 Anthropic API, Bedrock or OpenRouter? My product will call Claude. Do I buy it from Anthropic directly, through Amazon Bedrock in the client's AWS account, or through OpenRouter?
  25. 26 Which decision model? Jev, OpenAI Decisions, Clef, Baseten, Perplexity or Liquid? Which decision model fits my job, my volume and my risk?
  26. 27 Cloudflare AI Gateway, OpenRouter or LiteLLM? My app calls several LLMs. Do I need an AI gateway, and which one: Cloudflare AI Gateway, OpenRouter, or a LiteLLM proxy I host myself?
  27. 28 Function URL or API Gateway? My Lambda needs an HTTPS endpoint. When is a function URL enough, and when do I start on API Gateway from day one?
  28. 29 Serper or DataForSEO Should an agent get Google results from Serper or from DataForSEO, and do the two overlap or work together?

Want this stack in your AWS account?

Cloudless Labs is a Sydney consultancy. We migrate on-premises systems to AWS, build serverless apps on this stack, and coach teams to ship with Claude Code and Codex.

Talk to us

Contact Us

For general inquiries, please contact us at:

For e-learning capabilities, please contact: