How to Run Bash Scripts on AWS Lambda?

· 4 min read
How to Run Bash Scripts on AWS Lambda?

If you want to run bash scripts on AWS Lambda, create a Lambda function with a custom runtime. Here are the steps to follow:

Create a Lambda function with the "Author from scratch" option and "Use default bootstrap on Amazon Linux 1" as runtime.

Lambda function with the "Author from scratch" option and "Use default bootstrap on Amazon Linux 1" as runtime
🏖️
The custom runtime option - "Provide your own bootstrap on Amazon Linux 2" will generate a sample bootstrap for your quick start.

Once the function is created, you will see the bootstrap.sh file and the hello.js handler function in the code editor.  

As mentioned in the README file, the executable runtime file name of Bash should be bootstrap. So, rename bootstrap.sh to bootstrap. Otherwise, you'll get the following Runtime.InvalidEntrypoint error.

{
  "errorType": "Runtime.InvalidEntrypoint",
  "errorMessage": "RequestId: f611525c-7589-440d-92e0-f9d066486df6 Error: Couldn't find valid bootstrap(s): [/var/task/bootstrap /opt/bootstrap]"
}

Generated bootstrap file:

#!/bin/sh
set -euo pipefail

# Handler format: <script_name>.<bash_function_name>
#
# The script file <script_name>.sh  must be located at the root of your
# function's deployment package, alongside this bootstrap executable.
source $(dirname "$0")/"$(echo $_HANDLER | cut -d. -f1).sh"

while true
do
    # Request the next event from the Lambda runtime
    HEADERS="$(mktemp)"
    EVENT_DATA=$(curl -v -sS -LD "$HEADERS" -X GET "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/next")
    INVOCATION_ID=$(grep -Fi Lambda-Runtime-Aws-Request-Id "$HEADERS" | tr -d '[:space:]' | cut -d: -f2)

    # Execute the handler function from the script
    RESPONSE=$($(echo "$_HANDLER" | cut -d. -f2) "$EVENT_DATA")

    # Send the response to Lambda runtime
    curl -v -sS -X POST "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/$INVOCATION_ID/response" -d "$RESPONSE"
done
💦
Once the script is loaded, the runtime process events in an infinite while loop. It retrieves the invocation event using Lambda Runtime API, runs the handler function (e.g. handler ()) from the script (e.g. hello.js) and finally post the response to Lambda runtime using Runtime API.

Generated sample handler function (hello.js)

function handler () {
    EVENT_DATA=$1

    RESPONSE="{\"statusCode\": 200, \"body\": \"Hello from Lambda!!\"}"
    echo $RESPONSE
}

Add your bash scripts in the handler function.  For example, I added just one command to retrieve the date and echo the same in the response.

function handler () {
    EVENT_DATA=$1
    COMMAND=`date`
    RESPONSE="{\"statusCode\": 200, \"body\": \"Date: $COMMAND\"}"
    echo $RESPONSE
}

Now, deploy the change and test the lambda function.

Sample Execution Results:

Response
{
  "statusCode": 200,
  "body": "Date: Mon Sep 25 17:11:56 UTC 2023"
}

Function Logs
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 9001 (#0)
> GET /2018-06-01/runtime/invocation/next HTTP/1.1
> Host: 127.0.0.1:9001
> User-Agent: curl/7.61.1
> Accept: */*
> 
START RequestId: 5d883ecd-fa32-4c74-a5aa-de96d6f2d1eb Version: $LATEST
< HTTP/1.1 200 OK
< Content-Type: application/json
< Lambda-Runtime-Aws-Request-Id: 5d883ecd-fa32-4c74-a5aa-de96d6f2d1eb
< Lambda-Runtime-Deadline-Ms: 1695661919762
< Lambda-Runtime-Invoked-Function-Arn: arn:aws:lambda:us-west-2:551672154962:function:BashOnLambda
< Lambda-Runtime-Trace-Id: Root=1-6511bf5c-3cd07ebd5ffeef84556961d0;Parent=5f0d38345ecb74e9;Sampled=0;Lineage=03f3abf3:0
< Date: Mon, 25 Sep 2023 17:11:56 GMT
< Content-Length: 49
< 
{ [49 bytes data]
* Connection #0 to host 127.0.0.1 left intact
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 9001 (#0)
> POST /2018-06-01/runtime/invocation/5d883ecd-fa32-4c74-a5aa-de96d6f2d1eb/response HTTP/1.1
> Host: 127.0.0.1:9001
> User-Agent: curl/7.61.1
> Accept: */*
> Content-Length: 65
> Content-Type: application/x-www-form-urlencoded
> 
} [65 bytes data]
* upload completely sent off: 65 out of 65 bytes
< HTTP/1.1 202 Accepted
< Content-Type: application/json
< Date: Mon, 25 Sep 2023 17:11:56 GMT
< Content-Length: 16
< 
{ [16 bytes data]
* Connection #0 to host 127.0.0.1 left intact
{"status":"OK"}
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 9001 (#0)
> GET /2018-06-01/runtime/invocation/next HTTP/1.1
> Host: 127.0.0.1:9001
> User-Agent: curl/7.61.1
> Accept: */*
> 
END RequestId: 5d883ecd-fa32-4c74-a5aa-de96d6f2d1eb
REPORT RequestId: 5d883ecd-fa32-4c74-a5aa-de96d6f2d1eb	Duration: 263.19 ms	Billed Duration: 338 ms	Memory Size: 128 MB	Max Memory Used: 28 MB	Init Duration: 73.87 ms

Request ID
5d883ecd-fa32-4c74-a5aa-de96d6f2d1eb

References