Skip to main content

End-user CLI for deploying apps to urun

Project description

urun CLI

Deploy Python apps to urun from your terminal.

PyPI Python

Install

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

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.

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 list apps

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.

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 list sessions
urun list sessions | tail -20
urun list sessions --state failed
urun list sessions --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 list compute

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 list apps; for historical or in-flight sessions use urun list sessions.

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 list apps); every operation is org-scoped via your API key.

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

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.

Retire 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 delete lingbot-handle      # prompts for confirmation
urun app delete lingbot-handle --yes  # or urun app rm lingbot-handle --yes

Reverse a retire and bring the app back online:

urun app activate lingbot-handle

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.

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

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.

Non-Python assets such as templates, static files, and data files are not auto-included yet.

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 not auto-included yet.

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.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

urun_cli-0.5.3.tar.gz (68.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

urun_cli-0.5.3-py3-none-any.whl (70.2 kB view details)

Uploaded Python 3

File details

Details for the file urun_cli-0.5.3.tar.gz.

File metadata

  • Download URL: urun_cli-0.5.3.tar.gz
  • Upload date:
  • Size: 68.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for urun_cli-0.5.3.tar.gz
Algorithm Hash digest
SHA256 8baa6b3403d2919a149ddcff7c25089414fdf4158deee1117fc5a7a4e215a6eb
MD5 d13d153a672d33274304236b0460ee96
BLAKE2b-256 55ccad5f32b5204503205c080766569cefd8c1183a58d5f49667fe7ad23f154c

See more details on using hashes here.

Provenance

The following attestation bundles were made for urun_cli-0.5.3.tar.gz:

Publisher: release.yaml on urun-sh/urun-cli

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file urun_cli-0.5.3-py3-none-any.whl.

File metadata

  • Download URL: urun_cli-0.5.3-py3-none-any.whl
  • Upload date:
  • Size: 70.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for urun_cli-0.5.3-py3-none-any.whl
Algorithm Hash digest
SHA256 09a9ffdba08b31dcecb17833414cecf0337a063bb45ce508b4ed6c6968601da7
MD5 a5c3a88b559d86cfef315f1066b928ac
BLAKE2b-256 c8b18d4dc725a4b8614b2430b5cb8a4ff874658eb24240700df06b1f5507dc16

See more details on using hashes here.

Provenance

The following attestation bundles were made for urun_cli-0.5.3-py3-none-any.whl:

Publisher: release.yaml on urun-sh/urun-cli

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page