Serverless Workers on AWS Lambda - .NET SDK
The Temporalio.Extensions.Aws.Lambda NuGet package lets you run a Temporal Serverless Worker on AWS Lambda.
Deploy your Worker code as a Lambda function, and Temporal Cloud invokes it when Tasks arrive.
Each invocation starts a Worker, polls for Tasks, then gracefully shuts down before a configurable invocation deadline.
You register Workflows and Activities the same way you would with a standard Worker.
For a full end-to-end deployment guide covering AWS IAM setup, compute configuration, and verification, see Deploy a Serverless Worker.
Create and run a Worker in Lambda
Add the Temporalio.Extensions.Aws.Lambda NuGet package:
dotnet add package Temporalio.Extensions.Aws.Lambda
Create a static handler delegate and call it from the AWS Lambda handler method:
using Amazon.Lambda.Core;
using Temporalio.Common;
using Temporalio.Extensions.Aws.Lambda;
public class Function
{
private static readonly Func<object?, ILambdaContext, Task> WorkerHandler =
TemporalLambdaWorker.CreateHandler(
new WorkerDeploymentVersion("my-app", "build-1"),
config =>
{
config.WorkerOptions.TaskQueue = "serverless-task-queue-1";
config.WorkerOptions.AddWorkflow<SampleWorkflow>();
config.WorkerOptions.AddActivity(SampleActivities.HelloActivityAsync);
});
public Task HandlerAsync(object? input, ILambdaContext context) =>
WorkerHandler(input, context);
}
TemporalLambdaWorker.CreateHandler takes a WorkerDeploymentVersion and a configure callback, and returns a handler delegate.
Assign the delegate to a static field so it is created once during Lambda cold start and reused across invocations.
The configure callback runs once during creation, not on every invocation.
The WorkerDeploymentVersion is required.
Worker Deployment Versioning is always enabled for Serverless Workers.
Each Workflow must have a versioning behavior, either AutoUpgrade or Pinned.
Set it per-Workflow with the [Workflow] attribute, or set a worker-level default with DefaultVersioningBehavior in DeploymentOptions.
The default versioning behavior is AutoUpgrade.
using Temporalio.Common;
using Temporalio.Workflows;
[Workflow(VersioningBehavior = VersioningBehavior.Pinned)]
public class SampleWorkflow
{
[WorkflowRun]
public async Task<string> RunAsync(string name)
{
return await Workflow.ExecuteActivityAsync(
() => SampleActivities.HelloActivityAsync(name),
new() { StartToCloseTimeout = TimeSpan.FromSeconds(10) });
}
}
Configure the Temporal connection
The Temporalio.Extensions.Aws.Lambda package automatically loads Temporal client configuration from a TOML config file and environment variables. Refer to Environment Configuration for more details.
The config file is resolved in order:
TEMPORAL_CONFIG_FILEenvironment variable, if set.temporal.tomlin$LAMBDA_TASK_ROOT(typically/var/task).temporal.tomlin the current working directory.
The file is optional. If absent, only environment variables are used.
To bypass config loading, assign explicit client options in the configure callback:
config.ClientOptions = new TemporalClientConnectOptions
{
TargetHost = "my-namespace.a1b2c.tmprl.cloud:7233",
Namespace = "my-namespace.a1b2c",
ApiKey = Environment.GetEnvironmentVariable("TEMPORAL_API_KEY"),
Tls = new TlsOptions(),
};
Encrypt sensitive values like TLS keys or API keys. Refer to AWS documentation for options.
TLS/CA loading on Lambda
Some AWS Lambda .NET images override the SSL_CERT_FILE environment variable in a way that prevents the SDK's Rust-based runtime from loading system root CAs.
If you encounter TLS certificate errors on Lambda, see the AWS Lambda .NET CA loading workaround in the SDK README.
Adjust Worker defaults for Lambda
The Temporalio.Extensions.Aws.Lambda package applies conservative defaults suited to short-lived Lambda invocations.
These differ from standard Worker defaults to avoid overcommitting resources in a constrained environment.
| Setting | Lambda default |
|---|---|
MaxConcurrentActivities | 2 |
MaxConcurrentWorkflowTasks | 10 |
MaxConcurrentLocalActivities | 2 |
MaxConcurrentNexusTasks | 5 |
MaxConcurrentWorkflowTaskPolls | 2 |
MaxConcurrentActivityTaskPolls | 1 |
MaxConcurrentNexusTaskPolls | 1 |
MaxCachedWorkflows | 30 |
GracefulShutdownTimeout | 5 seconds |
DisableEagerActivityExecution | Always true |
ShutdownDeadlineBuffer | 7 seconds |
DisableEagerActivityExecution is always true and cannot be overridden.
Eager Activities require a persistent connection, which Lambda invocations don't maintain.
ShutdownDeadlineBuffer is specific to the Temporalio.Extensions.Aws.Lambda package.
It controls the time reserved after the worker run budget for worker shutdown and hooks.
The default is 7 seconds.
If your Worker handles long-running Activities, increase GracefulShutdownTimeout, ShutdownDeadlineBuffer, and the Lambda invocation deadline (--timeout) together.
For guidance on how these values relate, see Tuning for long-running Activities.
Add observability with OpenTelemetry
The Temporalio.Extensions.Aws.Lambda.OpenTelemetry NuGet package provides OpenTelemetry integration with defaults configured for the AWS Distro for OpenTelemetry (ADOT) Lambda layer.
With this enabled, the Worker emits SDK metrics and distributed traces for Workflow and Activity executions.
The ADOT Lambda layer collects this telemetry and can forward traces to AWS X-Ray and metrics to Amazon CloudWatch.
The underlying metrics and traces are the same ones the .NET SDK emits in any environment. For general observability concepts and the full list of available metrics, see the SDK metrics reference.
Add the OpenTelemetry extension package:
dotnet add package Temporalio.Extensions.Aws.Lambda.OpenTelemetry
Call LambdaWorkerOpenTelemetry.ApplyDefaults in the configure callback:
using Amazon.Lambda.Core;
using Temporalio.Common;
using Temporalio.Extensions.Aws.Lambda;
using Temporalio.Extensions.Aws.Lambda.OpenTelemetry;
public class Function
{
private static readonly Func<object?, ILambdaContext, Task> WorkerHandler =
TemporalLambdaWorker.CreateHandler(
new WorkerDeploymentVersion("my-app", "build-1"),
config =>
{
LambdaWorkerOpenTelemetry.ApplyDefaults(config);
config.WorkerOptions.TaskQueue = "serverless-task-queue-1";
config.WorkerOptions.AddWorkflow<SampleWorkflow>();
config.WorkerOptions.AddActivity(SampleActivities.HelloActivityAsync);
});
public Task HandlerAsync(object? input, ILambdaContext context) =>
WorkerHandler(input, context);
}
ApplyDefaults configures Temporal tracing with TracingInterceptor, creates an OTLP trace exporter and tracer provider, configures Core SDK OTLP metrics, uses AWS X-Ray-compatible trace IDs, and registers a per-invocation shutdown hook that force-flushes traces.
By default, telemetry is sent to localhost:4317, which is the ADOT Lambda layer's default collector endpoint.
The endpoint can be overridden with the OTEL_EXPORTER_OTLP_ENDPOINT environment variable.
You can customize the defaults by passing a LambdaWorkerOpenTelemetryOptions object:
LambdaWorkerOpenTelemetry.ApplyDefaults(
config,
new LambdaWorkerOpenTelemetryOptions
{
CollectorEndpoint = "http://localhost:4317",
ServiceName = "my-worker",
MetricsExportInterval = TimeSpan.FromSeconds(5),
});
Core SDK metrics export every 10 seconds by default. Set MetricsExportInterval shorter than your Lambda timeout to increase the chance that at least one metrics export happens during each invocation.
To collect this telemetry, attach the ADOT Collector layer to your Lambda function. .NET does not need a language-specific ADOT layer because the OTel SDK is included as a dependency of the package.
The default Collector configuration does not route OpenTelemetry Protocol (OTLP) data to the traces pipeline.
You must provide a custom Collector configuration that wires the OTLP receiver to both the traces and metrics pipelines.
Bundle the following otel-collector-config.yaml in your Lambda deployment package:
receivers:
otlp:
protocols:
grpc:
endpoint: "localhost:4317"
http:
endpoint: "localhost:4318"
exporters:
debug:
awsxray:
region: us-west-2
awsemf:
namespace: TemporalWorkerMetrics
log_group_name: /aws/lambda/<your-function-name>
region: us-west-2
dimension_rollup_option: NoDimensionRollup
resource_to_telemetry_conversion:
enabled: true
service:
pipelines:
traces:
receivers: [otlp]
exporters: [awsxray, debug]
metrics:
receivers: [otlp]
exporters: [awsemf]
telemetry:
logs:
level: debug
metrics:
address: localhost:8888
Set the following environment variable on the Lambda function to point the Collector at the bundled config:
OPENTELEMETRY_COLLECTOR_CONFIG_URI=/var/task/otel-collector-config.yaml
Enable X-Ray active tracing on the Lambda function:
aws lambda update-function-configuration \
--function-name <your-function-name> \
--tracing-config Mode=Active
The Lambda execution role must have permissions to write to X-Ray and CloudWatch.
Add xray:PutTraceSegments, xray:PutTelemetryRecords, and cloudwatch:PutMetricData permissions to the execution role.
Without these permissions, the Collector fails silently and no telemetry appears.
You can also configure tracing and metrics manually using TracingInterceptor and TemporalRuntime:
using Temporalio.Extensions.OpenTelemetry;
config.ClientOptions.Interceptors = new[] { new TracingInterceptor() };
config.ClientOptions.Runtime = new TemporalRuntime(new TemporalRuntimeOptions
{
Telemetry = new TelemetryOptions
{
Metrics = new MetricsOptions(new OpenTelemetryOptions("http://collector:4317")),
},
});