Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions docs/cli-reference/invoke.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ serverless invoke [local] --function functionName
- `--context` String data to be passed as a context to your function. Same like with `--data`, context included in `--contextPath` will overwrite the context you passed with `--context` flag.
- `--type` or `-t` The type of invocation. Either `RequestResponse`, `Event` or `DryRun`. Default is `RequestResponse`.
- `--log` or `-l` If set to `true` and invocation type is `RequestResponse`, it will output logging data of the invocation. Default is `false`.
- `--durable-execution-name` Unique name for the durable function execution (enables idempotency). Execution names must be 1-64 characters: alphanumeric, hyphens, or underscores.

## Provided lifecycle events

Expand Down Expand Up @@ -124,6 +125,16 @@ serverless invoke local --function functionName \

This example will pass the json context in the `lib/context.json` file (relative to the root of the service) while invoking the specified/deployed function.

### Function invocation with durable execution name

```bash
serverless invoke --function functionName --contextPath lib/context.json --durable-execution-name order-12345
```

This example invokes a durable function with a unique execution name. If you invoke the same function with the same execution name again, Lambda returns the cached result from the previous execution instead of re-executing the function. This enables idempotent invocations for reliable workflow orchestration.

**Note:** Durable execution names must be 1-64 characters and contain only alphanumeric characters, hyphens, or underscores. See the [Functions guide](../guides/functions.md#aws-lambda-durable-functions) for more information on configuring durable functions.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😅 sorry that's still not good, now the new section is right in the middle of the "invoke locally" documentation:

Image

Can you move it up after the #### Example data.json`` section and before the documentation about local invokes?

### Limitations

Currently, `invoke local` only supports the Node.js, Python, Java and Ruby runtimes.
Expand Down
59 changes: 59 additions & 0 deletions docs/guides/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,65 @@ functions:

**Note:** Lambda SnapStart only supports the Java 11, Java 17 and Java 21 runtimes and does not support provisioned concurrency, the arm64 architecture, the Lambda Extensions API, Amazon Elastic File System (Amazon EFS), AWS X-Ray, or ephemeral storage greater than 512 MB.

## AWS Lambda Durable Functions

[AWS Lambda Durable Functions](https://docs.aws.amazon.com/lambda/latest/dg/) enable long-running, fault-tolerant workflows without requiring custom state management. Durable functions use checkpoint-and-replay mechanisms to reliably execute workflows that can run for up to 1 year.

Durable functions are ideal for:
- Multi-step order processing workflows
- Long-running data processing jobs
- Reliable task orchestration
- Workflows requiring idempotent execution guarantees

### Configuration

To enable durable function configuration for your Lambda function, add the `durableConfig` object property in the function configuration:

```yaml
functions:
orderProcessor:
handler: handler.processOrder
runtime: nodejs22.x
durableConfig:
executionTimeout: 3600 # Required: 1-31536000 seconds (1 sec to 1 year)
retentionPeriodInDays: 30 # Optional: 1-90 days
```

### Configuration Properties

- **`executionTimeout`** (required, integer): Maximum execution time for the durable function in seconds. Range: 1 to 31,536,000 (1 year).
- **`retentionPeriodInDays`** (optional, integer): Number of days to retain execution history and state. Range: 1 to 90 days. This determines how long AWS maintains execution metadata after completion.

### Invoking Durable Functions

When invoking a durable function, you can provide a unique execution name to enable idempotent invocations:

```bash
serverless invoke --function orderProcessor --durable-execution-name order-12345
```

If you invoke the same function with the same execution name again, Lambda returns the cached result instead of re-executing. Execution names must be 1-64 characters long and contain only alphanumeric characters, hyphens, or underscores.

See the [invoke command documentation](../cli-reference/invoke.md) for more details on the `--durable-execution-name` option.

### IAM Permissions

When you configure a function with `durableConfig`, the Serverless Framework automatically adds the required IAM managed policy (`AWSLambdaBasicDurableExecutionRolePolicy`) to the Lambda execution role.

**Note:** If you use a custom IAM role for your function (via `functions[].role` or `provider.iam.role`), you must manually add the `AWSLambdaBasicDurableExecutionRolePolicy` to your custom role. The framework cannot automatically modify custom roles.

### Supported Runtimes and Limitations

**Supported Runtimes:**
- Node.js: `nodejs22.x`, `nodejs24.x`
- Python: `python3.13`, `python3.14`
- Container images with compatible runtimes

**Notes:**
- Durable functions require versioning, which is automatically enabled when `durableConfig` is present
- Functions must be invoked with a specific version or alias for durability guarantees
- Review the [AWS Lambda Durable Functions documentation](https://docs.aws.amazon.com/lambda/latest/dg/) for complete compatibility information and limitations

## VPC Configuration

You can add VPC configuration to a specific function in `serverless.yml` by adding a `vpc` object property in the function configuration. This object should contain the `securityGroupIds` and `subnetIds` array properties needed to construct VPC for this function. Here's an example configuration:
Expand Down
4 changes: 4 additions & 0 deletions docs/guides/serverless.yml.md
Original file line number Diff line number Diff line change
Expand Up @@ -658,6 +658,10 @@ functions:
kmsKeyArn: arn:aws:kms:us-east-1:XXXXXX:key/some-hash
# Defines if you want to make use of SnapStart, this feature can only be used in combination with a Java runtime. Configuring this property will result in either None or PublishedVersions for the Lambda function
snapStart: true
# Configure AWS Lambda Durable Functions for long-running workflows (auto-enables versioning)
durableConfig:
executionTimeout: 3600 # Required: 1-31536000 seconds
retentionPeriodInDays: 30 # Optional: 1-90 days
# Disable the creation of the CloudWatch log group
disableLogs: false
# Duration for CloudWatch log retention (default: forever). Overrides provider setting.
Expand Down
3 changes: 3 additions & 0 deletions lib/cli/commands-schema/aws-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ commands.set('invoke', {
contextPath: {
usage: 'Path to JSON or YAML file holding context data',
},
'durable-execution-name': {
usage: 'Unique name for durable function execution (enables idempotency)',
},
},
lifecycleEvents: ['invoke'],
});
Expand Down
9 changes: 9 additions & 0 deletions lib/plugins/aws/deploy-function.js
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,15 @@ class AwsDeployFunction {
};
}

if (functionObj.durableConfig) {
params.DurableConfig = {
ExecutionTimeout: functionObj.durableConfig.executionTimeout,
};
if (functionObj.durableConfig.retentionPeriodInDays !== undefined) {
params.DurableConfig.RetentionPeriodInDays = functionObj.durableConfig.retentionPeriodInDays;
}
}

if (
functionObj.description &&
functionObj.description !== remoteFunctionConfiguration.Description
Expand Down
4 changes: 4 additions & 0 deletions lib/plugins/aws/invoke.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ class AwsInvoke {
params.Qualifier = this.options.qualifier;
}

if (this.options['durable-execution-name']) {
params.DurableExecutionName = this.options['durable-execution-name'];
}

return this.provider.request('Lambda', 'invoke', params);
}

Expand Down
16 changes: 15 additions & 1 deletion lib/plugins/aws/package/compile/functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -466,7 +466,9 @@ class AwsCompileFunctions {
const shouldVersionFunction =
functionObject.versionFunction != null
? functionObject.versionFunction
: this.serverless.service.provider.versionFunctions;
: this.serverless.service.provider.versionFunctions ||
// Durable Functions require versioning (qualified ARNs), so we enable it automatically
!!functionObject.durableConfig;

if (
shouldVersionFunction ||
Expand Down Expand Up @@ -632,6 +634,18 @@ class AwsCompileFunctions {
}
}

if (functionObject.durableConfig) {
const durableConfig = {
ExecutionTimeout: functionObject.durableConfig.executionTimeout,
};

if (functionObject.durableConfig.retentionPeriodInDays !== undefined) {
durableConfig.RetentionPeriodInDays = functionObject.durableConfig.retentionPeriodInDays;
}

functionResource.Properties.DurableConfig = durableConfig;
}

const logs =
functionObject.logs ||
(this.serverless.service.provider.logs && this.serverless.service.provider.logs.lambda);
Expand Down
22 changes: 22 additions & 0 deletions lib/plugins/aws/package/lib/merge-iam-templates.js
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,28 @@ module.exports = {
]);
}

// check if one of the functions contains durable configuration
const durableConfigProvided = this.serverless.service.getAllFunctions().some((functionName) => {
const functionObject = this.serverless.service.getFunction(functionName);
return 'durableConfig' in functionObject;
});

if (durableConfigProvided) {
// add managed iam policy for durable execution
this.mergeManagedPolicies([
{
'Fn::Join': [
'',
[
'arn:',
{ Ref: 'AWS::Partition' },
':iam::aws:policy/service-role/AWSLambdaBasicDurableExecutionRolePolicy',
],
],
},
]);
}

return;
},

Expand Down
9 changes: 9 additions & 0 deletions lib/plugins/aws/provider.js
Original file line number Diff line number Diff line change
Expand Up @@ -1489,6 +1489,15 @@ class AwsProvider {
},
kmsKeyArn: { $ref: '#/definitions/awsKmsArn' },
snapStart: { type: 'boolean' },
durableConfig: {
type: 'object',
properties: {
executionTimeout: { type: 'integer', minimum: 1, maximum: 31536000 },
retentionPeriodInDays: { type: 'integer', minimum: 1, maximum: 90 },
},
required: ['executionTimeout'],
additionalProperties: false,
},
layers: { $ref: '#/definitions/awsLambdaLayers' },
logRetentionInDays: {
$ref: '#/definitions/awsLogRetentionInDays',
Expand Down
53 changes: 53 additions & 0 deletions test/unit/lib/plugins/aws/invoke.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -481,4 +481,57 @@ describe('test/unit/lib/plugins/aws/invoke.test.js', () => {
})
).to.be.eventually.fulfilled;
});

it('should support --durable-execution-name option', async () => {
const lambdaInvokeStub = sinon.stub();
const result = await runServerless({
fixture: 'invocation',
command: 'invoke',
options: {
function: 'callback',
'durable-execution-name': 'order-123',
},
awsRequestStubMap: {
Lambda: {
invoke: (args) => {
lambdaInvokeStub.returns('payload');
return lambdaInvokeStub(args);
},
},
},
});
expect(lambdaInvokeStub.args[0][0]).to.deep.equal({
FunctionName: result.serverless.service.getFunction('callback').name,
InvocationType: 'RequestResponse',
LogType: 'None',
DurableExecutionName: 'order-123',
Payload: Buffer.from('{}'),
});
});

it('should not include DurableExecutionName when option is not provided', async () => {
const lambdaInvokeStub = sinon.stub();
const result = await runServerless({
fixture: 'invocation',
command: 'invoke',
options: {
function: 'callback',
},
awsRequestStubMap: {
Lambda: {
invoke: (args) => {
lambdaInvokeStub.returns('payload');
return lambdaInvokeStub(args);
},
},
},
});
expect(lambdaInvokeStub.args[0][0]).to.not.have.property('DurableExecutionName');
expect(lambdaInvokeStub.args[0][0]).to.deep.equal({
FunctionName: result.serverless.service.getFunction('callback').name,
InvocationType: 'RequestResponse',
LogType: 'None',
Payload: Buffer.from('{}'),
});
});
});
53 changes: 53 additions & 0 deletions test/unit/lib/plugins/aws/package/compile/functions.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -964,6 +964,59 @@ describe('AwsCompileFunctions', () => {

expect(cfTemplate.Resources.BasicLambdaFunction.Properties).to.not.have.property('SnapStart');
});

it('should set function DurableConfig when enabled with all properties', async () => {
const { cfTemplate } = await runServerless({
fixture: 'function',
configExt: {
functions: {
basic: {
durableConfig: {
executionTimeout: 3600,
retentionPeriodInDays: 30,
},
},
},
},
command: 'package',
});

expect(cfTemplate.Resources.BasicLambdaFunction.Properties.DurableConfig).to.deep.equal({
ExecutionTimeout: 3600,
RetentionPeriodInDays: 30,
});
});

it('should set function DurableConfig with only executionTimeout', async () => {
const { cfTemplate } = await runServerless({
fixture: 'function',
configExt: {
functions: {
basic: {
durableConfig: {
executionTimeout: 7200,
},
},
},
},
command: 'package',
});

expect(cfTemplate.Resources.BasicLambdaFunction.Properties.DurableConfig).to.deep.equal({
ExecutionTimeout: 7200,
});
});

it('should not set function DurableConfig when not specified', async () => {
const { cfTemplate } = await runServerless({
fixture: 'function',
command: 'package',
});

expect(cfTemplate.Resources.BasicLambdaFunction.Properties).to.not.have.property(
'DurableConfig'
);
});
});

describe('#compileRole()', () => {
Expand Down
50 changes: 50 additions & 0 deletions test/unit/lib/plugins/aws/package/lib/merge-iam-templates.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,56 @@ describe('lib/plugins/aws/package/lib/mergeIamTemplates.test.js', () => {
],
});
});

it('should ensure needed IAM configuration when `functions[].durableConfig` is configured', async () => {
const { cfTemplate, awsNaming } = await runServerless({
fixture: 'function',
command: 'package',
configExt: {
functions: {
basic: {
durableConfig: {
executionTimeout: 3600,
},
},
},
},
});

const IamRoleLambdaExecution = awsNaming.getRoleLogicalId();
const { Properties } = cfTemplate.Resources[IamRoleLambdaExecution];
expect(Properties.ManagedPolicyArns).to.deep.includes({
'Fn::Join': [
'',
[
'arn:',
{ Ref: 'AWS::Partition' },
':iam::aws:policy/service-role/AWSLambdaBasicDurableExecutionRolePolicy',
],
],
});
});

it('should not add durable IAM policy when `functions[].durableConfig` is not configured', async () => {
const { cfTemplate, awsNaming } = await runServerless({
fixture: 'function',
command: 'package',
});

const IamRoleLambdaExecution = awsNaming.getRoleLogicalId();
const { Properties } = cfTemplate.Resources[IamRoleLambdaExecution];
const durablePolicyArn = {
'Fn::Join': [
'',
[
'arn:',
{ Ref: 'AWS::Partition' },
':iam::aws:policy/service-role/AWSLambdaBasicDurableExecutionRolePolicy',
],
],
};
expect(Properties.ManagedPolicyArns || []).to.not.deep.includes(durablePolicyArn);
});
});
});
});