How it works

Add a deploy.yml workflow to your repository. The workflow runs your existing quality checks, then calls waft release <app-name> as the final step. Waft auto-detects the runtime from the files you deploy.

Deploy only on green. Waft deploys as the final step — after lint and tests pass. A failing test stops the pipeline before any code reaches production.

Step 1 — Get your API token

  1. Copy your waft API token Go to Profile → API Token and copy your token.
  2. Add it as a GitHub secret In your GitHub repository: Settings → Secrets and variables → Actions → New repository secret. Name it WAFT_TOKEN.

Step 2 — Add the workflow

Choose your runtime below and copy the workflow into .github/workflows/deploy.yml.

Python deploy workflow

Runs ruff for linting and pytest for tests, then deploys. Lambda entry point is handler.py with def handler(event, context).

.github/workflows/deploy.ymlname: Deploy to waft on: push: branches: [main] jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.12' - name: Install dependencies run: pip install -e ".[dev]" - name: Lint run: ruff check . - name: Test run: pytest - name: Install waft run: | curl -sSL https://github.com/waft-dev/waft/releases/latest/download/waft-linux-amd64 \ -o /usr/local/bin/waft chmod +x /usr/local/bin/waft - name: Deploy env: WAFT_TOKEN: ${{ secrets.WAFT_TOKEN }} run: waft release my-python-app

Replace my-python-app with your app name — a lowercase slug, e.g. my-api or data-processor. The app is created automatically on first deploy.

What gets deployed?

waft release zips the current directory and uploads it. By default it deploys . — your whole project. To deploy a subdirectory only, pass the path: waft release my-app ./src.

Handler structure

handler.py# Lambda entry point for Python apps def handler(event, context): return { "statusCode": 200, "body": "Hello from waft!", }

Go deploy workflow

Runs golangci-lint and go test, cross-compiles for Linux, then deploys. The bootstrap binary triggers the provided.al2023 custom runtime.

Cross-compile for Linux. Always build with GOOS=linux GOARCH=amd64 before deploying — a macOS or Windows binary will not run on Lambda.

.github/workflows/deploy.ymlname: Deploy to waft on: push: branches: [main] jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Go uses: actions/setup-go@v5 with: go-version: '1.22' - name: Lint uses: golangci/golangci-lint-action@v6 with: version: latest - name: Test run: go test ./... - name: Build Lambda binary run: GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o bootstrap . - name: Install waft run: | curl -sSL https://github.com/waft-dev/waft/releases/latest/download/waft-linux-amd64 \ -o /usr/local/bin/waft chmod +x /usr/local/bin/waft - name: Deploy env: WAFT_TOKEN: ${{ secrets.WAFT_TOKEN }} run: waft release my-go-app

Strip debug symbols with -ldflags="-s -w" to keep the binary small. A typical Go Lambda goes from ~11 MB to ~3.3 MB compressed — well within Lambda limits.

Handler structure

main.go// Lambda entry point — compile to "bootstrap" package main import ( "encoding/json" "fmt" "net/http" ) func handler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{"message": "Hello from waft!"}) } func main() { fmt.Println("Starting...") http.HandleFunc("/", handler) http.ListenAndServe(":8080", nil) }

Node.js / TypeScript deploy workflow

Runs ESLint, tests, compiles TypeScript, then deploys. The index.js entry point triggers the nodejs20.x runtime.

.github/workflows/deploy.ymlname: Deploy to waft on: push: branches: [main] jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Node uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' - name: Install dependencies run: npm ci - name: Lint run: npm run lint - name: Test run: npm test - name: Build run: npm run build - name: Install waft run: | curl -sSL https://github.com/waft-dev/waft/releases/latest/download/waft-linux-amd64 \ -o /usr/local/bin/waft chmod +x /usr/local/bin/waft - name: Deploy env: WAFT_TOKEN: ${{ secrets.WAFT_TOKEN }} run: waft release my-node-app ./dist

Deploy the compiled output. The waft release my-node-app ./dist command deploys only the dist/ directory. For plain JavaScript projects without a build step, use waft release my-node-app . instead.

Handler structure

index.ts (compiled to dist/index.js)// Lambda entry point for Node.js apps export const handler = async (event: any) => { return { statusCode: 200, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message: 'Hello from waft!' }), }; };

Runtime auto-detection

Waft detects the Lambda runtime from the files included in the deployment zip:

File present in zipRuntime used
bootstrapprovided.al2023 — Go, Rust, or any custom binary
index.jsnodejs20.x
(anything else)python3.12 (default)

Tips

Get your API token → MCP / AI assistant setup