Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 118 additions & 0 deletions sandboxes/complete/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# Serverless Framework — AWS Lambda MicroVMs (`sandboxes`) — complete example

A complete, **deploy-as-is** showcase of the `sandboxes` top-level property, which
deploys [AWS Lambda MicroVMs](https://www.serverless.com/framework/docs/providers/aws/guide/sandboxes).
`serverless deploy` works with no edits — read `serverless.yml` top to bottom and
you've seen most of what the feature does.

The single `api` sandbox exercises: a local Dockerfile build, `minimumMemory`,
`description`, `environment`, lifecycle `hooks` (`ready` + `run`), full
`observability` (custom metric `filters`, `alarms` → a self-contained SNS topic,
and a `dashboard`), custom `iam`, and `tags`. VPC egress and a pre-built `s3://`
artifact are included as commented blocks (they need account-specific IDs).

The app (`app/app.js`) is a tiny Node.js echo server on **:8080** plus a
lifecycle-hook server on **:9000**.

## What gets created

`serverless deploy` provisions, for the `api` sandbox:

- `AWS::Lambda::MicrovmImage` — the sandbox image (built in-cloud from `./app`)
- build + execution **IAM roles** (least-privilege, plus your custom `iam` statements)
- a CloudWatch **log group** (`/aws/lambda-microvms/sandboxes-example-api-<stage>`)
- a **metric filter + alarm** for each `observability.metrics.filters` entry (`errors`, `warnings`)
- an **SNS topic** (`AlertTopic`) the alarms publish to
- **one dashboard per service** — `sandboxes-example-<stage>-sandboxes`

## Deploy

```bash
serverless deploy
```

```text
✔ Service deployed to stack sandboxes-example-dev (…s)

dashboard: https://<region>.console.aws.amazon.com/cloudwatch/home?region=<region>#dashboards/dashboard/sandboxes-example-dev-sandboxes
```

`serverless deploy` prints the CloudWatch **dashboard** URL for the service — open it to see the metrics, logs, and alarm widgets.

After deploy, subscribe to the alerts topic to actually receive alarm notifications:

```bash
aws sns subscribe --protocol email --notification-endpoint you@example.com \
--topic-arn "$(aws sns list-topics --query "Topics[?contains(TopicArn,'AlertTopic')].TopicArn | [0]" --output text)"
```

## Invoke

```bash
serverless invoke --sandbox api --path /hello
# --sandbox is required (it names which sandbox to call) and is the invoke target.
# --method and --path are optional (default GET /).
```

The app echoes the request; `GREETING` is the `environment` variable from `serverless.yml`:

```json
{"method":"GET","path":"/hello","headers":{"host":"<microvm-id>.lambda-microvm.us-east-1.on.aws"},"body":"","GREETING":"hello-from-sandboxes"}
```

## See the alarm and dashboard react

`observability` is on by default. To watch it work, send a few requests whose log lines match the `errors` filter, then open the dashboard URL from the deploy output:

```bash
for i in 1 2 3 4 5 6; do serverless invoke --sandbox api --path "/error-$i" >/dev/null; done
```

Within ~2 minutes the dashboard's **errors** metric climbs to 6 and the **errors alarm flips to `ALARM`** (threshold 5); a few `/warn-*` requests move the warnings metric without tripping its alarm (3 ≤ 5).

## Logs

Defaults to the last 10 minutes of events; widen the window with `--startTime`:

```bash
serverless logs --sandbox api --startTime 30m
```

## Develop locally

Run the sandbox on your machine (Docker), with the same config — edits to `app/`
rebuild the image automatically:

```bash
serverless dev --sandbox api
```

It starts a local, SDK-compatible AWS Lambda MicroVMs emulator (prints its endpoint, e.g. `http://127.0.0.1:9100`); point the AWS SDK/CLI at that endpoint (`--endpoint-url`) to launch the sandbox locally with no cloud cost.

## Roll back

Each deploy is retained; roll back to a previous one:

```bash
serverless deploy list # list timestamps
serverless rollback --timestamp <timestamp>
```

## Remove

```bash
serverless remove
```

## Enabling the commented examples

- **VPC egress** (`worker`): uncomment the block and set `vpc.subnetIds` /
`vpc.securityGroupIds` to IDs from your account. Subnets must be in
MicroVM-supported Availability Zones (in `us-east-1`, avoid `use1-az3`).
- **Pre-packaged S3 artifact** (`prebuilt`): zip and upload the app source, then
set the `s3://` URI (the framework still builds the image in-cloud from it):
```bash
cd app && zip -r /tmp/worker-src.zip . && aws s3 cp /tmp/worker-src.zip s3://<your-bucket>/worker-src.zip
```

> See `../minimal/` for the smallest possible configuration (just `artifact`).
7 changes: 7 additions & 0 deletions sandboxes/complete/app/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
FROM public.ecr.aws/docker/library/node:22-slim
WORKDIR /app
COPY app.js .
# 8080 = application port (callers reach it through the proxy).
# 9000 = lifecycle-hook port (the platform posts ready/run/etc. here).
EXPOSE 8080 9000
CMD ["node", "app.js"]
45 changes: 45 additions & 0 deletions sandboxes/complete/app/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
'use strict'

const http = require('http')

function readBody(req) {
return new Promise((resolve) => {
let data = ''
req.on('data', (chunk) => (data += chunk))
req.on('end', () => resolve(data))
})
}

// Application server on :8080 — echoes the request and the GREETING env var.
// Callers reach this port through the MicroVM proxy.
http
.createServer(async (req, res) => {
const body = await readBody(req)
const payload = {
method: req.method,
path: req.url,
headers: req.headers,
body,
GREETING: process.env.GREETING || '<unset>',
}
console.log('APP_REQUEST ' + JSON.stringify(payload))
const out = JSON.stringify(payload)
res.writeHead(200, {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(out),
})
res.end(out)
})
.listen(8080, '0.0.0.0', () => console.log('BOOT app 0.0.0.0:8080'))

// Lifecycle-hook server on :9000 — the platform POSTs ready/run/etc. here.
// Each enabled hook must return 200 within its configured timeout.
http
.createServer(async (req, res) => {
const body = await readBody(req)
const hook = req.url.split('/').pop()
console.log(`HOOK ${hook} body=${body}`)
res.writeHead(200, { 'Content-Type': 'application/json', 'Content-Length': 2 })
res.end('{}')
})
.listen(9000, '0.0.0.0', () => console.log('BOOT hooks 0.0.0.0:9000'))
87 changes: 87 additions & 0 deletions sandboxes/complete/serverless.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
service: sandboxes-example
frameworkVersion: '4'

provider:
name: aws
region: us-east-1

# A complete, deploy-as-is showcase of the `sandboxes` feature (AWS Lambda
# MicroVMs). `serverless deploy` works with NO edits — every setting below is a
# live, working example. Account-specific capabilities (VPC egress, pre-built S3
# artifact) are shown as commented blocks you enable with your own IDs.
sandboxes:
api:
# ── Source ──────────────────────────────────────────────────────────────────
artifact: ./app # local directory containing a Dockerfile (built in-cloud)
description: Complete sandbox example # → MicrovmImage Description

# ── Runtime ─────────────────────────────────────────────────────────────────
minimumMemory: 2048 # 512 | 1024 | 2048 | 4096 | 8192 MiB (default 2048; arch is ARM64)
environment:
GREETING: hello-from-sandboxes # env vars baked into the image
# osCapabilities: [all] # grant extra Linux capabilities (advanced; default none)

# ── Lifecycle hooks — the app must answer these on :9000 ─────────────────────
hooks:
ready: true # build-time readiness probe (gates a successful image build)
run: { timeout: 5 } # per-instance hook; value is `true` or { timeout: <seconds> }

# ── Observability — on by default; shown explicitly here ─────────────────────
# Creates: a CloudWatch log group, a metric filter per `filters` entry, an
# alarm per filter, and ONE dashboard per service (`<service>-<stage>-sandboxes`).
observability:
logs:
retentionDays: 14 # retention for the owned log group (default 14)
metrics:
filters: # log pattern → CloudWatch metric (one metric + alarm per key)
errors: '%[Ee][Rr][Rr][Oo][Rr]|[Ee][Xx][Cc][Ee][Pp][Tt][Ii][Oo][Nn]%'
warnings: '%[Ww][Aa][Rr][Nn]%'
alarms:
notify: { Ref: AlertTopic } # SNS target (topic defined in `resources:` below)
thresholds:
errors: { threshold: 5, period: 300 } # override per filter; others use defaults
dashboard:
enabled: true # renders the metrics, logs, alarms + MicroVM-count widgets

# ── Custom IAM — merged into the auto-generated least-privilege roles ────────
iam:
executionRole:
statements: # extra permissions the running MicroVM needs
- Effect: Allow
Action: ['s3:GetObject']
Resource: arn:aws:s3:::my-app-data-bucket/*
# managedPolicies:
# - arn:aws:iam::aws:policy/AmazonSSMReadOnlyAccess

tags:
example: sandboxes # applied to every resource this sandbox creates

# Add more sandboxes by adding more keys. The two below are commented out
# because they need account-specific resources — uncomment and fill in to use.

# # VPC egress — route the MicroVM's outbound traffic through your VPC.
# worker:
# artifact: ./app
# vpc:
# subnetIds: # MicroVM-supported AZs only (in us-east-1, avoid use1-az3)
# - subnet-xxxxxxxxxxxxxxxxx
# - subnet-yyyyyyyyyyyyyyyyy
# securityGroupIds:
# - sg-xxxxxxxxxxxxxxxxx
# protocol: ipv4 # ipv4 | dualstack

# # Pre-packaged artifact already in S3 (skips local zipping — the framework
# # still builds the image in-cloud from this source). Create it with:
# # cd app && zip -r /tmp/worker-src.zip . && aws s3 cp /tmp/worker-src.zip s3://<bucket>/worker-src.zip
# prebuilt:
# artifact: s3://<your-bucket>/worker-src.zip
# minimumMemory: 1024

# The SNS topic the alarms publish to — defined here so the example is
# self-contained. After deploy, subscribe your email/Slack to it to get alerts.
resources:
Resources:
AlertTopic:
Type: AWS::SNS::Topic
Properties:
DisplayName: Sandbox alerts
78 changes: 78 additions & 0 deletions sandboxes/minimal/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Minimal sandbox example

The smallest possible `sandboxes` configuration: a single sandbox with only the
required `artifact` field. Every other setting falls back to a framework default.

```yml
sandboxes:
app:
artifact: ./app
```

## What the defaults resolve to

| Setting | Default |
| --------------- | -------------------------------------------------------- |
| `minimumMemory` | 2048 MiB |
| architecture | ARM64 |
| base image | latest managed `al2023` base |
| egress | `INTERNET_EGRESS` (outbound internet, no VPC) |
| hooks | off — the app only needs to serve its HTTP port |
| IAM | auto-generated build + execution roles (least-privilege) |
| logs | CloudWatch group `/aws/lambda-microvms/<image-name>` |

The app (`app/app.js`) is a tiny Node.js HTTP echo server on port **8080**. No
lifecycle-hook server is needed because hooks are off by default.

## Deploy

```bash
serverless deploy
```

```text
✔ Service deployed to stack sandboxes-minimal-dev (…s)

dashboard: https://<region>.console.aws.amazon.com/cloudwatch/home?region=<region>#dashboards/dashboard/sandboxes-minimal-dev-sandboxes
```

Observability is on by default, so `serverless deploy` prints a CloudWatch **dashboard** URL even for this minimal config.

## Invoke

```bash
serverless invoke --sandbox app --path /hello
# --sandbox names which sandbox to call and is required (even with one sandbox).
# --method and --path are optional (default GET /).
```

The app echoes the request:

```json
{"method":"GET","path":"/hello","body":""}
```

## Logs

Defaults to the last 10 minutes of events; widen the window with `--startTime`:

```bash
serverless logs --sandbox app --startTime 30m
```

## Develop locally

```bash
serverless dev --sandbox app # edits to app/ rebuild the image automatically
```

It starts a local, SDK-compatible AWS Lambda MicroVMs emulator (prints its endpoint, e.g. `http://127.0.0.1:9100`); point the AWS SDK/CLI at that endpoint (`--endpoint-url`) to launch the sandbox locally with no cloud cost.

## Remove

```bash
serverless remove
```

> See `../complete/` for a fuller example with a VPC, lifecycle hooks, environment
> variables, tags, and an `s3://` pre-built artifact.
5 changes: 5 additions & 0 deletions sandboxes/minimal/app/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
FROM public.ecr.aws/docker/library/node:22-slim
WORKDIR /app
COPY app.js .
EXPOSE 8080
CMD ["node", "app.js"]
22 changes: 22 additions & 0 deletions sandboxes/minimal/app/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
'use strict'

// Minimal sandbox app: an HTTP echo server on :8080.
// Hooks are off by default, so no :9000 hook server is needed.
const http = require('http')

http
.createServer((req, res) => {
let body = ''
req.on('data', (chunk) => (body += chunk))
req.on('end', () => {
const payload = { method: req.method, path: req.url, body }
console.log('APP_REQUEST ' + JSON.stringify(payload))
const out = JSON.stringify(payload)
res.writeHead(200, {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(out),
})
res.end(out)
})
})
.listen(8080, '0.0.0.0', () => console.log('BOOT app 0.0.0.0:8080'))
18 changes: 18 additions & 0 deletions sandboxes/minimal/serverless.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
service: sandboxes-minimal
frameworkVersion: '4'

provider:
name: aws
region: us-east-1

# Minimal sandbox: `artifact` is the only required field. Everything else is
# defaulted by the framework:
# minimumMemory: 2048 MiB
# architecture: ARM64
# base image: latest al2023 managed base
# egress: INTERNET_EGRESS (outbound internet)
# hooks: off (no lifecycle hooks)
# IAM: auto-generated build + execution roles
sandboxes:
app:
artifact: ./app # local directory containing a Dockerfile
9 changes: 9 additions & 0 deletions sandboxes/self-hosted-webhook/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Dependencies (committed lockfiles are kept; only the installed trees are ignored)
node_modules/

# Serverless Framework build artifacts
.serverless/

# Local env files
.env
.env.*
Loading