> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tensor9.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Common Workflows

This guide shows common workflows for using the tensor9 CLI to manage your apps, stacks, and appliances.

## Prerequisites

Before starting, ensure you have:

* [Installed the tensor9 CLI](/cli/install)
* Set your API key: `export T9_API_KEY=<your-key>`
* Configured AWS credentials for your Tensor9 AWS account

## Initial setup workflow

Set up Tensor9 in your AWS account for the first time.

<Steps>
  <Step title="Set up your control plane">
    Run the interactive setup:

    ```bash theme={null}
    tensor9 vendor setup
    ```

    Or provide all parameters:

    ```bash theme={null}
    tensor9 vendor setup \
      -cloud aws \
      -region us-west-2 \
      -awsProfile my-tensor9-profile
    ```

    This creates your Tensor9 control plane in your AWS account. It takes several minutes to complete.
  </Step>

  <Step title="Verify setup">
    Check that your control plane is ready:

    ```bash theme={null}
    tensor9 report
    ```

    You should see your vendor information displayed.
  </Step>

  <Step title="Create your first app">
    ```bash theme={null}
    tensor9 app create \
      -name my-app \
      -displayName "My Application"
    ```

    Your app is now created and ready to have a stack bound to it.
  </Step>
</Steps>

***

## Publish and bind your origin stack

Publish an origin stack and bind it to your app.

### For Terraform stacks

<Steps>
  <Step title="Publish your Terraform stack">
    From your Terraform workspace directory:

    ```bash theme={null}
    tensor9 stack publish \
      -stackType TerraformWorkspace \
      -stackS3Key my-app-stack \
      -dir .
    ```

    This uploads your Terraform files and returns a native stack ID like:

    ```
    s3://t9-ctrl-000001/terraform-stacks/origins/my-app-stack.tf.tgz
    ```
  </Step>

  <Step title="Bind the stack to your app">
    ```bash theme={null}
    tensor9 stack bind \
      -appName my-app \
      -stackType TerraformWorkspace \
      -nativeStackId "s3://t9-ctrl-000001/terraform-stacks/origins/my-app-stack.tf.tgz"
    ```

    Your stack is now bound to your app.
  </Step>
</Steps>

### For Docker container stacks

<Steps>
  <Step title="Push your container image to ECR">
    First, authenticate to your Tensor9 AWS account's ECR:

    ```bash theme={null}
    aws ecr get-login-password --region us-west-2 --profile my-tensor9-profile | \
      docker login --username AWS --password-stdin <account-id>.dkr.ecr.us-west-2.amazonaws.com
    ```

    Tag and push your image:

    ```bash theme={null}
    docker tag my-app:latest <account-id>.dkr.ecr.us-west-2.amazonaws.com/my-app:latest
    docker push <account-id>.dkr.ecr.us-west-2.amazonaws.com/my-app:latest
    ```

    The native stack ID is the image URI:

    ```
    <account-id>.dkr.ecr.us-west-2.amazonaws.com/my-app:latest
    ```
  </Step>

  <Step title="Bind the container to your app">
    ```bash theme={null}
    tensor9 stack bind \
      -appName my-app \
      -stackType DockerContainer \
      -nativeStackId "<account-id>.dkr.ecr.us-west-2.amazonaws.com/my-app:latest"
    ```

    Your container is now bound to your app.
  </Step>
</Steps>

### For Docker Compose stacks

<Note>
  Before publishing your Docker Compose file, ensure all container images referenced in your compose file are pushed to their registries (ECR, Docker Hub, etc.). These images must be available when you create a release.
</Note>

<Steps>
  <Step title="Publish your Docker Compose file">
    ```bash theme={null}
    tensor9 stack publish \
      -stackType DockerCompose \
      -stackS3Key my-app-compose \
      -file docker-compose.yml
    ```

    Returns a native stack ID like:

    ```
    s3://t9-ctrl-000001/my-app-compose.yml
    ```
  </Step>

  <Step title="Bind the stack to your app">
    ```bash theme={null}
    tensor9 stack bind \
      -appName my-app \
      -stackType DockerCompose \
      -nativeStackId "s3://t9-ctrl-000001/my-app-compose.yml"
    ```
  </Step>
</Steps>

### For CloudFormation stacks

<Steps>
  <Step title="Deploy your CloudFormation stack">
    Deploy your CloudFormation stack using the AWS CLI:

    ```bash theme={null}
    aws cloudformation create-stack \
      --stack-name my-app-stack \
      --template-body file://template.yaml \
      --region us-west-2 \
      --profile my-tensor9-profile
    ```
  </Step>

  <Step title="Get the stack ARN">
    ```bash theme={null}
    aws cloudformation describe-stacks \
      --stack-name my-app-stack \
      --region us-west-2 \
      --query 'Stacks[0].StackId' \
      --output text
    ```

    This returns an ARN like:

    ```
    arn:aws:cloudformation:us-west-2:123456789012:stack/my-app-stack/a1b2c3d4
    ```
  </Step>

  <Step title="Bind the CloudFormation stack">
    ```bash theme={null}
    tensor9 stack bind \
      -appName my-app \
      -stackType CloudFormation \
      -nativeStackId "arn:aws:cloudformation:us-west-2:123456789012:stack/my-app-stack/a1b2c3d4"
    ```
  </Step>
</Steps>

***

## Create a test appliance

Create a test appliance for testing your releases before deploying to customers.

<Steps>
  <Step title="Create a test appliance">
    ```bash theme={null}
    tensor9 test appliance create \
      -appName my-app \
      -name my-test-appliance
    ```

    The test appliance will be created asynchronously.
  </Step>

  <Step title="Monitor appliance creation">
    Check the status periodically:

    ```bash theme={null}
    tensor9 report
    ```

    Wait until the test appliance status shows "Live". This typically takes 10-15 minutes.
  </Step>
</Steps>

***

## Deploy to a test appliance

Deploy your stack to a test appliance to verify it works before releasing to customers.

<Steps>
  <Step title="Create a release">
    Create a release for your test appliance:

    ```bash theme={null}
    tensor9 stack release create \
      -appName my-app \
      -testApplianceName my-test-appliance \
      -vendorVersion "1.0.0" \
      -description "Initial release" \
      -notes "First production release"
    ```

    After a few minutes, the deployment stack downloads to a directory named `my-test-appliance`, alongside a companion `my-test-appliance.audit` directory containing the [audit stack](/fundamentals/stack-audit) for pre-deployment review.
  </Step>

  <Step title="Review the audit stack (optional)">
    Before deploying, you can scan the audit stack with your standard IaC security and policy tooling. The audit stack is a Tensor9-free copy of the deployment stack - same application infrastructure, without any Tensor9 providers or runtime plumbing - so scanners can plan and inspect it standalone:

    ```bash theme={null}
    cd my-test-appliance.audit
    tofu init
    tofu plan

    # Security / policy scans
    tfsec .
    checkov -d .
    ```

    <Note>
      The audit stack is for review only. Do not `apply` it. Only the deployment stack (in `my-test-appliance`) provisions a working install.
    </Note>
  </Step>

  <Step title="Deploy the release">
    ```bash theme={null}
    cd my-test-appliance
    tofu init
    tofu apply
    ```

    Your application is now deployed in the test appliance.
  </Step>

  <Step title="Verify the deployment">
    Check the deployed resources:

    ```bash theme={null}
    kubectl get deployments
    kubectl get services
    kubectl get pods
    ```

    For services with external ports, get the load balancer endpoint:

    ```bash theme={null}
    kubectl get service <service-name> -o jsonpath='{.status.loadBalancer.ingress[0].hostname}'
    ```

    Access your application through the load balancer URL.
  </Step>
</Steps>

***

## Create a customer appliance

Enable your customer to create an appliance for your app in their environment.

<Steps>
  <Step title="Generate signup link">
    Generate a signup link for your customer:

    ```bash theme={null}
    tensor9 app signup-link -appName my-app
    ```

    Send the generated URL to your customer. They'll use it to create their appliance.
  </Step>

  <Step title="Customer creates appliance">
    The customer uses the signup link to:

    1. Sign up and create their organization (if they haven't already)
    2. Select their cloud provider and region
    3. Complete the setup to create their appliance

    Their appliance will be provisioned automatically in their cloud account.
  </Step>

  <Step title="Monitor appliance creation">
    Check when the customer's appliance is ready:

    ```bash theme={null}
    tensor9 report
    ```

    Wait until their appliance status shows "Live".
  </Step>

  <Step title="Create a release for the customer">
    Once their appliance is ready:

    ```bash theme={null}
    tensor9 stack release create \
      -appName my-app \
      -customerName acme-corp \
      -vendorVersion "1.0.0" \
      -description "Initial deployment for Acme Corp" \
      -notes "Configured for production use"
    ```

    The deployment stack downloads to a directory named after their appliance.
  </Step>

  <Step title="Deploy to the customer's appliance">
    Deploy the release to the customer's appliance:

    ```bash theme={null}
    cd acme-corp-appliance
    tofu init
    tofu apply
    ```

    This deploys your application into the customer's appliance.
  </Step>
</Steps>

***

## Release to a customer appliance

Release infrastructure or code changes to your customer appliances.

<Steps>
  <Step title="Update your origin stack">
    Make changes to your Terraform files, docker-compose.yml, or CloudFormation template.
  </Step>

  <Step title="Republish the stack">
    For Terraform:

    ```bash theme={null}
    tensor9 stack publish \
      -stackType TerraformWorkspace \
      -stackS3Key my-app-stack \
      -dir .
    ```

    For Docker Compose:

    ```bash theme={null}
    tensor9 stack publish \
      -stackType DockerCompose \
      -stackS3Key my-app-compose \
      -file docker-compose.yml
    ```

    For CloudFormation:

    ```bash theme={null}
    aws cloudformation update-stack \
      --stack-name my-app-stack \
      --template-body file://template.yaml
    ```

    <Note>
      You don't need to re-bind the stack. Tensor9 will use the updated version for new releases.
    </Note>
  </Step>

  <Step title="Test in test appliance">
    Create a release to your test appliance:

    ```bash theme={null}
    tensor9 stack release create \
      -appName my-app \
      -testApplianceName my-test-appliance \
      -vendorVersion "1.1.0" \
      -description "Added new features" \
      -notes "Added caching layer and API improvements"
    ```
  </Step>

  <Step title="Deploy and verify">
    ```bash theme={null}
    cd my-test-appliance
    tofu apply
    ```

    Verify the changes work as expected.
  </Step>

  <Step title="Release to customers">
    After testing, release to your customers:

    ```bash theme={null}
    # Release to specific customer
    tensor9 stack release create \
      -appName my-app \
      -customerName acme-corp \
      -vendorVersion "1.1.0" \
      -description "Feature update" \
      -notes "New caching layer and performance improvements"

    # Or release to all customers
    tensor9 stack release create \
      -appName my-app \
      -all \
      -vendorVersion "1.1.0" \
      -description "Feature update" \
      -notes "New caching layer and performance improvements"
    ```
  </Step>
</Steps>

***

## Manage multiple form factors

Deploy your app to different cloud providers or connectivity modes.

<Steps>
  <Step title="Create form factors">
    Create form factors for different environments:

    ```bash theme={null}
    # AWS connected
    tensor9 form-factor create \
      -appName my-app \
      -formFactorName aws-connected \
      -description "AWS with internet connectivity" \
      -env Aws \
      -connectivity Connected

    # GCP connected
    tensor9 form-factor create \
      -appName my-app \
      -formFactorName gcp-connected \
      -description "Google Cloud with internet connectivity" \
      -env Gcp \
      -connectivity Connected

    # DigitalOcean connected
    tensor9 form-factor create \
      -appName my-app \
      -formFactorName digitalocean-connected \
      -description "DigitalOcean with internet connectivity" \
      -env DigitalOcean \
      -connectivity Connected
    ```
  </Step>

  <Step title="Test each form factor">
    Create test appliances for each form factor:

    ```bash theme={null}
    tensor9 test appliance create \
      -appName my-app \
      -name my-aws-test \
      -formFactorName aws-connected

    tensor9 test appliance create \
      -appName my-app \
      -name my-gcp-test \
      -formFactorName gcp-connected \
      -cloudRegion gcp:us-central1
    ```
  </Step>

  <Step title="Release to each test appliance">
    ```bash theme={null}
    tensor9 stack release create \
      -appName my-app \
      -testApplianceName my-aws-test \
      -vendorVersion "1.0.0" \
      -description "AWS test" \
      -notes "Testing AWS deployment"

    tensor9 stack release create \
      -appName my-app \
      -testApplianceName my-gcp-test \
      -vendorVersion "1.0.0" \
      -description "GCP test" \
      -notes "Testing GCP deployment"
    ```
  </Step>
</Steps>

***

## Onboard your vendor controller to Tailscale

By default, the listeners your CLI and Terraform use to reach the vendor controller are exposed on a public network load balancer with mTLS protection. If you operate a [Tailscale](https://tailscale.com) tailnet for your engineering team, you can have the vendor controller join that tailnet and (optionally) remove the public path entirely. See [Connectivity](/fundamentals/connectivity#operator-to-control-plane) for the broader picture.

This workflow rolls out in two phases: first attach the controller to your tailnet and verify operator access, then optionally enforce tunnel-only by removing the public listeners.

### Phase 1: attach the vendor controller to your tailnet

<Steps>
  <Step title="Ensure your tailnet ACL has the required tags and rules">
    This step is idempotent. It adds the `tag:tensor9-vctrl` (vendor controller) and `tag:tensor9-customer-ctrl` (appliance controller) tags, the `group:tensor9-operators` group, and the rules that allow operators and customer appliances to reach the vendor controller. Existing ACL entries are preserved.

    ```bash theme={null}
    export TAILSCALE_API_KEY=tskey-api-...

    tensor9 tailscale acl setup
    ```

    If you maintain ACLs in HuJSON via the dashboard, the command prints a copy-pasteable snippet you can apply by hand. Otherwise answer `y` at the confirmation prompt to have the CLI POST the change for you (a local backup is written under `~/.tensor9/tailscale-acl-backups/`).

    See [`tensor9 tailscale acl setup`](/cli/reference#tailscale-acl-setup) for full options.
  </Step>

  <Step title="Generate a pre-auth key for the vendor controller">
    The key is tagged so the ACL applies the right rules to the resulting node. Keep it single-use (the default) for production:

    ```bash theme={null}
    tensor9 tailscale key generate -tag VCtrl
    ```

    Save the printed `tskey-auth-...` value; you will pass it to the next command. See [`tensor9 tailscale key generate`](/cli/reference#tailscale-key-generate) for full options.

    Add operators to the `group:operators` group in the Tailscale dashboard so they can reach the controller's listeners over the tailnet.
  </Step>

  <Step title="Attach the vendor controller to the tailnet">
    ```bash theme={null}
    tensor9 vendor tailscale onboard \
      -vctrlKey tskey-auth-XXXXXXXXXXXX
    ```

    The command installs the Tailscale daemon on the vendor controller, joins it to the tailnet, and stamps the resulting tailnet hostname onto the controller's configuration. The controller's existing public listeners stay in place.

    See [`tensor9 vendor tailscale onboard`](/cli/reference#vendor-tailscale-onboard) for full options.
  </Step>

  <Step title="Verify operator access over the tailnet">
    With your operator account in `group:operators` and connected to the tailnet, run a read-only command:

    ```bash theme={null}
    tensor9 report
    ```

    Your CLI should be able to reach the vendor controller over the tailnet without any extra flags. The controller is still reachable over the public path too, so this step proves the tailnet route is working while leaving you a fallback.

    The endpoint used with Terraform/OpenTofu `plan` and `apply` operations is defined in the `tensor9` provider block of the compiled deployment stack. Compilation emits the publicly available listener to this block when it is available, even if you have a tunnel configured, to be compatible with CI deployments. For testing, you can manually modify the compiled stack to change the endpoint address to be the vendor controller's address on the tailnet and try a `plan`. Follow phase 2, below, to remove the TF endpoint from the public load balancer and re-compile your deployment stacks to enforce using the Tailscale for communication between your local Terraform/OpenTofu CLI and the vendor controller.
  </Step>
</Steps>

### Phase 2: remove the public listeners

Once you are satisfied that operators and Terraform-driven deploys work over the tailnet, you can enforce tunnel-only and tear the public listeners down. Roll this out per listener group rather than all at once so you can pause if something is missed.

<Steps>
  <Step title="Mark the operator listeners as tunnel-only">
    The `tunnel enforce` command mutates the controller's configuration; the listener teardown happens on the next infrastructure upgrade.

    ```bash theme={null}
    # CLI listeners first
    tensor9 vendor tunnel enforce -add CLI

    # After a soak period, the Terraform reactor too (make sure CI works!)
    tensor9 vendor tunnel enforce -add Terraform
    ```

    See [`tensor9 vendor tunnel enforce`](/cli/reference#vendor-tunnel-enforce) for full options. If you also want to remove the appliance-facing public listener, the command checks first that no customer appliance still depends on it; see the pre-flight notes in the reference.
  </Step>

  <Step title="Apply the change">
    Run an infrastructure upgrade to remove the now-redundant listeners from your network load balancer:

    ```bash theme={null}
    tensor9 vendor upgrade \
      -kind Infrastructure \
      -reason "remove public CLI and Terraform listeners after Tailscale cutover"
    ```

    After this completes, the only way to reach the enforced listeners is over the tailnet.
  </Step>
</Steps>

<Warning>
  Make sure every operator and every CI runner that drives `tensor9` or `terraform` against the vendor controller is on the tailnet before you run `tunnel enforce -add CLI` / `Terraform`. After the next infrastructure upgrade, anything off the tailnet will be locked out.
</Warning>

To roll back, run `tensor9 vendor tunnel enforce -remove CLI,Terraform` followed by `tensor9 vendor upgrade -kind Infrastructure`. The public listeners are recreated.

***

## Use stack tuning documents

Customize resource allocations per customer or environment.

<Steps>
  <Step title="Create a tuning document">
    Create a JSON file with resource overrides:

    ```json theme={null}
    {
      "version": "V1",
      "composeServices": {
        "web": {
          "replicas": 4,
          "resources": {
            "cpu": "2",
            "memory": "4Gi"
          }
        },
        "api": {
          "replicas": 3,
          "resources": {
            "cpu": "1",
            "memory": "2Gi"
          }
        }
      }
    }
    ```

    Save as `enterprise-tuning.json`.
  </Step>

  <Step title="Release with tuning document">
    ```bash theme={null}
    tensor9 stack release create \
      -appName my-app \
      -customerName enterprise-customer \
      -vendorVersion "1.0.0" \
      -description "Enterprise deployment" \
      -notes "High-performance configuration" \
      -tuningDoc enterprise-tuning.json
    ```

    The deployment stack will use the tuned resource specifications.
  </Step>
</Steps>

***

## Monitor appliances

Check the status of your apps, appliances, and releases.

### View comprehensive report

```bash theme={null}
tensor9 report
```

This shows:

* All your apps and their stacks
* All test appliances and their status
* All customer appliances and their status
* Active releases

### View detailed report

```bash theme={null}
tensor9 report -detailed
```

### List all appliances

```bash theme={null}
tensor9 appliance list
```

### Use the web portal

Start a local web interface:

```bash theme={null}
tensor9 portal
```

This opens a browser with a visual dashboard of your apps and appliances.

***

## Retire a test appliance

Remove a test appliance when no longer needed.

```bash theme={null}
tensor9 test appliance retire -testApplianceName my-test-appliance
```

<Warning>
  This permanently deletes the test appliance and all its infrastructure. It may take several minutes to fully deprovision.
</Warning>

***

## Manage your vendor team

Tensor9 distinguishes two kinds of vendor accounts:

* The **root account** is created automatically when you run `vendor setup`. It is the singleton account for your vendor and the only one that can invite, list, or revoke other accounts. If you lose your root cert, you can re-issue it from your control plane's seed bundle in your AWS account (`iam account recover`).
* **Operator accounts** are everyone else: teammates the root account invites by email. They get a long-lived mTLS cert pinned by your control plane after redeeming a one-time enrollment bundle. They can run ordinary vendor commands but cannot manage other accounts. If an operator loses their cert, the root account re-invites them.

See [IAM Commands](/cli/reference#iam-commands) in the reference for the full surface and authorization details.

### Invite a teammate

<Steps>
  <Step title="Mint an enrollment bundle">
    From your laptop (root account), invite by email:

    ```bash theme={null}
    tensor9 iam user invite -email alice@acme.com
    ```

    This writes a single-use bundle to `./alice@acme.com-enrollment-bundle.json`. The bundle is valid for 48 hours by default. Use `-ttl PT24H` (or any ISO-8601 duration) to set a different window.
  </Step>

  <Step title="Send the bundle out-of-band">
    Send the file to your teammate via Slack, encrypted email, or a similar channel. The bundle contains a one-time secret, so treat it as sensitive until redeemed.
  </Step>

  <Step title="Have your teammate redeem it">
    On their machine, your teammate runs:

    ```bash theme={null}
    tensor9 configure -enrollmentBundle ./alice@acme.com-enrollment-bundle.json
    ```

    This trades the bundle for a long-lived mTLS cert pinned by your control plane, persists it locally, and writes a CLI profile so subsequent `tensor9` commands authenticate automatically.
  </Step>
</Steps>

### List your team

```bash theme={null}
tensor9 iam user list
```

Shows the root account plus every invited operator with their enrollment status (`Enrolled`, `Invited`, or `Revoked`).

### Revoke access

To remove an operator's access, find their account ID and revoke:

```bash theme={null}
tensor9 iam user list
tensor9 iam user revoke -accountId 00000000000000000000000000000003
```

Revocation is two-stage on the server: any in-flight enrollment bundles are killed, and the leaf-fingerprint pin is dropped so the user's cached cert stops authenticating on its next handshake.

<Note>
  The root account cannot revoke itself. Use `tensor9 iam account recover` to rotate the root cert.
</Note>

### Recover the root account

If you lose your laptop or wipe `~/.tensor9/`, recover by re-issuing the root cert from your control plane's seed bundle:

```bash theme={null}
tensor9 iam account recover -vendorId <your-vendor-id>
```

The command reads your control plane's seed bundle directly from your AWS Secrets Manager / Parameter Store. Access to that secret-store path is the actual gate, so make sure the IAM policy on it is scoped tightly.

For non-root operator account recovery, ask your root account to re-invite you with `iam user invite`. Operator tokens are single-use, so there's no equivalent self-service recovery path on the operator side.

***

## Troubleshoot common issues

<AccordionGroup>
  <Accordion title="Release deployment fails">
    **Problem**: `tofu apply` fails when deploying a release.

    **Solution**:

    1. Check the Terraform error messages
    2. Verify your origin stack is valid: `tofu validate` in your workspace
    3. Check appliance status: `tensor9 report`
    4. Review deployment stack variables and configuration
    5. Check AWS credentials and permissions
  </Accordion>

  <Accordion title="Test appliance stuck in Creating status">
    **Problem**: Test appliance remains in "Creating" status for a long time.

    **Solution**:

    1. Wait 15-20 minutes (appliance creation can take time)
    2. Check `tensor9 report -detailed` for error messages
    3. Verify AWS quotas are sufficient for EKS, VPCs, etc.
    4. Check CloudFormation console in AWS for stack creation issues
    5. Contact support if stuck for more than 30 minutes
  </Accordion>

  <Accordion title="Stack publish fails">
    **Problem**: `tensor9 stack publish` fails with upload errors.

    **Solution**:

    1. Verify AWS credentials: `aws sts get-caller-identity --profile <profile>`
    2. Check that no .terraform directories are in your workspace
    3. Ensure you have write permissions to the control plane S3 bucket
    4. For large stacks, check your network connection
  </Accordion>

  <Accordion title="Customer can't access their appliance">
    **Problem**: Customer reports they can't reach their deployed application.

    **Solution**:

    1. Verify deployment completed: Check with customer that `tofu apply` succeeded
    2. Check load balancer: `kubectl get service` shows external IP/hostname
    3. Verify DNS configuration if using custom domains
    4. Check security groups/firewall rules in customer's cloud
    5. Review application logs: `kubectl logs <pod-name>`
  </Accordion>

  <Accordion title="Release not downloading">
    **Problem**: After creating a release, the deployment stack doesn't download.

    **Solution**:

    1. Wait a few minutes (compilation takes time)
    2. Check release status: `tensor9 report`
    3. Verify appliance is in "Live" status
    4. Check for stack validation errors in the report
    5. Review control plane CloudWatch logs for compilation errors
  </Accordion>
</AccordionGroup>

***

## Best practices

<AccordionGroup>
  <Accordion title="Version your releases semantically">
    Use semantic versioning for your releases:

    * `1.0.0` - Initial release
    * `1.0.1` - Patch (bug fixes)
    * `1.1.0` - Minor (new features, backward compatible)
    * `2.0.0` - Major (breaking changes)

    This helps customers understand the impact of updates.
  </Accordion>

  <Accordion title="Test every release">
    Always create a release to a test appliance before releasing to customers:

    1. Create release to test appliance
    2. Deploy and verify functionality
    3. Test upgrade path from previous version
    4. Only then release to customers
  </Accordion>

  <Accordion title="Use descriptive release notes">
    Write clear, customer-facing release notes:

    ```bash theme={null}
    tensor9 stack release create \
      -appName my-app \
      -customerName acme-corp \
      -vendorVersion "1.2.0" \
      -description "Bug fixes and performance improvements" \
      -notes "Fixed authentication timeout issue. Improved API response time by 40%. Added support for batch operations."
    ```

    Your customers see these notes when deploying.
  </Accordion>

  <Accordion title="Organize your origin stacks">
    Keep your origin stacks in version control and use consistent naming:

    * Use Git to track changes to origin stacks
    * Tag releases in Git: `git tag v1.0.0`
    * Use the same version in Git and Tensor9 releases
    * Document infrastructure changes in commit messages
  </Accordion>

  <Accordion title="Monitor appliance health">
    Regularly check appliance status:

    ```bash theme={null}
    # Weekly check
    tensor9 report

    # Monthly detailed review
    tensor9 report -detailed
    ```

    Set up alerts for customer appliance issues.
  </Accordion>
</AccordionGroup>

***

## Next steps

* **[CLI Reference](/cli/reference)**: Complete command reference
* **[Quick Start Guides](/getting-started/quick-start-terraform)**: Step-by-step tutorials
* **[Deployments](/fundamentals/deployments)**: Learn more about the deployment process
* **[Testing](/fundamentals/testing)**: Best practices for testing releases
