---
title: ggui deploy
description: Idempotent one-command provisioning — create a hosted ggui app, mint its connector key, upload blueprints and config, and wire the endpoint into .env.local.
---

`ggui deploy` provisions and wires a hosted ggui app for the current project in one pass: sign in, create the cloud app, mint a connector key, upload your local blueprint pool and app config, and write the endpoint into `.env.local`. It is the command form of everything the [dev-loop crossover](/build/dev-loop/) does by hand.

The command is **idempotent**. Each step is skipped when its gate is already satisfied, so re-running after a partial failure resumes from the first unsatisfied step instead of duplicating work.

## Quick start

From a directory containing (or under) a `ggui.json`:

```bash
ggui deploy
```

On completion it prints the app's MCP endpoint and where the bearer landed:

```text
  ggui deploy complete.

  App MCP endpoint:  https://mcp.ggui.ai/apps/<appId>
  Bearer key:        in .env.local (GGUI_MCP_BEARER)
```

Your agent picks up the hosted endpoint on its next start — `.env.local` now carries both `GGUI_MCP_URL` and `GGUI_MCP_BEARER`, the same two variables the [local ↔ hosted swap](/build/dev-loop/) is built on.

Without a `ggui.json` anywhere up the directory tree, the command exits 1 before doing anything — it needs the manifest both to persist the `appId` and to know what to push.

## The steps

Steps run in this order. The first three are gated on current state; the rest always run:

| Step          | Runs when                               | What it does                                                                                                                                                                                                                                                      |
| ------------- | --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `login`       | no valid session in `~/.ggui/auth.json` | Device-flow sign-in to `api.ggui.ai` — same flow as [`ggui login`](/cli/login/).                                                                                                                                                                                  |
| `create-app`  | `ggui.json` has no `appId`              | `POST /v1/apps`, then persists the new `appId` into `ggui.json`. If the app was created but `ggui.json` could not be written, the command stops loudly with the id to add by hand — re-running without it would create a duplicate app.                           |
| `mint-key`    | `.env.local` has no `GGUI_MCP_BEARER`   | Creates a connector key (named `ggui deploy`) and writes its one-time-reveal secret into `.env.local`. The secret is shown once; only the prefix is retrievable later on [the connector keys page](/hosted/connector-keys/).                                      |
| `push`        | always                                  | Compiles the local blueprint pool and uploads it to the app — the same operation as `ggui push --app <appId>`. Artifact ids are deterministic (`contractHash-variantKey`), so re-pushing the same pool is a no-op server-side.                                    |
| `push-config` | always                                  | Pushes the app config declared in `ggui.json`: the gadget catalog, `app.publicEnv` (keys must match `GGUI_PUBLIC_APP_<NAME>`), and the `generation` block (`model`, `keySource`). Loopback bundle URLs are rejected — a hosted app cannot fetch from `127.0.0.1`. |
| `push-keys`   | only with `--push-keys`                 | Pushes your LLM provider API key to the app — see [Provider keys](#provider-keys-push-keys) below.                                                                                                                                                                |
| `wire-env`    | always                                  | Writes `GGUI_MCP_URL` into `.env.local` — unless one is already set, which is **never clobbered**.                                                                                                                                                                |

`.env.local` lives next to `ggui.json` (not necessarily the invocation directory). Both files are edited in place; other keys and comments are preserved.

## The endpoint it writes

The per-app hosted endpoint is the **bare** per-app URL:

```text
https://mcp.ggui.ai/apps/<appId>
```

There is no `/mcp` suffix — that suffix is the local [`ggui serve`](/cli/serve/) convention, and appending it to a hosted app URL produces a 404. Writing the correct shape automatically is the practical reason to prefer `ggui deploy` over hand-editing `.env.local`; the trap is documented in [the dev loop](/build/dev-loop/).

Two details of `wire-env`:

- **First deploy** — the URL comes verbatim from the `connectUrl` the backend returned at `create-app`, so it is correct for whichever environment `GGUI_API_URL` pointed at.
- **Re-deploys** — an existing `GGUI_MCP_URL` is left untouched, whatever it points at. Delete the line and re-run if you want it rewritten.

## Provider keys (`--push-keys`)

Apps with `generation.keySource: "own"` render on **your** model account instead of drawing down ggui credits — but only once the app actually holds your provider key. `--push-keys` adds that step to the deploy:

```bash
ggui deploy --push-keys
```

The provider is derived from `ggui.json#generation.model`, and the key is read from that provider's environment variable:

| Provider     | Environment variable                              |
| ------------ | ------------------------------------------------- |
| `anthropic`  | `ANTHROPIC_API_KEY`                               |
| `openai`     | `OPENAI_API_KEY`                                  |
| `google`     | `GOOGLE_API_KEY` (falls back to `GEMINI_API_KEY`) |
| `openrouter` | `OPENROUTER_API_KEY`                              |

`bedrock` routes are rejected — IAM-only, no plaintext key to push. To name the provider explicitly instead of deriving it from the model string, call the underlying command directly: `ggui provider-key set --app <appId> --provider <name>`.

If `ggui.json` declares `keySource: "own"` and you deploy **without** `--push-keys`, the command still succeeds but ends with a warning: cloud renders will reject until a key is pushed. See [Credits, billing & BYOK](/hosted/billing/) for how the two key sources are charged.

## What it does not do

`ggui deploy` provisions the **ggui side only** — the interface layer. Hosting the agent itself (the process that talks to your users and calls ggui) is a separate concern: run it yourself, or use a hosting platform such as [guuey](/agentic-app-builders/). Nothing in this command deploys, restarts, or configures an agent runtime.

## Flags

```text
ggui deploy [--push-keys]
```

| Flag          | Purpose                                                                                        |
| ------------- | ---------------------------------------------------------------------------------------------- |
| `--push-keys` | Insert the `push-keys` step: push the provider API key from your environment to the cloud app. |

`GGUI_API_URL` (see [CLI configuration](/cli/#configuration)) controls which backend the deploy targets; the default is production `api.ggui.ai`.

## See also

- [`ggui` CLI overview](/cli/) — the full command surface, including standalone `ggui push` and `ggui provider-key`.
- [The dev loop](/build/dev-loop/) — iterate locally, then cross over to hosted with the two-variable swap this command writes.
- [`ggui login`](/cli/login/) — the device-flow sign-in `deploy` runs when needed.
- [Connector keys](/hosted/connector-keys/) — what the minted `ggui_user_*` key is and how to revoke it.
- [Blueprints & the cache](/concepts/blueprints-and-the-cache/) — why uploading a matured local pool makes a fresh hosted app fast on day one.