Skip to main content

urun CLI

Deploy Python and Rust (native SDK) apps to urun from your terminal.

PyPI Python

Install

uv tool install urun-cli
# or
pip install urun-cli

For Rust (native SDK) deploys, install with the [rust] extra — see Deploy a Rust app (native SDK):

uv tool install "urun-cli[rust]"
# or
pip install "urun-cli[rust]"

The package installs the urun command:

urun --version

For one-off uvx usage:

uvx --from urun-cli urun --version
# or the package-matching command alias
uvx urun-cli --version

Quick start

Save an org-scoped deploy API key locally with urun login:

urun login

If URUN_API_KEY is not already set, urun login opens the urun console API keys page, prompts you to paste the generated urun_sk_... key, verifies it with the urun API, and stores credentials for later commands.

If you already have a key, you can pass it directly:

urun login --api-key urun_sk_<secret>

For CI or one-off commands, you can still use the environment variable:

export URUN_API_KEY=urun_sk_<secret>

Create app.py:

import urun
from urun import App

app = App("hello-h100")


@app.function(gpus="h100:1")
def hello(ctx: urun.Context):
    print(f"running on {ctx.device}")
    return {"device": str(ctx.device)}

Run it:

urun run app.py

In this release, urun run uses the same deploy pipeline as urun deploy. deploy remains available as the lower-level command while the full deploy/run/monitor workflow is being built.

Attach an official browser frontend

For official urun-examples apps, the shortest browser stream path is:

cd ~/workspace/urun-examples
urun deploy prompt_canary/backend/app.py --name prompt-canary --timeout 900
urun dev --attach prompt-canary --examples-root ~/workspace/urun-examples

The positional argument is the deployed app slug (as shown by urun app list). Nothing is declared in a manifest — every value is discovered:

value where it comes from
frontend directory the one <example>/frontend under the examples root whose name matches the app slug (hyphens and underscores are equivalent, and a --dev suffix is ignored)
pnpm package that frontend's own package.json "name"
local port the --port N in that package's scripts.dev, else 3000
function read from the deployed app itself

urun dev --attach uses your resolved API key server-side to request a short-lived, function-scoped, origin-pinned browser JWT, sets the frontend's NEXT_PUBLIC_* environment variables, starts the local Next.js frontend, and opens it in your browser. The API key itself never reaches the page.

Discovery is never a guess: if no example directory matches the app — or if more than one does — the command fails and names what it found. Point it at the right directory yourself with --frontend-dir (relative to the examples root):

urun dev --attach test-matrix-game --frontend-dir matrix-game-3/frontend

There is no curated list of attachable examples

urun dev --attach will launch any <example>/frontend it can match, and it does not know which examples actually work. Earlier releases read a manifest that carried a supported flag with a reason, and refused the examples marked unsupported; that allowlist is gone along with the manifest.

The practical consequence: a frontend that is a stub, half-built, or known broken now starts a dev server and opens a browser instead of failing up front, and it fails on its own terms once loaded. notebook-playground is the clearest case — its frontend/ is a deliberate loud-failing stub. If a demo comes up wrong, check the example's own README before suspecting the CLI.

This was an accepted trade for deleting the manifest: which examples work is not derivable from the filesystem or the control plane, so the CLI cannot know it without a second declared source that would drift exactly as the manifest did.

Override the examples checkout with --examples-root or URUN_EXAMPLES_ROOT; override the session API with --session-url or URUN_SESSION_BASE_URL; pin the function with --function and the environment with --environment.

Deploy a Rust app (native SDK)

Rust apps use the native SDK crate urun and are declared by an inline module owning #[urun::app(name = "...")] with nested #[urun::function] handlers.

The executable calls urun::main!(app) from fn main(). Each handler crosses the ctx.self_test(...) barrier before accessing session state. The runnable native-echo example below includes the complete lifecycle.

Install the CLI with the Rust parser extra (a plain install stays dependency-free; the extra pulls the pinned tree-sitter/tree-sitter-rust wheels used for static declaration extraction — no Rust toolchain needed):

uv tool install "urun-cli[rust]"
# or
pip install "urun-cli[rust]"

Deploy by pointing at the project's Cargo.toml (a src/main.rs or the project directory also work):

urun deploy path/to/project/Cargo.toml
  • Vendor the SDK in-root. The urun SDK crate is not published to crates.io and the build environment has no private git credentials, so the crate must ship inside your deploy root: reference it by a relative path dependency (urun = { path = "vendor/urun" }) or as a member of the Cargo workspace rooted there. Credential-free registry/git dependencies are fine.
  • Cargo.lock is mandatory. The builder builds with --locked; a missing lock file fails the deploy.
  • The bundle is the full in-root Cargo workspace / path-dependency / resource closure, minus target/, .git, and .cargo/credentials{,.toml}.

A runnable starting point is the SDK checkout's examples/native-echo app, which vendors the SDK via a path dependency and deploys as-is:

git clone git@github.com:urun-sh/urun-rs.git
urun deploy urun-rs/examples/native-echo

Deploy a mixed Python + Rust app

ONE app can hold functions implemented in BOTH languages: one identity, one release, the full function set replaced atomically on every deploy. Declare the sources explicitly in the project's pyproject.toml — nothing is scanned or inferred, so vendored SDK examples are never pulled into your app:

# pyproject.toml
[tool.urun]
entrypoints = ["app.py", "native/src/main.rs"]

Python functions register on one App("mixed-app"). Rust functions use #[urun::function(...)] inside #[urun::app(name = "mixed-app")]. The builder assembles both into one function registry: the language belongs to the function, not to a separate app. See the runnable examples/mixed-app for complete Python and Rust implementations, including self-tests and calls in both directions.

Deploy the whole app with any of these — each resolves the ENTIRE declared app, so one component invocation never accidentally retires its sibling functions:

urun deploy .                # the config directory
urun deploy pyproject.toml   # the declaration itself
urun deploy app.py           # a declared component
urun deploy native/src/main.rs
  • One identity across all sources. Python helpers may import the same App object; Rust declarations use the same app-name literal. A different App object, mismatched name, or duplicate function name is a hard error.
  • Declared paths stay in the config directory (root-relative, no .. escapes, every declared file present). The bundle root may widen through ordinary Cargo workspace resolution — e.g. a crate that is a member of a wider workspace ships that whole workspace closure — and every entrypoint and manifest_path rebases onto it.
  • Python dependencies stay Python. The manifest's deps block is resolved from the Python source exactly like a pure Python deploy (Dependencies(python=...), requirements.txt, python_version); the Rust sources' apt/build_deps function options fold into the same lists.
  • Resources ride with their code in both halves: the Python import/data closure plus each Rust crate's full workspace/path closure and every referenced Cargo.lock.

A runnable starting point is the SDK checkout's examples/mixed-app app.

Serve a model from the catalog

urun serve deploys a model straight from the urun model catalog — no app code required. It is urun deploy with a serve app templated from the resolved catalog row (engine, HF repo, GPU, engine args).

List the enumerable model matrix (models, variants, the GPU each fits, engine shape, and whether the placement is dev-testable or prod-only):

urun serve catalog
urun serve catalog --json

Serve a model. With no variant the model's default (first) variant is used; with no --gpu the variant's first placement is used:

urun serve qwen-coder                    # default variant + first placement
urun serve glm-5.2:UD-IQ2_M              # explicit <id>:<variant>
urun serve qwen-coder-480b:fp8 --gpu b200:8

The catalog is anon-readable reference data published by a urun-infra migration. Reads go over PostgREST on the same control-plane host as the API URL; supply the public Supabase anon key via --anon-key or URUN_CATALOG_ANON_KEY (URUN_SUPABASE_ANON_KEY is also honored). Override the PostgREST base with --catalog-url / URUN_CATALOG_URL if needed.

By default urun serve <id> renders a templated serve app and deploys it. If you maintain your own _serve app, point --entrypoint at it; the CLI deploys that app with URUN_SERVE_CONFIG and URUN_SERVE_CATALOG_ROW set to the resolved row instead of rendering one.

Inspect apps

List every app deployed in your org and its current status:

urun app list

Sample output:

APP                    FUNCTION        COMPUTE  STATUS         RELEASE       DETAIL
causal-forcing-stream  generate_video  h100:1   ready          fa61f31b0961  0/1 GPU units in use
queued-app             warmup          a10:1    provisioning   000000000000  building

The STATUS column is one of provisioning, ready, pending, paused, or failed. It is derived from three backend signals reporting on sequential lifecycle phases:

Build (S3 status) Promotion (app_deployments) Capacity (function_ready) STATUS
queued | building (no row yet) - provisioning
failed (no row yet) - failed
ready active false pending
ready active true ready
ready paused (irrelevant) paused
ready failed (irrelevant) failed

The DETAIL column carries the disambiguating signal (raw build state, error message, ready_reason, or in-use GPU counts). Pass --json for the raw payload.

This command is experimental and requires the server-side GET /apps endpoint, which is in development.

View logs

Use urun build logs, urun app logs, urun function logs, urun session logs, or urun log list for build logs or runtime logs scoped to an app, a function within an app, or a session:

urun log list
urun build logs <build-id>
urun app logs <app>
urun function logs <app> <function>
urun session logs <session>

Runtime commands accept --follow/-f for live output and --json for JSON (one-shot) or NDJSON (followed) output. Use urun log list without filters to list all accessible logs, or add the same app, function, session, level, text, and time filters. urun log tail remains available for the legacy follow workflow. The current logs API does not filter by environment.

Inspect sessions

List live and historical sessions in your org. Newest sessions appear at the bottom of the table so the command works well with tail:

urun session list
urun session list | tail -20
urun session list --state failed
urun session list --limit 500

Sample output:

ID            APP                FUNCTION        SHAPE    STARTED               DURATION  STATE       DETAIL
2cc8a91f4b3d  helios             world_gen       h100:4   2026-06-02 10:55 UTC       44s  failed      no_capacity
3f0017daee01  helios             world_gen       h100:1   2026-06-02 11:08 UTC    18m43s  completed   client_disconnect
4a1c886e2d0a  causal-forcing-…   generate_video  h100:1   2026-06-02 14:21 UTC     3m12s  live        -

The STATE column maps the raw backend status to a user-friendly label:

Backend status STATE
allocated starting
connected live
closed completed
failed failed
cancelled cancelled

DURATION is computed from allocated_at to closed_at for terminal sessions, or allocated_at to now for live ones. DETAIL carries close_reason when present. Pass --json for the raw payload (full IDs, ISO timestamps, all fields).

Pass --limit to control how many rows are fetched (default 100).

This command is experimental and requires the server-side GET /sessions endpoint, which is in development.

Inspect active compute

List the compute slices your org currently has provisioned:

urun compute list

Sample output:

APP                    FUNCTION        SHAPE   INSTANCES  GPU UNITS  SESSIONS  AGE
causal-forcing-stream  generate_video  h100:1  1/2        1/2        1         12s
helios                 world_gen       h100:4  0/1        0/4        0         3m

Each row is one actively provisioned (app, function, compute_shape) slice. INSTANCES and GPU UNITS show <allocated>/<provisioned> — a row with 0/1 is an idle warm runtime with no active sessions on it. SESSIONS is the live session count. AGE is how stale the capacity snapshot is; very old ages may indicate the runtime is no longer reporting.

Slices with no provisioned capacity are omitted, so this command answers "what is running right now". For the full deployment catalogue (including paused / failed / unprovisioned apps) use urun app list; for historical or in-flight sessions use urun session list.

Pass --limit to control how many rows are fetched (default 100).

This command is experimental and requires the server-side GET /compute endpoint, which is in development.

Manage apps

Manage the lifecycle of a single deployed app. The app is addressed by its slug (the name shown under APP in urun app list); every operation is org-scoped via your API key.

Show detailed status for one app (the single-app complement to app list):

urun app status lingbot
            App: lingbot
           Name: LingBot
    Environment: prod
     App status: active
     Deployment: active
Desired replicas: 2
       Function: handle_lingbot_runtime
        Compute: b200:4
            GPU: 4 x b200
        Release: 1c6d6287abcd
  Live sessions: 1

Scale an app's runtime replica count (the backend's scaling knob; the control plane turns it into the runtime StatefulSet replica count):

urun app scale lingbot --replicas 3
urun app scale lingbot --replicas 0   # drain to zero without retiring

GPU count and compute shape are fixed at deploy time per release (set via @app.function), so scale intentionally exposes only --replicas.

Disable an app so the control plane stops running it (drives the deployment to paused and the app to disabled, so the materializer stops recreating its runtime). This is the clean, reversible, API-driven alternative to a manual database edit:

urun app disable lingbot-handle        # prompts for confirmation
urun app disable lingbot-handle --yes  # skip confirmation

Enable a disabled app and bring it back online:

urun app enable lingbot-handle

If urun deploy or urun dev --attach reports that a function was paused after repeated startup failures, inspect its runtime logs before overriding the safety pause. Then explicitly resume every paused function:

urun app enable rapid-clips --force

--force may restart a known-crashing function; it does not change the platform's automatic pause policy.

Stop a queued, starting, or live session:

urun session stop sess_123 --yes

All app subcommands accept --environment (default prod) and --json. These commands are experimental and require the server-side app lifecycle endpoint, which is in development.

Scratch occupants (GPU lane)

urun scratch runs an isolated, TTL-bounded scratch occupant for an app release — an app function or a bare python -c command in that release's venv with its weights. It rides the SAME pull-dispatch path as serving (a BullMQ demand job → node scheduler admission → resident spawn), so there is no separate provisioner and no kubectl. Authz is org-scoped via your API key, exactly like urun deploy.

# Reproduce a broken app in isolation on its release's shape:
urun scratch gemma-voice dg-brain --gpu rtx6000:1 --ttl 2h

# CPU-only occupant running a bare command (after --) instead of the server:
urun scratch run --app demo --cpu 4 --ttl 90m -- "import torch; print(torch.__version__)"

urun scratch list                      # live occupants: name, app, shape, node, state, expires
urun scratch logs sc_8f3a…             # stream an occupant's stdout log
urun scratch exec sc_8f3a… -- "print(os.environ['URUN_SCRATCH_ID'])"
urun scratch end sc_8f3a…              # tear down before the TTL

What it does:

  • Isolated, never serving-touching: scratch demand is never counted by the autoscaler (it never mints nodes) and scratch residents are the preferred eviction victims — scratch can never take capacity from serving. Pin a release with --release <id> or let it resolve the app's current promoted release.
  • Explicit compute: --gpu <shape> (e.g. l40s:1) or --cpu N (shape cpu:N). Omit both to use the release's default shape.
  • Auto-reclaimed: every occupant carries a TTL (--ttl 2h / 90m; default 4h) and is reaped when it lapses; urun scratch end ends it sooner. --no-logs skips streaming the occupant's log after creation (Ctrl-C also detaches without tearing the occupant down).

What gets deployed

urun deploy creates a source manifest from your Python entrypoint:

Entrypoint Included source
urun deploy app.py app.py and local Python files it imports
urun deploy Cargo.toml The in-root Cargo workspace / path-dependency closure (see Deploy a Rust app (native SDK)).
urun deploy . / pyproject.toml The whole explicitly-declared [tool.urun] app — Python + Rust sources, both closures (see Deploy a mixed Python + Rust app). A declared component (app.py, native/src/main.rs) resolves the same entire app.

Dependencies are declared in your urun app code. Project-level files such as pyproject.toml and requirements.txt are not uploaded as dependency declarations by the CLI.

Generated/cache content such as .git, dotfiles, __pycache__, and .pyc files is excluded. Add .urunignore to exclude additional paths.

Shipping extra files: Dependencies(files=[...])

The collector's heuristic is Python-first: imported .py source, plus the non-Python files sitting next to that source. Anything it does not reach — a prompt tensor in its own directory, an asset an .urunignore pattern drops — is declared explicitly on your app's dependencies:

from urun import App, Dependencies

app = App("flashvsr-superres")


@app.function(
    deps=Dependencies(
        python=["torch"],
        files=["flashvsr_utils/prompt_tensor/posi_prompt.pth"],
    )
)
def superres(): ...
  • Paths are relative to the entrypoint file (app.py).
  • Declared files ship regardless of .urunignore and the built-in exclusions — that is the point of declaring them.
  • files= is purely additive. With no files=, collection is exactly what it was before.
  • Entries are individual files, not directories or globs. List each file.
  • A declared path that does not exist, escapes the app directory (.., a symlink pointing outside), or is absolute fails the deploy immediately, naming the path and the directory it was looked up under. A declared file is never silently skipped.

Common options

Shared by run and deploy:

Option Description
--name Override the derived app name.
--api-url Override the API URL; defaults to URUN_API_URL, saved login credentials, or https://api.urun.sh/v1.
--api-key Deploy API key; defaults to URUN_API_KEY or saved login credentials.
--no-wait Finalize but do not poll for readiness.
--poll-interval, --timeout Control readiness polling.

Troubleshooting

Error Fix
missing API key Run urun login, set URUN_API_KEY, or pass --api-key.
invalid API key format Use urun_<32 lowercase hex chars>.
entrypoint not found Run from the project root or pass the entrypoint path.
path is outside the project root Move the file under the project before deploying.
Expected files are missing Import local Python files from app.py. Non-Python assets are collected only when they sit next to collected source — declare anything else with Dependencies(files=[...]).

Development

Contributing and test instructions are in CONTRIBUTING.md.

License

MIT.

Development environment

This repo has a Nix/direnv/devcontainer baseline:

direnv allow
just sync
just check

Use VS Code Dev Containers to open the repository with the same toolchain in a container. Copy devcontainer.env.example to .devcontainer.env if you need to pass local git identity or other non-secret development settings into the container.

Metadata

Release files for urun-cli 0.7.5

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for urun-cli 0.7.5
File Size Uploaded Provenance
urun_cli-0.7.5.tar.gz 330.0 kB With provenance Details

Built distributions (wheels)

Table of built distributions (wheels) for urun-cli 0.7.5
File
urun_cli-0.7.5-py3-none-manylinux_2_25_x86_64.whl Python 3 none Linux glibc 2.25+ x86-64 Details
urun_cli-0.7.5-py3-none-manylinux_2_25_aarch64.whl Python 3 none Linux glibc 2.25+ ARM64 Details
urun_cli-0.7.5-py3-none-macosx_15_0_arm64.whl Python 3 none macOS 15.0+ ARM64 Details
urun_cli-0.7.5-py3-none-any.whl Python 3 none any Details

Total release size: 106.6 MB

Release files / urun_cli-0.7.5.tar.gz

Download URL urun_cli-0.7.5.tar.gz
Size 330.0 kB
Tags Source
SHA-256 checksum
How to use checksums
baf2997012a20b61804ce4bdc91d31eb648f8fe088b8a966026c7ee96bb62aef
BLAKE2b-256 checksum
How to use checksums
dad7518ec15b41c37d7b59ee5a1d5da57d478e167c4e62738709e3aaf26b817e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.13

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 30, 2026.

Transparency log

Release files / urun_cli-0.7.5-py3-none-manylinux_2_25_x86_64.whl

Download URL urun_cli-0.7.5-py3-none-manylinux_2_25_x86_64.whl
Size 41.1 MB
Tags Linux glibc 2.25+ x86-64 Python 3
SHA-256 checksum
How to use checksums
6f0bced75bc0fa9051770f69649b34455675cc9c50a5ce96faa7d73aa91f2089
BLAKE2b-256 checksum
How to use checksums
ccec9c1a92aa237bb4dd5b2c869fd18aaada0c2d49563a9b82fff6420b100b7c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.13

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 30, 2026.

Transparency log

Release files / urun_cli-0.7.5-py3-none-manylinux_2_25_aarch64.whl

Download URL urun_cli-0.7.5-py3-none-manylinux_2_25_aarch64.whl
Size 40.7 MB
Tags Linux glibc 2.25+ ARM64 Python 3
SHA-256 checksum
How to use checksums
fb183c30a5fa558eaf8a894d3e53e837c96e6d3779a1c3cd011d0b62ed0772e2
BLAKE2b-256 checksum
How to use checksums
ee9829624600cc685596b3adcec23ebf94ea4ac0e85b315a133fa8f17fcf4fb7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.13

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 30, 2026.

Transparency log

Release files / urun_cli-0.7.5-py3-none-macosx_15_0_arm64.whl

Download URL urun_cli-0.7.5-py3-none-macosx_15_0_arm64.whl
Size 24.1 MB
Tags Python 3 macOS 15.0+ ARM64
SHA-256 checksum
How to use checksums
f6df91ab8c552fefa9eb043bc1f9b016889a8a3629fb1286ef02b647de1e3168
BLAKE2b-256 checksum
How to use checksums
815450205c6e4ca5cbe13be53630210f977c06c542c4fb319c4a9c6eb6edabe0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.13

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 30, 2026.

Transparency log

Release files / urun_cli-0.7.5-py3-none-any.whl

Download URL urun_cli-0.7.5-py3-none-any.whl
Size 329.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
9957dbb2762be5e6f666229e57e7dadafc0f86afc3a0fa6e5537b20b5fe886b7
BLAKE2b-256 checksum
How to use checksums
ba0d773a8551bcf2216aa550245e8777fe1104e9acb1e848e151641fb94327ae
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.13

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 30, 2026.

Transparency log

Security for urun-cli 0.7.5

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 5 files in this release have been uploaded with provenance metadata.

Select from 5 files

Attestation for urun_cli-0.7.5.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 30, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.7.5 This release

5 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page