> ## Documentation Index
> Fetch the complete documentation index at: https://controlplanecorporation-majid-docs-content-expansion.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# 2. Deploy your own application

> Download the quickstart frontend, build its container image with the CLI, push it to your org's private registry, and run it as a workload.

## Overview

Part 1 ran an image someone else built. This part builds your own from source with `cpln image build`, pushes it to your [org](/concepts/org)'s [private registry](/reference/image#private-registry), and runs it as the `frontend` [workload](/concepts/workload), a Next.js launch page for a product about to open its waitlist. Every later update [rolls out](/reference/workload/general#rollout-options) the same way, with the version already serving answering until the new one is ready in each [location](/concepts/location).

**What you'll build:**

* A container image of the quickstart frontend, built from its source with no Dockerfile.
* The image stored in your org's private registry.
* A `frontend` workload serving the launch page from both locations of `quickstart-gvc`.

<img src="https://mintcdn.com/controlplanecorporation-majid-docs-content-expansion/6V8SaiZLWK_iHnrB/images/quickstart/build-and-run.svg?fit=max&auto=format&n=6V8SaiZLWK_iHnrB&q=85&s=d1574c7e2443a064d665297ff30f098c" alt="The source folder frontend is built into the image frontend:1.0 in the org's private registry, and the image runs as the workload frontend. One build into your registry, and a workload that runs it in every location." style={{maxWidth:'720px',width:'100%',margin:'1.75rem auto',display:'block'}} width="720" height="222" data-path="images/quickstart/build-and-run.svg" />

## Prerequisites

* Completed [1. Deploy your first workload](/quickstart/deploy-workload): the `quickstart-gvc` [GVC](/concepts/gvc) exists.
* [Docker](https://www.docker.com/get-started) installed and running, or build on Control Plane with the `--remote` flag in step 4.

## Step 1: Install the CLI

The CLI builds the image and pushes it to your org's private registry.

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

    <Note>Requires [Node.js](https://nodejs.org/en/download/) version 18 or later (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">
    <Card title="Download the binary" icon="download" href="/cli-reference/installation#binary" horizontal>
      Pick your platform on the installation page.
    </Card>
  </Tab>
</Tabs>

Confirm the installation:

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

The CLI version number prints.

## 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, then waits. Open the URL in a browser on any device, sign in, and enter the code. After the CLI confirms the login, set your default org (replace `my-org` with your org name):

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

## Step 3: Download the frontend

<CardGroup cols={2}>
  <Card title="macOS / Linux" icon="download" href="https://controlplane.com/downloads/quickstarts/frontend.tgz">
    Downloads `frontend.tgz`, a gzipped tar archive.
  </Card>

  <Card title="Windows" icon="download" href="https://controlplane.com/downloads/quickstarts/frontend.zip">
    Downloads `frontend.zip`, the same source as a zip archive.
  </Card>
</CardGroup>

Extract the archive and navigate to the directory:

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

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

The frontend is a Next.js site: the launch page of a product called Beacon, with a waitlist form. `site.config.ts` holds the product name, tagline, description, and three features. The footer names the location that served the request, read from the [environment variables Control Plane injects](/reference/workload/general#built-in-variables). The waitlist stays closed until part 3 connects the API that stores the signups.

## Step 4: Build and push the image

<Tabs>
  <Tab title="With Docker">
    ```bash theme={null}
    cpln image build --name frontend:1.0 --push
    ```

    The build runs through Docker on your machine and pushes the result to your org's private registry.
  </Tab>

  <Tab title="Without Docker">
    ```bash theme={null}
    cpln image build --name frontend:1.0 --remote
    ```

    The source is uploaded and built on Control Plane, and the image is pushed for you.
  </Tab>
</Tabs>

`cpln image build` detects the application type from its source and builds the image, so the application needs no Dockerfile: with Docker it uses [Buildpacks](/guides/buildpacks), and with `--remote` the Control Plane build service does the detection. When a Dockerfile is present, the command uses that instead. The command ends by printing the image it pushed and its link, where `your-org` is the name of your org:

```json theme={null}
{
  "image": "your-org.registry.cpln.io/frontend:1.0",
  "link": "/org/your-org/image/frontend:1.0"
}
```

With `--remote`, a `Pushed your-org.registry.cpln.io/frontend:1.0 in 42s` line reports the push just above it.

Your org's private registry now holds the image. List it:

```bash theme={null}
cpln image get frontend:1.0
```

```text theme={null}
NAME           PULL                                    DIGEST             CREATED         TAGS
frontend:1.0   your-org.registry.cpln.io/frontend:1.0  sha256:5d2afe...   5 minutes ago
```

`PULL` is the address Docker would pull from. Workloads in your org reference the image as `//image/frontend:1.0`.

<Tabs>
  <Tab title="Console" icon="display">
    ## Step 5: Create the workload

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

      <Step title="Configure basic settings">
        Enter `frontend` as the name and make sure `quickstart-gvc` is the selected GVC.
      </Step>

      <Step title="Configure the container">
        Click `Containers` in the left pane and keep `Control Plane` as the image source. In the image dropdown, type `frontend` and select `frontend:1.0`. Under `Ports`, keep protocol `http` and number `8080`.
      </Step>

      <Step title="Configure the firewall and create">
        Click `Firewall` in the left pane, click `Make Public`, then click `Create`. The workload summary page opens.
      </Step>
    </Steps>

    ## Step 6: Open the application

    <Steps>
      <Step title="Wait for deployment">
        The workload health shows `Ready` once the first [deployment](/concepts/deployment) completes.
      </Step>

      <Step title="Open the canonical endpoint">
        Under `Endpoints`, click the link next to `Canonical Endpoint (Global)`. The launch page opens in a new window.
      </Step>
    </Steps>
  </Tab>

  <Tab title="CLI" icon="terminal">
    ## Step 5: Create the workload

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

    <Note>
      The [`//image/` prefix](/reference/image#image-reference-formats) tells Control Plane to pull the image from your org's private registry.
    </Note>

    ## Step 6: Open the application

    Wait for the workload to become ready:

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

    When the `READY` column shows `true`, open the launch page:

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

  <Tab title="Terraform" icon="https://mintcdn.com/controlplanecorporation-majid-docs-content-expansion/Ry1Mkgc7uPHC-gur/icons/terraform.svg?fit=max&auto=format&n=Ry1Mkgc7uPHC-gur&q=85&s=19deabd5e978d39905a6c83ea1f7904d" width="256" height="291" data-path="icons/terraform.svg">
    ## Step 5: Define the workload

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

    ```hcl theme={null}
    resource "cpln_workload" "frontend" {
      gvc  = cpln_gvc.quickstart.name
      name = "frontend"
      type = "standard"

      container {
        name   = "frontend"
        image  = "/org/my-org/image/frontend: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 "frontend_endpoint" {
      value = cpln_workload.frontend.status[0].canonical_endpoint
    }
    ```

    <Note>
      Replace `my-org` with your org name; Terraform needs the full image path.
    </Note>

    Apply the configuration:

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

    ## Step 6: Open the application

    Open the endpoint URL from the Terraform output:

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

  <Tab title="Pulumi" icon="https://mintcdn.com/controlplanecorporation-majid-docs-content-expansion/Ry1Mkgc7uPHC-gur/icons/pulumi.svg?fit=max&auto=format&n=Ry1Mkgc7uPHC-gur&q=85&s=7a7f4b9390dfa8fecf6223c88c658dcd" width="256" height="271" data-path="icons/pulumi.svg">
    ## Step 5: Define the workload

    Add to your existing Pulumi project from part 1:

    <Tabs>
      <Tab title="TypeScript">
        ```typescript theme={null}
        // The quickstart frontend
        const frontend = new cpln.Workload("frontend", {
          gvc: gvc.name,
          name: "frontend",
          type: "standard",
          containers: [
            {
              name: "frontend",
              image: "/org/my-org/image/frontend: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 frontendEndpoint = frontend.statuses.apply(
          (s) => s?.[0]?.canonicalEndpoint ?? "pending"
        );
        ```
      </Tab>

      <Tab title="Python">
        ```python theme={null}
        # The quickstart frontend
        frontend = cpln.Workload("frontend",
            gvc=gvc.name,
            name="frontend",
            type="standard",
            containers=[cpln.WorkloadContainerArgs(
                name="frontend",
                image="/org/my-org/image/frontend: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("frontend_endpoint", frontend.statuses[0].canonical_endpoint)
        ```
      </Tab>

      <Tab title="Go">
        ```go theme={null}
        // The quickstart frontend
        frontend, err := cpln.NewWorkload(ctx, "frontend", &cpln.WorkloadArgs{
        	Gvc:  gvc.Name,
        	Name: pulumi.String("frontend"),
        	Type: pulumi.String("standard"),
        	Containers: cpln.WorkloadContainerArray{
        		&cpln.WorkloadContainerArgs{
        			Name:   pulumi.String("frontend"),
        			Image:  pulumi.String("/org/my-org/image/frontend: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("frontend_endpoint", frontend.Statuses.Index(pulumi.Int(0)).CanonicalEndpoint())
        ```
      </Tab>

      <Tab title="C#">
        ```csharp theme={null}
        // The quickstart frontend
        var frontend = new Workload("frontend", new WorkloadArgs
        {
            Gvc = gvc.Name,
            Name = "frontend",
            Type = "standard",
            Containers = new[]
            {
                new WorkloadContainerArgs
                {
                    Name = "frontend",
                    Image = "/org/my-org/image/frontend: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}
        ["frontend_endpoint"] = frontend.Statuses.Apply(s => s[0].CanonicalEndpoint)
        ```
      </Tab>
    </Tabs>

    <Note>
      Replace `my-org` with your org name; Pulumi needs the full image path.
    </Note>

    Deploy the configuration:

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

    ## Step 6: Open the application

    Open the endpoint URL from the Pulumi output:

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

  <Tab title="AI Agent" icon="sparkles">
    ## Step 5: Create the workload

    The [MCP server](/ai/mcp) builds only from a git repository, so the build you ran in step 4 stays with the CLI. The agent takes over once the image is in your registry. The first prompt names your org and the GVC, so it works in a new conversation too. Replace `my-org` with your org name:

    ```text theme={null}
    Using org "my-org" and GVC "quickstart-gvc", create a public
    workload called "frontend" from the image //image/frontend:1.0
    on port 8080, with one replica per location and the container
    named "frontend". Keep everything else at its default.
    ```

    The agent creates the workload, follows its deployments until both locations report ready, and reports the canonical endpoint.

    ## Step 6: Open the application

    Ask for the endpoint of each location. The prompt also returns the canonical endpoint, in case the agent stopped before reporting it:

    ```text theme={null}
    Once it is ready in both locations, give me the endpoint of
    each location and its canonical endpoint.
    ```

    Open the canonical endpoint in your browser. The launch page opens.
  </Tab>
</Tabs>

## Verify

Once `frontend` reports `Ready`, its [canonical endpoint](/reference/workload/general#canonical-endpoint-global) serves the launch page: the `Launching soon` label, the tagline `On-call that gets out of your way.`, a waitlist form that is disabled with the note `The waitlist opens once an API is connected. Set API_URL on this workload.`, three feature cards, and an empty `Recent signups` section. The footer names the location that served the request, the one nearest to you, for example:

```text theme={null}
Served from aws-us-west-2 by workload frontend, version 2
```

Open each location's endpoint from the `Deployments` page, the `cpln workload get-deployments frontend --gvc quickstart-gvc` output, or the list your AI agent returned, and the footer switches to the other location.

<Check>
  Your own image is in the org's private registry and running as a globally distributed workload.
</Check>

## Ship a change

Edit the product name or tagline in `site.config.ts`, then build a new tag:

<Tabs>
  <Tab title="With Docker">
    ```bash theme={null}
    cpln image build --name frontend:1.1 --push
    ```
  </Tab>

  <Tab title="Without Docker">
    ```bash theme={null}
    cpln image build --name frontend:1.1 --remote
    ```
  </Tab>
</Tabs>

Update the workload to the new image:

<Tabs>
  <Tab title="Console" icon="display">
    Open `frontend`, click `Containers` in the left pane, select `frontend:1.1` in the image dropdown, and click `Update`.
  </Tab>

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

  <Tab title="Terraform" icon="https://mintcdn.com/controlplanecorporation-majid-docs-content-expansion/Ry1Mkgc7uPHC-gur/icons/terraform.svg?fit=max&auto=format&n=Ry1Mkgc7uPHC-gur&q=85&s=19deabd5e978d39905a6c83ea1f7904d" width="256" height="291" data-path="icons/terraform.svg">
    Change the image tag to `1.1` in your Terraform configuration and run `terraform apply`.
  </Tab>

  <Tab title="Pulumi" icon="https://mintcdn.com/controlplanecorporation-majid-docs-content-expansion/Ry1Mkgc7uPHC-gur/icons/pulumi.svg?fit=max&auto=format&n=Ry1Mkgc7uPHC-gur&q=85&s=7a7f4b9390dfa8fecf6223c88c658dcd" width="256" height="271" data-path="icons/pulumi.svg">
    Change the image tag to `1.1` in your Pulumi code and run `pulumi up`.
  </Tab>

  <Tab title="AI Agent" icon="sparkles">
    ```text theme={null}
    Update that workload to the image //image/frontend:1.1 and
    tell me when the new version is serving in both locations.
    ```

    The agent reads the workload, changes only the container image, and follows the rollout until both locations serve the new version.
  </Tab>
</Tabs>

The version already serving keeps answering until the new one reports ready in each location. Then the header and footer carry your product name, the hero shows your tagline, and the version in the footer has increased.

## What you've learned

* **Private registry**: your images are stored within your org.
* **Image references**: the `//image/` prefix for the CLI, the Console, and an AI agent, the full `/org/<org>/image/` path for Terraform and Pulumi.
* **No Dockerfile needed**: `cpln image build` detects your application's type and containerizes it, on your machine or on Control Plane.
* **Rolling versions**: an image update creates a new version that takes traffic in a location only once it reports ready there; the previous version serves until then.
* **Environment variables**: Control Plane injects `CPLN_LOCATION`, `CPLN_WORKLOAD`, `CPLN_WORKLOAD_VERSION`, and other `CPLN_*` variables automatically.

## Next steps

<Card title="3. Service-to-service communication" icon="arrows-left-right" href="/quickstart/connect-workloads" horizontal>
  Build the API, keep it off the internet, and let the frontend reach it by name through the mesh.
</Card>

## Clean up

To remove everything the series has created so far:

<Tabs>
  <Tab title="Console" icon="display">
    <Steps>
      <Step title="Delete the GVC">
        Open `quickstart-gvc`, click `Actions`, then `Delete`, type the GVC name to confirm, and click `Delete`. `web` and `frontend` go with it.
      </Step>

      <Step title="Delete the images">
        Open `Images`, select `frontend`, and click `Actions`, then `Delete` to remove all its tags.
      </Step>
    </Steps>
  </Tab>

  <Tab title="CLI" icon="terminal">
    ```bash theme={null}
    cpln gvc delete quickstart-gvc
    cpln image delete frontend:1.0
    cpln image delete frontend:1.1
    ```
  </Tab>

  <Tab title="Terraform" icon="https://mintcdn.com/controlplanecorporation-majid-docs-content-expansion/Ry1Mkgc7uPHC-gur/icons/terraform.svg?fit=max&auto=format&n=Ry1Mkgc7uPHC-gur&q=85&s=19deabd5e978d39905a6c83ea1f7904d" width="256" height="291" data-path="icons/terraform.svg">
    ```bash theme={null}
    terraform destroy
    ```

    The images were built by the CLI, so delete them with it: `cpln image delete frontend:1.0` and `cpln image delete frontend:1.1`.
  </Tab>

  <Tab title="Pulumi" icon="https://mintcdn.com/controlplanecorporation-majid-docs-content-expansion/Ry1Mkgc7uPHC-gur/icons/pulumi.svg?fit=max&auto=format&n=Ry1Mkgc7uPHC-gur&q=85&s=7a7f4b9390dfa8fecf6223c88c658dcd" width="256" height="271" data-path="icons/pulumi.svg">
    ```bash theme={null}
    pulumi destroy
    ```

    The images were built by the CLI, so delete them with it: `cpln image delete frontend:1.0` and `cpln image delete frontend:1.1`.
  </Tab>

  <Tab title="AI Agent" icon="sparkles">
    ```text theme={null}
    Delete the GVC "quickstart-gvc" and the images frontend:1.0
    and frontend:1.1.
    ```

    The agent tells you that the GVC takes `web` and `frontend` with it, asks you to confirm, then deletes the GVC and both images.
  </Tab>
</Tabs>

<Note>
  A `--remote` build also pushes a build cache image, `frontend-cache:latest`. Delete it with `cpln image delete frontend-cache:latest` if you built without Docker.
</Note>
