# Deploy from CI

> Generate a GitHub Actions workflow with helix ci init that deploys your project on merge or on demand, authenticated with a Tray API user token.

`helix ci init` generates the CI pipeline that deploys your project, so a merge or a button press ships it instead of a laptop. It asks which CI service you use (GitHub Actions is the only option today) and how deploys should be triggered, writes the workflow file, and prints the one-time setup to do on GitHub. The workflow itself runs `helix deploy --wait`, so the job fails with the build errors when a deployment fails.

## Generate the workflow

Run it from the project root, in a terminal:

```bash
helix ci init
```

You answer two questions. The first is the CI service. The second is how deploys should be triggered:

| Strategy | Files written | When it deploys |
|---|---|---|
| Manual production deploys (default) | `deploy-production.yml` | On demand only, from the Actions tab or `gh workflow run deploy-production.yml` |
| Deploy production on merge | `deploy-production.yml` | On every push to your default branch, and on demand |
| Staging on merge, production manually | `deploy-staging.yml`, `deploy-production.yml` | Staging on every push to your default branch; production on demand, behind an approval gate |

The files land in `.github/workflows/`. The command asks before overwriting one that already exists.

Without a terminal, for example when an agent runs it, pass the answers as flags:

```bash
helix ci init --provider github --strategy manual-production
```

The strategy ids are `manual-production`, `production-on-merge`, and `staging-on-merge`. Add `--force` to overwrite existing files without asking.

## What the command checks first

The command reads your project before it writes anything, and stops with the fix when a target isn't ready:

- **A config file per target.** The production target uses `helix.production.config.ts` when it exists, otherwise `helix.config.ts`. The staging strategy needs both `helix.staging.config.ts` and `helix.production.config.ts`, so a merge never deploys your development target by accident. See [one config file per target](/documentation/concepts/configuration/#one-config-file-per-target).
- **A provisioned project.** Every target the workflow deploys must already have a `projectId` and `workspaceId` in its config file. A first deploy writes the project ID it provisions back into the file, and a CI runner throws that write away, so an unprovisioned target would create a new project on every run. Deploy the target once from your machine, or set the ids with `helix project set` and `helix workspace set`, then run `helix ci init` again.
- **A lockfile.** `helix deploy` needs `package-lock.json` or `pnpm-lock.yaml`; the workflow installs with the matching package manager.

The workflow pins `@trayai/helix-cli` to your project's `@trayai/helix-sdk` version. The two are released together, and a CLI ahead of the SDK breaks deploys, so bump both at once.

## Create the token

The workflow authenticates with a secret named `TRAY_CLI_TOKEN`: a Tray API user token for the workspace the target deploys to. Create one in Tray:

1. Go to **Account Settings**, then **Tokens**, then **Create API user**. Organization admins can do this for any workspace; workspace admins can when an organization admin has allowed it.
2. Name the API user, pick the workspace your target deploys to, and give it a workspace role.
3. Name the token and set its expiry. Copy it when it's shown: it isn't shown again, and it's deleted when it expires.

Tokens are scoped to one workspace. When staging and production are different workspaces, create one API user and token for each.

:::warning{title="An API user token reaches the whole workspace"}
The token isn't limited to Helix. It acts as its API user across everything in that workspace, including shared Tray iPaaS assets such as workflows and authentications. Treat it as a production credential: give the API user the lowest workspace role that can still deploy, keep the token only in your CI secret store, set an expiry, and delete the API user if the token is ever exposed.
:::

Check a token before saving it on GitHub:

```bash
TRAY_CLI_TOKEN=<token> helix whoami
```

When `TRAY_CLI_TOKEN` is set, every `helix` command uses it instead of a saved `helix login` session. Nothing is written to disk and the token is never refreshed. A rejected token is reported as such, rather than as an expired login.

:::note{title="Don't reuse your own session"}
The token `helix login` saves in `~/.tray.config` belongs to your account and expires with your session. Use an API user for CI so deploys keep working when you're away, and so the deploy is attributed to CI rather than to you.
:::

## Set up GitHub

For the single-workflow strategies, add `TRAY_CLI_TOKEN` as a repository secret (Settings, Secrets and variables, Actions). The recommended upgrade is a GitHub environment named `Production` with required reviewers, with the secret moved onto it and `environment: Production` added to the job, so every deploy waits for approval.

The staging strategy sets that up from the start: each job runs on its own environment, `Staging` or `Production`, and reads `TRAY_CLI_TOKEN` from that environment's secrets. Add required reviewers on `Production` and a production deploy waits for one of them.

The exact steps are printed when `helix ci init` finishes, and repeated in a comment at the top of each generated file.

## What the workflow does

Each generated workflow, in order:

1. Checks out the repository.
2. Sets up Node.js 24 and installs your dependencies from the lockfile.
3. Installs `@trayai/helix-cli` at the pinned version.
4. Runs `helix deploy --wait` (with `--config <target file>` for a named target), with `TRAY_CLI_TOKEN` supplied from the secret.

`--wait` keeps polling after the upload until the platform finishes rolling out. The job passes once the deployment is ready, and fails with the build errors printed in the log when it isn't. A deployment still rolling out after 10 minutes fails the job with a "still deploying" message; check it later with `helix deployment get <id>`.

One deploy runs at a time per target, and a running deploy is never cancelled by a newer one. Each job has a 20 minute limit and read-only repository permissions.

Add your own steps, such as tests, before the deploy step. The generated file marks where.

## Use another CI service

The generator writes GitHub Actions workflows today. On any other service, the same two pieces make a deploy job:

```bash
npm install -g @trayai/helix-cli@<your @trayai/helix-sdk version>
helix deploy --config helix.production.config.ts --wait
```

Supply `TRAY_CLI_TOKEN` from your service's secret store as an environment variable on that step. Everything in [Create the token](#create-the-token) applies unchanged.

---

Canonical: https://helix.tray.ai/documentation/guides/continuous-deployment/
Any link on this page is available as markdown by appending .md to its URL.
Full corpus: https://helix.tray.ai/documentation/llms-full.txt