Skip to content

Deployment

Mantle bundles Lambda functions with esbuild and deploys infrastructure with OpenTofu — the mantle build and mantle deploy commands handle both.

Quick start

bash
mantle build                        # bundle all Lambdas
mantle deploy --stage staging       # generate infra, plan, apply

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.

bash
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
FlagDescription
-w, --watchWatch for changes and rebuild incrementally
-f, --function <name>Build a specific function only
--analyzeGenerate esbuild metafiles for bundle analysis
--sourcemapInclude source maps in the output
--no-minifySkip minification (useful for debugging)

For each Lambda file discovered in src/lambdas/, the build:

  1. Bundles the handler and all imports into a single .mjs file using esbuild
  2. Tree-shakes unused code
  3. Minifies (unless --no-minify is passed)
  4. 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

Generates Terraform configuration, then runs tofu init / tofu plan / tofu apply.

bash
mantle deploy --stage staging
mantle deploy --stage production
FlagDescription
--stage <env>Target stage — required (dev, staging, production)

What happens under the hood

  1. Reads mantle.config.ts and scans src/lambdas/
  2. Generates .tf files for each Lambda, API Gateway routes, EventBridge rules, and scheduled rules
  3. Auto-wires environment variables detected via getRequiredEnv() / getOptionalEnv() calls
  4. Runs tofu init (first time only), tofu plan, then tofu apply
  5. Injects infrastructure-derived values (endpoints, ARNs, table names) as Lambda environment variables

Infrastructure generation is safe to run repeatedly — it skips ejected files.

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).

bash
mantle dev                    # http://localhost:3000
mantle dev --port 8080
mantle dev --localstack       # route AWS SDK calls to LocalStack
FlagDefaultDescription
-p, --port <port>3000Port to listen on
--localstackfalseRoute AWS SDK calls to LocalStack at http://localhost:4566

How it works

  1. Runs an initial mantle build (unminified, with source maps)
  2. Starts an HTTP server that routes requests to the matching Lambda handler
  3. Watches src/lambdas/ with chokidar; rebuilds only the changed function on each file change
  4. On each request, imports the built .mjs file 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=test

This 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:

typescript
// 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:

bash
mantle db migrate

The 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:

typescript
import { getRequiredEnv, getOptionalEnv } from '@mantleframework/env'

const endpoint = getRequiredEnv('DSQL_ENDPOINT')    // throws if missing
const debug = getOptionalEnv('DEBUG_MODE', 'false') // returns default if missing

The following environment variables are used by various Mantle packages:

VariableUsed byDescription
AWS_REGIONAll packagesAWS region for SDK clients
DSQL_ENDPOINT@mantleframework/databaseAurora DSQL cluster endpoint
DATABASE_URL@mantleframework/databaseNeon / Aurora v2 connection string
API_BEARER_TOKEN@mantleframework/validationStatic bearer token for auth: 'bearer'
CORS_ALLOWED_ORIGINS@mantleframework/coreComma-separated allowed origins
IDEMPOTENCY_TABLE_NAME@mantleframework/resilienceDynamoDB table for idempotency
EVENT_BUS_NAME@mantleframework/awsEventBridge custom bus name
POWERTOOLS_SERVICE_NAME@mantleframework/observabilityService name for Powertools
LOG_LEVEL@mantleframework/observabilityLogger 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