# Deploying an app

> How a coding agent inspects a project, picks defaults, deploys it to Tarout, and wires its environment variables

# Deploying an app

> **Preflight - check auth first.** Run `tarout whoami --json`. If it does not
> return `success: true`, authenticate with the table in
> [start.md](https://tarout.sa/docs/for-ai/start.md)
> before running anything on this page - every command below fails without a
> credential, and the failure reads like a broken deploy rather than a missing
> login.

Run everything from the project root.

## The one-command flow

```sh
tarout up --json --yes
```

`tarout up` inspects the project, ensures auth, creates the app on the org's
subscribed tier, sets up the source, deploys it, and streams JSON status events.
The final line is the success/error envelope - read `success` and `data.url` (or
`error.code`). No extra orchestration commands are needed.

**A project on GitHub deploys from GitHub.** If the directory has a github.com
remote, `up` binds that repo so every later push redeploys; `tarout deploy` does
the same. A folder with no GitHub remote is uploaded. Never pass
`--source upload` to a project on GitHub unless the user asked for an upload: an
explicit upload switches the bind off, and an uploaded app only changes when
someone reruns the CLI, so it goes stale the moment work is pushed instead of
deployed.

The bind needs a GitHub connection in the org that can read the repo. When there
is none, the deploy still uploads (the user still gets their deploy) and the
final envelope says so: `data.source.pushToDeploy` is `false` and
`data.source.next` holds the command that fixes it. **Run it in the same turn**,
do not just mention it:

```sh
tarout providers github connect --wait --app <app-id>
```

It opens Tarout's GitHub setup in the browser, waits until GitHub can read the
repo, and binds the app. The GitHub step is browser-only; tell the user to
finish it in that tab, the way they finish login or payment. Every later app in
the same org binds with no browser at all.

A GitHub build clones the **pushed** branch, not this folder. When local commits
or edits are not on it, the envelope lists them in `data.warnings`; relay them,
and never report unpushed work as live.

**Neither command will silently replace a Git source with an upload.** Run `up`
on an app that deploys from a connected repo and it stops and points you at
`tarout deploy <app> --wait`. Passing `--source upload` explicitly is how you say
you really did mean to abandon the connection.

## Inspect, decide, deploy

Use inspection to choose the defaults, then deploy with them - never present them
as a question:

| Choice | Default | Options |
|--------|---------|---------|
| Source | The GitHub repo when the folder has a github.com remote, otherwise a current-folder upload | GitHub repo, current-folder upload, configured Git provider |
| App plan | Org's subscribed tier (omit `--plan`) | Free, shared, dedicated |
| Database | Create one only if code indicates database usage | None, Postgres (MySQL is not available yet) |
| Database plan | Subscribed tier (omit `--database-plan`); with no open slot an agent run returns `NEEDS_UPGRADE`, see [database.md](https://tarout.sa/docs/for-ai/database.md) | Free, starter, standard, pro |
| File storage | Create it only if code indicates uploads/object storage | No, yes |

**Database detection cues**

- `prisma/schema.prisma` with `provider = "postgresql"` or `provider = "mysql"`
- Dependencies such as `pg`, `postgres`, `mysql2`, `mariadb`, `@prisma/client`,
  `drizzle-orm`, `sequelize`, `typeorm`, or `knex`
- Environment keys or code strings such as `DATABASE_URL`, `POSTGRES_`, `PGHOST`,
  `MYSQL_`, `postgres://`, or `mysql://`

Always pass `--database postgres` (or `none`) explicitly. Left to detection, a
project with MySQL cues gets `mysql`, and MySQL creation is not available yet;
see [database.md](https://tarout.sa/docs/for-ai/database.md).

**File storage detection cues**

- Dependencies such as `@aws-sdk/client-s3`, `aws-sdk`, `@google-cloud/storage`,
  `firebase-admin`, `uploadthing`, `multer`, `formidable`, `busboy`, or `minio`
- Code strings such as `S3Client`, `PutObjectCommand`, `S3_BUCKET`,
  `STORAGE_BUCKET`, `FIREBASE_STORAGE_BUCKET`, `multipart/form-data`,
  `upload.single`, or `upload.array`

**Git detection**

- A github.com remote (`git remote -v`): deploy from GitHub. Let `up`/`deploy`
  bind it, and if the envelope reports `data.source.pushToDeploy: false`, run
  `data.source.next` in the same turn.
- A GitLab or other Git remote, or no remote: the current folder is uploaded.
  Tarout registers no push webhook for GitLab, so those apps redeploy only when
  `tarout deploy` runs.

After inspection, announce the plan in one short message and **deploy
immediately** - a statement, not a question:

> "Found Postgres usage and file uploads. Deploying acme/site from GitHub to
> Dammam on your subscribed tier with a managed Postgres database and a storage
> bucket, so every push to main redeploys. Tarout will open a browser if it needs
> you to log in, pay, or connect GitHub. (Say 'no storage' any time and I'll
> adjust.)"

## Command mapping

```sh
# Default: GitHub repo when there is one, otherwise this folder; no database
tarout deploy --wait --database none

# With Postgres
tarout deploy --wait --database postgres

# Existing Tarout app that already has a configured Git provider source
tarout deploy <app-id-or-name> --wait --source configured

# Put an app on push-to-deploy from this folder's GitHub repo
tarout providers github connect --wait --app <app-id-or-name>

# Only when the user explicitly asks to upload this folder instead of GitHub
tarout deploy <app-id-or-name> --wait --source upload
```

`tarout deploy` handles the whole flow: authenticates if needed, creates or
reuses the app, saves `.tarout/project.json`, binds the GitHub repo (or packages
and uploads the current directory when there is none), deploys, and streams logs
with `--wait`. On later runs from the same directory it reuses the linked app.

## Existing app or Git source

```sh
tarout deploy <app-id-or-name> --wait
```

If the app already has a GitHub, GitLab, Dockerfile, or Docker Hub source
configured, Tarout deploys that. If it has no source or uses uploaded source, the
CLI binds this folder's GitHub repo when it can, and uploads the current
directory when it cannot. An uploaded app whose folder is on GitHub is a gap to
close: `tarout providers github connect --wait --app <app>`.

To link the current directory first:

```sh
tarout link
tarout deploy --wait
```

## No project yet? Scaffold one

**Write it yourself.** You are a coding agent; a deployable starter is a
`package.json` with a `start` script and a server that listens on
`process.env.PORT`. That is the whole contract - anything more is your choice.
Then:

```sh
tarout deploy --wait --database none
```

There is also a canned starter - a tiny Node.js to-do list with a `/health`
route and no build step:

```sh
curl -fsSL https://tarout.sa/create-sample-project.sh | sh
cd tarout-todo-list
```

If your permission model refuses to pipe a remote script into a shell, that
refusal is correct - do not work around it. Write the starter yourself instead;
it is three files.

## Relaying input requests (`needs_input`)

`tarout up --json` is non-interactive. When it hits a choice that was not passed
as a flag it emits one line and exits with code `6`:

```json
{ "type": "needs_input", "field": "name", "kind": "input", "question": "Application name:", "default": "my-project", "flag": "--name" }
```

You should:

1. Answer it **yourself** whenever the answer is derivable: application name → the
   folder name, create-vs-reuse → `--new-app` (or `--app <id>` when
   `.tarout/project.json` links one), region → omit
   (defaults to Dammam), confirms → `--yes`. Relay to the human only what you
   genuinely cannot know: an API key on a headless machine (`kind: "password"` -
   relay privately) or a payment step. Never answer a source question with
   `--source upload` on your own: on an app that deploys from Git it replaces the
   repo with this folder and ends push-to-deploy.
2. Re-invoke the same command with the same arguments **plus** `<flag> <answer>`.
3. Loop until the CLI exits `0` (success envelope) or any non-`6` code (real error).

Better: pass the flags upfront so no `needs_input` fires at all. The common ones
are `--name`, `--plan`, `--source`, `--repo`, `--branch`, `--region`, `--yes`,
`--billing-period`, `--addon`.

These events fire from **every** interactive prompt site in the CLI, not just
`up`/`deploy` - `billing upgrade`, `billing cancel`, `billing addon:*` and the
rest use the same shape. A prompt that has not yet been given an agent-friendly
flag emits `field: "unannotated_prompt"` with `flag: "--yes"`, so you never hang
on a closed TTY; surface the `question` verbatim, and retry with `--yes` if the
user confirms.

Add `--non-interactive` to any invocation that runs outside `--json` but must
still fail hard on missing input - the CLI then never falls back to a TTY prompt.

## Environment variables

Setting the variables the app needs to run - generated auth secrets, seed values,
`DATABASE_URL` wiring - is part of the deploy: do it and say so, don't ask. Ask
only before **revealing** existing secret values, since those may be the user's
credentials.

```sh
tarout env <app> list                      # masked
tarout env <app> reveal KEY                # asks before printing the secret
tarout env <app> set KEY=VALUE             # secret by default; --no-secret to opt out
tarout env <app> unset KEY
tarout env <app> pull --output .env        # asks before revealing
tarout env <app> push --input .env.production [--replace]
```

Redeploy after changing runtime variables:

```sh
tarout deploy <app> --wait
```

There is no environment layer - variables are per application.

## Run locally with cloud env vars

When the project is linked (or `--app` is passed), run dev or build commands
against the linked app's environment without copying secrets locally:

```sh
tarout dev
tarout dev --port 4000 --command "bun run start"
tarout build
tarout build --app <app>
```

These do not redeploy. They run the user's local scripts with the cloud env
injected into the process.

## Regions

| Region ID | Location |
|-----------|----------|
| `me-central2` | Dammam, Saudi Arabia |

If the user asks for another region or passes `--region`, explain that moving
workloads outside the Saudi region may affect data residency and compliance. The
CLI prompts before continuing when a non-default region is requested.

## Reporting success

Tell the user what happened in one compact block, filled in from the final JSON
envelope:

```text
┌─ Tarout Deploy Complete ─────────────────────────────┐
│  ✓ App       <name>  →  <data.url>                   │
│  ✓ Source    <owner/repo (branch), pushes deploy>    │
│  ✓ Database  <postgres/none>                         │
│  ✓ Storage   <bucket/none>                           │
│  ✓ Region    me-central2 (Dammam)                    │
│                                                      │
│  Dashboard: https://tarout.sa/dashboard              │
└──────────────────────────────────────────────────────┘
```

Then offer the obvious next actions: set environment variables, attach a
[custom domain](https://tarout.sa/docs/for-ai/domains.md), or run `tarout dev` locally.

## Common commands

```sh
tarout apps list
tarout apps create [name]
tarout apps info <app>
tarout apps open <app>
tarout apps logs <app>
tarout apps restart <app>
tarout apps stop <app>

tarout deploy --wait
tarout deploy:status <app>
tarout deploy:list <app> [-n <count>]
tarout deploy:logs <deployment-id> --follow
tarout deploy:rollback <app> --previous --wait     # ask first
tarout deploy:cancel <app>                         # ask first

tarout link [app]
tarout status
tarout unlink
```

The free plan includes one application slot, one managed database, and one
storage bucket. Apps are served from `*.tarout.app` with HTTPS.

---

## Every Tarout agent guide

- [Start Here (Agents)](https://tarout.sa/docs/for-ai/start.md)
- [Overview](https://tarout.sa/docs/for-ai.md)
- [Databases](https://tarout.sa/docs/for-ai/database.md)
- [Object storage](https://tarout.sa/docs/for-ai/storage.md)
- [Custom domains](https://tarout.sa/docs/for-ai/domains.md)
- [Plans and upgrades](https://tarout.sa/docs/for-ai/billing.md)
- [Troubleshooting](https://tarout.sa/docs/for-ai/troubleshoot.md)
- [Agent Onboarding](https://tarout.sa/docs/for-ai/onboarding.md)
- [CLI Reference](https://tarout.sa/docs/for-ai/cli-reference.md)
- [CLI JSON Schema](https://tarout.sa/docs/for-ai/cli-json-schema.md)

Whole corpus in one file: https://tarout.sa/llms-full.txt · index: https://tarout.sa/llms.txt
Any docs page is raw markdown at the same URL + `.md`. Short link to the entry point: https://tarout.sa/deploy.md
