> ## Documentation Index
> Fetch the complete documentation index at: https://controlplanecorporation-jakob-ess-2-0-0.mintlify.site/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
```

The CLI prints a login URL and a six-digit confirmation code. Open the URL in a browser on any device, sign in, and enter the code. After a 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.

  This build runs through Docker on your machine. Without Docker, replace `--push` with `--remote` to build remotely; the image is pushed for you.
</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>
