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

# 2. Deploy Your Own Application

> Containerize your own application, push it to the Control Plane private image registry, and deploy it as a workload.

## 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 on 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

* Completed [Quickstart 1](/quickstart/quick-start-1-deploy-workload) (or have an existing [org](/guides/create-org) with a GVC)
* [Docker](https://www.docker.com/get-started) installed and running

## Step 1: Install the CLI

The CLI is required to build and push images to your org's private registry in Control Plane.

<Tabs>
  <Tab title="npm">
    ```bash theme={null}
    npm install -g @controlplane/cli
    ```

    <Note>Requires [Node.js](https://nodejs.org/en/download/) version 18+ (an active LTS release is recommended).</Note>
  </Tab>

  <Tab title="Homebrew">
    ```bash theme={null}
    brew tap controlplane-com/cpln && brew install cpln
    ```
  </Tab>

  <Tab title="Binary">
    See the [Installation page](/cli-reference/installation#binary) to download the binary for your platform.
  </Tab>
</Tabs>

Verify the installation:

```bash theme={null}
cpln --version
```

## Step 2: Authenticate

Log in to Control Plane:

```bash theme={null}
cpln login
```

This opens your browser to authenticate. After successful login, set your default organization:

```bash theme={null}
cpln profile update default --org YOUR_ORG_NAME
```

## Step 3: Download the Sample Application

Download and extract the sample Node.js application:

<CardGroup cols={2}>
  <Card title="macOS / Linux" icon="download" href="https://controlplane.com/downloads/cpln_app.tgz">
    Sample Application
  </Card>

  <Card title="Windows" icon="download" href="https://controlplane.com/downloads/cpln_app.zip">
    Sample Application
  </Card>
</CardGroup>

Extract the archive and navigate to the directory:

<Tabs>
  <Tab title="macOS / Linux">
    ```bash theme={null}
    tar -xvf cpln_app.tgz && cd cpln_app
    ```
  </Tab>

  <Tab title="Windows PowerShell">
    ```powershell theme={null}
    Expand-Archive cpln_app.zip && cd cpln_app
    ```
  </Tab>
</Tabs>

The application is a web app that displays runtime environment variables, including Control Plane-injected 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:

```bash theme={null}
cpln image build --name my-app:1.0 --push
```

<Tip>
  The `cpln image build` command uses [Buildpacks](https://buildpacks.io) to automatically detect your application type and build the image. No Dockerfile is required. If your application has an existing Dockerfile, the command uses that instead.
</Tip>

The image is now available in your organization's private registry as `//image/my-app:1.0`.

<Tabs>
  <Tab title="Console UI">
    ## Step 5: Create the Workload

    <Steps>
      <Step title="Navigate to Workloads">
        Click `Workloads` in the left menu, then click `New`.
      </Step>

      <Step title="Configure workload basics">
        1. Enter the name `my-app`
        2. Select `quickstart-gvc` from the GVC dropdown
      </Step>

      <Step title="Configure container">
        1. Click `Containers` in the left pane
        2. Select `Control Plane` as the image source
        3. In the select image dropdown, type `my-app` and select `my-app:1.0`
        4. Under `Ports`, set Protocol to `http` and Number to `8080`
      </Step>

      <Step title="Configure firewall and create">
        1. Click `Firewall` in the left pane
        2. Click `Make Public`
        3. Click `Create`
      </Step>
    </Steps>

    ## Step 6: Access Your Application

    <Steps>
      <Step title="Wait for deployment">
        The workload status changes to show `Ready` under Health after 1-2 minutes.
      </Step>

      <Step title="Open the application">
        Under `Endpoints`, click the link next to `Canonical Endpoint`. Your application displays the Control Plane environment variables, including the location and provider.
      </Step>
    </Steps>
  </Tab>

  <Tab title="CLI">
    ## Step 5: Create the Workload

    If you completed Quickstart 1, use the existing GVC:

    ```bash theme={null}
    cpln workload create --name my-app \
      --image //image/my-app:1.0 \
      --gvc quickstart-gvc \
      --port 8080 \
      --public
    ```

    <Note>
      The `//image/` prefix tells Control Plane to pull the image from your organization's private registry.
    </Note>

    ## Step 6: Access Your Application

    Wait for the workload to become ready:

    ```bash theme={null}
    cpln workload get my-app --gvc quickstart-gvc
    ```

    Once ready, open the application:

    ```bash theme={null}
    cpln workload open my-app --gvc quickstart-gvc
    ```
  </Tab>

  <Tab title="Terraform">
    ## Step 5: Define the Workload

    Add to your existing `main.tf` from Quickstart 1:

    ```hcl theme={null}
    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
    }
    ```

    <Note>
      Replace `YOUR_ORG_NAME` with your organization name. The full image path is required for Terraform.
    </Note>

    Apply the configuration:

    ```bash theme={null}
    terraform apply
    ```

    ## Step 6: Access Your Application

    Open the endpoint URL from the Terraform output:

    ```bash theme={null}
    terraform output
    ```
  </Tab>

  <Tab title="Pulumi">
    ## Step 5: Define the Workload

    Add to your existing Pulumi project from Quickstart 1:

    <Tabs>
      <Tab title="TypeScript">
        ```typescript theme={null}
        // 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"
        );
        ```
      </Tab>

      <Tab title="Python">
        ```python theme={null}
        # 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)
        ```
      </Tab>

      <Tab title="Go">
        ```go theme={null}
        // 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())
        ```
      </Tab>

      <Tab title="C#">
        ```csharp theme={null}
        // 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:

        ```csharp theme={null}
        ["my_app_endpoint"] = myApp.Statuses.Apply(s => s[0].CanonicalEndpoint)
        ```
      </Tab>
    </Tabs>

    <Note>
      Replace `YOUR_ORG_NAME` with your organization name. The full image path is required for Pulumi.
    </Note>

    Deploy the configuration:

    ```bash theme={null}
    pulumi up
    ```

    ## Step 6: Access Your Application

    Open the endpoint URL from the Pulumi output:

    ```bash theme={null}
    pulumi stack output
    ```
  </Tab>
</Tabs>

## Updating Your Application

To deploy a new version, update your code and build with a new image tag:

```bash theme={null}
cpln image build --name my-app:1.1 --push
```

Then update your workload to use the new image:

<Tabs>
  <Tab title="Console UI">
    Navigate to your workload, click `Containers`, select the new tag, and click `Update`.
  </Tab>

  <Tab title="CLI">
    ```bash theme={null}
    cpln workload update my-app --gvc quickstart-gvc --set spec.containers.my-app.image=//image/my-app:1.1
    ```
  </Tab>

  <Tab title="Terraform">
    Update the `image` attribute in your Terraform configuration and run `terraform apply`.
  </Tab>

  <Tab title="Pulumi">
    Update the `image` property in your Pulumi code and run `pulumi up`.
  </Tab>
</Tabs>

## 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

<Card title="3. Configure a Custom Domain" icon="globe" href="/quickstart/quick-start-3-custom-domain" horizontal>
  Map your own domain to your workload for production-ready URLs.
</Card>

## Clean Up

To delete just the `my-app` workload created in this quickstart:

<Tabs>
  <Tab title="Console UI">
    Navigate to `Workloads`, select `my-app`, and from `Actions` click `Delete`.
  </Tab>

  <Tab title="CLI">
    ```bash theme={null}
    cpln workload delete my-app --gvc quickstart-gvc
    ```
  </Tab>

  <Tab title="Terraform">
    Remove the `cpln_workload.my_app` resource from your `main.tf` and run:

    ```bash theme={null}
    terraform apply
    ```
  </Tab>

  <Tab title="Pulumi">
    Remove the `my-app` workload from your code and run:

    ```bash theme={null}
    pulumi up
    ```
  </Tab>
</Tabs>

<Note>
  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`.
</Note>
