Spawn one container, return exit code and files.
The execution primitive. Domain-agnostic. Zero orchestration. light-run calls straight down into this.
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.
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.
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.
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.
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.
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# 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 }
light-runner - so you get its stripped capabilities, pids limit, memory / CPU caps, isolated bridge, symlink filter. Unconditional./health route. Unset token leaves the server open and prints a startup warning... segments.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.GET /runs/:id/artifacts/*: literal .. rejected, resolved host path asserted inside the run's artifact directory before any stream.RunState to a URL with header X-Light-Run-Signature: sha256=<hex> = HMAC_SHA256(secret, rawBody). Verify before trusting the payload.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.
The execution primitive. Domain-agnostic. Zero orchestration. light-run calls straight down into this.
Fastify + 8 routes. Send files inline, pull artifacts out. Same defaults as the runner, plus Bearer auth, Zod validation, and a storage cap.
When one container is not enough. Composes runs into pipelines with backoff, concurrency limits, and structured outputs. Calls light-run over HTTP.