v0.1.0 HTTP layer - experimental

Run a container,
over HTTP.

A Fastify wrapper around light-runner. POST files, an image, and an entrypoint - get back the exit code, artifacts you asked for, and a run id to poll or delete. Nothing else.

Runtime
Node >=22
Routes
8 endpoints
Dependencies
3 fastify, light-runner, zod
Tests
38 / 38 e2e + adversarial
License
AGPL 3.0
what you get

Post a JSON body. Get back an exit code, logs, and the files you extracted.

01 / files inline

Send files in the body. Run them in a tmpdir.

The body carries your files as a { path -> content } map. Written to a tmpdir, seeded as the container workdir, torn down as soon as the container exits.

02 / artifacts out

Pull any file back over HTTP.

List paths in extract. They land in an internal artifact directory, served by GET /runs/:id/artifacts/*. Path-traversal guarded, binary-safe, subdirectories listed on hit.

03 / sync or detached

Respond when done, or hand back a handle.

Default: wait for exit, return the full run state. Set detached: true for 202 Accepted + an id to poll, DELETE, or cancel. Optional HMAC-signed callback when it finishes.

04 / auto-evict

Artifacts self-clean past a size cap.

Default 20 GiB across all runs. When the cap is exceeded, oldest-created directories go first - the just-finished run and anything still running are protected. Configurable via env var.

quick start

One curl away from an isolated run.

Install, start the server with a bearer token, POST /run with an image, an entrypoint and some files. Extract anything the container wrote to disk.

No config file. No workflow engine. No vendor SDK.

npm install -g @enixcode/light-run
example.sh
# start the server (pick one)
light-run serve --token $(openssl rand -hex 32)
#  or: npm run dev      # docker compose up --build, port 3001

# from another shell
curl -X POST http://localhost:3000/run \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "image": "alpine:3.19",
    "entrypoint": "sh main.sh",
    "files": { "main.sh": "echo hi > /app/out.txt" },
    "extract": ["/app/out.txt"],
    "network": "none",
    "timeout": 30000
  }'

# -> { id, status, exitCode, durationMs, artifacts }
security model

Hardened by inheritance, tightened at the edge.

Runner inheritance
Every run goes through light-runner - so you get its stripped capabilities, pids limit, memory / CPU caps, isolated bridge, symlink filter. Unconditional.
Bearer auth
Timing-safe comparison on every non-/health route. Unset token leaves the server open and prints a startup warning.
Body validation
Zod on every request. No free-form fields reach the runner. File paths must be relative and cannot contain .. segments.
Body size cap
The whole request body is parsed as JSON in memory before Zod validation. Fastify rejects anything past 10 MiB (default) with 413 Payload Too Large. This is a light-run concern only - light-runner reads from disk and does not see the HTTP body. Tune via --body-limit, LIGHT_RUN_BODY_LIMIT, or createServer({ bodyLimit }). The files map is { path: string }, ideal for source code, shell scripts, small configs. Binary or very large assets (datasets, wheels, node_modules) should ship inside the Docker image rather than be base64-stuffed into the request - each request is held in memory until validated.
Path-traversal guard
On GET /runs/:id/artifacts/*: literal .. rejected, resolved host path asserted inside the run's artifact directory before any stream.
Storage cap
Total bytes across all artifact directories compared to 20 GiB (default) after every run. Oldest dirs evicted until under cap. Currently-running runs and the just-finished run never evicted.
HMAC callbacks
Async runs optionally POST the final RunState to a URL with header X-Light-Run-Signature: sha256=<hex> = HMAC_SHA256(secret, rawBody). Verify before trusting the payload.
TLS
Not built in. Terminate at Caddy / nginx / Traefik - proxies do TLS better than a Node process.
Does not cover - rate limiting, concurrency caps, kernel exploits, runc CVEs. Also out of scope: request / result caching, content-addressable file stores, memoization - those belong in light-process, one layer up. For genuinely hostile code, combine with a safer runtime inside light-runner.
ecosystem

Three tools. Each does one thing.

light-runner

Spawn one container, return exit code and files.

The execution primitive. Domain-agnostic. Zero orchestration. light-run calls straight down into this.

Released - v0.9.0
light-run

HTTP wrapper around light-runner.

Fastify + 8 routes. Send files inline, pull artifacts out. Same defaults as the runner, plus Bearer auth, Zod validation, and a storage cap.

Experimental - v0.1.0
light-process

DAG orchestration, retries, fan-out.

When one container is not enough. Composes runs into pipelines with backoff, concurrency limits, and structured outputs. Calls light-run over HTTP.

Planned