I am using LocalStack Docker and AWS CDK v2 image and I want to hot reload Lambda functions after file saves
services:
backend:
container_name: "${LOCALSTACK_DOCKER_NAME:-backend}"
image: localstack/localstack
ports:
- "127.0.0.1:4566:4566" # LocalStack Edge Proxy
- "127.0.0.1:4510-4559:4510-4559" # External services ports
- "127.0.0.1:8080:8080" # LocalStack Web UI (optional)
- "127.0.0.1:4000:4000" # LocalStack Web UI (optional)
environment:
- DEBUG=1
- SERVICES=dynamodb,lambda,apigateway,appsync,events,ssm,secretsmanager,s3,sqs,sns,cloudformation,iam,logs
- DEFAULT_REGION=us-east-1
- LAMBDA_DOCKER_NETWORK=app_network # Critical for Lambda networking
- HOSTNAME_EXTERNAL=backend # For local service discovery
- DOCKER_HOST=unix:///var/run/docker.sock
- START_WEB=1 # Explicitly enable Web UI
- PERSISTENCE=1 # Enable data persistence
- LOCALSTACK_HOST=0.0.0.0
- LOCALSTACK_MOUNT_CODE=true
- LAMBDA_EXECUTOR=docker-reuse
- LAMBDA_REMOTE_DOCKER=false
- LAMBDA_RELOAD_CODE=True # 👈 Critical new line
- LAMBDA_CODE_MOUNT_DIR=/var/task # Correct Lambda directory
- LAMBDA_REMOVE_CONTAINERS=True # 👈 Destroy containers after each invoke
- LAMBDA_DOCKER_FLUSH=1 # Force fresh pulls
volumes:
- "${LOCALSTACK_VOLUME_DIR:-./volume}:/var/lib/localstack"
- "/var/run/docker.sock:/var/run/docker.sock"
- "./functions/build:/var/task:rw,cached" # macOS/Windows optimization
networks:
- app_network
I am using this script to generate minified code inside /functions/build
:
"watch": "esbuild functions/*.ts --bundle --minify --sourcemap --platform=node --target=es2020 --outdir=functions/build --watch"
This is the function constructor
new CustomFunction(scope, id, {
runtime,
environment: props.environment,
code: Code.fromAsset(LAMBDA_MOUNT_DIR),
handler: `${fileName}.${handler}`,
})
This is how you instantiate it
const fnHello = new lambda.NodejsFunction(this, "FnHello", {
handler: "handler",
entry: path.join(__dirname, "../functions/hello.ts"),
environment: {
TABLE_NAME: table.tableName,
},
});
I tried those options
- "./functions/build:/var/task"
- "./functions/build:/var/task:rw"
- "./functions/build:/var/task:rw,cached"
and also tweaked many values inside docker-compose.yml
The result generates the correct files /functions/build
- functions
- build
- hello.js
- hello.js.map
And inside /var/task
in the backend
Docker container (which is the LocalStack one), it maps the files
- var
- task
- hello.js
- hello.js.map
It even gets the newest updates, so when I change the code of /functions/hello.ts
it updates /var/task/hello.js
inside the Docker container
The problem is that when I invoke the function, I get the old response.
AWS CDK, when it creates the Lambda using Code.fromAsset()
, packages the code into a ZIP archive during deployment. This ZIP is uploaded to LocalStack (which mocks S3 behind the scenes), and from then on, LocalStack uses that uploaded copy of your code, not the updated file on disk.
Once deployed to LocalStack, the Lambda gets snapshotted into the LocalStack state and is no longer linked to the live filesystem. That's why when you invoke the function, you get the old response.
What you can do is: deploy once the lambda, and don't redeploy the CDK after that: update only the file. Use the update-function-code
in the existing Lambda code using awslocal:
awslocal lambda update-function-code \
--function-name yourFunction \
--zip-file fileb://functions/build/hello.zip
Why? Because redeploying via CDK re-uploads the code, and defeats the point of hot reloading. The above snippet tells LocalStack: replace the code for this Lambda with the new ZIP I give you.
Reference:
https://docs.aws.amazon.com/cli/latest/reference/lambda/update-function-code.html