Skip to content

Commit a77a781

Browse files
authored
feat: initial version (#1)
docs: updated readme feat: add jq layer, update example function, add .env.example docs: update readme
1 parent 28d8102 commit a77a781

19 files changed

Lines changed: 950 additions & 1 deletion

.env.example

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
2+
#
3+
AWS_PROFILE=<aws_profile>
4+
AWS_REGION=us-east-1
5+
NAMESPACE=<your_org>
6+
NAME=<project_name>
7+
8+
TF_VAR_name=${NAME}
9+
TF_VAR_namespace=${NAMESPACE}
10+
TF_VAR_region=${AWS_REGION}
11+
TF_VAR_profile=${AWS_PROFILE}
12+
13+
14+
TERRAFORM_VERSION="v1.14.3"
15+
TERRAFORM_BIN="/usr/local/bin/terraform-$TERRAFORM_VERSION"

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
2+
.env
3+

README.md

Lines changed: 335 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,335 @@
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+

activate

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/bin/bash
2+
3+
set -a
4+
# shellcheck source=/dev/null
5+
source .env && \
6+
PATH="$(pwd):$PATH"
7+
set +a
8+

app/src/handler.sh

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#!/bin/bash
2+
3+
set -euo pipefail
4+
5+
run () {
6+
curl -Ss https://api.github.com/events \
7+
| jq '{
8+
generated_at: now | todate,
9+
events: map({
10+
type,
11+
repo: .repo.name,
12+
actor: .actor.login,
13+
created_at
14+
})
15+
}'
16+
17+
}

infra/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.terraform
2+
.terraform.lock.hcl
3+
terraform.tfstate*

0 commit comments

Comments
 (0)