Overview
This quickstart guides you through building and deploying your own application to Control Plane. You’ll containerize a sample Node.js application, push it to your org’s private image registry at Control Plane, and deploy it as a workload.
What you’ll accomplish:
Install the Control Plane CLI
Containerize a sample application using Buildpacks
Push the image to your organization’s private registry
Deploy the application as a workload
Prerequisites
Step 1: Install the CLI
The CLI is required to build and push images to your org’s private registry at Control Plane.
npm install -g @controlplane/cli
brew tap controlplane-com/cpln && brew install cpln
See the Installation page to download the binary for your platform.
Verify the installation:
Step 2: Authenticate
Log in to Control Plane:
This opens your browser for authentication. After successful login, set your default organization:
cpln profile update default --org YOUR_ORG_NAME
Step 3: Download the Sample Application
Download and extract the sample Node.js application:
Extract the archive and navigate to the directory:
macOS / Linux
Windows PowerShell
tar -xvf cpln_app.tgz && cd cpln_app
Expand-Archive cpln_app.zip && cd cpln_app
The application is a web app that displays the environment variables of the running process, including Control Plane variables such as cloud provider and location.
Step 4: Build and Push the Image
Build and push the image to Control Plane’s private registry:
cpln image build --name my-app:1.0 --push
The cpln image build command uses Buildpacks to automatically detect your application type and build the image. No Dockerfile required! If your application has an existing Dockerfile, the command will use that instead.
The image is now available in your organization’s private registry at //image/my-app:1.0.
Console UI
CLI
Terraform
Pulumi
Step 5: Create the Workload
Navigate to Workloads
Click Workloads in the left menu, then click New.
Configure workload basics
Enter the name my-app
Select quickstart-gvc from the GVC dropdown
Configure container
Click Containers in the left pane
Select Control Plane as the image source
In the select image dropdown, type my-app and select my-app:1.0
Under Ports, set Protocol to http and Number to 8080
Configure firewall and create
Click Firewall in the left pane
Click Make Public
Click Create
Step 6: Access Your Application
Wait for deployment
The workload shows Ready in Workload Health (1-2 minutes).
Open the application
Click Open next to the Canonical Endpoint. Your application displays the Control Plane environment variables including the location and provider.
Step 5: Create the Workload If you completed Quickstart 1, use the existing GVC: cpln workload create --name my-app \
--image //image/my-app:1.0 \
--gvc quickstart-gvc \
--port 8080 \
--public
The //image/ prefix tells Control Plane to pull from your organization’s private registry.
Step 6: Access Your Application Wait for the workload to become ready: cpln workload get my-app --gvc quickstart-gvc
Once ready, open the application: cpln workload open my-app --gvc quickstart-gvc
Step 5: Define the Workload Add to your existing main.tf from Quickstart 1: resource "cpln_workload" "my_app" {
gvc = cpln_gvc . quickstart . name
name = "my-app"
type = "standard"
container {
name = "main"
image = "/org/YOUR_ORG_NAME/image/my-app:1.0"
cpu = "50m"
memory = "128Mi"
ports {
protocol = "http"
number = 8080
}
}
options {
capacity_ai = true
timeout_seconds = 5
autoscaling {
metric = "disabled"
target = 95
min_scale = 1
max_scale = 1
}
}
firewall_spec {
external {
inbound_allow_cidr = [ "0.0.0.0/0" ]
}
}
}
output "my_app_endpoint" {
value = cpln_workload . my_app . status [ 0 ] . canonical_endpoint
}
Replace YOUR_ORG_NAME with your organization name. The full image path is required for Terraform.
Apply the configuration: Step 6: Access Your Application Open the endpoint URL from the Terraform output: Step 5: Define the Workload Add to your existing Pulumi project from Quickstart 1: // My App workload
const myApp = new cpln . Workload ( "my-app" , {
gvc: gvc . name ,
name: "my-app" ,
type: "standard" ,
containers: [
{
name: "main" ,
image: "/org/YOUR_ORG_NAME/image/my-app:1.0" ,
cpu: "50m" ,
memory: "128Mi" ,
ports: [{ protocol: "http" , number: 8080 }],
},
],
options: {
capacityAi: true ,
timeoutSeconds: 5 ,
autoscaling: {
metric: "disabled" ,
target: 95 ,
minScale: 1 ,
maxScale: 1 ,
},
},
firewallSpec: {
external: {
inboundAllowCidrs: [ "0.0.0.0/0" ],
},
},
});
export const myAppEndpoint = myApp . statuses . apply (
( s ) => s ?.[ 0 ]?. canonicalEndpoint ?? "pending"
);
# My App workload
my_app = cpln.Workload( "my-app" ,
gvc = gvc.name,
name = "my-app" ,
type = "standard" ,
containers = [cpln.WorkloadContainerArgs(
name = "main" ,
image = "/org/YOUR_ORG_NAME/image/my-app:1.0" ,
cpu = "50m" ,
memory = "128Mi" ,
ports = [cpln.WorkloadContainerPortArgs(
protocol = "http" ,
number = 8080 ,
)],
)],
options = cpln.WorkloadOptionsArgs(
capacity_ai = True ,
timeout_seconds = 5 ,
autoscaling = cpln.WorkloadOptionsAutoscalingArgs(
metric = "disabled" ,
target = 95 ,
min_scale = 1 ,
max_scale = 1 ,
),
),
firewall_spec = cpln.WorkloadFirewallSpecArgs(
external = cpln.WorkloadFirewallSpecExternalArgs(
inbound_allow_cidrs = [ "0.0.0.0/0" ],
),
))
pulumi.export( "my_app_endpoint" , my_app.statuses[ 0 ].canonical_endpoint)
// My App workload
myApp , err := cpln . NewWorkload ( ctx , "my-app" , & cpln . WorkloadArgs {
Gvc : gvc . Name ,
Name : pulumi . String ( "my-app" ),
Type : pulumi . String ( "standard" ),
Containers : cpln . WorkloadContainerArray {
& cpln . WorkloadContainerArgs {
Name : pulumi . String ( "main" ),
Image : pulumi . String ( "/org/YOUR_ORG_NAME/image/my-app:1.0" ),
Cpu : pulumi . String ( "50m" ),
Memory : pulumi . String ( "128Mi" ),
Ports : cpln . WorkloadContainerPortArray {
& cpln . WorkloadContainerPortArgs {
Protocol : pulumi . String ( "http" ),
Number : pulumi . Int ( 8080 ),
},
},
},
},
Options : & cpln . WorkloadOptionsArgs {
CapacityAi : pulumi . Bool ( true ),
TimeoutSeconds : pulumi . Int ( 5 ),
Autoscaling : & cpln . WorkloadOptionsAutoscalingArgs {
Metric : pulumi . String ( "disabled" ),
Target : pulumi . Int ( 95 ),
MinScale : pulumi . Int ( 1 ),
MaxScale : pulumi . Int ( 1 ),
},
},
FirewallSpec : & cpln . WorkloadFirewallSpecArgs {
External : & cpln . WorkloadFirewallSpecExternalArgs {
InboundAllowCidrs : pulumi . StringArray { pulumi . String ( "0.0.0.0/0" )},
},
},
})
if err != nil {
return err
}
ctx . Export ( "my_app_endpoint" , myApp . Statuses . Index ( pulumi . Int ( 0 )). CanonicalEndpoint ())
// My App workload
var myApp = new Workload ( "my-app" , new WorkloadArgs
{
Gvc = gvc . Name ,
Name = "my-app" ,
Type = "standard" ,
Containers = new []
{
new WorkloadContainerArgs
{
Name = "main" ,
Image = "/org/YOUR_ORG_NAME/image/my-app:1.0" ,
Cpu = "50m" ,
Memory = "128Mi" ,
Ports = new []
{
new WorkloadContainerPortArgs
{
Protocol = "http" ,
Number = 8080
}
}
}
},
Options = new WorkloadOptionsArgs
{
CapacityAi = true ,
TimeoutSeconds = 5 ,
Autoscaling = new WorkloadOptionsAutoscalingArgs
{
Metric = "disabled" ,
Target = 95 ,
MinScale = 1 ,
MaxScale = 1
}
},
FirewallSpec = new WorkloadFirewallSpecArgs
{
External = new WorkloadFirewallSpecExternalArgs
{
InboundAllowCidrs = new [] { "0.0.0.0/0" }
}
}
});
Add to your return dictionary: [" my_app_endpoint "] = myApp . Statuses . Apply ( s => s [ 0 ]. CanonicalEndpoint )
Replace YOUR_ORG_NAME with your organization name. The full image path is required for Pulumi.
Deploy the configuration: Step 6: Access Your Application Open the endpoint URL from the Pulumi output:
Updating Your Application
To deploy a new version, update your code and build with a new tag:
cpln image build --name my-app:1.1 --push
Then update your workload to use the new image:
Console UI
CLI
Terraform
Pulumi
Navigate to your workload, click Containers, select the new tag, and click Update.
cpln workload update my-app --gvc quickstart-gvc --set spec.containers.my-app.image=//image/my-app:1.1
Update the image attribute in your Terraform configuration and run terraform apply.
Update the image property in your Pulumi code and run pulumi up.
What You’ve Learned
Private registry stores your images securely within your organization
Image references use //image/ prefix for CLI or full path for IaC tools
Buildpacks automatically containerize your application without a Dockerfile
Environment variables like CPLN_LOCATION and CPLN_PROVIDER are injected automatically
Continue
3. Configure a Custom Domain Map your own domain to your workload for production-ready URLs.
Clean Up
To delete just the my-app workload created in this quickstart:
Console UI
CLI
Terraform
Pulumi
Navigate to Workloads, select my-app, and from Actions click Delete.
cpln workload delete my-app --gvc quickstart-gvc
Remove the cpln_workload.my_app resource from your main.tf and run: Remove the my-app workload from your code and run:
To delete images, navigate to Images in the Console, select the image name, and from Actions click Delete to remove all tags. With the CLI, delete each tag individually: cpln image delete my-app:1.0.