Skip to main content
Deployments are how you deliver your application to customer appliances. In Tensor9, a deployment is the result of creating a release for a specific appliance and then applying that release using standard infrastructure-as-code tooling. The release process compiles your origin stack into an appliance-specific deployment stack. You then use that deployment stack to deploy the release to an appliance.
Deployment workflow showing origin stack compilation to deployment stack

How deployments work

Deployments in Tensor9 follow a three-stage process: publish, release, and deploy.
1

Publish your origin stack

First, you publish your origin stack to your control plane. This makes your origin stack available for release:
tensor9 stack publish \
  -stackType TerraformWorkspace \
  -stackS3Key my-stack \
  -dir /path/to/terraform
This command uploads your infrastructure code to your control plane’s artifact storage and returns a native stack ID (e.g., s3://t9-ctrl-000001/my-stack.tf.tgz).Important: You only need to bind your stack to your app once using tensor9 stack bind. After the initial bind, you can publish new versions without re-binding.
2

Create a release (compilation)

Next, you create a release for a specific appliance. This triggers your control plane to compile your origin stack into a deployment stack tailored to that appliance’s form factor:
tensor9 stack release create \
  -appName my-app \
  -testApplianceName my-test-appliance \
  -vendorVersion "1.0.0" \
  -description "Initial production release" \
  -notes "Deployed by [email protected]"
During compilation, your control plane:
  1. Validates your origin stack to ensure it’s well-formed
  2. Ports cloud-specific resources to match the appliance’s form factor (e.g., AWS RDS → Google Cloud SQL)
  3. Instruments the stack for observability (logs, metrics, traces)
  4. Identifies artifacts (container images, S3 objects) and rewrites references to point to appliance-local locations
  5. Generates a deployment stack - a ready-to-deploy infrastructure-as-code artifact
For Terraform/OpenTofu origin stacks, after a few minutes the compiled deployment stack downloads into a new directory named after your appliance (e.g., ./my-test-appliance/). For CloudFormation origin stacks, the control plane automatically creates the deployment stack in your control plane’s AWS account.
3

Deploy to the appliance

Finally, you deploy the compiled stack. During deployment, Tensor9 copies any referenced artifacts (container images, S3 objects) to the appliance’s local environment. The deployment process depends on your stack type:For Terraform/OpenTofu: You deploy the compiled stack using standard tooling:
cd acme-corp-production
tofu init
tofu apply
For CloudFormation: Your control plane automatically creates the CloudFormation stack in your control plane’s AWS account. You can monitor deployment progress using:
# View deployment status
tensor9 report -customerName acme-corp

# View CloudFormation stack events in your control plane's account
aws cloudformation describe-stack-events \
  --stack-name myapp-stack-000000007e
The deployment executes in the target appliance, creating all the infrastructure resources your application needs.

Creating releases for different appliance types

The release creation process differs slightly depending on whether you’re deploying to a test appliance or a customer appliance.

Releasing to test appliances

Test appliances are environments you control, used for validation before production deployments:
tensor9 stack release create \
  -appName my-app \
  -testApplianceName my-test-appliance \
  -vendorVersion "1.0.0" \
  -description "Testing new feature" \
  -notes "QA validation build"

Releasing to customer appliances

Customer appliances are production environments running in your customer’s infrastructure. To create a release for a customer appliance, use the customer name:
tensor9 stack release create \
  -appName my-app \
  -customerName <CUSTOMER_NAME> \
  -vendorVersion "1.0.0" \
  -description "Production release" \
  -notes "Deployed after successful QA validation"
You can find customer names using tensor9 report.

Version management

The -vendorVersion parameter allows you to track which version of your application is deployed to each appliance. This should match your internal versioning scheme (e.g., semantic versioning):
# Initial release
tensor9 stack release create \
  -appName my-app \
  -testApplianceName my-test \
  -vendorVersion "1.0.0" \
  -description "Initial release"

# Bug fix release
tensor9 stack release create \
  -appName my-app \
  -testApplianceName my-test \
  -vendorVersion "1.0.1" \
  -description "Fix authentication bug"

# Feature release
tensor9 stack release create \
  -appName my-app \
  -testApplianceName my-test \
  -vendorVersion "1.1.0" \
  -description "Add user analytics dashboard"
Version numbers are stored with each release and visible in tensor9 report, making it easy to track which version is deployed where.

Deployment stack structure

The deployment stack structure depends on your origin stack type:

Terraform/OpenTofu deployment stacks

After creating a release, Tensor9 downloads a deployment stack into a directory named after your appliance:
my-test-appliance/
├── main.tf                 # Compiled Terraform configuration
├── variables.tf            # Variables (includes injected instance_id)
├── outputs.tf              # Outputs from your origin stack
├── modules/                # Any child modules from your origin stack
└── .terraform/             # Created after `tofu init`
The deployment stack is self-contained and ready to deploy with no additional configuration required.

CloudFormation deployment stacks

For CloudFormation origin stacks, your control plane automatically creates the compiled deployment stack as a CloudFormation stack in your control plane’s AWS account when you create a release. There’s no local directory to download - the stack is created and managed directly in CloudFormation.

Compilation process

When you create a release, your control plane compiles your origin stack through several transformation steps:

Service equivalents

During compilation, Tensor9 compiles cloud-specific services in your origin stack to their functional equivalents in the target appliance’s environment. This compilation is based on a service equivalents registry that maps services across cloud providers.
For a comprehensive guide to service equivalents, including detailed examples and best practices, see Service Equivalents.
Tensor9 groups services into equivalent sets based on their function:
Service CategoryAWSGoogle CloudAzureDigitalOceanOn-Prem
ContainersEKS, ECSGKEAKSDOKKubernetes
FunctionsLambdaCloud FunctionsAzure FunctionsFunctionsKnative (unmanaged)
NetworkingVPCVPCVNet--
Load balancingLoad BalancerLoad BalancerLoad BalancerLoad BalancerCloudflare (optional)
DNSRoute 53Cloud DNSAzure DNSDigitalOcean DNSCloudflare (optional)
Identity and access managementIAMIAMIAM--
Object storageS3Cloud Storage (GCS)Azure Blob StorageSpacesBackblaze B2, MinIO (unmanaged)
Databases (PostgreSQL)RDS Aurora PostgreSQL, RDS PostgreSQLCloud SQL PostgreSQLAzure Database for PostgreSQLManaged PostgreSQLNeon, CloudNative PostgreSQL (unmanaged)
Databases (MySQL)RDS Aurora MySQL, RDS MySQLCloud SQL MySQLAzure Database for MySQLManaged MySQLPlanetScale, MySQL (unmanaged)
Databases (MongoDB)DocumentDBAtlas MongoDBCosmos DB (MongoDB API)Managed MongoDBMongoDB Atlas, MongoDB (unmanaged)
CachingElastiCacheMemorystoreAzure Cache for RedisManaged RedisRedis Enterprise Cloud, Redis (unmanaged)
Message streamingMSK (Managed Streaming for Kafka)Confluent Cloud, Kafka (unmanaged)Event Hubs (Kafka compatible)Confluent Cloud, Kafka (unmanaged)Confluent Cloud, Kafka (unmanaged)
SearchOpenSearch ServiceOpenSearch (unmanaged)OpenSearch (unmanaged)OpenSearch (unmanaged)OpenSearch (unmanaged)
WorkflowMWAA (Managed Airflow)Cloud ComposerAzure Data FactoryAstronomer, Airflow (unmanaged)Astronomer, Airflow (unmanaged)
AnalyticsAmazon AthenaBigQueryAzure Synapse AnalyticsPresto (unmanaged)Presto (unmanaged)
Third-party managed equivalents (Backblaze B2, Neon, PlanetScale, MongoDB Atlas, Redis Enterprise Cloud, Confluent Cloud, Astronomer) require your customers to bring their own credentials and accounts with these services.
Some popular AWS services (EC2, DynamoDB, EFS) are not currently supported. See Unsupported AWS services for the full list and recommended alternatives.
When you compile an origin stack for a specific form factor, Tensor9 automatically replaces services with their equivalents in the target environment. Example: AWS to Google Cloud
# Origin stack (AWS)
resource "aws_db_instance" "postgres" {
  engine         = "postgres"
  instance_class = "db.t3.micro"
}

resource "aws_s3_bucket" "data" {
  bucket = "my-app-data"
}

# Compiled deployment stack (Google Cloud)
resource "google_sql_database_instance" "postgres" {
  database_version = "POSTGRES_15"
  tier            = "db-f1-micro"
}

resource "google_storage_bucket" "data" {
  name = "my-app-data"
}
Example: AWS to private Kubernetes
# Origin stack (AWS)
resource "aws_eks_cluster" "app" {
  name = "my-app-cluster"
}

resource "aws_db_instance" "postgres" {
  engine = "postgres"
}

# Compiled deployment stack (Kubernetes private)
resource "kubernetes_cluster" "app" {
  name = "my-app-cluster"
}

# CloudNative PostgreSQL operator
resource "kubectl_manifest" "postgres" {
  yaml_body = <<-YAML
    apiVersion: postgresql.cnpg.io/v1
    kind: Cluster
    metadata:
      name: postgres
    spec:
      instances: 3
      storage:
        size: 20Gi
  YAML
}
Service equivalents ensure your application works consistently across different cloud providers and deployment environments, without requiring you to maintain separate infrastructure code for each target platform.

Parameterization

Tensor9 automatically injects an instance_id variable into every deployment to ensure resource uniqueness:
variable "instance_id" {
  type        = string
  description = "Uniquely identifies the instance to deploy into"
  # Automatically set by Tensor9 (e.g., "000000000000007e")
}
Your origin stack should use this variable to make all resource names unique:
resource "aws_s3_bucket" "data" {
  bucket = "my-app-data-${var.instance_id}"
}

Artifact identification and rewriting

During compilation, Tensor9 identifies artifact references (container images, S3 objects) in your origin stack and rewrites them to point to appliance-local locations:
# Origin stack
resource "aws_lambda_function" "api" {
  image_uri = "123456789012.dkr.ecr.us-west-2.amazonaws.com/my-app:v1.0.0"
}

# Compiled deployment stack (for customer appliance in account 999888777666)
resource "aws_lambda_function" "api" {
  image_uri = "999888777666.dkr.ecr.us-west-2.amazonaws.com/my-app:v1.0.0"
}
The actual copying of artifacts from your origin account to the appliance happens during deployment (when you run tofu apply). This ensures your artifacts are available locally within the appliance without requiring cross-account permissions.

Observability instrumentation

Your control plane configures telemetry routing so logs, metrics, and traces flow back to your observability sink:
# Origin stack
resource "aws_cloudwatch_log_group" "api_logs" {
  name = "/aws/lambda/my-function/${var.instance_id}"
}

# Compiled deployment stack
resource "aws_cloudwatch_log_group" "api_logs" {
  name = "/aws/lambda/my-function/${var.instance_id}"
  # Tensor9 automatically configures log forwarding to your sink
}

Deploying updates

To deploy changes to an existing appliance, publish a new version of your origin stack and create a new release:
  1. Make changes to your origin stack (add resources, update configurations, etc.)
  2. Publish the updated origin stack:
For Terraform/OpenTofu:
tensor9 stack publish \
  -stackType TerraformWorkspace \
  -stackS3Key my-stack \
  -dir /path/to/terraform
For CloudFormation:
aws cloudformation update-stack \
  --stack-name myapp-origin-stack \
  --template-body file://template.yaml \
  --capabilities CAPABILITY_IAM
  1. Create a new release with an incremented version:
tensor9 stack release create \
  -appName my-app \
  -testApplianceName my-test \
  -vendorVersion "1.1.0" \
  -description "Add caching layer"
  1. Deploy the update:
For Terraform/OpenTofu:
cd acme-corp-production
tofu init
tofu apply
Terraform/OpenTofu will compute the diff between the current state and the new deployment stack, applying only the necessary changes. For CloudFormation: The control plane automatically updates the CloudFormation stack in your control plane’s AWS account. Monitor the update progress using tensor9 report -customerName <CUSTOMER_NAME> or by viewing CloudFormation stack events.

Testing strategy

Always test releases in test appliances before deploying to customer appliances:

1. Create a test appliance

tensor9 test appliance create \
  -appName my-app \
  -formFactorName aws-connected \
  -region aws:us-west-2

2. Deploy and validate in test

# Create release for test appliance
tensor9 stack release create \
  -appName my-app \
  -testApplianceName my-test \
  -vendorVersion "1.2.0-rc1" \
  -description "Release candidate for testing"

# Deploy
cd my-test
tofu init && tofu apply

# Validate functionality
curl https://api.my-test.my-app.acme.com/health

3. Deploy to production after validation

# Create release for customer appliance
tensor9 stack release create \
  -appName my-app \
  -customerName <CUSTOMER_NAME> \
  -vendorVersion "1.2.0" \
  -description "Production release with caching"

# Deploy to customer
cd customer-appliance
tofu init && tofu apply

Multi-stack deployments

Some applications consist of multiple independently deployable components. You can bind multiple origin stacks to a single app and deploy them separately:
# Publish API stack
tensor9 stack publish \
  -stackType TerraformWorkspace \
  -stackS3Key api-stack \
  -dir /path/to/api

# Publish worker stack
tensor9 stack publish \
  -stackType TerraformWorkspace \
  -stackS3Key worker-stack \
  -dir /path/to/worker

# Bind both to the same app
tensor9 stack bind -appName my-app -stackType TerraformWorkspace -nativeStackId s3://t9-ctrl-000001/api-stack.tf.tgz
tensor9 stack bind -appName my-app -stackType TerraformWorkspace -nativeStackId s3://t9-ctrl-000001/worker-stack.tf.tgz

# Create releases for each stack
tensor9 stack release create -appName my-app -testApplianceName my-test -vendorVersion "1.0.0" -nativeStackId s3://t9-ctrl-000001/api-stack.tf.tgz
tensor9 stack release create -appName my-app -testApplianceName my-test -vendorVersion "1.0.0" -nativeStackId s3://t9-ctrl-000001/worker-stack.tf.tgz
Each stack compiles and deploys independently, allowing you to update components without redeploying the entire application.

Integration with CI/CD

Tensor9 integrates with standard CI/CD tools and practices. Here’s an example GitHub Actions workflow:
name: Deploy to Test Appliance

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Install Tensor9 CLI
        run: |
          curl https://t9-artifacts-prod-1.s3.us-west-2.amazonaws.com/tensor9-latest > tensor9
          chmod +x tensor9
          sudo mv tensor9 /usr/local/bin/

      - name: Publish origin stack
        env:
          T9_API_KEY: ${{ secrets.T9_API_KEY }}
        run: |
          tensor9 stack publish \
            -stackType TerraformWorkspace \
            -stackS3Key my-stack \
            -dir ./terraform

      - name: Create release
        env:
          T9_API_KEY: ${{ secrets.T9_API_KEY }}
        run: |
          tensor9 stack release create \
            -appName my-app \
            -testApplianceName ci-test \
            -vendorVersion "${GITHUB_SHA:0:7}" \
            -description "CI build from commit ${GITHUB_SHA}"

      - name: Deploy to test appliance
        run: |
          cd ci-test
          tofu init
          tofu apply -auto-approve
You can also use specialized tools like Atlantis or Spacelift for Terraform automation.

Backend configuration

Tensor9 does not modify backend configuration in your origin stack. Any backend configuration you include in your origin stack is preserved in the compiled deployment stack, giving you full control over Terraform state management. You are responsible for managing backend configuration for your deployments. You can include backend configuration directly in your origin stack, or provide it at deployment time: Option 1: Include in origin stack
# In your origin stack's backend.tf
terraform {
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "appliances/${var.instance_id}/terraform.tfstate"
    region         = "us-west-2"
    dynamodb_table = "terraform-locks"
  }
}
This backend configuration will be preserved in the compiled deployment stack. Terraform will use the instance_id variable to ensure each appliance has its own state file. Option 2: Provide at deployment time
# No backend config in origin stack
# Provide via CLI arguments when deploying
tofu init \
  -backend-config="bucket=my-terraform-state" \
  -backend-config="key=appliances/customer-123/terraform.tfstate" \
  -backend-config="region=us-west-2"

tofu apply
Option 3: Separate backend config file
# Create backend.tf in deployment directory after compilation
cat > backend.tf <<EOF
terraform {
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "appliances/customer-123/terraform.tfstate"
    region         = "us-west-2"
    dynamodb_table = "terraform-locks"
  }
}
EOF

tofu init
tofu apply
You have full control over state management and can implement your own strategy (e.g., separate state per customer, state encryption, state locking).

Rollback and recovery

Tensor9 uses a roll forward approach to recovery. If a deployment fails or causes issues, you recover by deploying a new release based on a previous working version of your origin stack.

Example: Recovering from a broken deployment

Suppose you deployed version 1.4.6 and it caused issues. To recover, you roll forward to version 1.4.7 with the previous working configuration. In this example:
  • Version 1.4.5 was the last working version
  • Version 1.4.6 was deployed and caused issues
  • Version 1.4.7 is the new release that restores the 1.4.5 configuration
1

Restore your origin stack to the previous working state

Update your origin stack to the state before the problematic changes in 1.4.6. This could mean:
  • Checking out the git commit from version 1.4.5 (the last working version)
  • Reverting the problematic changes in your repository
  • Restoring from a backup of your infrastructure code
# Example: revert to the commit before the broken changes
git revert abc123
2

Create a new release with an incremented version

Create a new release from the restored origin stack. Note that this is version 1.4.7, not 1.4.5 - you’re rolling forward, not backward:
tensor9 stack release create \
  -appName my-app \
  -testApplianceName my-test \
  -vendorVersion "1.4.7" \
  -description "Roll forward to restore working configuration from 1.4.5"
3

Deploy the new release

Deploy the new release to the appliance:For Terraform/OpenTofu:
cd acme-corp-production
tofu init
tofu apply
Terraform will compute the difference between the current state (1.4.6) and the desired configuration (1.4.7, which restores 1.4.5’s config), reverting any changes introduced in the failed 1.4.6 release.For CloudFormation: The control plane automatically updates the CloudFormation stack when you create the release. Monitor the rollback progress using tensor9 report or CloudFormation stack events. CloudFormation will compute the changes needed to restore the working configuration.
This is a roll forward approach rather than a traditional rollback - you’re creating a new release (1.4.7) that happens to restore a previous working configuration (1.4.5). This ensures all changes go through the same compilation and deployment workflow, maintaining consistency and auditability.

Alternative recovery approaches

For emergency recovery with Terraform/OpenTofu, you can also:
  • Re-deploy a previous deployment stack directory if you’ve retained it (bypasses compilation but uses known-good deployment stack)
  • Use Terraform state management (tofu state pull, tofu state push) to manually revert state (advanced users only)
For CloudFormation, you can:
  • Use CloudFormation stack rollback features in the AWS console or CLI to revert to a previous stack state
  • View stack change sets to understand what changes were applied in each release

Monitoring deployments

Track deployment status and health using several tools:

Tensor9 report

tensor9 report
Shows all appliances, active releases, and deployment status.

Terraform output

After deployment, view outputs defined in your origin stack:
tofu output

Observability sink

Once deployed, your appliance forwards logs, metrics, and traces to your configured observability sink. Monitor application health in your preferred tool (Datadog, New Relic, etc.).

Best practices

Adopt a consistent versioning scheme for the -vendorVersion parameter:
  • Major version (1.0.0 → 2.0.0): Breaking changes
  • Minor version (1.0.0 → 1.1.0): New features, backward compatible
  • Patch version (1.0.0 → 1.0.1): Bug fixes
Always create and validate releases in test appliances before deploying to customer appliances. This catches issues early and reduces customer-facing incidents.
Include meaningful descriptions and notes with every release:
tensor9 stack release create \
  -appName my-app \
  -testApplianceName my-test \
  -vendorVersion "1.2.0" \
  -description "Add user analytics dashboard with real-time metrics" \
  -notes "Deployed by [email protected] after QA sign-off on JIRA-123"
This creates an audit trail and makes it easy to understand what changed in each release.
Document your deployment process, including:
  • Required backend configuration
  • Post-deployment validation steps
  • Rollback procedures
  • Contact information for escalations
Use CI/CD pipelines to automate the publish → release → deploy workflow. This reduces manual errors and ensures consistent deployments across all appliances.
Archive deployment stack directories after successful deployments. This allows quick rollbacks and serves as a historical record of what was deployed.

Troubleshooting

Symptom: tensor9 stack release create fails with compilation errors.Solutions:
  • Run tofu validate on your origin stack to catch syntax errors
  • Check that all required variables are defined
  • Verify artifact references (container images, S3 objects) are accessible
  • Review Tensor9 logs for specific compilation errors
Symptom: tofu apply fails with resource creation errors.Solutions:
  • Check that the appliance has necessary permissions (IAM roles, service accounts)
  • Verify resource names don’t conflict (use instance_id variable)
  • Check cloud provider quotas (e.g., VPC limits, compute limits)
  • Review Terraform error output for specific resource failures
Symptom: Release created but deployment doesn’t occur.For Terraform/OpenTofu - deployment stack doesn’t download:
  • Verify appliance is in “Live” status using tensor9 report
  • Check network connectivity between your environment and control plane
  • Ensure API key is valid: echo $T9_API_KEY
  • Wait a few minutes - compilation can take time for large stacks
For CloudFormation - CloudFormation stack not created in control plane:
  • Verify the control plane has necessary permissions to create CloudFormation stacks
  • Check CloudFormation events in your control plane’s AWS account for errors: aws cloudformation describe-stack-events --stack-name <stack-name>
  • Verify the release was successfully created: tensor9 report -customerName <CUSTOMER_NAME>
  • Check AWS service quotas for CloudFormation stacks in your control plane’s account
Symptom: tofu init fails with backend errors.Solutions:
  • Ensure backend configuration is provided (either in origin stack or at deployment time)
  • If using CLI arguments, verify all required backend parameters are specified
  • Ensure state bucket exists and is accessible
  • Check that backend configuration uses ${var.instance_id} for unique state paths per appliance

Next steps

Now that you understand deployments, explore these related topics: