HTTP surface - light-runner

Run code over HTTP,
on someone else's Docker.

A thin HTTP server around light-runner. POST inline files, an image and an entrypoint; it runs them in a container and serves back the exit code, logs and any artifacts you asked for. No GitHub fetch, no workflow, no orchestration.

Runtime
Node >=24
Server
Fastify + OTel
Wraps
light-runner SDK
License
MIT permissive
A glowing sphere passing through a wireframe gateway above an isolated dark grid, the visual metaphor for code crossing an HTTP boundary into a sandbox.
Fig. 01 - One request, one container, one grid.light-run / visual
what you get

POST code. Get back a run state, logs, and the artifacts you asked for.

01 / one endpoint

POST /run takes files + image + entrypoint.

Send inline files, a Docker image and a command. light-run writes them to a tmpdir, runs them in a container via light-runner, and returns a RunState: id, status, exit code, duration, artifacts.

02 / pull artifacts

Extract any container path, stream it back.

List container paths in extract; their bytes land in an internal artifact dir, served over GET /runs/:id/artifacts/* with a path-traversal guard. Auto-evicted past a size cap.

03 / control the run

Stop, pause, resume, or cancel.

Run detached and poll GET /runs/:id, or drive the lifecycle with POST /runs/:id/stop|pause|resume|cancel. Run state is persisted by light-runner, the source of truth.

04 / manage networks

Create and delete Docker networks over HTTP.

A remote orchestrator can provision isolated networks (POST /networks), attach runs to them, and sweep orphans (POST /networks/cleanup) - all without shell access to the host.

quick start

One POST, and your container runs.

Start the server (light-run serve), then POST a job. Inline your files, name an image, give a command, list what to extract.

Stateless past the artifact directory. No caching, ever.

npm install -g @enixcode/light-run
example.ts
const res = await fetch('http://localhost:3000/run', {
  method: 'POST',
  headers: { 'content-type': 'application/json', authorization: 'Bearer ...' },
  body: JSON.stringify({
    image:      'python:3.12-alpine',
    entrypoint: 'python main.py',
    files:      { 'main.py': 'open("out.json","w").write(...)' },
    extract:    ['out.json'],
  }),
});
const run = await res.json();
run.status      // succeeded | failed | running | cancelled
run.exitCode    // the container exit code
run.artifacts   // [{ path, bytes, type }] - fetch via /runs/:id/artifacts
security model

Auth on every route. Isolation inherited from light-runner.

Bearer auth
When a token is configured, every route except /health requires Authorization: Bearer. No token = open server with a startup warning.
Path-traversal guard
Artifact downloads reject a literal ..and assert the resolved path stays inside the run's artifact directory. A container cannot serve files it never wrote.
Body limit
POST bodies are capped (10 MiB default, tunable). An oversized upload is refused with 413 before it can grow the heap.
Tmpdir hygiene
Inputs live under os.tmpdir() and are removed the moment the container exits, success or failure. Files are Zod-validated: no absolute paths, no .. segments.
Auto-eviction
Total artifact bytes are compared to a cap after every run; oldest run dirs are evicted until under. Running and just-finished runs are never evicted.
Inherited isolation
Cap drops, PID limits, memory/CPU budgets, isolated networks and the gVisor/Kata runtimes are all light-runner's job. Configure them on the runner, not here.
Does not cover - request or result caching. Workload containers are non-deterministic; memoization needs workflow-level identity that only light-process has. This layer stays stateless past the artifact directory.
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 down to this for every job.

Loading
light-run

CLI and HTTP surface around light-runner.

Point a POST endpoint at it, pipe bodies through, fetch artifacts back. Stateless wrapper, same defaults, same guarantees.

Loading
light-process

DAG orchestration, retries, fan-out.

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

Loading