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 hctl CLI  installed locally and authenticated (hctl 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 
    • E.g. Having the htcl CLI installed locally and authenticated
  • 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/humanitec/platform-orchestrator-use-cases//serverless-targets/lambda"

  # Platform Orchestrator Configuration
  org_id = "your-org-id"

  # 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 OIDC provider configured for oidc.humanitec.dev, configure it here:

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

  # Platform Orchestrator Configuration
  org_id = "your-org-id"

  # 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.humanitec.dev"
}
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/humanitec/platform-orchestrator-use-cases//serverless-targets/lambda"

  # Platform Orchestrator Configuration
  org_id = "your-org-id"

  # 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/humanitec/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:

hctl deploy $PROJECT_ID development manifest.yaml

5. Validate the result

Once the deployment finished, open the 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

Name Version
terraform >= 1.8.0
aws >= 4.0
platform-orchestrator ~> 2.0
random >= 3.0

Providers

Name Version
aws 6.21.0
platform-orchestrator 2.10.1
random 3.7.2

Modules

Name Source Version
ecs_runner github.com/humanitec-tf-modules/serverless-ecs-orchestrator-runner v1.1.0

Resources

Name Type
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

Name Description Type Default Required
additional_tags Additional tags to apply to resources map(string) {} no
aws_region AWS region where resources will be deployed string "eu-central-1" no
ecs_runner_cluster_name Name of the existing ECS cluster for the runner string null no
ecs_runner_environment Plain text environment variables to expose in the ECS runner map(string) {} no
ecs_runner_force_delete_s3 Force delete the ECS runner S3 state files bucket on destroy even if it’s not empty bool false no
ecs_runner_id The ID of the ECS runner. If not provided, one will be generated using ecs_runner_prefix string null no
ecs_runner_prefix Prefix for the ECS runner resources (used when ecs_runner_cluster_name is null) string "ecs-runner" no
ecs_runner_secrets Secret environment variables to expose in the ECS runner. Each value should be a secret or property ARN map(string) {} no
ecs_runner_security_group_ids List of security group IDs for the ECS runner list(string) [] no
ecs_runner_subnet_ids List of subnet IDs for the ECS runner list(string) [] no
env_id The environment ID within the project string "development" no
env_type_id_prefix The environment type ID prefix (e.g., ‘development’, ‘production’) string "development" no
existing_oidc_provider_arn ARN of the existing OIDC provider string null no
lambda_additional_inline_policies Map 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_arns List of additional managed IAM policy ARNs to attach to the Lambda execution role list(string) [] no
lambda_additional_tags Additional tags to apply to the Lambda function map(string) {} no
lambda_architectures Instruction set architecture for the Lambda function. Valid values: [‘x86_64’] or [‘arm64’] list(string)
[
“x86_64”
]
no
lambda_enable_function_url Enable Lambda function URL bool false no
lambda_function_url_auth_type Authorization type for the Function URL. ‘NONE’ = public access, ‘AWS_IAM’ = requires AWS credentials string "AWS_IAM" no
lambda_function_url_cors CORS 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)
})
null no
lambda_handler Lambda function handler string "index.handler" no
lambda_iam_role_arn Optional IAM role ARN to use for the Lambda function. If not provided, a new role will be created string null no
lambda_iam_role_prefix Prefix 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_size Amount of memory in MB that your Lambda function can use at runtime. Valid value between 128 MB to 10,240 MB number 128 no
lambda_module_id_prefix Prefix for the Lambda module and resource type IDs string "lambda-zip-" no
lambda_name_prefix Prefix for Lambda function names. Supports Platform Orchestrator context variables like ${context.project_id} string "${context.project_id}" no
lambda_package_s3_bucket S3 bucket name for Lambda deployment packages string n/a yes
lambda_runtime Lambda runtime environment string "nodejs22.x" no
lambda_timeout Default timeout for Lambda functions in seconds number 100 no
oidc_hostname OIDC hostname for authentication string "oidc.humanitec.dev" no
org_id The Platform Orchestrator organization ID string n/a yes
project_id_prefix The Platform Orchestrator project ID prefix string "lambda-project-" no

Outputs

Name Description
ecs_runner_id The ID of the ECS runner
ecs_runner_task_role_arn The ARN of the ECS runner task role
ecs_runner_task_role_name The name of the ECS runner task role
environment_id The ID of the Platform Orchestrator environment
lambda_deployment_policy_arn The ARN of the IAM policy for Lambda deployment
lambda_deployment_policy_name The name of the IAM policy for Lambda deployment
lambda_zip_module_id The ID of the lambda-zip module
lambda_zip_resource_type_id The ID of the lambda-zip resource type
project_id The ID of the Platform Orchestrator project
Top