Skip to main content
A Deployment defines what you want to deploy and how it gets deployed. It represents a service or application along with its job agent configuration — connecting the what (your service and its versions) with the how (the agent and workflow that executes the deployment).
In Ctrlplane’s mental model: Deployments = what & how, Environments = where, Policies = when.

What is a Deployment?

A deployment is a logical unit of software that you want to orchestrate:
  • API Service - Your backend API
  • Frontend Application - Web or mobile frontend
  • Background Worker - Async job processor
  • Database Migration - Schema changes
  • Configuration Update - Infrastructure configuration
Each deployment can have multiple versions (builds, releases) that get deployed to different environments and resources.

Deployment Properties

PropertyTypeRequiredDescription
idstringAutoUnique identifier
namestringYesHuman-readable display name
slugstringYesURL-friendly identifier, unique within the workspace
descriptionstringNoWhat this deployment does
resourceSelectorstringNoCEL expression limiting which resources can be targeted
jobAgentsarrayNoJob agent configurations (see below)
metadataobjectNoKey-value pairs for classification

name

Human-readable display name for the deployment. Examples: “API Service”, “Frontend Application”, “Payment Processor”

slug

URL-friendly identifier, unique within the workspace. In the Terraform provider, the slug is auto-generated from the name field. Examples: api-service, frontend-app, payment-processor

resourceSelector

A CEL expression that limits which resources this deployment can target. If specified, only resources matching this expression will have release targets created. Examples:
resource.kind == "Kubernetes"
resource.version == 'ctrlplane.dev/kubernetes/cluster/v1' && resource.metadata['kubernetes/status'] == 'running'
resource.metadata['region'] == 'us-east-1'
If not specified, the deployment can target any resource in its environments.

jobAgents

An array of job agent configurations that execute deployment jobs. Each entry specifies which agent to use and how to configure it, with optional routing via selectors.
FieldTypeRequiredDescription
refstringYesJob agent reference identifier
configobjectYesAgent-specific configuration
selectorstringNoCEL expression for routing to specific resources
When multiple job agents are configured, the selector field determines which agent handles which resources. This enables patterns like using ArgoCD for some clusters and GitHub Actions for others within the same deployment.

metadata

Optional key-value pairs for classification and policy matching:
{
  "team": "backend",
  "language": "nodejs",
  "tier": "critical"
}
Metadata can be referenced in policy selectors (e.g., deployment.metadata['tier'] == 'critical').

Creating a Deployment

Via Terraform

resource "ctrlplane_deployment" "api" {
  name              = "API Service"
  resource_selector = "resource.version == 'ctrlplane.dev/kubernetes/cluster/v1' && resource.metadata['kubernetes/status'] == 'running'"

  metadata = {
    team    = "backend"
    service = "api"
  }

  job_agent {
    id = ctrlplane_job_agent.github.id

    github {
      owner       = "my-org"
      repo        = "api-service"
      workflow_id = 12345678
    }
  }
}

resource "ctrlplane_deployment_system_link" "api" {
  deployment_id = ctrlplane_deployment.api.id
  system_id     = ctrlplane_system.main.id
}
Deployments must be linked to a system using ctrlplane_deployment_system_link. This is a separate resource that associates a deployment with a system.

Via REST API

curl -X POST https://api.ctrlplane.com/v1/workspaces/{workspaceId}/deployments \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "API Service",
    "slug": "api-service",
    "description": "Main backend API service",
    "resourceSelector": "resource.kind == '\''Kubernetes'\''",
    "jobAgents": [
      {
        "ref": "github-actions-agent",
        "config": {
          "workflow": "deploy.yml",
          "owner": "my-org",
          "repo": "api-service"
        }
      }
    ],
    "metadata": {
      "team": "backend",
      "language": "nodejs"
    }
  }'
The API returns 202 Accepted with the deployment id. Reads are available immediately via GET /v1/workspaces/{workspaceId}/deployments/{deploymentId}.

Via CLI (YAML)

# deployment.yaml
type: Deployment
name: API Service
slug: api-service
description: Main backend API service
jobAgent:
  ref: github-actions-agent
jobAgentConfig:
  workflow: deploy.yml
  owner: my-org
  repo: api-service
resourceSelector: resource.kind == "Kubernetes"
ctrlc apply -f deployment.yaml

Multiple Deployments

resource "ctrlplane_deployment" "api" {
  name              = "API Service"
  resource_selector = "resource.kind == 'Kubernetes'"
  metadata          = { service = "api" }

  job_agent {
    id = ctrlplane_job_agent.github.id
    github {
      owner       = "my-org"
      repo        = "api-service"
      workflow_id = 12345678
    }
  }
}

resource "ctrlplane_deployment" "frontend" {
  name              = "Frontend App"
  resource_selector = "resource.kind == 'Kubernetes'"
  metadata          = { service = "frontend" }

  job_agent {
    id = ctrlplane_job_agent.github.id
    github {
      owner       = "my-org"
      repo        = "frontend-app"
      workflow_id = 87654321
    }
  }
}

resource "ctrlplane_deployment_system_link" "api" {
  deployment_id = ctrlplane_deployment.api.id
  system_id     = ctrlplane_system.main.id
}

resource "ctrlplane_deployment_system_link" "frontend" {
  deployment_id = ctrlplane_deployment.frontend.id
  system_id     = ctrlplane_system.main.id
}

Job Agent Configuration

Each job_agent block (Terraform) or entry in the jobAgents array (API) configures how a specific agent executes deployments. The Terraform provider supports typed provider blocks for each agent type.

GitHub Actions

job_agent {
  id = ctrlplane_job_agent.github.id

  github {
    owner       = "my-org"
    repo        = "api-service"
    workflow_id = 12345678
    ref         = "main"
  }
}
AttributeTypeDescription
ownerstringGitHub repository owner
repostringGitHub repository name
workflow_idintGitHub Actions workflow ID
refstringGit ref to run on (defaults to "main")
installation_idintGitHub App installation ID

ArgoCD

job_agent {
  id = ctrlplane_job_agent.argocd.id

  argocd {
    template   = file("${path.module}/application.yaml")
    server_url = "https://argocd.example.com"
  }
}
AttributeTypeDescription
templatestringArgoCD Application template
server_urlstringArgoCD server address
api_keystringArgoCD API token (sensitive)

Terraform Cloud

job_agent {
  id = ctrlplane_job_agent.tfc.id

  terraform_cloud {
    organization = "my-org"
    template     = file("${path.module}/workspace.json")
    token        = var.tfc_token
  }
}
AttributeTypeDescription
organizationstringTerraform Cloud organization name
templatestringWorkspace template
addressstringTerraform Cloud address
tokenstringAPI token (sensitive)

Agent Routing with Selectors

When a deployment uses multiple job agents, use selector to route to specific resources:
resource "ctrlplane_deployment" "app" {
  name              = "Application"
  resource_selector = "resource.kind == 'Kubernetes'"

  job_agent {
    id       = ctrlplane_job_agent.argocd.id
    selector = "resource.metadata['cluster-type'] == 'managed'"

    argocd {
      template = file("${path.module}/managed-app.yaml")
    }
  }

  job_agent {
    id       = ctrlplane_job_agent.github.id
    selector = "resource.metadata['cluster-type'] == 'self-hosted'"

    github {
      owner       = "my-org"
      repo        = "app-deploy"
      workflow_id = 12345678
    }
  }
}

Deployment Versions

Versions represent specific builds or releases of your deployment. They are typically created by your CI system after building.

Version Properties

PropertyTypeRequiredDescription
idstringAutoUnique identifier
deploymentIdstringAutoParent deployment
tagstringYesVersion tag (e.g., "v1.2.3", git SHA)
namestringNoHuman-readable name
statusstringNobuilding, ready, or failed
configobjectNoGeneral version configuration, visible in UI
jobAgentConfigobjectNoExecution config (overrides deployment’s config)
metadataobjectNoCustom key-value pairs
createdAtstringAutoCreation timestamp

Creating Versions (from CI)

After your CI builds an artifact, create a version in Ctrlplane:
# GitHub Actions example
- name: Create Ctrlplane version
  env:
    CTRLPLANE_API_KEY: ${{ secrets.CTRLPLANE_API_KEY }}
  run: |
    ctrlc api upsert version \
      --workspace ${{ vars.CTRLPLANE_WORKSPACE }} \
      --deployment ${{ vars.CTRLPLANE_DEPLOYMENT_ID }} \
      --tag ${{ github.sha }} \
      --name "Build #${{ github.run_number }}" \
      --metadata git/commit=${{ github.sha }} \
      --metadata git/branch=${{ github.ref_name }} \
      --metadata build/number=${{ github.run_number }}
Or via the REST API:
curl -X POST https://api.ctrlplane.com/v1/workspaces/{workspaceId}/deployments/{deploymentId}/versions \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "tag": "v1.2.3",
    "name": "Release 1.2.3",
    "status": "ready",
    "config": {
      "buildNumber": "456"
    },
    "jobAgentConfig": {
      "imageTag": "my-org/api-service:v1.2.3"
    },
    "metadata": {
      "git_commit": "abc123def",
      "git_branch": "main"
    }
  }'

Version Status

Versions have a status field:
  • building - Version is being built (won’t be deployed yet)
  • ready - Version is ready for deployment (default for policies)
  • failed - Build failed (won’t be deployed)
Workflow:
  1. CI starts building → Create version with status building
  2. Build succeeds → Update status to ready
  3. Ctrlplane creates releases/jobs for ready versions
Or simpler:
  1. Build completes → Create version with status ready immediately

Version Config vs Job Agent Config

config: General version configuration, visible in UI
{
  "buildNumber": "456",
  "gitCommit": "abc123"
}
jobAgentConfig: Specific configuration for job execution (merged with the deployment’s job agent config at runtime)
{
  "imageTag": "my-org/api-service:v1.2.3",
  "helmValues": {
    "replicas": 3
  }
}

Deployment Variables

Variables allow environment-specific or resource-specific configuration that gets resolved at job creation time and passed to the job agent.

Via Terraform

The Terraform provider has dedicated resources for deployment variables and their values:
resource "ctrlplane_deployment_variable" "replica_count" {
  deployment_id = ctrlplane_deployment.api.id
  key           = "REPLICA_COUNT"
  description   = "Number of replicas to run"
  default_value = "1"
}

resource "ctrlplane_deployment_variable_value" "replica_count_prod" {
  deployment_id     = ctrlplane_deployment.api.id
  variable_id       = ctrlplane_deployment_variable.replica_count.id
  priority          = 0
  resource_selector = "resource.metadata['environment'] == 'production'"
  literal_value     = "5"
}

Reference Values

Variable values can reference data from the workspace or resource metadata instead of using literal values:
resource "ctrlplane_deployment_variable" "size" {
  deployment_id = ctrlplane_deployment.redis.id
  key           = "size"
  description   = "The size of the Redis deployment"
  default_value = "small"
}

resource "ctrlplane_deployment_variable_value" "size_from_workspace" {
  deployment_id     = ctrlplane_deployment.redis.id
  variable_id       = ctrlplane_deployment_variable.size.id
  priority          = 0
  resource_selector = "resource.version == 'ctrlplane.dev/kubernetes/cluster/v1'"

  reference_value = {
    reference = "workspace"
    path      = ["metadata", "size"]
  }
}

Via REST API

Create a variable:
curl -X PUT https://api.ctrlplane.com/v1/workspaces/{workspaceId}/deployments/{deploymentId}/variables/{variableId} \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "key": "REPLICA_COUNT",
    "description": "Number of replicas to run"
  }'
Set an environment-specific value:
curl -X PUT https://api.ctrlplane.com/v1/workspaces/{workspaceId}/deployments/{deploymentId}/variables/{variableId}/values/{valueId} \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "priority": 0,
    "value": "5"
  }'

Using Variables in Jobs

Variables are resolved during job creation and passed to the job agent:
# In GitHub Actions workflow
- name: Get job inputs
  uses: ctrlplanedev/get-job-inputs@v1
  id: job
  with:
    job_id: ${{ inputs.job_id }}

- name: Deploy with variables
  run: |
    echo "Replicas: ${{ steps.job.outputs.variable_REPLICA_COUNT }}"
    helm upgrade my-app ./chart \
      --set replicaCount=${{ steps.job.outputs.variable_REPLICA_COUNT }}

Release Targets

When you create a deployment, Ctrlplane automatically creates release targets by crossing the deployment with environments and resources. Formula: Deployment × Environment × Resource = Release Targets Example: Given:
  • Deployment: “API Service”
  • Environments: Development, Staging, Production
  • Resources in Production: 3 clusters
Release Targets Created:
  1. API Service → Development → dev-cluster
  2. API Service → Staging → staging-cluster
  3. API Service → Production → prod-cluster-1
  4. API Service → Production → prod-cluster-2
  5. API Service → Production → prod-cluster-3
Each release target can receive deployment versions independently.

Filtering Release Targets

Use the deployment’s resourceSelector to limit which targets are created:
resource.version == 'ctrlplane.dev/kubernetes/cluster/v1' && resource.metadata['kubernetes/status'] == 'running'
Only resources matching this CEL expression will have release targets for this deployment.

Deployment Lifecycle

1. Create Deployment

Define the deployment in Ctrlplane with job agent configuration.

2. CI Builds and Creates Version

Your CI pipeline builds the artifact and creates a deployment version.

3. Ctrlplane Evaluates Policies

Ctrlplane checks policies (approvals, environment progression, etc.).

4. Jobs Created

For each release target that should receive the version, a job is created.

5. Job Agent Executes

The configured job agent picks up the job and executes the deployment.

6. Status Updated

The job reports status back to Ctrlplane.

REST API Reference

Deployment CRUD

Create:
POST /v1/workspaces/{workspaceId}/deployments
Get:
GET /v1/workspaces/{workspaceId}/deployments/{deploymentId}
Update:
PUT /v1/workspaces/{workspaceId}/deployments/{deploymentId}
Delete:
DELETE /v1/workspaces/{workspaceId}/deployments/{deploymentId}
List:
GET /v1/workspaces/{workspaceId}/deployments

Versions

Create version:
POST /v1/workspaces/{workspaceId}/deployments/{deploymentId}/versions
List versions:
GET /v1/workspaces/{workspaceId}/deployments/{deploymentId}/versions
Update version:
PATCH /v1/workspaces/{workspaceId}/deployment-versions/{deploymentVersionId}

Variables

Upsert variable:
PUT /v1/workspaces/{workspaceId}/deployments/{deploymentId}/variables/{variableId}
Upsert variable value:
PUT /v1/workspaces/{workspaceId}/deployments/{deploymentId}/variables/{variableId}/values/{valueId}

Terraform Provider Reference

ResourceDescription
ctrlplane_deploymentThe deployment itself
ctrlplane_deployment_system_linkLinks a deployment to a system
ctrlplane_deployment_variableDefines a deployment variable
ctrlplane_deployment_variable_valueSets a value for a variable (with selectors)

Viewing Deployment Status

Via Web UI

  1. Navigate to the deployment
  2. See tabs:
    • Versions: All versions created
    • Releases: Active releases across targets
    • Jobs: Execution history
    • Variables: Configured variables

Via API

Get deployment details:
curl https://api.ctrlplane.com/v1/workspaces/{workspaceId}/deployments/{deploymentId} \
  -H "Authorization: Bearer $TOKEN"
List versions:
curl https://api.ctrlplane.com/v1/workspaces/{workspaceId}/deployments/{deploymentId}/versions \
  -H "Authorization: Bearer $TOKEN"

Common Patterns

Microservices

resource "ctrlplane_deployment" "user_service" {
  name              = "User Service"
  resource_selector = "resource.kind == 'Kubernetes'"
  metadata          = { service = "user" }

  job_agent {
    id = ctrlplane_job_agent.argocd.id
    argocd { template = file("${path.module}/user-service.yaml") }
  }
}

resource "ctrlplane_deployment" "order_service" {
  name              = "Order Service"
  resource_selector = "resource.kind == 'Kubernetes'"
  metadata          = { service = "order" }

  job_agent {
    id = ctrlplane_job_agent.argocd.id
    argocd { template = file("${path.module}/order-service.yaml") }
  }
}

resource "ctrlplane_deployment" "payment_service" {
  name              = "Payment Service"
  resource_selector = "resource.kind == 'Kubernetes'"
  metadata          = { service = "payment" }

  job_agent {
    id = ctrlplane_job_agent.argocd.id
    argocd { template = file("${path.module}/payment-service.yaml") }
  }
}

Database + Application

resource "ctrlplane_deployment" "database_migration" {
  name              = "Database Migration"
  resource_selector = "resource.kind == 'Kubernetes'"

  job_agent {
    id = ctrlplane_job_agent.github.id
    github {
      owner       = "my-org"
      repo        = "migrations"
      workflow_id = 11111111
    }
  }
}

resource "ctrlplane_deployment" "api_service" {
  name              = "API Service"
  resource_selector = "resource.kind == 'Kubernetes'"

  job_agent {
    id = ctrlplane_job_agent.github.id
    github {
      owner       = "my-org"
      repo        = "api-service"
      workflow_id = 22222222
    }
  }
}
Use a Deployment Dependency policy to ensure migrations complete before the API deploys.

Multi-Platform

resource "ctrlplane_deployment" "web_service" {
  name              = "Web Service"
  resource_selector = "resource.kind == 'Kubernetes'"

  job_agent {
    id = ctrlplane_job_agent.argocd.id
    argocd { template = file("${path.module}/web.yaml") }
  }
}

resource "ctrlplane_deployment" "lambda_function" {
  name              = "Lambda Function"
  resource_selector = "resource.kind == 'AWSLambda'"

  job_agent {
    id = ctrlplane_job_agent.github.id
    github {
      owner       = "my-org"
      repo        = "lambda-deploy"
      workflow_id = 33333333
    }
  }
}

Best Practices

Naming

Good Names:
  • ✅ “API Service”
  • ✅ “Frontend Application”
  • ✅ “Payment Processor”
Good Slugs:
  • api-service
  • frontend-app
  • payment-processor
Avoid:
  • ❌ “Service” (too generic)
  • ❌ “api_service” (use hyphens)
  • ❌ “API-SERVICE” (use lowercase)

Job Agent Configuration

Do:
  • ✅ Use version’s jobAgentConfig for version-specific values (like image tags)
  • ✅ Use deployment’s job agent config for stable configuration
  • ✅ Keep sensitive data in secrets (not in config)
  • ✅ Use agent selector when routing to different resource types

Version Tags

Good Tags:
  • v1.2.3 (semantic version)
  • 2024-01-15-prod (date-based)
  • abc123def (git commit)
Avoid:
  • latest (not specific)
  • prod (not a version)
  • test (too vague)

Resource Selectors

Use CEL resource selectors to:
  • Limit Kubernetes deployments to Kubernetes resources
  • Filter by resource status (e.g., resource.metadata['status'] == 'running')
  • Separate platform-specific deployments
  • Control which resources can receive a deployment

Troubleshooting

No release targets created

  • Check resourceSelector CEL expression matches some resources
  • Verify environments have matching resources
  • Ensure deployment is linked to a system (via ctrlplane_deployment_system_link in Terraform)

Jobs not being created for new versions

  • Check version status is ready
  • Review policies for denials/pending approvals
  • Verify job agent is configured
  • Check release target exists

Job agent config not working

  • Verify job agent block matches the agent type (e.g., github {} for GitHub Actions agents)
  • Check version’s jobAgentConfig overrides correctly
  • Review job agent logs for configuration errors

Next Steps