# Pack & Deploy

Pack a solution into a deployable package, publish to the feed, and deploy to Orchestrator with optional configuration.

> For full option details on any command, use `--help` (e.g., `uip solution deploy run --help`).

## When to Use

- Deploying automations to staging or production environments
- CI/CD pipelines that pack, publish, and deploy on every merge
- Multi-environment promotion (dev -> staging -> production)

## Prerequisites

- Authenticated — verify with `uip login status`; if not logged in, ask the user to run `uip login` (it opens an interactive browser flow)
- Solution developed and ready to pack (see [develop-solution.md](develop-solution.md))
- Solution state verified — `.uipx` and `resources/solution_folder/` agree on the project set (see [develop-solution.md - Always verify state after every mutation](develop-solution.md#always-verify-state-after-every-mutation))

## Flow

```mermaid
graph LR
    R["(optional)<br/>solution restore"] --> A[solution pack]
    A --> B[solution publish]
    B --> C[deploy config get]
    C --> D[config set / link]
    D --> E["deploy run<br/>(auto-activate by default)"]
    B --> E
    E --> F[deploy status]
    E --> G[deploy list]
    E -->|--skip-activate| H[deploy activate]
```

---

## Step 0 (Optional): Restore Dependencies

Resolve NuGet dependencies for every project in the solution, in place, using the authenticated session:

```bash
uip solution restore ./MySolution --output json
```

| Option | Description | Default |
|--------|-------------|---------|
| `<solutionPath>` | Solution directory (containing a `.uipx`) or a `.uis` file (required) | -- |
| `--login-validity <minutes>` | Minimum minutes left on the access token before the CLI refreshes it before restore starts | 10 |
| `--nuget-sources-config-path <path>` | Local `NuGet.config` that sets the package sources used for resolution | -- |

`restore` resolves dependencies on disk and does **not** produce a package. It needs an authenticated session (`uip login`) to reach private Orchestrator feeds. `pack` already restores internally, so this step is an **optimization, not a requirement** — its value is in CI, where running `login → restore → pack` fails fast on a missing or unreachable feed before the heavier pack step runs. Skip it for a plain local pack.

## Step 1: Pack the Solution

Build a deployable .zip from a solution directory:

```bash
uip solution pack ./MySolution ./output --output json

# With explicit name and version
uip solution pack ./MySolution ./output --name "MySolution" --version "2.0.0" --output json
```

| Option | Description | Default |
|--------|-------------|---------|
| `<solutionPath>` | Directory containing a `.uipx` or `.uis` file (required) | -- |
| `<output-path>` | Directory where the .zip will be written (required unless `--dry-run`) | -- |
| `--name <name>` | Override the package name | Name from `.uipx` |
| `--version <version>` | Set the package version | `1.0.0` |
| `--nuget-sources-config-path <path>` | Local `NuGet.config` that sets the package sources used for resolution | -- |
| `--author <author>` | Set the package author | `UiPath` |
| `--description <text>` | Set the package description | `Created by UiPath` |
| `--dry-run` | Run the full pack pipeline against a temporary directory and discard the output — proves the solution packs without producing a package | off |

The output is a `.zip` file named `<name>_<version>.zip` — **underscore between name and version, not a dot** — written under `<output-path>/` (e.g., `MySolution_2.0.0.zip`). Don't guess the filename: read it from the command's `Data.Packages` field, or list `<output-path>/`. Run `solution resources refresh` first (from inside the solution dir, or with `--solution-folder <path>`) to ensure the solution's artefact files and debug overwrites are up to date — they're bundled into the package.

**Controlling package sources.** In offline or air-gapped environments, or when packages must be resolved from specific feeds, pass `--nuget-sources-config-path <path>` pointing at a local `NuGet.config`. Both `pack` and `restore` accept the flag; the sources declared in that file determine where each project's dependencies are resolved from, giving you precise control over feed selection.

## Step 2: Publish to the Solution Feed

Upload the packed .zip so it appears in the UiPath solution feed:

```bash
uip solution publish ./output/MySolution_2.0.0.zip --output json

# Target a specific tenant
uip solution publish ./output/MySolution_2.0.0.zip --tenant "Production" --output json
```

After publishing, the package is visible via `uip solution packages list` and available for deployment.

### Publishing to a non-tenant feed

By default everything targets the **tenant** feed. A package can instead go to your
Personal Workspace or to a specific **folder feed**. Start by listing what you can
actually target — a raw folder key is not necessarily a publishable feed:

```bash
uip solution feeds list --output json
```

`Data` is a list of `{ Name, Key, Type, IsMyPersonalWorkspace }` where `Type` is
`Tenant`, `PersonalWorkspace`, or `Folder`. Only feeds in this list can be
targeted; passing anything else fails with *"is not an available publish
location"*. Personal Workspaces belonging to other users are filtered out even
when you can browse them.

Then target a feed with `--feed <name-or-key>` (accepts either the feed's name or
its folder key) or `--personal-workspace`:

```bash
# Publish into a folder feed
uip solution publish ./output/MySolution_2.0.0.zip --feed "Finance" --output json

# Publish into your own Personal Workspace
uip solution publish ./output/MySolution_2.0.0.zip --personal-workspace --output json
```

The same two flags scope the rest of the lifecycle, and they are **mutually
exclusive** everywhere:

| Command | What the flag scopes |
|---|---|
| `uip solution publish --feed / --personal-workspace` | Which feed the package is uploaded to |
| `uip solution deploy run --feed / --personal-workspace` | Which feed the package is deployed **from** |
| `uip solution deploy list --feed / --personal-workspace` | Which feed's deployments are listed |
| `uip solution packages list --feed / --personal-workspace` | Which feed's packages are listed |
| `uip solution packages delete --feed / --personal-workspace` | Which feed the version is deleted from |

**Deploy from the feed you published to.** A package published to a folder feed or
Personal Workspace is not in the tenant feed, so a plain `deploy run` cannot see
it — pass the same `--feed` / `--personal-workspace` there too. Likewise, omitting
the flag on `packages list` shows only tenant packages, which is the usual reason
a freshly published feed package looks missing.

`deploy run --feed` also accepts `--parent-folder-path` / `--parent-folder-key` to
pick **where inside** the feed's folder the deployment lands; without them it goes
to the feed folder itself.

## Step 3 (Alternative): Upload to Studio Web

If the goal is browser-based editing rather than deployment, use `upload` instead of `publish`:

```bash
uip solution upload ./MySolution --output json
```

This uploads to Studio Web for collaborative editing. It does **not** place the package on the solution feed and cannot be used with `deploy run`. If the `SolutionId` in `.uipx` already exists in Studio Web, `upload` overwrites that solution in place; otherwise it imports as new — see [develop-solution.md § `upload` decides import or overwrite from what the cloud holds](develop-solution.md#upload-decides-import-or-overwrite-from-what-the-cloud-holds) for the decision rules and snapshot recording.

`upload` always lands the solution in Studio Web's **Cloud workspace** tab, not the Local tab. SW's Local tab is a separate registration for solutions whose source of truth is a tracked local folder — populated by SW-initiated flows (creating a solution from the SW UI, or downloading a cloud solution to local) or by Studio Desktop signing into the same tenant. `uip solution upload` does not address the Local tab. Authoring with `uip solution init` then `upload` produces a Cloud-tab solution; the local folder on disk has no live link to either tab afterward — edits in one place do not propagate to the other without a re-upload (Cloud) or a download (Local).

## Step 4: Deploy to Orchestrator

Deploy the published package. By default this creates a new Orchestrator folder, provisions all solution resources, **and activates the deployment** in one call:

```bash
uip solution deploy run -n "InvoiceAutomation-v2" \
  --package-name "MySolution" --package-version "2.0.0" \
  --folder-name "MySolutionFolder" --output json
```

A successful run returns `Status: DeploymentSucceeded` and `ActivationStatus: SuccessfulActivate`, along with `DeploymentKey`, `PipelineDeploymentId` and `InstanceId`. When a resource needs "Setup activation" in Orchestrator before the deployment can be activated — an Integration Service connection that must be authenticated, typically — the run is still `Result: Success`: the install landed, `ActivationStatus: NeedsSetupToActivate`, `PendingResources` names each resource with its `Reason` (`[CNS1072] You will need to authenticate this connection before proceeding further`) and its Orchestrator folders (`FolderPaths`, `null` when the server does not report them), and `NextSteps` gives the Orchestrator step. When the server already reports that state after the install, `deploy run` does not attempt the activation. Do **not** run the deploy again: the deployment exists. On such a run the pipeline itself can end at `ValidationFailed` **after** the install landed (the deployment record reports `SuccessfulInstall`); the CLI still reports the deployment, keeps the pipeline's word in `Status`, and opens `NextSteps` with the pipeline's errors, which come from the activation validation the pipeline runs after the install. `ReadyToActivate` means the install landed and the activation did not run: `uip solution deploy activate <name>`. Any other refused activation is a failure with `Retry activation with: uip solution deploy activate <name>`.

This holds on **every** feed. A `--feed` or `--personal-workspace` deploy goes through the same Pipelines install as a tenant one, so it validates the package against the target before installing and waits for the run to reach a terminal state — it does not return early. A configuration the target rejects fails the command (exit `1`, `ErrorCode: invalid_argument`, `ValidationFailed` in `Message`) with the per-resource errors in `Instructions`, and nothing is provisioned — that is a `ValidationFailed` before the install; the one whose record reports `SuccessfulInstall` is the landed case above, and a success. (`--wait` is still accepted and does nothing; older CLI versions needed it on those feeds.)

The rejected attempt stays behind as a **pending draft** under that `--name`, and its key is in `Instructions` — see [A deploy that stops in `Draft`](#a-deploy-that-stops-in-draft) for how to finish or drop it.

To skip auto-activation (legacy behaviour — leaves the deployment in `Inactive (Ready to activate)`):

```bash
uip solution deploy run … --skip-activate --output json
```

Key options:

| Option | Description | Default |
|--------|-------------|---------|
| `-n, --name <name>` | Deployment name (required) | -- |
| `--package-name <name>` | Published solution package name (required) | -- |
| `--package-version <version>` | Package version to deploy (required) | -- |
| `--folder-name <name>` | New Orchestrator folder to create (required). Always creates; a taken name is collision-renamed, never reused — see [`deploy run` Always Creates a New Folder](#deploy-run-always-creates-a-new-folder) | -- |
| `--parent-folder-path <path>` | Parent folder under which the new folder is created | -- |
| `--parent-folder-key <key>` | Parent folder key (GUID, alternative to `--parent-folder-path`) | -- |
| `--config-file <path>` | Configuration file from `deploy config get` | -- |
| `--skip-activate` | Skip the post-deploy activation; leaves the deployment in `Inactive (Ready to activate)` | (off — auto-activate) |
| `--acknowledge-permanent-deletion` | Confirm that installed resources this deploy replaces with a link are deleted with their data; newer CLI builds only — see [Replacing an installed resource with a link](#replacing-an-installed-resource-with-a-link) | (off — the deploy stops and lists them) |
| `--timeout <seconds>` | Polling timeout, applied per phase (deploy and, when not skipped, activate) | 360 |
| `--poll-interval <ms>` | Polling interval used during both phases | 5000 |
| `--login-validity <minutes>` | Minimum minutes left on the access token before the CLI proactively refreshes it before the deploy starts. Useful for long deploys close to token expiry. | 10 |
| `-t, --tenant <name>` | Tenant override | Current tenant |

### A deploy that stops in `Draft`

`Draft` is where the server parks a deployment whose install never completed — nearly always because deploy-time validation rejected something (a virtual resource with no `value`, a resource conflict, a folder that no longer exists). Nothing advances it on its own, and it **cannot be activated**: `deploy activate` on it fails.

One narrow exception: a deployment that was *just* requested sits in `Draft` for a moment before the pipeline promotes it to `InProgress`, so a `Draft` read seconds after firing the command says nothing yet. What is never normal is a `Draft` that is still there after the command returned, or after a wait has ended.

`deploy run` waits for its run on every feed — tenant, `--personal-workspace` and `--feed` alike — so the failure itself carries the server's own reasons. `Message` is only `Deployment failed with status: …`; the detail is in **`Instructions`**, as `Validation: …`, `Conflicts: …`, `Schedule: …`, `Deployment: …` — read that field, it names the resource and the property — together with the draft's key. To look at the deployment afterwards, run `uip solution deploy list` **carrying the same feed flag** — `deploy list` is tenant-scoped when none is passed and does not show a Personal Workspace or folder-feed deployment at all, so a missing row there is not an answer:

```bash
uip solution deploy list --personal-workspace --output json
uip solution deploy list --feed "<name-or-key>" --output json
```

The fix is the deploy config, not a retry — rerunning the same command with the same config reproduces the same `Draft`. (One exception, a refusal that asks for consent rather than a fix: [Replacing an installed resource with a link](#replacing-an-installed-resource-with-a-link).)

```bash
uip solution deploy config get "MySolution" -d config.json --output json

# fix what the validation named — set the missing value, or link to an existing resource
uip solution deploy config set config.json MyQueue maxNumberOfRetries 5
uip solution deploy config link config.json MyBucket --name "ExistingBucket" --folder-path "Shared"

uip solution deploy run -n "MyDeployment" \
  --package-name "MySolution" --package-version "2.0.0" \
  --folder-name "MySolutionFolder" --config-file config.json --output json
```

Rerun it with the **same** `--name` and `--package-version` as the attempt that failed — the server re-validates the draft with the new file. That holds for a draft left by an install and by an update of a live deployment alike, and the CLI's "already deployed" guard deliberately lets it through, because rerunning is the fix. Settings and resource links the file does not name keep the values the draft was rejected with, so name every one you mean to change.

While the draft exists it owns the deployment, as in the Orchestrator UI, which offers only "continue editing" and "delete draft". `deploy run` refuses any *other* version for that name before sending anything (`ErrorCode: invalid_argument`, naming the draft) — including the version already live, which the server would otherwise answer by installing the draft's version instead — and `deploy upgrade` refuses the deployment outright. To drop the attempt instead, discard the draft first: in Orchestrator (Delete draft), with `uip solution deploy delete <deployment-name> --yes` (see [Then Delete the Deployment Record](activate-and-manage.md#then-delete-the-deployment-record)), or, for an update of an existing deployment, `uip solution deploy upgrade <draft-key> --discard-draft --version <live-version>` (see [Upgrade a Deployment In Place](activate-and-manage.md#upgrade-a-deployment-in-place)). Under an external app that `deploy run` check only warns and the deploy goes ahead — the server then runs the draft and installs the draft's version — so from CI/CD ask only for the draft's version, or discard the draft first.

#### Replacing an installed resource with a link

When the `--config-file` links a resource (`deploy config link`, `linkToResource`) that the deployment has already **installed** — an in-place update under an existing `--name` — the server replaces the installed copy with the link and deletes the copy, **with its data**. Newer servers ask for that to be confirmed first (schedule error `4026`), and newer CLI builds stop there instead of deploying:

```json
{
  "Result": "Failure",
  "Message": "Deploying '<DEPLOYMENT_NAME>' will permanently delete 1 installed resource(s) replaced by links: bucket '<BUCKET_NAME>', along with their data.",
  "Instructions": "Nothing was installed; the attempt is kept as a pending draft (<DRAFT_KEY>). To confirm the deletion, re-run the same command with --acknowledge-permanent-deletion. To keep the installed copies instead, remove those links from the --config-file (uip solution deploy config unlink <file> <resource>) and re-run.",
  "ErrorCode": "invalid_argument",
  "Retry": "RetryWillNotFix",
  "Data": {
    "PipelineDeploymentId": "<PIPELINE_DEPLOYMENT_ID>",
    "DeploymentKey": "<DRAFT_KEY>",
    "PermanentDeletionResources": [{ "Key": "<RESOURCE_KEY>", "Kind": "bucket", "Name": "<BUCKET_NAME>" }]
  }
}
```

`Message` names each resource as `kind 'name'`, `Data.PermanentDeletionResources` lists them with their keys, and nothing was installed — the live version keeps running and the attempt stays as a pending draft. The config is not wrong, so there is nothing to fix in it; the question is whether the user wants those copies and their data gone. Ask, then take one of two ways out:

- **Confirm:** re-run the **same** command with `--acknowledge-permanent-deletion`. Same `--name`, same `--package-version`, same `--config-file`, so the server reuses the draft. The CLI acknowledges each listed resource, runs the identical install once more within the same `--timeout`, and the success output names what was deleted in `PermanentlyDeletedResources`. It never acknowledges a list it could not read, and if the server still asks after that one retry the command stops rather than loop (`The server still requires confirmation for '<name>' after acknowledging …`). An acknowledgement the server refuses fails (`Acknowledging the permanent deletion of <kind> '<name>' (<key>) failed: …`) before the second install. A `404` there means the server does not offer the acknowledge route yet, so confirm in Orchestrator instead.
- **Keep the installed copies:** remove those links (`uip solution deploy config unlink <file> <resource>`) and re-run the same command without the flag.

`-y, --yes` is not this consent: it only skips the already-deployed check. Nothing prompts. The same stop applies to `deploy upgrade` when the new version replaces an installed resource with a link, and there the confirming re-run must not add `--discard-draft` — see [Upgrade a Deployment In Place](activate-and-manage.md#upgrade-a-deployment-in-place). The flag only exists on newer CLI builds; on an older one the option is unknown (check `uip solution deploy run --help`). Against a server that predates the confirmation, such a deploy never reaches a terminal status, and the CLI reports its usual timeout ("may still be in progress").

## Step 5: Check Deployment Status

The `deploy run` command returns a pipeline deployment ID. Use it to check progress:

```bash
uip solution deploy status <pipeline-deployment-id> --output json
```

`deploy status` also accepts a **deployment name** or **key** (the `DeploymentKey` in the `deploy run` output, or a key from `deploy list`): it then reports the record's `OperationStatus`, `DeploymentStatus`, `ActivationStatus`, `Actions`, whether the key is `Superseded`, and, when the deployment is not live, `PendingResources` (each with its `Reason` and `FolderPaths`) and `NextSteps` — see [Check Deployment Status](activate-and-manage.md#step-2-check-deployment-status).

The CLI also falls back to the persistent `searchSearchDeployments22` record if the pipeline service has already recycled the in-flight tracking ID — so a deployment that finishes while the CLI is between polls is still surfaced as `DeploymentSucceeded` rather than a polling failure.

## Step 6: List Deployments

```bash
uip solution deploy list --output json
uip solution deploy list --folder-path "Shared" --limit 20 --sort-by "Name" --sort-order "Ascending" --output json
uip solution deploy list --limit 50 --offset 50 --output json   # page 2
```

Options: `--folder-path`, `--limit` (default 50), `--offset` (default 0), `--sort-by`, `--sort-order` (`Ascending`/`Descending`).

The response's `Pagination` block reports `Total` and `HasMore`; when `HasMore` is `true`, fetch the next page with `--offset`. Note that `--folder-path` filters client-side after the fetch, so with that flag `Returned` counts the filtered rows while `Offset`/`Total` stay server-side — page with `--offset` first, then filter.

---

## Configuration Workflow

The deploy config is a plain JSON file that lists every resource the package exposes, along with its default properties and an optional `linkToResource` directive. It controls two things at deploy time:

- **Per-resource properties** — queue retry count, retention periods, conflict-resolution behaviour, etc.
- **Link vs. install** — whether each resource is bound to an existing Orchestrator/Apps entity (`linkToResource` set) or provisioned fresh in the deployment folder (no `linkToResource`).

The CLI exposes `get` / `set` / `link` / `unlink` for the common edits, but the file is just JSON — you can also open it in an editor and modify any field directly. Pass the file to `deploy run --config-file <path>` to apply.

### File Structure

`deploy config get` writes a top-level `resources` array. Each entry looks like:

```jsonc
{
  "kind": "bucket",                                   // resource kind
  "name": "test",                                     // resource name in the solution
  "resourceKey": "19f344d2-...",                      // solution-resource key (cloud GUID for resources imported from RCS)
  "folderPaths": ["solution_folder"],                 // folders the resource lives in inside the solution
  "configuration": { /* kind-specific properties */ },
  "linkToResource": {                                 // optional — present only when linked
    "name": "ProductionBucket",
    "folderPath": "Shared/Production"
  }
}
```

Only `linkToResource` toggles link vs. install. Everything else under `configuration` is forwarded to the resource at deploy time and can be edited freely — change retention periods, conflict-fixing actions, queue/process settings, or the kind-specific nested fields (e.g. an index's `storageBucketReference`, a process's `retentionBucketRef`) by writing to the JSON directly. Some `configuration` fields cross-reference other resources by `resourceKey`; when you edit link state on one resource, align those cross-refs on its dependents by hand if the topology changes.

### Fetch the Default Configuration

```bash
uip solution deploy config get "MySolution" -d config.json --output json

# For a specific version (latest is used if omitted)
uip solution deploy config get "MySolution" -d config.json --package-version "2.0.0" --output json
```

The fetched file already contains every resource with default values — no manual scaffolding required.

### Set a Resource Property

```bash
uip solution deploy config set config.json MyQueue maxNumberOfRetries 5
```

Arguments: `<config-file> <resource-name> <property> <value>`. Property names match the keys under `configuration` (visible in the file).

### Set a Property for All Resources

```bash
uip solution deploy config set config.json --all conflictFixingAction UseExisting
```

`--all` is restricted to `conflictFixingAction` — the field that controls what happens when a resource with the same name already exists in the target folder.

### Link to an Existing Orchestrator Resource

Add `linkToResource` so the deployment binds to an existing entity instead of creating a new one:

```bash
uip solution deploy config link config.json MyQueue \
  --name ProductionQueue --folder-path "Shared/Production"
```

Effect on the file: appends `linkToResource: { name, folderPath }` to the matched resource entry. The `--name` / `--folder-path` arguments must point to a real Orchestrator/Apps resource — the deployment fails if it doesn't exist.

When the same resource name appears multiple times in the config (different kinds), pass `resourceKey` instead of `name` to disambiguate. The CLI surfaces the available keys in the error message.

### Unlink a Resource

Remove the link so the resource is provisioned fresh in the deployment folder:

```bash
uip solution deploy config unlink config.json MyQueue
```

Effect on the file: deletes the `linkToResource` field. The unlink command requires a `linkToResource` to already exist — to start from a default-install config, simply leave (or never add) the field.

### Editing the Config File Directly

Every change the CLI makes is a small edit to the JSON, and the file isn't restricted to the fields `set` / `link` / `unlink` know about. Any property under `configuration`, any cross-reference (e.g. `storageBucketReference.key`), or any new top-level resource entry can be edited or added by hand when the built-in commands don't cover the case. Validate by running `deploy run --config-file <path>` — the deploy fails fast if a referenced resource doesn't exist.

### Deploy with Configuration

Pass the customized config file to `deploy run`:

```bash
uip solution deploy run -n "InvoiceAutomation-Prod" \
  --package-name "MySolution" --package-version "2.0.0" \
  --folder-name "ProdFolder" --parent-folder-path "Production" \
  --config-file config.json --output json
```

Without `--config-file`, the deployment uses package defaults. With `--config-file`, every resource is provisioned exactly as the file describes — fresh-install for any entry without `linkToResource`, bound for any entry with one.

Re-running under an existing `--name` updates that deployment in place. A link there to a resource the deployment already installed deletes the installed copy, so the deploy stops for consent (`--acknowledge-permanent-deletion`) — see [Replacing an installed resource with a link](#replacing-an-installed-resource-with-a-link).

---

## CI/CD Example (GitHub Actions)

```yaml
name: Deploy UiPath Solution
on:
  push:
    branches: [main]
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm install -g @uipath/cli
      - run: uip login --client-id "${{ secrets.UIPATH_CLIENT_ID }}" --client-secret "${{ secrets.UIPATH_CLIENT_SECRET }}" --tenant "${{ secrets.UIPATH_TENANT }}" --output json
      - run: uip solution restore ./MySolution --output json   # optional: fail fast on a missing feed before pack
      - run: uip solution pack ./MySolution ./output --version "1.0.${{ github.run_number }}" --output json
      - run: uip solution publish ./output/MySolution_*.zip --output json
      - run: uip solution deploy run -n "MySolution-${{ github.run_number }}" --package-name "MySolution" --package-version "1.0.${{ github.run_number }}" --folder-name "MySolution" --config-file deploy-config.json --output json
```

---

## Environment Promotion

Pack once, then publish and deploy to each environment in sequence:

```bash
uip solution pack ./MySolution ./output --version "1.2.0" --output json

# Staging
uip login tenant set "Staging" --output json
uip solution publish ./output/MySolution_1.2.0.zip --output json
uip solution deploy run -n "MySolution-Staging" --package-name "MySolution" --package-version "1.2.0" \
  --folder-name "MySolution" --config-file staging-config.json --output json

# Production (after validation)
uip login tenant set "Production" --output json
uip solution publish ./output/MySolution_1.2.0.zip --output json
uip solution deploy run -n "MySolution-Prod" --package-name "MySolution" --package-version "1.2.0" \
  --folder-name "MySolution" --config-file production-config.json --output json
```

---

## Version Bumping

Always increment the version when republishing (patch for fixes, minor for features, major for breaking changes). The solution feed rejects duplicate name+version pairs.

---

## Variations and Gotchas

### `publish` vs `upload`

These are different commands with different destinations:

| Command | Destination | Purpose |
|---------|-------------|---------|
| `solution publish` | Solution feed | For deployment via `deploy run` |
| `solution upload` | Studio Web | For browser-based editing |

### `deploy run` Always Creates a New Folder

`--folder-name` specifies a folder to **create**, not an existing folder to deploy into. Use `--parent-folder-path` to set the parent folder where the new folder is created.

**Re-running with a name that is already taken does not fail and does not reuse the folder** — Orchestrator collision-renames the new one (`MySolution` → `MySolution 1`) and the deployment lands there. Repeated deploys therefore accumulate `MySolution 1`, `MySolution 2`, …

The CLI resolves the real folder and says so when it differs from what you asked for:

```json
{
  "FolderName": "MySolution 4",
  "FolderPath": "MySolution 4",
  "RequestedFolderName": "MySolution",
  "FolderNote": "Requested folder 'MySolution' already existed, so Orchestrator created 'MySolution 4' instead. ..."
}
```

`RequestedFolderName` and `FolderNote` appear **only** on a rename, so their presence is the signal to check. Always provision against `Data.FolderPath`, never against the name you passed.

**There is no way to deploy into a pre-existing folder.** The install API takes `solutionRootFolderName` — a name, not a folder key — so the target cannot be addressed. This matters when the folder you want already holds hand-provisioned assets, an IXP folder-deployment, or an assigned robot: the deployment will land somewhere else and runtime resolution then fails on the missing resources.

Two ways around it:

```bash
# A. Let deploy create the folder FIRST, then provision into it.
uip solution deploy run --name my-deployment --package-name my-package \
  --package-version 1.0.0 --folder-name MySolution --output json
# → read Data.FolderPath / Data.FolderKey from the response, then create the
#   assets, folder-deploy IXP, and assign the robot into THAT folder.

# B. Skip solution deploy and bind each piece into the existing folder.
uip or packages upload ./piece.nupkg --output json
uip or processes create --package-key <id> --package-version <ver> \
  --folder-key <existing-folder-key> --output json
```

Option A is preferred — it keeps the solution deployment intact. Option B loses the solution grouping, so use it only when the folder's existing contents cannot be recreated.

### `--parent-folder-path` is the Parent

On `deploy run`, `--parent-folder-path` is the **parent** folder, not the deployment folder itself. The deployment folder is `--folder-name`, created inside `--parent-folder-path`. To produce a nested layout like `Shared/Nica/Solution`, pre-create `Shared/Nica` (or use a previous deploy to make it) and pass `--parent-folder-path "Shared/Nica" --folder-name "Solution"`.

### Config `link` Connects to Existing Resources

`config link` does not copy or move a resource. It writes a `linkToResource` directive that points the deployment at an existing Orchestrator/Apps resource instead of creating a new one. The linked resource must already exist in the specified folder when `deploy run` executes. Linking a resource the deployment has already installed replaces that copy and deletes it with its data, and the deploy stops until that is confirmed — see [Replacing an installed resource with a link](#replacing-an-installed-resource-with-a-link).

### Cross-Resource References Don't Auto-Update

Some resources reference others through `configuration` fields (an index's `storageBucketReference.key` pointing at a bucket, a process's `retentionBucketRef`, etc.). `link` / `unlink` only touch the targeted resource — they do not rewrite cross-references on its dependents. When you change link state for a resource that other entries point at, open the config file and align the cross-reference fields by hand.

### Config `set --all` is Limited

The `--all` flag on `config set` only works with `conflictFixingAction`. It cannot be used to set arbitrary properties across all resources.

### `deploy list --limit` and Folder Filtering

Folder filtering with `--folder-path` happens **after** fetching `--limit` results. If your deployment is missing from the list, increase `--limit` to ensure the server returns enough results before filtering.

### Units Mismatch

`--poll-interval` is in **milliseconds** (default 5000ms = 5s). `--timeout` is in **seconds** (default 360s = 6min). Do not confuse the two.

---

## Related

- [develop-solution.md](develop-solution.md) -- Create and structure a solution from scratch
- [scenarios.md](scenarios.md) -- Recipes for same-name resources, cross-refs, shared cloud entities, virtual assets at deploy
- [activate-and-manage.md](activate-and-manage.md) -- Activate deployments, uninstall, manage packages
