Deployment
Mantle bundles Lambda functions with esbuild and deploys infrastructure with OpenTofu — the mantle build and mantle deploy commands handle both.
Quick start
mantle build # bundle all Lambdas
mantle deploy --stage staging # regenerate infra, plan, applymantle deploy regenerates infra/ from your handler source before every apply, so the deployed infrastructure always matches the current handlers. See Infrastructure regeneration on deploy.
Always specify --stage. Running mantle deploy without --stage defaults to dev, which has no tfvars and will prompt interactively.
mantle build
Bundles all Lambda functions under src/lambdas/ using esbuild. Output goes to build/lambdas/<FunctionName>/index.mjs.
mantle build # production build (minified)
mantle build --watch # watch mode — rebuilds on change
mantle build --function ItemsGet # single function
mantle build --sourcemap --no-minify # debug build
mantle build --analyze # emit bundle size metafiles| Flag | Description |
|---|---|
-w, --watch | Watch for changes and rebuild incrementally |
-f, --function <name> | Build a specific function only |
--analyze | Generate esbuild metafiles for bundle analysis |
--sourcemap | Include source maps in the output |
--no-minify | Skip minification (useful for debugging) |
For each Lambda file discovered in src/lambdas/, the build:
- Bundles the handler and all imports into a single
.mjsfile using esbuild - Tree-shakes unused code
- Minifies (unless
--no-minifyis passed) - Writes output to
build/lambdas/<FunctionName>/index.mjs
Entry-point discovery follows file-system routing conventions: api/<name>.<method>.ts, eventbridge/<Name>/index.ts, scheduled/<Name>/index.ts, and standalone/<Name>/index.ts.
mantle deploy
Regenerates Terraform configuration from your handler source, then runs tofu init / tofu plan / tofu apply.
mantle deploy --stage staging
mantle deploy --stage production| Flag | Description |
|---|---|
--stage <env> | Target stage -- required (dev, staging, production) |
--dry-run | Run tofu plan without applying |
--skip-build | Skip the Lambda build step |
--skip-generate | Skip regenerating infra/ from source; use the on-disk infra/ as-is (errors if absent) |
--modules-path <path> | Path to the mantle modules directory used during regeneration (auto-detected if omitted) |
--force-redeploy | Force an API Gateway redeployment after apply |
-y, --yes | Skip confirmation prompts (e.g. the production gate) |
What happens under the hood
- Reads
mantle.config.tsand scanssrc/lambdas/ - Regenerates
.tffiles for each Lambda, API Gateway routes, EventBridge rules, and scheduled rules (unless--skip-generate) - Auto-wires environment variables detected via
getRequiredEnv()/getOptionalEnv()calls - Runs
tofu init(first time only),tofu plan, thentofu apply - Injects infrastructure-derived values (endpoints, ARNs, table names) as Lambda environment variables
Infrastructure generation is safe to run repeatedly — it skips ejected files.
Infrastructure regeneration on deploy
By default, mantle deploy runs mantle generate infra for you before tofu apply, regenerating infra/ from your current handler source (src/lambdas/). This guarantees the deployed infrastructure matches the handlers in your tree.
This closes a dangerous footgun: previously, mantle deploy applied the on-disk infra/ verbatim. If infra/ was stale — for example, a handler was added or removed without re-running mantle generate infra — OpenTofu interpreted the missing resources as deletions and silently destroyed them on apply. Because all .tf is CLI-generated and never hand-edited (C67), regenerating before apply is always safe: it reconciles infra/ with the source. Regeneration reads handler source, not build output, so it is independent of mantle build.
Pass --skip-generate to opt out and deploy the on-disk infra/ as-is. This is intended for advanced/debug workflows or CI pipelines that generate infra/ in a separate, explicit step; in that mode a missing infra/ directory is an error rather than being created.
mantle dev
Starts a local HTTP server that simulates API Gateway, rebuilds on file changes, and re-imports handlers on each request (module cache-busting via timestamp query parameter).
mantle dev # http://localhost:3000
mantle dev --port 8080
mantle dev --localstack # route AWS SDK calls to LocalStack| Flag | Default | Description |
|---|---|---|
-p, --port <port> | 3000 | Port to listen on |
--localstack | false | Route AWS SDK calls to LocalStack at http://localhost:4566 |
How it works
- Runs an initial
mantle build(unminified, with source maps) - Starts an HTTP server that routes requests to the matching Lambda handler
- Watches
src/lambdas/with chokidar; rebuilds only the changed function on each file change - On each request, imports the built
.mjsfile with a cache-busting query string so changes take effect immediately without restarting the server
LocalStack integration
When --localstack is passed, the dev server sets:
AWS_ENDPOINT_URL=http://localhost:4566
AWS_ACCESS_KEY_ID=test
AWS_SECRET_ACCESS_KEY=testThis routes all AWS SDK calls (DynamoDB, S3, SQS, etc.) to a locally running LocalStack instance. Start LocalStack separately before running mantle dev --localstack.
CORS in dev
If your mantle.config.ts includes a cors configuration, the dev server respects it for preflight (OPTIONS) requests:
// mantle.config.ts
export default defineConfig({
name: "my-api",
cors: {
origins: ["http://localhost:5173"],
methods: ["GET", "POST", "DELETE"],
headers: ["Content-Type", "Authorization"],
},
});Database migrations
After tofu apply, run migrations against the live database:
mantle db migrateThe command auto-detects the DSQL endpoint from OpenTofu state when infra/.terraform exists — no manual endpoint configuration needed after the first apply.
Environment variables
Use @mantleframework/env instead of raw process.env:
import { getRequiredEnv, getOptionalEnv } from "@mantleframework/env";
const endpoint = getRequiredEnv("DSQL_ENDPOINT"); // throws if missing
const debug = getOptionalEnv("DEBUG_MODE", "false"); // returns default if missingThe following environment variables are used by various Mantle packages:
| Variable | Used by | Description |
|---|---|---|
AWS_REGION | All packages | AWS region for SDK clients |
DSQL_ENDPOINT | @mantleframework/database | Aurora DSQL cluster endpoint |
DATABASE_URL | @mantleframework/database | Neon / Aurora v2 connection string |
API_BEARER_TOKEN | @mantleframework/validation | Static bearer token for auth: 'bearer' |
CORS_ALLOWED_ORIGINS | @mantleframework/core | Comma-separated allowed origins |
IDEMPOTENCY_TABLE_NAME | @mantleframework/resilience | DynamoDB table for idempotency |
EVENT_BUS_NAME | @mantleframework/aws | EventBridge custom bus name |
POWERTOOLS_SERVICE_NAME | @mantleframework/observability | Service name for Powertools |
LOG_LEVEL | @mantleframework/observability | Logger level (DEBUG, INFO, etc.) |
The Lambda module injects ENVIRONMENT with the short token (dev, staging, prod) — use these tokens in any environment-conditional logic.
OpenTofu modules inject the infrastructure-derived values (ARNs, endpoints, table names) automatically as Lambda environment variables.
Next steps
- Handlers — file-system routing and handler factories
- Infrastructure — Terraform module layout and customisation
- Database — migrations, schema, and DSQL configuration