---
title: Quickstart | Runware Docs
url: https://runware.ai/docs/serverless/quickstart
description: Deploy a Python app to a Runware GPU and invoke it, from one install command to a running endpoint.
relatedDocuments:
  - https://runware.ai/docs/serverless/writing-a-model
  - https://runware.ai/docs/serverless/examples
  - https://runware.ai/docs/serverless/deploying
  - https://runware.ai/docs/serverless/invoking
---
## Introduction

One file, one command, one call. This deploys a Python app, waits for it to build, and invokes it.

## Before you start

You need a Runware API key and **one command**. It installs the CLI, creates the directory your app lives in, and puts the authoring package in that directory's own environment.

**macOS / Linux**:

```bash
curl -fsSL https://serverless.runware.ai/install.sh | sh -s -- my-greeter
```

**Windows**:

```powershell
$env:RUNWARE_APP_DIR = "my-greeter"
irm https://serverless.runware.ai/install.ps1 | iex
```

**CLI already installed**:

```bash
runware serverless init my-greeter
```

The argument names the directory, and **the directory names the app**: it becomes the id you deploy with, so it has to fit that shape, which is 6 to 30 characters of lowercase letters, digits and hyphens, starting with a letter and ending with a letter or a digit.

`init` is the half of that command that prepares the directory, and it writes only what is missing.

<table><tbody><tr><td>What it writes</td><td>What it is</td></tr><tr><td><code>pyproject.toml</code></td><td>A minimal project named after the directory, requiring Python 3.10 or newer, with no runtime dependencies</td></tr><tr><td><code>app.py</code></td><td>A starter app with one endpoint, written only when the directory holds no Python files of its own</td></tr><tr><td><code>.venv</code></td><td>The directory's own environment, with the authoring package in it as a dev dependency</td></tr></tbody></table>

**Nothing is installed globally.** `uv` does the work where it is on your `PATH`, and otherwise the first Python 3.10 or newer that `init` finds creates the `.venv` and installs into that. Running `init` again on a directory that already has a `pyproject.toml` fills in what is missing and leaves everything else alone.

> [!NOTE]
> The package is **what you write against rather than what the app runs on**: it resolves the import, checks your handler signatures and lets your own tests call them. It declares no dependencies, and **a deploy supplies its own copy either way**.

Then authenticate, from inside the new directory.

```bash
cd my-greeter
runware auth login
```

One login binds the CLI to your organization, and **everything after that is implicit**. Your API key already names your organization, so every command after the login carries it for you.

## Write the app

`init` leaves an `app.py` holding a starter app with one endpoint, which is there to be replaced. This one has more to say about what a signature decides.

```python
# app.py
from runware_serverless import endpoint, serve

@serve
class Greeter:
    def load(self) -> None:
        self.greeting = "hello"

    @endpoint
    def greet(self, name: str, excited: bool = False) -> dict:
        excited = False if excited is None else excited
        suffix = "!" if excited else "."
        return {"message": f"{self.greeting}, {name}{suffix}"}
```

`load` runs **once per worker** before any request arrives, which is where a real model would be loaded. `@endpoint` makes `greet` callable at the path `greet`. **The schema comes from the signature**, so `name` is required and `excited` is optional.

> [!NOTE]
> An omitted optional field arrives as **an explicit `null`**, which is why `excited` applies its own default inside the handler rather than relying on the Python one.

## Deploy

```bash
runware serverless deploy app.py --id my-greeter --gpu-type l40s --wait
```

A deploy zips and submits **the whole directory**, so your app can import its own modules and read its own data files. The `.venv` is not part of that, and neither are `.env` files or the usual caches.

1. **Create**

   A first deploy with a new `--id` **creates the app**. Later deploys with the same id upload a new source and record the next version.

2. **Build**

   The platform builds an image and **reads your endpoints out of the source**. The app stays `initializing` until that first build rolls out.

3. **Roll**

   `--wait` polls until the app is `active` or `failed`, so the command finishes when the deploy does rather than when the upload does.

> [!WARNING]
> **A successful wait is not a running worker.** With `minWorkers` at its default of `0` the app sits scaled to zero until the first invocation arrives, which is also when you first pay for a GPU.

## Invoke it

```bash
runware serverless apps invoke my-greeter greet --sync -f payload.json
```

Or over HTTP, which is the same thing without the CLI:

```bash
curl -X POST https://api.serverless.runware.ai/v1/apps/my-greeter/invoke-sync/greet \
  -H "Authorization: Bearer $RUNWARE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "taskId": "8f14e45f-ceea-467a-9a2f-6d2b1f2c0b3e",
    "payload": { "name": "world", "excited": true }
  }'
```

Your fields go inside `payload`. **The `taskId` is yours to generate**, and sending the same one twice returns the task it already names rather than running the work again.

```json
{
  "id": "8f14e45f-ceea-467a-9a2f-6d2b1f2c0b3e",
  "appId": "my-greeter",
  "status": "completed",
  "endpointPath": "greet",
  "output": { "message": "hello, world!" },
  "error": null
}
```

The first call pays the cold start. **Subsequent calls reach a warm worker** until the idle window closes.

## What to change next

Worker settings are **create-time flags**. Passing `--gpu-type` or `--max-workers` to a deploy on an app that already exists is an error, because those live on the app itself.

```bash
runware serverless apps scale my-greeter --max-workers 4
```

Two more things worth setting early.

**Weights belong on a volume.** The app runs in a sandbox whose filesystem is part of the checkpointed state, so anything downloaded at runtime and left unmounted is **copied into every checkpoint and fetched again on every cold start**.

```bash
runware serverless deploy app.py --id my-greeter --gpu-type l40s \
  --volume /root/.cache/huggingface
```

**Secrets belong in a file, not a flag.** A value passed with `--env` is visible in the process list and recorded in your shell history. Use `--env-file` for anything that matters.

> [!WARNING]
> An app's environment belongs to a version, so **setting a variable afterwards records a new version and rolls the workload** to pick it up. That is a redeploy of the same image, and a write that does not change the stored value records nothing at all.

## Where to go next

- [Writing a model](https://runware.ai/docs/serverless/writing-a-model) for the full authoring surface
- [Examples](https://runware.ai/docs/serverless/examples) for whole apps you can deploy as they are
- [Compute and scaling](https://runware.ai/docs/serverless/compute-and-scaling) for the cost and latency trade
- [Invoking an app](https://runware.ai/docs/serverless/invoking) for async, polling and retries
- [Bringing a container](https://runware.ai/docs/serverless/bringing-a-container) if you would rather ship your own image