|
1 | | -# Lambda Shell Endopint |
| 1 | +# Lambda Shell Endpoint |
| 2 | + |
| 3 | +> Minimal serverless JSON endpoints using Bash on AWS Lambda (`provided.al2023`) with a Go bootstrap layer. |
| 4 | +
|
| 5 | +## What This Actually Is |
| 6 | + |
| 7 | +This is not just a Bash Lambda template. |
| 8 | + |
| 9 | +This boilerplate embeds a **compiled Go bootstrap** (packaged as a Lambda layer) that communicates with the Lambda Runtime API using **raw TCP**, and delegates execution to shell handlers. |
| 10 | + |
| 11 | +This project builds on the research documented in the Cloudless article: |
| 12 | + |
| 13 | +[Lambda Performance Deep Dive: Container Images, Raw TCP, and the UPX Trap](https://cloudless.sh/log/lambda-container-images-beat-zip-packages/) |
| 14 | + |
| 15 | +That article explains the runtime design decisions and benchmark results in detail. |
| 16 | + |
| 17 | +That research concluded: |
| 18 | + |
| 19 | +- The fastest and most predictable approach for shell-based Lambdas |
| 20 | +- Is a compiled Go bootstrap |
| 21 | +- Using raw TCP to communicate with the Lambda Runtime API |
| 22 | +- Packaged as a layer |
| 23 | +- Combined with `provided.al2023` |
| 24 | + |
| 25 | +This repository operationalizes that conclusion. |
| 26 | + |
| 27 | +## Why This Exists |
| 28 | + |
| 29 | +Sometimes you don’t need: |
| 30 | + |
| 31 | +- A framework |
| 32 | +- A container image |
| 33 | +- Node or Python runtimes |
| 34 | +- A database |
| 35 | +- A data pipeline |
| 36 | + |
| 37 | +Sometimes you just need: |
| 38 | + |
| 39 | +> A small, deterministic JSON endpoint that shapes data and returns it. |
| 40 | +
|
| 41 | +This repository provides: |
| 42 | + |
| 43 | +- Shell-based Lambda functions |
| 44 | +- Compiled Go bootstrap (raw TCP runtime client) |
| 45 | +- Optional `jq` layer for data shaping |
| 46 | +- Terraform infrastructure (function + layer + function URL) |
| 47 | +- Minimal deployment surface |
| 48 | +- Extremely small function packages (often <1KB) |
| 49 | + |
| 50 | +## The Architecture |
| 51 | + |
| 52 | +### Runtime Model |
| 53 | + |
| 54 | +- Runtime: `provided.al2023` |
| 55 | +- Architecture: `arm64` |
| 56 | +- Custom Go bootstrap (compiled binary) |
| 57 | +- Shell handler (`handler.sh`) |
| 58 | +- Optional `jq` layer |
| 59 | +- Lambda Function URL (no API Gateway required) |
| 60 | + |
| 61 | +### Execution Flow |
| 62 | + |
| 63 | +``` |
| 64 | +Lambda Runtime API |
| 65 | + ↓ |
| 66 | +Go bootstrap (raw TCP client) |
| 67 | + ↓ |
| 68 | +Shell handler.sh |
| 69 | + ↓ |
| 70 | +curl / jq / system tools |
| 71 | + ↓ |
| 72 | +JSON response |
| 73 | +``` |
| 74 | + |
| 75 | +The bootstrap handles: |
| 76 | + |
| 77 | +- Fetching invocation events |
| 78 | +- Passing payload to the shell handler |
| 79 | +- Writing responses back to the Runtime API |
| 80 | +- Minimal overhead, no SDKs |
| 81 | + |
| 82 | +The shell handles: |
| 83 | + |
| 84 | +- Calling upstream APIs |
| 85 | +- Aggregating or normalizing data |
| 86 | +- Returning JSON |
| 87 | + |
| 88 | +## Why Raw TCP? |
| 89 | + |
| 90 | +- The standard Lambda runtime client adds avoidable overhead. |
| 91 | +- SDK-based implementations increase cold start size and complexity. |
| 92 | +- Container images introduce unnecessary weight. |
| 93 | +- UPX compression can degrade real performance. |
| 94 | + |
| 95 | +A small compiled Go binary speaking directly over raw TCP: |
| 96 | + |
| 97 | +- Minimizes runtime overhead |
| 98 | +- Avoids heavy dependencies |
| 99 | +- Keeps cold starts predictable |
| 100 | +- Keeps the system inspectable |
| 101 | + |
| 102 | +For benchmark details and performance comparisons, refer to: |
| 103 | + |
| 104 | +> [*Lambda Performance Deep Dive: Container Images, Raw TCP, and the UPX Trap*](https://cloudless.sh/log/lambda-container-images-beat-zip-packages/) |
| 105 | +
|
| 106 | +This repository is the production-ready evolution of that research. |
| 107 | + |
| 108 | +## Repository Structure |
| 109 | + |
| 110 | +``` |
| 111 | +├── activate |
| 112 | +├── app |
| 113 | +│ └── src |
| 114 | +│ └── handler.sh |
| 115 | +├── infra |
| 116 | +│ ├── main.tf |
| 117 | +│ ├── output.tf |
| 118 | +│ ├── terraform.tf |
| 119 | +│ ├── variables.tf |
| 120 | +│ └── versions.tf |
| 121 | +├── layers |
| 122 | +│ └── jq |
| 123 | +│ ├── build.sh |
| 124 | +│ ├── Dockerfile |
| 125 | +│ ├── layer |
| 126 | +│ └── README.md |
| 127 | +├── README.md |
| 128 | +├── runtime |
| 129 | +│ ├── build |
| 130 | +│ │ └── bootstrap |
| 131 | +│ ├── build.sh |
| 132 | +│ ├── go.mod |
| 133 | +│ └── main.go |
| 134 | +└── tf |
| 135 | +``` |
| 136 | + |
| 137 | +Infrastructure leverages existing QL4B Terraform modules: |
| 138 | + |
| 139 | +- `terraform-aws-lambda-function` |
| 140 | +- `terraform-aws-lambda-layer` |
| 141 | + |
| 142 | +This keeps the Terraform surface minimal. |
| 143 | + |
| 144 | +## The optional `jq` layer |
| 145 | + |
| 146 | +If your endpoint uses jq for shaping or aggregation, you must build a Lambda-compatible layer for the target architecture. |
| 147 | + |
| 148 | +Lambda supports: |
| 149 | +* arm64 |
| 150 | +* x86_64 |
| 151 | + |
| 152 | +Because binaries must match the Lambda architecture, the jq layer is built per architecture. |
| 153 | + |
| 154 | +The layers/jq directory contains: |
| 155 | +* Dockerfile — multi-stage build producing a statically-linked binary |
| 156 | +* build.sh — packages the layer in the correct /opt structure |
| 157 | +* layer/ — final Lambda layer layout |
| 158 | + |
| 159 | + |
| 160 | +To build the layer |
| 161 | + |
| 162 | +``` |
| 163 | +cd layers/jq |
| 164 | +./build.sh arm64 # or x86_64 |
| 165 | +``` |
| 166 | + |
| 167 | +The build process produces a Lambda-ready layer compatible with provided.al2023. |
| 168 | + |
| 169 | +This layer is derived from the broader CLI layer collection maintained in: |
| 170 | + |
| 171 | +https://github.com/ql4b/lambda-shell-layers |
| 172 | + |
| 173 | +That repository contains additional optional tools (htmlq, yq, http-cli, uuid, etc.) built using the same pattern and compatible with this runtime model. |
| 174 | + |
| 175 | + |
| 176 | +## The Pattern |
| 177 | + |
| 178 | +This template is ideal for: |
| 179 | + |
| 180 | +``` |
| 181 | +REST API |
| 182 | + ↓ |
| 183 | +Shell + curl |
| 184 | + ↓ |
| 185 | +jq (aggregate / normalize) |
| 186 | + ↓ |
| 187 | +JSON |
| 188 | + ↓ |
| 189 | +Grafana / dashboards / other systems |
| 190 | +``` |
| 191 | + |
| 192 | +Typical use cases: |
| 193 | + |
| 194 | +- GitHub traffic aggregation |
| 195 | +- Stripe summaries |
| 196 | +- Multi-endpoint API fan-out |
| 197 | +- Lightweight observability adapters |
| 198 | +- Rapid data prototypes |
| 199 | +- Internal tooling |
| 200 | + |
| 201 | +No ingestion. |
| 202 | +No persistence. |
| 203 | +No ceremony. |
| 204 | + |
| 205 | + |
| 206 | +## Quick Start |
| 207 | + |
| 208 | +### 1. Clone |
| 209 | + |
| 210 | +``` |
| 211 | +git clone <repo> |
| 212 | +cd lambda-shell-endpoint |
| 213 | +``` |
| 214 | + |
| 215 | +### 1.1 Environment variables |
| 216 | + |
| 217 | +``` |
| 218 | +cp .env.example .emv |
| 219 | +``` |
| 220 | + |
| 221 | +Edit the `.env` and |
| 222 | + |
| 223 | +``` |
| 224 | +source ./activate |
| 225 | +``` |
| 226 | + |
| 227 | +### 2. Build the bootstrap |
| 228 | + |
| 229 | +``` |
| 230 | +cd runtime |
| 231 | +./build.sh |
| 232 | +cd .. |
| 233 | +``` |
| 234 | + |
| 235 | +### 2.1 Build `jq` |
| 236 | + |
| 237 | +``` |
| 238 | +cd layers/jq |
| 239 | +./build |
| 240 | +``` |
| 241 | + |
| 242 | +### 3. Deploy |
| 243 | + |
| 244 | +``` |
| 245 | +tf init |
| 246 | +tf apply |
| 247 | +``` |
| 248 | + |
| 249 | +Terraform outputs a Lambda Function URL: |
| 250 | + |
| 251 | +``` |
| 252 | +https://xxxx.lambda-url.<region>.on.aws/ |
| 253 | +``` |
| 254 | + |
| 255 | +### 4. Test |
| 256 | + |
| 257 | +``` |
| 258 | +curl https://xxxx.lambda-url.<region>.on.aws/ | jq |
| 259 | +``` |
| 260 | + |
| 261 | +Done. |
| 262 | + |
| 263 | +## Writing an Endpoint |
| 264 | + |
| 265 | +Inside `handler.sh`, define a function that: |
| 266 | + |
| 267 | +1. Reads the invocation payload |
| 268 | +2. Calls upstream APIs |
| 269 | +3. Shapes data with `jq` |
| 270 | +4. Returns a JSON document |
| 271 | + |
| 272 | +Example: |
| 273 | + |
| 274 | +``` |
| 275 | +run () { |
| 276 | + curl -sS "https://api.example.com/data" \ |
| 277 | + | jq '{ result: .items }' |
| 278 | +} |
| 279 | +``` |
| 280 | + |
| 281 | +Keep the logic: |
| 282 | + |
| 283 | +- Deterministic |
| 284 | +- Stateless |
| 285 | +- Inspectable |
| 286 | +- Small |
| 287 | + |
| 288 | +## Security Notes |
| 289 | + |
| 290 | +By default, Lambda Function URL may be public. |
| 291 | + |
| 292 | +You should consider: |
| 293 | + |
| 294 | +- `authorization_type = "AWS_IAM"` |
| 295 | +- Adding a shared secret header check |
| 296 | +- Restricting CORS origins |
| 297 | +- Adding lightweight response caching |
| 298 | + |
| 299 | +The template stays minimal. |
| 300 | +Security posture is intentionally configurable. |
| 301 | + |
| 302 | +## When To Use This |
| 303 | + |
| 304 | +Use this when: |
| 305 | + |
| 306 | +- You need fast iteration |
| 307 | +- You need a thin data-shaping layer |
| 308 | +- You are building observability surfaces |
| 309 | +- You want deterministic minimal infrastructure |
| 310 | + |
| 311 | +Do not use this when: |
| 312 | + |
| 313 | +- You need persistent state |
| 314 | +- You need heavy compute |
| 315 | +- You require complex authentication systems |
| 316 | +- You are building a full application backend |
| 317 | + |
| 318 | + |
| 319 | +## Philosophy |
| 320 | + |
| 321 | +This is aligned with the Cloudless approach: |
| 322 | + |
| 323 | +- Infrastructure that gets out of your way |
| 324 | +- Compiled where necessary, interpreted where useful |
| 325 | +- Small, composable primitives |
| 326 | +- Clear contracts |
| 327 | +- Minimal moving parts |
| 328 | + |
| 329 | +Shell as data engine. |
| 330 | +Go as runtime spine. |
| 331 | +Lambda as distribution layer. |
| 332 | +JSON as contract. |
| 333 | + |
| 334 | + |
| 335 | + |
0 commit comments