AWS Lambda function deployment

This use case demonstrates deploying an AWS Lambda function with the Platform Orchestrator using an ECS-based serverless runner.

Overview

The use case sets up the complete infrastructure needed to deploy Lambda functions through the Platform Orchestrator, including:

  • Platform Orchestrator project and environment
  • Platform Orchestrator resource types and modules for Lambda deployment with optional Lambda function URL configuration
  • ECS serverless runner for Lambda deployments
  • IAM policies and roles for Lambda function management
  • S3 integration for Lambda deployment packages

You need to supply a Lambda deployment package and place it in an S3 bucket to run this use case. A sample deployment package is provided.

Prerequisites

  • A Platform Orchestrator organization
  • The octl CLI  installed locally and authenticated (octl login)
  • An AWS account with appropriate permissions to set up the objects named above
  • AWS credentials configured for the AWS provider 
    • E.g. Having the aws CLI installed locally and authenticated
  • Platform Orchestrator credentials configured for the platform-orchestrator provider 
    • For example, by authenticating locally with octl login
  • Outbound HTTPS from the ECS runner subnets to the Platform Orchestrator runner gateway
  • Either the Terraform CLI  or the OpenTofu CLI  installed locally
  • (optional) An S3 bucket for Lambda deployment packages
  • (optional) A Lambda function deployment package of your choice

The setup uses an ECS runner  for the Orchestrator to execute deployments. The use case can create all required infrastructure for you, or you can bring your own. The examples below show all scenarios.

Usage

1. Provide a deployment package S3 bucket

If you do not have an S3 bucket to host Lambda deployment packages, create one now via the AWS console , using all default settings.

2. Install the use case setup

Create a local file main.tf and populate it using one the example setups shown below. Fill in all values according to your setup.

Example 1: Create everything for me

This minimal prerequisites setup will create all AWS infrastructure and provide a publicly accessible function URL for testing.

# main.tf
module "lambda_serverless" {
  source = "github.com/stellwerk-labs/platform-orchestrator-use-cases//serverless-targets/lambda"

  # Platform Orchestrator Configuration
  org_id        = "your-org-id"
  oidc_hostname = "oidc.orchestrator.example.com"

  # AWS Configuration
  aws_region = "your-aws-region" # "e.g. eu-central-1"

  # Lambda Configuration
  lambda_package_s3_bucket      = "your-deployment-package-bucket-name"
  lambda_enable_function_url    = true
  lambda_function_url_auth_type = "NONE"  # Make function URL publicly accessible
}
Example 2: Use existing OIDC provider

If you already have an AWS OIDC provider configured for the issuer exposed by your Platform Orchestrator installation, configure it here:

# main.tf
module "lambda_serverless" {
  source = "github.com/stellwerk-labs/platform-orchestrator-use-cases//serverless-targets/lambda"

  # Platform Orchestrator Configuration
  org_id        = "your-org-id"
  oidc_hostname = "oidc.orchestrator.example.com"

  # AWS Configuration
  aws_region = "eu-central-1"

  # Lambda Configuration
  lambda_package_s3_bucket = "your-deployment-package-bucket-name"

  # OIDC configuration
  existing_oidc_provider_arn = "arn:aws:iam::123456789012:oidc-provider/oidc.orchestrator.example.com"
}
Example 3: Use a different Lambda runtime and handler

If you are using your own deployment package, configure the proper Lambda runtime:

# main.tf
module "lambda_serverless" {
  source = "github.com/stellwerk-labs/platform-orchestrator-use-cases//serverless-targets/lambda"

  # Platform Orchestrator Configuration
  org_id        = "your-org-id"
  oidc_hostname = "oidc.orchestrator.example.com"

  # AWS Configuration
  aws_region = "eu-central-1"

  # Lambda Configuration
  lambda_runtime = "<your-lambda-runtime>"
  lambda_handler = "<your-lambda-handler>"
}
Example 4: Showing advanced options
module "lambda_serverless" {
  source = "github.com/stellwerk-labs/platform-orchestrator-use-cases//serverless-targets/lambda"

  # Platform Orchestrator Configuration
  org_id             = "your-org-id"
  project_id_prefix  = "lambda-use-case"
  env_id             = "dev"
  env_type_id_prefix = "development"

  # AWS Configuration
  aws_region = "eu-central-1"

  # Existing ECS Runner Configuration
  ecs_runner_cluster_name       = "your-ecs-cluster"
  ecs_runner_subnet_ids         = ["subnet-xxxxxxxxxxxxx"]
  ecs_runner_security_group_ids = ["sg-xxxxxxxxxxxxx"]

  # Optional: Prefix for ECS runner resources (used when ecs_runner_cluster_name is null)
  # ecs_runner_prefix = "ecs-runner-"

  # Optional: Specify a custom ECS runner ID instead of auto-generating one
  # ecs_runner_id = "my-custom-runner-id"

  # Optional: Pass environment variables to the ECS runner
  # ecs_runner_environment = {
  #   LOG_LEVEL = "info"
  #   DEBUG     = "false"
  # }

  # Optional: Pass secrets to the ECS runner (ARNs from AWS Secrets Manager or Systems Manager Parameter Store)
  # ecs_runner_secrets = {
  #   API_KEY     = "arn:aws:secretsmanager:eu-central-1:123456789012:secret:my-api-key-AbCdEf"
  #   DB_PASSWORD = "arn:aws:ssm:eu-central-1:123456789012:parameter/db/password"
  # }

  # Optional: Do not force delete the S3 bucket used for runner state files on destroy
  # ecs_runner_force_delete_s3 = false

  # OIDC Configuration
  oidc_hostname              = "your-oidc.hostname.dev"
  existing_oidc_provider_arn = "arn:aws:iam::123456789012:oidc-provider/your-oidc.hostname.dev"

  # Lambda Configuration
  lambda_package_s3_bucket = "your-lambda-function-packages"
  lambda_timeout           = 100

  # Optional: customize the module/resource type ID
  # lambda_module_id_prefix = "lambda-zip"

  # Optional: customize the Lambda function name prefix
  # lambda_name_prefix = "$${context.project_id}-$${context.env_id}"

  # Optional: add or override inline IAM policies for Lambda execution role
  # lambda_additional_inline_policies = {
  #   s3_access = jsonencode({
  #     Version = "2012-10-17"
  #     Statement = [{
  #       Effect   = "Allow"
  #       Action   = ["s3:GetObject", "s3:PutObject"]
  #       Resource = "arn:aws:s3:::my-bucket/*"
  #     }]
  #   })
  # }

  lambda_runtime     = "provided.al2023"
  lambda_handler     = "bootstrap"
  lambda_memory_size = 128
  # lambda_architectures = ["arm64"]  # Use ARM architecture instead of x86_64

  # Optional: Use a custom IAM role for Lambda
  # lambda_iam_role_arn = "arn:aws:iam::123456789012:role/my-lambda-role"

  # Optional: Add managed IAM policies to Lambda execution role
  # lambda_additional_managed_policy_arns = [
  #   "arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess"
  # ]

  # Optional: Add tags to Lambda function
  # lambda_additional_tags = {
  #   Application = "MyApp"
  #   Component   = "Backend"
  # }

  # Function URL Configuration (disabled by default)
  # lambda_enable_function_url = true  # Uncomment to enable function URL
  # lambda_function_url_auth_type = "NONE"  # Make function URL publicly accessible
  # lambda_function_url_cors = {
  #   allow_origins     = ["https://example.com"]
  #   allow_methods     = ["GET", "POST"]
  #   allow_headers     = ["Content-Type"]
  #   max_age           = 300
  # }

  # Optional: Additional tags for ECS runner and other resources
  additional_tags = {
    Environment = "Development"
    ManagedBy   = "Terraform"
  }
}

Apply the setup:
# When using Terraform
terraform init
terraform apply
# When using OpenTofu
tofu init
tofu apply

3. Upload a deployment package

Upload a deployment package to the S3 bucket.

A sample deployment package containing a simple Node.js application is available for download here .

If you are using your own deployment package, make sure the Lambda runtime is set correctly. Refer to the example above to see how to set a different runtime.

Upload the package to the S3 bucket in this folder structure using the AWS console. It uses the Orchestrator project/environment hiearchy as a way to organize the packages:

<project_id>/development/function.zip

You may obtain the <project_id> of the newly created Orchestrator project from the TF outputs:

# When using Terraform
export PROJECT_ID=$(terraform show -json terraform.tfstate | jq -r '.values.root_module.child_modules[].resources[] | select(.address == "module.lambda_serverless.platform-orchestrator_project.this") | .values.id')
echo $PROJECT_ID
# When using OpenTofu
export PROJECT_ID=$(tofu show -json terraform.tfstate | jq -r '.values.root_module.child_modules[].resources[] | select(.address == "module.lambda_serverless.platform-orchestrator_project.this") | .values.id')
echo $PROJECT_ID

4. Perform a deployment

Create a deployment manifest file named manifest.yaml:

workloads:
  # The name you assign to the workload in the context of the manifest
  demo-app:
    resources:
      # The name you the assign to this resource in the context of the manifest
      demo-workload:
        # The resource type of the resource you wish to provision
        type: lambda-zip
        # The resource parameters. They are mapped to the module_params of the module
        params:
          s3_key: "${context.project_id}/${context.env_id}/function.zip"
          environment_variables:
            ENV: ${context.env_id}

Deploy the manifest into the project environment created by the use case:

octl deploy $PROJECT_ID development manifest.yaml

5. Validate the result

Once the deployment finished, open your Orchestrator console and find the project created for this use case in the “Projects” view. Use echo $PROJECT_ID to obtain the project ID.

Open the “development” environment. In the resource graph, select the lambda-zip resource representing the Lambda function.

Click on the AWS Console URL metadata to open the function in the AWS console.

Click on the Function URL metadata to open the function URL. If you used the sample deployment package, the URL will show a message from the running function, including the environment which was injected into an environment variable in the manifest.

Clean up

First remove the environment to deprovision all real-world resources:

# When using Terraform
terraform destroy -target="module.lambda_serverless.platform-orchestrator_environment.this"
# When using OpenTofu
tofu destroy -target="module.lambda_serverless.platform-orchestrator_environment.this"

Then destroy all resources created by this use case:

# When using Terraform
terraform destroy
# When using OpenTofu
tofu destroy

Resources Created

This module creates the following resources:

  • Platform Orchestrator Resources

    • Project
    • Environment
    • Runner rule
    • Resource type (lambda-zip)
    • Module configuration
    • Module rule
  • AWS Resources

    • IAM policy for Lambda deployment
    • IAM role policy attachment
  • External Modules

    • ECS serverless runner

IAM Permissions

The module creates an IAM policy that grants the ECS runner the following permissions:

  • Lambda function lifecycle management (create, update, delete)
  • Lambda function URL configuration
  • IAM role and policy management for Lambda execution roles
  • S3 access for deployment packages

Security Considerations

  • When the module creates IAM roles (lambda_iam_role_arn is null), the ECS runner is granted permissions to manage roles matching the lambda_iam_role_prefix pattern (default: lambda-role-*)
  • When using a custom IAM role (lambda_iam_role_arn is provided), no IAM role/policy management permissions are granted to the ECS runner
  • S3 bucket access is limited to the specified deployment package bucket
  • Lambda functions have inline policies for accessing project-specific S3 buckets
  • All resources are tagged for tracking and governance

References

Requirements

NameVersion
terraform>= 1.8.0
aws>= 4.0
platform-orchestrator~> 1.0
random>= 3.0

Providers

NameVersion
aws6.58.0
platform-orchestrator1.0.1
random3.9.0

Modules

NameSourceVersion
ecs_runnergithub.com/stellwerk-tf-modules/serverless-ecs-orchestrator-runnerv4.0.0

Resources

NameType
aws_iam_policy.lambda_deployment resource
aws_iam_role_policy_attachment.ecs_runner_lambda_deployment resource
platform-orchestrator_environment.this resource
platform-orchestrator_environment_type.this resource
platform-orchestrator_module.lambda_zip resource
platform-orchestrator_module_rule.lambda_zip resource
platform-orchestrator_project.this resource
platform-orchestrator_resource_type.lambda_zip resource
platform-orchestrator_runner_rule.this resource
random_id.env_type_id resource
random_id.lambda_module_id resource
random_id.project_id resource
aws_iam_policy_document.lambda_deployment data source

Inputs

NameDescriptionTypeDefaultRequired
additional_tagsAdditional tags to apply to resourcesmap(string){}no
aws_regionAWS region where resources will be deployedstring"eu-central-1"no
ecs_runner_cluster_nameName of the existing ECS cluster for the runnerstringnullno
ecs_runner_environmentPlain text environment variables to expose in the ECS runnermap(string){}no
ecs_runner_force_delete_s3Force delete the ECS runner S3 state files bucket on destroy even if it’s not emptybooltrueno
ecs_runner_idThe ID of the ECS runner. If not provided, one will be generated using ecs_runner_prefixstringnullno
ecs_runner_prefixPrefix for the ECS runner resources (used when ecs_runner_cluster_name is null)string"ecs-runner"no
ecs_runner_secretsSecret environment variables to expose in the ECS runner. Each value should be a secret or property ARNmap(string){}no
ecs_runner_security_group_idsList of security group IDs for the ECS runnerlist(string)[]no
ecs_runner_subnet_idsList of subnet IDs for the ECS runnerlist(string)[]no
env_idThe environment ID within the projectstring"development"no
env_type_id_prefixThe environment type ID prefix (e.g., ‘development’, ‘production’)string"development"no
existing_oidc_provider_arnARN of the existing OIDC providerstringnullno
lambda_additional_inline_policiesMap of additional inline IAM policies to attach to the Lambda execution role. Each key is the policy name and value is the JSON-encoded policy document.map(string){}no
lambda_additional_managed_policy_arnsList of additional managed IAM policy ARNs to attach to the Lambda execution rolelist(string)[]no
lambda_additional_tagsAdditional tags to apply to the Lambda functionmap(string){}no
lambda_architecturesInstruction set architecture for the Lambda function. Valid values: [‘x86_64’] or [‘arm64’]list(string)
[
“x86_64”
]
no
lambda_enable_function_urlEnable Lambda function URLboolfalseno
lambda_function_url_auth_typeAuthorization type for the Function URL. ‘NONE’ = public access, ‘AWS_IAM’ = requires AWS credentialsstring"AWS_IAM"no
lambda_function_url_corsCORS configuration for the Function URL. Only applies if lambda_enable_function_url is true
object({
allow_credentials = optional(bool, false)
allow_origins = optional(list(string), [""])
allow_methods = optional(list(string), ["
"])
allow_headers = optional(list(string), [])
expose_headers = optional(list(string), [])
max_age = optional(number, 0)
})
nullno
lambda_handlerLambda function handlerstring"index.handler"no
lambda_iam_role_arnOptional IAM role ARN to use for the Lambda function. If not provided, a new role will be createdstringnullno
lambda_iam_role_prefixPrefix for Lambda execution IAM role names when roles are created by the module (only used when lambda_iam_role_arn is null)string"lambda-role-"no
lambda_memory_sizeAmount of memory in MB that your Lambda function can use at runtime. Valid value between 128 MB to 10,240 MBnumber128no
lambda_module_id_prefixPrefix for the Lambda module and resource type IDsstring"lambda-zip-"no
lambda_name_prefixPrefix for Lambda function names. Supports Platform Orchestrator context variables like ${context.project_id}string"${context.project_id}"no
lambda_package_s3_bucketS3 bucket name for Lambda deployment packagesstringn/ayes
lambda_runtimeLambda runtime environmentstring"nodejs22.x"no
lambda_timeoutDefault timeout for Lambda functions in secondsnumber100no
oidc_hostnameHostname of the OIDC issuer exposed by your Platform Orchestrator installationstringn/ayes
org_idThe Platform Orchestrator organization IDstringn/ayes
project_id_prefixThe Platform Orchestrator project ID prefixstring"lambda-project-"no

Outputs

NameDescription
ecs_runner_idThe ID of the ECS runner
ecs_runner_task_role_arnThe ARN of the ECS runner task role
ecs_runner_task_role_nameThe name of the ECS runner task role
environment_idThe ID of the Platform Orchestrator environment
lambda_deployment_policy_arnThe ARN of the IAM policy for Lambda deployment
lambda_deployment_policy_nameThe name of the IAM policy for Lambda deployment
lambda_zip_module_idThe ID of the lambda-zip module
lambda_zip_resource_type_idThe ID of the lambda-zip resource type
project_idThe ID of the Platform Orchestrator project
Top