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).
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
| Property | Type | Required | Description |
|---|
id | string | Auto | Unique identifier |
name | string | Yes | Human-readable display name |
slug | string | Yes | URL-friendly identifier, unique within the workspace |
description | string | No | What this deployment does |
resourceSelector | string | No | CEL expression limiting which resources can be targeted |
jobAgents | array | No | Job agent configurations (see below) |
metadata | object | No | Key-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.
| Field | Type | Required | Description |
|---|
ref | string | Yes | Job agent reference identifier |
config | object | Yes | Agent-specific configuration |
selector | string | No | CEL 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.
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
GitHub Actions Agent
ArgoCD Agent
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
}
resource "ctrlplane_deployment" "operator" {
name = "Weights & Biases Operator"
resource_selector = "resource.version == 'ctrlplane.dev/kubernetes/cluster/v1' && resource.metadata['kubernetes/status'] == 'running'"
metadata = {}
job_agent {
id = ctrlplane_job_agent.argocd.id
argocd {
template = file("${path.module}/templates/application.yaml")
}
}
}
resource "ctrlplane_deployment_system_link" "operator" {
deployment_id = ctrlplane_deployment.operator.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
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"
}
}
| Attribute | Type | Description |
|---|
owner | string | GitHub repository owner |
repo | string | GitHub repository name |
workflow_id | int | GitHub Actions workflow ID |
ref | string | Git ref to run on (defaults to "main") |
installation_id | int | GitHub 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"
}
}
| Attribute | Type | Description |
|---|
template | string | ArgoCD Application template |
server_url | string | ArgoCD server address |
api_key | string | ArgoCD API token (sensitive) |
job_agent {
id = ctrlplane_job_agent.tfc.id
terraform_cloud {
organization = "my-org"
template = file("${path.module}/workspace.json")
token = var.tfc_token
}
}
| Attribute | Type | Description |
|---|
organization | string | Terraform Cloud organization name |
template | string | Workspace template |
address | string | Terraform Cloud address |
token | string | API 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
| Property | Type | Required | Description |
|---|
id | string | Auto | Unique identifier |
deploymentId | string | Auto | Parent deployment |
tag | string | Yes | Version tag (e.g., "v1.2.3", git SHA) |
name | string | No | Human-readable name |
status | string | No | building, ready, or failed |
config | object | No | General version configuration, visible in UI |
jobAgentConfig | object | No | Execution config (overrides deployment’s config) |
metadata | object | No | Custom key-value pairs |
createdAt | string | Auto | Creation 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:
- CI starts building → Create version with status
building
- Build succeeds → Update status to
ready
- Ctrlplane creates releases/jobs for
ready versions
Or simpler:
- 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.
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:
- API Service → Development → dev-cluster
- API Service → Staging → staging-cluster
- API Service → Production → prod-cluster-1
- API Service → Production → prod-cluster-2
- 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}
| Resource | Description |
|---|
ctrlplane_deployment | The deployment itself |
ctrlplane_deployment_system_link | Links a deployment to a system |
ctrlplane_deployment_variable | Defines a deployment variable |
ctrlplane_deployment_variable_value | Sets a value for a variable (with selectors) |
Viewing Deployment Status
Via Web UI
- Navigate to the deployment
- 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
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.
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
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