Serverless deployment

Serverless architecture allows developers to focus on writing application logic without worrying about managing servers or infrastructure. AWS Lambda automatically handles provisioning, scaling, and maintaining resources, letting you concentrate solely on your code. Following are the Ballerina functions in a serverless environment,

AWS Lambda

The AWS Lambda extension provides the functionality to write AWS Lambda-compatible packages by exposing a Ballerina function as an AWS Lambda function.

Info: Ballerina functions can be deployed in AWS Lambda by annotating a Ballerina function with @lambda:Function adhering to the following function signature:
function (lambda:Context, json|EventType) returns json|error

Supported triggers

An AWS Lambda function can be triggered by various AWS services. You can find the list of supported notification types below.

Each trigger delivers its event in a different payload format. Therefore, the package provides a record type per trigger, which you can use as the parameter type of the function instead of json.

TriggerEvent record type
Direct invocationjson
SQSlambda:SQSEvent
S3lambda:S3Event
DynamoDBlambda:DynamoDBEvent
SESlambda:SESEvent
API Gatewaylambda:APIGatewayProxyRequest
EventBridgelambda:EventBridgeEvent
SNSlambda:SNSEvent
Kinesis Data Streamslambda:KinesisEvent
Function URLlambda:FunctionURLRequest

Follow the instructions in the sections below to deploy a Ballerina function in AWS Lambda.

Set up the AWS account

Follow the steps below to set up an AWS account and the AWS CLI.

  1. Install the AWS CLI.

  2. Create an AWS account and sign in to it.

Create a user

Follow the steps below to create a new user in your AWS account.

  1. Create a new AWS user.

    Note: Enter a username and enable access to the AWS Management Console.

  2. Set the permissions to the created user.

    Note: Enable AWSLambda_FullAccess or higher permissions.

  3. Obtain the access keys.

  4. Configure the AWS CLI for the user by providing the access key and secret generated in the user creation.

Create a role

Follow the steps below to create a new role in your AWS account.

  1. Create a new role.

  2. Set the permissions to the created role.

    Info: Enable AWSLambda_FullAccess or higher permissions.

  3. Access the newly created role in the AWS Console, and copy the role ARN to use when the Lambda function is being deployed.

Create the function

You can write AWS Lambda functions that use different triggers based on your use case.

Functions annotated as @lambda:Function should always have the first parameter with the lambda:Context object, which contains the information and operations related to the current function execution in AWS Lambda such as the request ID and the remaining execution time.

The second parameter with the json value contains the input request data. This input value format will vary depending on the source, which invoked the function (e.g., an AWS S3 bucket update event). The return type of the function is json. When the function is triggered by the event, the function body executes and it simply logs the input JSON and returns the JSON.

Alternatively, you can declare the second parameter with the event record type of the trigger, in which case the payload is converted to that record and its fields become directly accessible as shown below.

Copy
import ballerinax/aws.lambda;

@lambda:Function
public function notifySNS(lambda:Context ctx, lambda:SNSEvent event) returns json {
    return event.Records[0].Sns.Message;
}

Tip: Some event fields have names that are not valid Ballerina identifiers. For example, the detail-type field of an EventBridge event is accessed as event.detail\-type.

A function can also be invoked over a function URL, which is a dedicated HTTPS endpoint for the function. Function URLs use the API Gateway payload format 2.0. Therefore, declare the parameter as lambda:FunctionURLRequest rather than lambda:APIGatewayProxyRequest, and return a lambda:FunctionURLResponse to control the status code and the headers of the response as shown below.

Copy
import ballerinax/aws.lambda;

@lambda:Function
public function greet(lambda:Context ctx, lambda:FunctionURLRequest request) returns json {
    lambda:FunctionURLResponse response = {
        statusCode: 201,
        headers: {"Content-Type": "application/json"},
        body: string `{"path": "${request.rawPath}"}`
    };
    return response.toJson();
}

Note: The compiler validates the signature of every annotated function and fails the build when it does not match, for example, when the function has no parameters, has defaultable or rest parameters, has a first parameter that is not lambda:Context, or returns an unsupported type. Earlier versions excluded such a function from the generated artifacts without reporting anything.

Info: For examples of creating AWS Lambda functions, see Examples.

Build the function

The AWS Lambda functionality is implemented as a compiler extension. Therefore, artifact generation happens automatically when you build a Ballerina module by executing the command below.

Copy
$ bal build --cloud="aws_lambda"

Tip: You can append the --graalvm flag to the above build command to build the native executable. This executable will have a much smaller memory footprint and faster startup time. For more information, see Build the executable in a container.

Info: To package the function as a container image instead of a ZIP file, see Deploy the function as a container image.

Deploy the function

The AWS Lambda functionality in Ballerina is implemented as a custom AWS Lambda layer. This information is provided when the function is created. The compiler generates the aws-ballerina-lambda-functions.zip file, which encapsulates all the AWS Lambda functions that are generated. This ZIP file can be used with the AWS web console or the AWS CLI to deploy the functions.

To deploy the function, execute the command, which you get in the CLI output logs after you build the function. For examples, see Examples.

Note: When you are deploying, make sure to replace the $FUNCTION_NAME, $LAMBDA_ROLE_ARN, and $REGION_ID placeholders with the corresponding values you obtained when setting up the AWS account and creating a role.

Info: The functions are deployed on the provided.al2023 custom runtime. The provided runtime, which earlier versions used, has been retired by AWS and can no longer be used to create or update a function.

Info: For the supported parameters, go to the create-function documentation. You might need to change parameters such as the MemorySize and Timeout depending on your application and connection speed.

Redeploy the function

After the function is created, you can push a new build of it by executing the aws lambda update-function-code command, which you also get in the CLI output logs after you build the function. The command uploads the regenerated ZIP file, or refers to the new image with the --image-uri parameter when the function is deployed as a container image. The runtime, the role, and the layer are configured when the function is created. Therefore, they are not repeated when you redeploy.

A function created before AWS retired the provided runtime in February 2024 is still configured with a runtime that can no longer be selected, and it cannot be redeployed as it is. Move it to provided.al2023 once by executing the command below, and then redeploy as usual.

Copy
$ aws lambda update-function-configuration --function-name $FUNCTION_NAME --runtime provided.al2023

Deploy the function as a container image

Building with --cloud="aws_lambda" produces a ZIP deployment package, which AWS Lambda limits to 250 MB unzipped. To package the function as a container image instead, which supports images of up to 10 GB, execute the command below.

Copy
$ bal build --cloud="aws_lambda_image"

The image is named after the package and tagged with the package version. To deploy it, execute the commands, which you get in the CLI output logs, to create an Amazon ECR repository, push the image to it, and create the function from the pushed image.

The handler is selected per function at deployment time with the --image-config parameter of the create-function command. Therefore, a single image can serve every @lambda:Function in the package.

Note: Install and configure Docker in your machine before building a container image.

Note: A package version that is not a valid Docker tag is converted to one, and the compiler reports the tag it used.

Tip: You can append the --graalvm flag to the above build command to produce a native image on the provided.al2023 base image instead of a JVM image on public.ecr.aws/lambda/java:21, which gives a considerably smaller image and faster cold starts.

Route the invocation results to a destination

A function can route the result of an asynchronous invocation to another AWS service, without any code in the function itself, by declaring destinations in the annotation as shown below.

Copy
import ballerinax/aws.lambda;

@lambda:Function {
    destinations: {
        onSuccess: "arn:aws:sqs:<REGION_ID>:<ACCOUNT_ID>:orders-processed",
        onFailure: "arn:aws:sns:<REGION_ID>:<ACCOUNT_ID>:alerts"
    }
}
public function processOrder(lambda:Context ctx, json input) returns json {
    return {status: "ok"};
}

Note: The destination ARNs are ordinary Ballerina strings. Therefore, replace the <REGION_ID> and <ACCOUNT_ID> placeholders in the source itself before you build the package.

Either field can be omitted, and a destination can be an SQS queue, an SNS topic, an EventBridge event bus, or another Lambda function. Destinations are configured separately from the function itself. Therefore, the compiler prints an aws lambda put-function-event-invoke-config command alongside the deploy commands, which you execute after the function is deployed.

AWS delivers a record that describes the invocation to the destination rather than the value the function returned. A successful invocation delivers the request payload and the response payload with a condition of Success, as shown below. A failed one carries a condition of either RetriesExhausted, when the function kept failing until the retries ran out, or EventAgeExceeded, when the event waited longer than the MaximumEventAgeInSeconds configured for the function.

Copy
{
  "version": "1.0",
  "requestContext": {"condition": "Success", "approximateInvokeCount": 1},
  "requestPayload": {"hello": "world"},
  "responsePayload": {"status": "ok"}
}

Note: The destinations field configures function-level destinations, which apply to asynchronous invocations only. A function invoked synchronously, such as through a function URL or through an aws lambda invoke command without --invocation-type Event, returns its result to the caller instead. An event source mapping that polls a queue or a stream also invokes the function synchronously and does not use this field, although some event source mappings support an OnFailure destination of their own, which is configured on the mapping rather than on the function.

Note: The execution role of the function needs permission to write to the destination (e.g., sqs:SendMessage for an SQS queue). Without it, the invocation succeeds but the record is never delivered, and Lambda reports this through the DestinationDeliveryFailures CloudWatch metric rather than as an invocation error.

Info: Every field of the annotation is optional. Therefore, @lambda:Function remains valid with no annotation value, and existing functions require no change.

CI/CD deployment with AWS Lambda

This Ballerina GitHub action workflow automates the continuous integration and continuous deployment (CI/CD) process for a Ballerina project. It is triggered by every push to the repository and automatically builds the project.

Copy
name: Ballerina CI/CD with AWS Lambda

on: [push]

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest

    steps:
      # Step 1: Checkout the Repository
      - name: Checkout
        uses: actions/checkout@v4

      # Step 2: Build the Ballerina Project
      - name: Ballerina Build
        uses: ballerina-platform/ballerina-action@master
        with:
          args: build --cloud=aws_lambda

      # Step 3: Configure AWS Credentials
      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ secrets.AWS_REGION }}

      # Step 4: Deploy to AWS Lambda
      - name: Deploy to AWS Lambda
        run: |
          aws lambda update-function-code \
            --function-name your_lambda_function_name \
            --zip-file fileb://target/aws_lambda/aws-ballerina-lambda-functions.zip

      # Optional: Test the Lambda Function
      - name: Invoke Lambda Function
        run: |
          aws lambda invoke --function-name your_lambda_function_name --payload '{}' \
            --cli-binary-format raw-in-base64-out response.json
          cat response.json
  • Step 01: The push event can initiate a build and deployment process
  • Step 02: This command tells Ballerina to compile the project specifically for AWS Lambda. It optimizes the output for Lambda’s serverless runtime requirements, and generates the aws-ballerina-lambda-functions.zip deployment package in the target/aws_lambda directory.
  • Step 03: Ensures that the workflow has the necessary credentials to interact with AWS services.
  • Step 04: Deploy the packaged Ballerina function to AWS Lambda by uploading the generated ZIP file.
  • Step 05: Test the deployed Lambda function by invoking it and outputting the response to confirm that the function works as expected.

Note: The workflow above updates a function that already exists. Create it once with the aws lambda create-function command, which you get in the CLI output logs after you build the function.

Examples

In a more practical scenario, the AWS Lambda functions will be used by associating them with an external event source such as Amazon DynamoDB or Amazon SQS. For more information on this, go to AWS Lambda event source mapping documentation.

For examples of the usage of AWS Lambda functions, see AWS Lambda.

Azure Functions

The Azure Functions extension provides the functionality to expose a Ballerina function as a serverless function in the Azure Functions platform.

Info: Azure Functions can be written in Ballerina using the listeners and services provided by the Azure Functions package.

Supported triggers and bindings

An Azure Function consists of a trigger and optional bindings. A trigger defines how a function is invoked. A binding is an approach in which you can declaratively connect other resources to the function.

There are input and output bindings. An input binding is a source of data that flows into the function. An output binding allows outputting data from the function to an external resource. For more information, go to Azure Functions triggers and bindings concepts.

The following Azure Functions triggers and bindings are currently supported in Ballerina.

Supported triggersSupported output bindingsSupported input bindings
HTTPHTTP-
QueueQueue-
BlobBlobBlob
-Twilio SMS-
Cosmos DBCosmos DBCosmos DB
Timer--

Follow the instructions in the sections below to deploy a Ballerina function in Azure Functions.

Set up Azure Functions

Follow the steps below to set up the Azure Functions.

  1. Create an Azure subscription.
  2. Install the Azure CLI.
  3. Install and configure the Functions Core Tools.

Create the app

Follow the steps below to create the Azure function app.

Note: Make sure to remember the function application name and storage account name as they will be required in the code samples.

  1. Execute the az login command on the CLI to log in to the Azure CLI.

  2. Create an Azure Function app with the default option of creating a resource group automatically and the requirements below.

    • Runtime stack - Java 21
    • Hosting operating system - Windows (Currently, Linux is not supported in Azure by default for custom handlers.)

Create the function

You can write Azure functions that use different triggers to invoke the function, and output bindings to generate the response based on your use case.

Info: For examples of creating Azure functions, see Examples.

Build the function

The Azure Functions functionality is implemented as a compiler extension. Therefore, artifact generation happens automatically when you build a Ballerina module by executing the command below.

Copy
$ bal build --cloud="azure_functions"

Deploy the function

To deploy the function, execute the command, which you get in the CLI output logs after you build the function. For examples, see Examples.

Note: When you are deploying, make sure to replace the <function_app_name> placeholder with the app name of the created function.

Azure Functions as a native executable

You can use Azure Functions in the native approach of Ballerina.

Set up the prerequisites

Follow the steps below to set up the prerequisites, which are specifically required to follow the native approach.

  1. Set up the general prerequisites.
  2. Install and configure Docker in your machine.
  3. Install and configure GraalVM in your machine.

Create the app

Create an Azure Function app with the given resource group with the following requirements.

  • Runtime stack - Java 21
  • Hosting operating system - Linux (As of now, Ballerina Azure functions native support is only available for Linux.)

Create the function

An Azure Functions package supports the two build options below.

Info: Both build options for the non-native JVM-based approach will behave the same since the JVM is platform-independent.

  • cloud="azure_functions" - Builds the native executable to be compatible with the Azure Functions cloud.
  • cloud="azure_functions_local" - Builds the native executable to be compatible with your machine for development purposes.

You can use these build options along with the graalvm build option (--graalvm) while building the package, as shown below.

Build locally

The example command below will build the package and run it locally.

Copy
$ bal build --cloud="azure_functions_local" --graalvm

Info: This will use the GraalVM you installed on your machine to build the native image and make the generated executable compatible with your machine.

Build for the cloud

The example command below will build the package for the Azure Functions cloud.

Copy
$ bal build --cloud="azure_functions" --graalvm

Info: This will perform the compilation inside the Docker image to make it compatible with the Azure Functions cloud environment.

Deploy the function

You can either deploy the Azure Functions package locally or on the Azure Functions cloud.

Deploy locally

The example command below will deploy the package locally.

Copy
$ func start --script-root target/azure_functions

Deploy on the cloud

The example command below will deploy the package on the Azure Functions cloud.

Copy
$ func azure functionapp publish <function_app_name> --script-root target/azure_functions

CI/CD with Azure Functions

This GitHub Action workflow automates the continuous integration and continuous deployment (CI/CD) process for a Ballerina project. It is triggered by every push to the repository and automatically builds the project.

Copy
name: Ballerina CI/CD with Azure Functions

on: [push]

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest

    steps:
      # Step 1: Checkout the Repository
      - name: Checkout
        uses: actions/checkout@v2

      # Step 2: Build the Ballerina Application
      - name: Build Ballerina Application
        run: bal build

      # Step 3: Set up Azure Functions Core Tools
      - name: Set up Azure Functions Core Tools
        uses: philippelemaire/install-azure-functions-core-tools@v1

      # Step 4: Deploy to Azure Functions
      - name: Azure Functions Action
        uses: Azure/functions-action@v1
        with:
          app-name: 'your-function-app-name'
          package: './'
        env:
          AZURE_FUNCTIONAPP_PUBLISH_PROFILE: ${{ secrets.AZURE_FUNCTIONAPP_PUBLISH_PROFILE }}

      # Optional: Test the Azure Function
      - name: Invoke Azure Function
        run: |
          curl https://your-function-app-name.azurewebsites.net/api/your-function-name
  • Step 01: The push event can initiate a build and deployment process
  • Step 02: This step compiles the Ballerina application to generate an executable JAR file. The bal build command compiles the code and packages it, making it ready for deployment.
  • Step 03: Install the Azure Functions Core Tools, which are necessary for deploying and testing Azure Functions in this GitHub Actions environment.
  • Step 04: Deploy the built Ballerina application to an Azure Function App. This step uploads the packaged function to Azure and configures it for serverless execution.

Examples

For examples of the usage of Azure Functions, see Azure Functions.