Duration: ~20 min
Goal: Write a Node.js Lambda function, deploy it to Floci, invoke it synchronously and asynchronously, and update its code.
export AWS_ENDPOINT_URL=http://localhost:4566
export AWS_DEFAULT_REGION=us-east-1
export AWS_ACCESS_KEY_ID=test
export AWS_SECRET_ACCESS_KEY=test
export AWS_PAGER=""
node --version # Node.js required to write the function locally💡 Already set from Lab 01? Just verify:
echo $AWS_ENDPOINT_URL
⚠️ Podman users — one-time setup required before Lambda works.
Podman'shost-gatewayfeature (used by Floci to wire Lambda containers) needs to be enabled inside the Podman VM. Run this once:# 1. Configure host-gateway IP inside the Podman VM podman machine ssh "sudo mkdir -p /etc/containers && printf '[containers]\nhost_containers_internal_ip = \"10.88.0.1\"\n' | sudo tee /etc/containers/containers.conf" # 2. Restart the Podman socket daemon to pick up the change podman machine ssh "sudo systemctl restart podman.socket" # 3. Restart Floci podman compose down && podman compose up -dYour
compose.yamlmust also include these two env vars (already in the repo'scompose.yaml):environment: FLOCI_SERVICES_LAMBDA_DOCKER_NETWORK: floci_default FLOCI_SERVICES_LAMBDA_DOCKER_HOST_OVERRIDE: floci
mkdir -p lambda-demo && cd lambda-demoCreate index.mjs:
cat > index.mjs << 'EOF'
export const handler = async (event) => {
console.log("Received event:", JSON.stringify(event, null, 2));
const name = event.name ?? "World";
const timestamp = new Date().toISOString();
return {
statusCode: 200,
body: JSON.stringify({
message: `Hello, ${name}!`,
timestamp,
}),
};
};
EOFzip function.zip index.mjs
ls -lh function.zipaws lambda create-function \
--function-name hello-floci \
--runtime nodejs22.x \
--role arn:aws:iam::000000000000:role/lambda-role \
--handler index.handler \
--zip-file fileb://function.zipaws lambda get-function --function-name hello-floci | jq .Configurationaws lambda list-functions | jq '.Functions[] | {name: .FunctionName, runtime: .Runtime}'aws lambda invoke \
--function-name hello-floci \
--payload '{"name":"Developer Kommunity"}' \
--cli-binary-format raw-in-base64-out \
response.json
cat response.json | jq .Expected output:
{
"statusCode": 200,
"body": "{\"message\":\"Hello, Developer Kommunity!\",\"timestamp\":\"2026-06-25T...\"}"
}aws lambda invoke \
--function-name hello-floci \
--cli-binary-format raw-in-base64-out \
response2.json
cat response2.json | jq .aws lambda invoke \
--function-name hello-floci \
--invocation-type Event \
--payload '{"name":"Async Call"}' \
--cli-binary-format raw-in-base64-out \
/dev/null
echo "Exit code: $?" # 202 = acceptedModify the handler to add more fields:
cat > index.mjs << 'EOF'
export const handler = async (event) => {
console.log("Received event:", JSON.stringify(event, null, 2));
const name = event.name ?? "World";
const timestamp = new Date().toISOString();
const region = process.env.AWS_DEFAULT_REGION ?? "unknown";
return {
statusCode: 200,
body: JSON.stringify({
message: `Hello, ${name}!`,
timestamp,
region,
version: "v2",
}),
};
};
EOF
zip function.zip index.mjs
aws lambda update-function-code \
--function-name hello-floci \
--zip-file fileb://function.zip | jq .LastModified
# Wait for update to complete
aws lambda wait function-updated --function-name hello-floci
# Invoke the updated function
aws lambda invoke \
--function-name hello-floci \
--payload '{"name":"Updated Lambda"}' \
--cli-binary-format raw-in-base64-out \
response3.json
cat response3.json | jq .aws lambda update-function-configuration \
--function-name hello-floci \
--environment 'Variables={APP_ENV=local,FEATURE_FLAG=true}'
aws lambda get-function-configuration \
--function-name hello-floci \
--query Environment | jq .Hot reload lets you edit index.mjs and have it take effect on the next invocation — no re-zip, no re-deploy.
How it works: Floci passes the S3Key path directly to Podman as a bind-mount source for the Lambda container. That path must be an absolute path visible to the Podman VM (not just the macOS host).
Since Podman machine shares your macOS home directory into the VM at the same path, this works:
- Update
compose.yaml— remove the/hot-reloadvolume (not needed) and add the allowed paths env var:
services:
floci:
image: floci/floci:latest
# ... other config unchanged ...
environment:
FLOCI_SERVICES_LAMBDA_HOT_RELOAD_ENABLED: "true"
FLOCI_SERVICES_LAMBDA_HOT_RELOAD_ALLOWED_PATHS: /Users- Restart Floci:
podman compose down && podman compose up -d- Deploy pointing at the absolute path of your code directory:
# Make sure you're in the lambda-demo directory
cd ~/floci-workshop/lambda-demo # or wherever your lambda-demo lives
aws lambda create-function \
--function-name hello-hot \
--runtime nodejs22.x \
--role arn:aws:iam::000000000000:role/lambda-role \
--handler index.handler \
--code S3Bucket=hot-reload,S3Key=$(pwd)- Invoke it:
aws lambda invoke \
--function-name hello-hot \
--payload '{"name":"Hot Reload"}' \
--cli-binary-format raw-in-base64-out \
response-hot.json
cat response-hot.json | jq .- Now edit
index.mjs(change the message, add a field), then invoke again — the change takes effect immediately, no redeployment needed.
⚠️ FLOCI_SERVICES_LAMBDA_HOT_RELOAD_ALLOWED_PATHS=/Usersis required on macOS — it whitelists the bind-mount source so Floci doesn't reject the path.
Build a Lambda function that:
- Accepts an event with
{"action": "write", "key": "...", "value": "..."}or{"action": "read", "key": "..."}- Writes to / reads from an SSM Parameter Store key
- Deploy it to Floci and invoke it both ways
➡️ Next: Lab 06 — Developer Workflow & CI