Recently, we received AWS notifications that certain Node.js 18.x runtimes for our Lambda functions are approaching EOL and need to be upgraded. As part of our standard process, we first validate the new runtime locally before performing the actual update.

We’ve encountered issues where macOS on Apple Silicon (M1/M2) cannot reliably run Node.js 20.x. To work around this, we perform our runtime tests directly in AWS CloudShell. Since CloudShell does not support host.docker.internal for Docker networking, we’ve documented a workaround here.

Preparing the Basic Environment on CloudShell

After logging into AWS, open CloudShell and download your Lambda code:

Get the pre-signed S3 URL for your Lambda package

aws lambda get-function \
  --function-name {your-lambda-name} \
  --query 'Code.Location' \
  --output text

用 curl 把上面輸出的 s3 url 進行下載

curl -o lambda.zip "https://..."

Unzip the package

unzip lambda.zip -d ./

Initialize package.json

npm init -y

Running with SAM Locally on cloudshell

sam --version

# (If you need to upgrade)
brew upgrade aws-sam-cli

準備 template 與 event test

Prepare template.yml and Test Event To invoke a single function locally, you need both a template.yml and an example event payload:

template.yml

Resources:
  MyFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: main.run         # entry point
      Runtime: nodejs20.x       # target new runtime
      Timeout: 30               # seconds
      Architectures:
        - x86_64
      CodeUri: .

event.js

{
  "Records": [
    {
      "eventVersion": "2.1",
      "eventSource": "aws:s3",
      "awsRegion": "ap-northeast-1",
      "eventTime": "2025-04-22T10:00:00.000Z",
      "eventName": "ObjectCreated:Put",

  "Parameters": {
    "DOCKER_DEFAULT_PLATFORM": "linux/arm64"
  },
      "s3": {
        "bucket": {
          "name": "my-video-bucket"
        },
        "object": {
          "key": "uploads/metadata/test-video.mp4"
        }
      }
    }
  ]
}

Mocking a Third-Party Service

Since the Lambda logic interacts with an external service during execution, you must mock that service locally. Below is a simple TCP mock that listens on port 4730:

mock_service.js

const net = require('net');
const server = net.createServer((socket) => {
  console.log('Client connected');

  socket.on('data', (data) => {
    console.log('Received data:', data.toString());

    const jobHandle = 'REQ00000001'; 
    const response = `OK${jobHandle}`;  

    console.log('Sending response:', response);
    socket.write(response);

    socket.end();
  });

  socket.on('end', () => {
    console.log('Client disconnected');
  });
});

server.listen(4730,'0.0.0.0', () => {
  console.log('Mock server listening on port 4730');
});

Solution for CloudShell Cannot Use host.docker.internal

On macOS, you can normally use host.docker.internal to let a Dockerized Lambda container reach a local mock service. CloudShell does not support that hostname, so instead we create a user-defined Docker network:

Create a custom network

docker network create local-test

Run your mock service in its own container

docker run -d \
  --name mock-host \
  --network local-test \
  -v "$(pwd)":/usr/src/app \
  -w /usr/src/app \
  node:18-alpine \
  node mock_service.js

Verify the container is running

docker logs mock-host

Set the mock host name as an environment variable

MOCK_HOST=mock-host

Update your Lambda code (e.g., main.js)

- gmClients.push(new third_party('127.0.0.1', 4730));
+ const MOCK_HOST = process.env.MOCK_HOST || 'mock-host';
+ gmClients.push(new third_party(MOCK_HOST, 4730));

Build and invoke with SAM using the same network

sam build 

sam local invoke MyFunction \
  -e event.json \
  --docker-network local-test

This ensures your Lambda container and the mock service are on the same Docker network, allowing you to connect to mock-host:4730 directly.

Conclusion

Through this implementation and testing in the CloudShell environment, we confirmed that even when local development environments — such as Mac M1 series — face compatibility issues running the newer Node.js 20.x runtime, it’s still possible to leverage AWS CloudShell combined with Docker networks and the SAM CLI to build a testing environment that closely mirrors the actual AWS Lambda runtime.

Additionally, we addressed the limitation where host.docker.internal is unavailable in CloudShell by creating a custom Docker network and referencing container names directly, successfully enabling communication between the Lambda function and a mock service within the same network.

This approach not only increases flexibility for development and testing but also allows teams to quickly perform runtime upgrade verifications and third-party service integration tests without relying on specific local setups. It offers a stable and repeatable workflow, which is especially valuable for multi-runtime, multi-region, or multi-service integration scenarios in the future.