Sell your GPU

A graphics card that sits idle most of the day is a listing: agents rendering, transcoding, analysing hours of video or training a model send it jobs and pay per job, and the machine earns while you are away from it. Nothing of yours is exposed: every job runs in a container the buyer names, with the buyer's files at /in, and only what the job writes to /out leaves the box.

What you need

  • Docker on the machine. docker info must answer; without it the listing refuses with "docker is not installed on this node" or "docker is installed but its daemon does not answer", each naming the fix.
  • For a GPU listing, the NVIDIA container toolkit. docker info must show the NVIDIA runtime, else: "this node's docker has no NVIDIA runtime, so GPU jobs cannot run: install nvidia-container-toolkit, run sudo nvidia-ctk runtime configure --runtime=docker and restart docker". npm run hardware:check in offchain/ prints what the machine has, GPUs, VRAM, Docker and the runtime included.
  • A node: the local stack, or the same one command on any machine (Sell a service).

The two templates

compute-own-gpu (GPU job on your own machine, category compute:gpu) and compute-own-cpu (CPU job on your own machine, compute:serverless) are the templates. Both carry local: true and requires: a GPU for the first, two CPU cores and 4 GB of RAM for the second, so the catalogue lists them only on a machine that meets that, and says why when it skips one.

A buyer's call is run {image, command, inputs?, env?, timeoutSeconds?}: the node pulls the image, downloads each {url, path} under /in, runs docker run --rm (with --gpus all on the GPU listing) under the limits below, captures stdout and stderr, and returns every file the job wrote to /out as URLs served by the node, with the exit code. A job still running after the sync window comes back as a job id, "still running after 60 s; call status with this jobId", and status {jobId} reports running, succeeded, failed or timeout.

LimitGPU templateCPU templateField
Longest job3,600 s1,800 smaxSeconds
Output written to /out2,048 MB1,024 MBmaxOutputMb
Inputs downloaded to /in8,192 MB4,096 MBmaxInputMb
Jobs at once12maxConcurrent
Answer within, else a job id60 s60 ssyncSeconds
Minute rate in the tool's description0.02 USDC0.005 USDCpricePerMinute
Price per job0.25 USDCthe listing's pricepriceUsdc

images restricts which images may run (empty means any); network is none, bridge or host; memory and cpus cap the container. Edit pricePerMinute and priceUsdc to yours: the buyer pays the listing's price per job, and the minute rate in the description is what an agent reads to size a long job.

List it

On the local stack the bootstrap lists both when docker info answers, the GPU one only with the NVIDIA runtime. On any node:

cd offchain
npm run templates -- add compute-own-gpu --out services.json
AIOUM_SERVICES_CONFIG=./services.json npm run multinode

Or from the Earn page: Sell your hardware shows the two templates with what each needs ("needs Docker and a GPU", "needs Docker, 2 CPUs and 4 GB RAM"); pick one, set the price and your stake, press Run the test call (it runs alpine:3.20 and writes hello.txt to /out), and Go live now. From the command line, aioum sell add --template compute-own-gpu --price 0.25; from Claude, aioum_sell_add with template: "compute-own-gpu".

Jobs live under <AIOUM_NODE_DIR>/compute/<jobId>; output files are served from the node's /media path at AIOUM_NODE_PUBLIC_URL.

Who needs it

The video, image and training pipelines require a GPU: analysing reference clips, running a vision model over frames, rendering and training run on one, never on a CPU. The need is met by the node's own GPU first (what hardware.check reports, Apple Silicon included) and otherwise by a compute:gpu listing that runs jobs, this template on someone else's machine, or by a GPU rented for the project, which answers the same run call from the compute pod below. A GPU price feed under compute:gpu is a listing, not a GPU. When neither is there the pipeline's card reads a GPU for frame analysis and rendering: none on this machine; add a compute key (RUNPOD_API_KEY, …) to rent one, or run the node on a machine with a GPU, and Use this stays off.

When the node has no GPU and a listing is live, the runner's GPU jobs carry the heavy steps to it: analyse_frames samples one frame a second from each reference clip with PyAV and runs a captioning model (Salesforce/blip-image-captioning-base, fp16 on CUDA) over them as a docker job in pytorch/pytorch:2.6.0-cuda12.4-cudnn9-runtime, returning one caption per frame and the frames; assemble concatenates the shots, mixes narration and music and encodes with h264_nvenc in jrottenberg/ffmpeg:7.1-nvidia. Each is the exact run call the listing executes, paid from the run like any other call; a job that finds no CUDA device ends with exit 3 rather than falling back to the CPU, and the answer names the listing and its device, which the run log repeats as ran on …. The listing's node adds its own docker settings (--gpus all, the optimizer's --shm-size).

Rent one for a project

With a rental key on the runner (RUNPOD_API_KEY or VAST_API_KEY) and no listing that runs jobs, the setup rents the GPU the heavy stages need for the length of the project (Build an agent network). What it rents is a pod running the compute pod image, offchain/docker/compute-pod: CUDA PyTorch 2.6, node 22, ffmpeg with NVENC and the frame job's packages, with a job server that answers the contract above, run and status, directly in the pod's container. The repository's workflow, .github/workflows/compute-pod-image.yml, builds the image on every change to offchain/docker/compute-pod and pushes it to ghcr.io/oum-dev/aioum-compute-pod, tagged with the commit and latest; the runner names it with AIOUM_POD_IMAGE (default ghcr.io/oum-dev/aioum-compute-pod:latest). A fork's workflow pushes under the fork's own owner, ghcr.io/<owner>/aioum-compute-pod, so a fork sets AIOUM_POD_IMAGE to that. GHCR makes the package private on its first push: make it public in its settings on GitHub, since the runner hands the provider no registry credentials. The image is the first choice, and never a blocker: the runner asks the registry for it before renting, and when the answer is not 200 (private, unpublished, no such tag, unreachable) it rents the public base the image is built on, pytorch/pytorch:2.6.0-cuda12.4-cudnn9-runtime, and bootstraps the pod at start: the start command (RunPod's dockerStartCmd, Vast.ai's onstart) installs node 22 and the same static ffmpeg, writes the job server from the pod's environment (AIOUM_POD_SERVER_B64, gzip+base64 in pieces of 4000 characters), installs the frame job's packages and starts the server. That pod's /live reports bootstrapped: pytorch base + node + ffmpeg, the Hardware row adds "bootstrapped at start (no registry image needed)", and the wait for /live is a few minutes instead of seconds. To build the image yourself:

What the pod does for a project: the frame job captions sampled frames, the style job reads images and frames for style (a caption, the eight dominant colours by k-means on the GPU with their shares, flatness and edge density, and CLIP likeness to the reference frames you name), and the assembly cuts the shots with the narration and the music. A file in the workspace on your node goes to the pod as an upload the runner ships (PUT /upload/<name> on the job server, the run naming the upload id), since the pod cannot reach your node; a GPU listing's job takes only http(s) URLs its node can fetch. When a project pauses, its pod is kept for AIOUM_AGENTS_RENTAL_IDLE_MIN minutes (30) in case it resumes, then torn down by its id; a resume finds it up, or rents again the way the setup did before the stage runs.

docker build -t ghcr.io/oum-dev/aioum-compute-pod:latest offchain/docker/compute-pod
docker push ghcr.io/oum-dev/aioum-compute-pod:latest

What happens, every line of it in the setup's log: the runner reads the instance's hourly price and checks it against half the budget, creates the pod named aioum-<project id>-<short> with a token of this project in its environment (AIOUM_POD_TOKEN) and port 8000 exposed (RunPod reaches it at https://<pod id>-8000.proxy.runpod.net; Vast.ai on the instance's public IP and the port it mapped, the job server started by onstart), keeps the provider's id on the project record the moment it exists, waits for the pod and then for its job server's /live, and the Hardware row reads "renting a GeForce RTX 4090 on RunPod for frame analysis and render (…) · up". From then on the run's GPU jobs go to that pod with the token, nothing paid per job: run ignores the image (the pod is the container; the assembly names ffmpeg), downloads the inputs to /in, runs the command, and serves every file the job wrote to /out behind the token or the job's own key; the runner pulls them into your workspace under gpu/<job>/<id>/ and the log reads ran on RunPod RTX 4090 (rented for this project). Before the job server listens it encodes one second of video through h264_nvenc, and /live carries the result as encoders: {h264_nvenc: true}, or {h264_nvenc: false, reason} in ffmpeg's words. Two secure-cloud RTX 4090 hosts refused the session (OpenEncodeSessionEx failed: unsupported device (2)) although the build lists the encoder, so the assembly names h264_nvenc only when the pod said it works and libx264 (-preset veryfast -crf 20, the pod's CPU) otherwise; the Hardware line says which as the pod comes up, the assembly's rented estimate takes the rate that holds, and the run log reads assembly encoded with libx264 on the rented GPU: NVENC not available on this host (…). When the project ends the pod is terminated by the id on the record, and only that id: the runner never lists your account's pods, never matches by name, never touches a pod it did not create, and a rental from before a runner restart is terminated from the record it left. Lambda and DigitalOcean keys name a price and an instance but rent nothing: they launch bare VMs reached over SSH, where the pod cannot ride. A pod whose port never opens or whose job server never answers (the image not pushed where the provider can pull it) is terminated and the row says so.

Next: MCP servers as listings — any MCP server as a paid listing, verified only inside a container, and the host-access rule.