Skip to content

Job definition

The body you POST /q/<queue>:

json
{
  "id": "optional — defaults to sha256(definition)",
  "definition": { "...": "the container spec" },
  "control": { "...": "optional queue behaviour" },
  "debug": false
}

definition

FieldTypeNotes
imagestringDocker image, e.g. python:3.12-slim. Or a git URL to build from.
buildobjectBuild instead of pull — see below.
commandstringOverrides the image CMD. Shell syntax needs an explicit sh -c "…".
entrypointstringOverrides the image entrypoint.
envobject{ "KEY": "value" } env vars.
workdirstringWorking directory inside the container.
inputsobjectFilename → DataRef. Mounted read-only at /inputs.
configFilesobjectSame shape as inputs, but part of the job hash.
shmSizestringe.g. "2g" — needed by PyTorch dataloaders and friends.
maxDurationstring"30s", "20m", "2h".
requirementsobject{ cpus, gpus, memory, maxDuration }.

definition.tags also exists in the types, intended to pin a job to workers with matching tags. Nothing matches on it yet — treat it as reserved.

inputs vs configFiles

Both land in the container. inputs are the data you vary per run; configFiles are fixed and folded into the job hash, so changing one produces a different job id. If you want a change to force a re-run, put it in configFiles (or env).

Minimal

json
{ "definition": { "image": "alpine:3.19.1", "command": "echo hello" } }

With a script and data

json
{
  "definition": {
    "image": "python:3.12-slim",
    "command": "python /inputs/analyse.py",
    "inputs": {
      "analyse.py": {
        "type": "utf8",
        "value": "import json,os\nprint(os.listdir('/inputs'))\nopen('/outputs/result.json','w').write(json.dumps({'ok':True}))\n"
      },
      "data.csv": { "type": "url", "value": "https://container.mtfm.io/f/9f86d0…" }
    },
    "requirements": { "cpus": 2, "memory": "4g", "maxDuration": "10m" }
  }
}

GPU

json
{
  "definition": {
    "image": "nvidia/cuda:12.4.1-runtime-ubuntu22.04",
    "command": "nvidia-smi",
    "requirements": { "gpus": 1 }
  }
}

The worker allocates a specific device and sets CUDA_VISIBLE_DEVICES=0 inside the container — from the container's point of view its GPU is always index 0. Only workers started with GPUs available will take the job.

Build an image

build makes the worker build the image instead of pulling one. See Building containers for the whole workflow; the fields are:

FieldTypeNotes
dockerfilestringThe contents of a Dockerfile, inline. Simplest option.
contextstringWhere the build context comes from — a GitHub URL or an archive URL. See below.
buildContextstringSubdirectory within the context to build from.
filenamestringDockerfile name within the context (default Dockerfile).
targetstringMulti-stage build target.
buildArgsstring[]["VERSION=1.2.3"].
platformstringe.g. linux/amd64.
json
{
  "definition": {
    "build": {
      "context": "https://github.com/me/my-tool/commit/9f3c1a2",
      "filename": "Dockerfile",
      "buildArgs": ["VERSION=1.2.3"],
      "platform": "linux/amd64"
    },
    "command": "my-tool --run"
  }
}

dockerfile and context combine: the context supplies the files, and an inline dockerfile is written into it, overwriting any Dockerfile already there.

Accepted context values:

  • https://github.com/owner/repo — the default branch
  • https://github.com/owner/repo/tree/<branch-or-tag>
  • https://github.com/owner/repo/commit/<sha>prefer this, it pins the build
  • any URL serving a gzip or zip archive — the worker identifies the format from the archive's magic bytes, so a content-addressed blob URL with no file extension works

If the archive contains exactly one top-level directory it is treated as the context root, which is what makes GitHub's wrapper folder transparent. An archive with files at its root is used as-is — so you can tar a local directory, PUT it to blob storage, and use that URL as the context.

Two caches, not one

The job is cached on the whole definition; the image is cached on the build block alone. Change only the command and the container re-runs against the already-built image, producing no build logs at all — that is the cache working, not a failure. Changing anything inside build forces a rebuild.

Build logs are kept separate from the container's own output. Read them at /q/<queue>/j/<jobId>/build-logs.json, follow them live over SSE, or watch them on the websocket.

control

FieldNotes
namespaceOne live job per namespace; submitting evicts the previous one.
maxDurationQueue-side kill switch.
callbacks.queued{ url, payload } — POSTed when the job is enqueued. See below.
json
{
  "definition": { "image": "alpine:3.19.1", "command": "echo hi" },
  "control": {
    "namespace": "user-42",
    "callbacks": { "queued": { "url": "https://my.app/hooks/queued", "payload": { "requestId": "abc" } } }
  }
}

There is no finished callback yet

control.callbacks.finished appears in the TypeScript types but nothing reads it. Only callbacks.queued fires. To learn that a job completed, poll or use the websocket — see Backend integration.

Container environment

Every container gets:

VariableValue
JOB_IDThe job id
JOB_INPUTS/inputs
JOB_OUTPUTS/outputs
JOB_CACHE/job-cache — shared across jobs
JOB_URL_PREFIX<api>/j/<jobId>
JOB_INPUTS_URL_PREFIX<api>/j/<jobId>/inputs/
JOB_OUTPUTS_URL_PREFIX<api>/j/<jobId>/outputs/
CUDA_VISIBLE_DEVICES0, when a GPU was allocated

/job-cache is the right place for model weights and datasets: it persists on the worker between jobs, so the second run of an ML job skips the download.