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.