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

# Kubernetes Operator

> Deploy and manage Control Plane resources using Kubernetes custom resource definitions (CRDs).

The Control Plane [Kubernetes Operator](/core/kubernetes-operator) enables you to manage Control Plane resources directly from your Kubernetes cluster using custom resource definitions (CRDs). It bridges the gap between Kubernetes-native workflows and Control Plane infrastructure.

## What you'll achieve

By the end of this guide, you will have:

1. A Kubernetes cluster with the Control Plane operator installed
2. Authentication configured between your cluster and Control Plane
3. The ability to deploy and manage Control Plane resources using Kubernetes CRDs
4. Optional ArgoCD integration for GitOps workflows

## When to use this

<CardGroup cols={2}>
  <Card title="GitOps workflows" icon="code-branch">
    Manage Control Plane resources alongside your Kubernetes manifests in Git
  </Card>

  <Card title="ArgoCD integration" icon="arrows-rotate">
    Deploy Control Plane resources through ArgoCD applications
  </Card>

  <Card title="Kubernetes-native experience" icon="dharmachakra">
    Manage GVCs, workloads, secrets, and more using CRDs
  </Card>

  <Card title="Infrastructure as Code" icon="file-code">
    Define your entire Control Plane infrastructure declaratively
  </Card>
</CardGroup>

## Prerequisites

<AccordionGroup>
  <Accordion title="Kubernetes cluster">
    A running Kubernetes cluster (v1.19+). This can be:

    * A managed cluster (EKS, GKE, AKS)
    * A local cluster (kind, minikube, Docker Desktop)
    * Any conformant Kubernetes distribution

    If you don't have a cluster, see [Quick start (local cluster)](#quick-start-local-cluster) to set one up.
  </Accordion>

  <Accordion title="Helm CLI">
    Install Helm v3.0+ for deploying the operator:

    ```bash theme={null}
    # macOS
    brew install helm

    # Linux
    curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash

    # Windows
    choco install kubernetes-helm
    ```

    For other installation methods, see the [Helm installation guide](https://helm.sh/docs/intro/install/).
  </Accordion>

  <Accordion title="kubectl configured">
    Ensure kubectl is configured and can communicate with your cluster:

    ```bash theme={null}
    kubectl cluster-info
    ```
  </Accordion>

  <Accordion title="Control Plane account">
    You need to be signed up to Control Plane and have access to an org. If you don't already:

    * [Sign up](https://console.cpln.io/signup) for Control Plane and create a [billing account](/concepts/billing)
    * [Create an Org](/guides/create-org)

    You also need permissions to create service accounts within your org.
  </Accordion>
</AccordionGroup>

## Quick start (local cluster)

If you don't have a Kubernetes cluster, you can set up a local one for testing.

<Note>
  Docker must be installed and running on your machine for local Kubernetes clusters.
</Note>

<Tabs>
  <Tab title="kind">
    [Install kind](https://kind.sigs.k8s.io/docs/user/quick-start/#installation), then create a cluster:

    ```bash theme={null}
    kind create cluster --name cpln-operator

    # Verify
    kubectl cluster-info
    ```

    Then continue with the [installation steps](#installation) below.
  </Tab>

  <Tab title="minikube">
    [Install minikube](https://minikube.sigs.k8s.io/docs/start/), then start a cluster:

    ```bash theme={null}
    minikube start

    # Verify
    kubectl cluster-info
    ```

    Then continue with the [installation steps](#installation) below.
  </Tab>
</Tabs>

## Installation

### Step 1: Install cert-manager

The operator requires [cert-manager](https://cert-manager.io/) for webhook certificate management.

```bash theme={null}
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.16.3/cert-manager.yaml
```

Wait for cert-manager to be ready:

```bash theme={null}
kubectl wait --for=condition=Available deployment --all -n cert-manager --timeout=300s
```

### Step 2: Install the operator

Add the Control Plane Helm repository and install the operator:

```bash theme={null}
# Add the Helm repository
helm repo add cpln https://controlplane-com.github.io/k8s-operator
helm repo update

# Install the operator in the controlplane namespace
helm install cpln-operator cpln/cpln-operator \
  -n controlplane \
  --create-namespace
```

Verify the operator is running:

```bash theme={null}
kubectl get pods -n controlplane -l app=operator
```

### Step 3: Configure authentication

The operator needs credentials to communicate with the Control Plane API. You have two options:

<Tabs>
  <Tab title="Using the CLI (Recommended)">
    The CLI automatically creates the service account and Kubernetes secret:

    ```bash theme={null}
    cpln operator install \
      --serviceaccount k8s-operator \
      --org YOUR_ORG_NAME
    ```

    See the [CLI reference](/cli-reference/commands/operator#operator-install) for available options.
  </Tab>

  <Tab title="Manual setup">
    If you prefer manual control, create the secret yourself:

    **1. Create a service account in Control Plane:**

    ```bash theme={null}
    cpln serviceaccount create --name k8s-operator --org YOUR_ORG_NAME
    ```

    **2. Add it to a group with appropriate permissions:**

    ```bash theme={null}
    cpln group add-member superusers --serviceaccount k8s-operator --org YOUR_ORG_NAME
    ```

    **3. Generate a key:**

    ```bash theme={null}
    cpln serviceaccount add-key k8s-operator --description "cpln operator" --org YOUR_ORG_NAME -o json
    ```

    Save the `key` value from the output.

    **4. Create the Kubernetes secret:**

    ```bash theme={null}
    # Create the namespace if it doesn't exist
    kubectl create namespace controlplane --dry-run=client -o yaml | kubectl apply -f -
    ```

    Create a file named `operator-secret.yaml` with the following content:

    ```yaml theme={null}
    apiVersion: v1
    kind: Secret
    metadata:
      name: YOUR_ORG_NAME
      namespace: controlplane
      labels:
        app.kubernetes.io/managed-by: cpln-operator
      annotations:
        cpln.io/service-account: k8s-operator
    data:
      token: BASE64_ENCODED_KEY  # base64 encode your key: echo -n "YOUR_KEY" | base64
    ```

    Apply the secret:

    ```bash theme={null}
    kubectl apply -f operator-secret.yaml
    ```
  </Tab>
</Tabs>

## Deploying resources

All Control Plane resources are managed through CRDs. Each resource requires:

* `org`: The target Control Plane organization
* `gvc`: The target GVC (for GVC-scoped resources like workloads and identities)

<Note>
  We recommend organizing resources by namespace:

  * One namespace per GVC for GVC-scoped resources (workloads, identities, volumesets)
  * One namespace per org for org-scoped resources (GVCs, secrets, policies)
</Note>

<Warning>
  The CRD structure differs from standard Kubernetes resources. Fields like `org`, `gvc`, and `description` are at the top level, not inside `spec`. Always use the [export feature](/core/kubernetes-operator#exporting-resources-as-crds) to generate accurate manifests.
</Warning>

### Apply with kubectl

You can apply CRD manifests directly using kubectl. Save the following GVC manifest to a file (e.g., `gvc.yaml`):

```yaml theme={null}
apiVersion: cpln.io/v1
kind: gvc
metadata:
  name: my-gvc
  namespace: default
org: YOUR_ORG_NAME  # Replace with your Control Plane org name
description: my-gvc
spec:
  staticPlacement:
    locationLinks:
      - //location/aws-eu-central-1
```

Apply it to your cluster:

```bash theme={null}
kubectl apply -f gvc.yaml
```

Verify the resource was created:

```bash theme={null}
kubectl get gvcs
```

The operator syncs the CRD to Control Plane. You can verify the GVC exists:

```bash theme={null}
cpln gvc get my-gvc --org YOUR_ORG_NAME
```

## ArgoCD integration

The operator integrates seamlessly with ArgoCD for GitOps workflows. Once the operator is installed, you can point ArgoCD at a Git repository containing YAML manifests or a Helm chart.

<Note>
  This section assumes ArgoCD is already installed on your cluster. See the [ArgoCD installation guide](https://argo-cd.readthedocs.io/en/stable/getting_started/) if you haven't set it up yet.
</Note>

### Defining ArgoCD applications

An ArgoCD Application defines what to deploy (source) and where to deploy it (destination). For Control Plane CRDs, you can use either a Helm chart or raw YAML manifests stored in a Git repository.

Save the manifest to a file (e.g., `app.yaml`) and update the placeholder values before applying.

<Tabs>
  <Tab title="Helm Chart">
    Point ArgoCD at a Helm repository containing your Control Plane CRD templates:

    ```yaml theme={null}
    apiVersion: argoproj.io/v1alpha1
    kind: Application
    metadata:
      name: my-helm-app
      namespace: argocd  # This is usually where ArgoCD is installed
    spec:
      project: default
      destination:
        server: https://kubernetes.default.svc  # Cluster API server URL
        namespace: your-namespace  # Target namespace in your cluster
      source:
        repoURL: https://your-org.github.io/your-repo/  # URL of your Helm repository
        chart: my-cpln-chart  # Name of your Helm chart
        targetRevision: 0.1.0  # Chart version
        helm:
          values: |
            org: your-org-name
      syncPolicy:
        automated:
          prune: true  # Automatically delete resources no longer defined in the chart
          selfHeal: true  # Automatically sync drifted resources
    ```
  </Tab>

  <Tab title="YAML Manifests">
    Point ArgoCD at a Git repository containing Control Plane CRD manifests:

    ```yaml theme={null}
    apiVersion: argoproj.io/v1alpha1
    kind: Application
    metadata:
      name: my-cpln-app
      namespace: argocd  # This is usually where ArgoCD is installed
    spec:
      project: default
      destination:
        server: https://kubernetes.default.svc  # Cluster API server URL
        namespace: your-namespace  # Target namespace in your cluster
      source:
        repoURL: https://github.com/your-org/your-repo.git  # Your Git repository URL
        path: manifests  # Path to the directory containing YAML files
        targetRevision: main  # Branch, tag, or commit
      syncPolicy:
        automated:
          prune: true  # Automatically delete resources no longer defined in the repo
          selfHeal: true  # Automatically sync drifted resources
    ```
  </Tab>
</Tabs>

Apply your ArgoCD Application:

```bash theme={null}
kubectl -n argocd apply -f app.yaml
```

### Example application

The [k8s-operator repository](https://github.com/controlplane-com/k8s-operator) includes a ready-to-use example that deploys a GVC, workload, identity, and other resources.

Copy the following manifest and save it to a file (e.g., `example-app.yaml`):

```yaml theme={null}
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: my-helm-app
  namespace: argocd
spec:
  project: default
  destination:
    server: 'https://kubernetes.default.svc'
    namespace: fresh
  source:
    repoURL: 'https://cuppojoe.github.io/argo-example/'
    chart: argo-example
    targetRevision: 0.2.3
    helm:
      values: |
        org: YOUR_ORG_NAME  # Replace with your Control Plane org name
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
```

<Warning>
  Replace `YOUR_ORG_NAME` with your actual Control Plane org name before applying.
</Warning>

Apply the manifest:

```bash theme={null}
kubectl apply -f example-app.yaml
```

The example Helm chart creates the following Control Plane resources:

* **GVC** named `fresh` in `aws-eu-central-1`
* **Workload** with a serverless container
* **Identity** for cloud access
* **Policy** for permissions
* **Secret** for credentials
* Additional resources (agent, domain, ipset)

### Connecting to the ArgoCD UI

To access the ArgoCD UI, retrieve the admin password and port-forward the service:

```bash theme={null}
# Print the initial admin password
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath='{.data.password}' | base64 -d && echo

# Port-forward to the ArgoCD UI
kubectl -n argocd port-forward service/argocd-server 18081:443
```

Open a browser and navigate to `https://localhost:18081`. Accept the self-signed certificate and log in with username `admin` and the password from the command above.

<Tip>
  Store your Control Plane CRD manifests in a Git repository and point ArgoCD at it. Changes merged to your main branch will automatically sync to Control Plane.
</Tip>

## Uninstalling

### Remove operator credentials

Remove the authentication secret:

```bash theme={null}
cpln operator uninstall --org YOUR_ORG_NAME
```

Or manually:

```bash theme={null}
kubectl delete secret YOUR_ORG_NAME -n controlplane
```

### Remove the operator

```bash theme={null}
helm uninstall cpln-operator -n controlplane
```

### Remove cert-manager (optional)

If you no longer need cert-manager:

```bash theme={null}
kubectl delete -f https://github.com/cert-manager/cert-manager/releases/download/v1.16.3/cert-manager.yaml
```

<Warning>
  Uninstalling the operator does not delete Control Plane resources that were created by it. Resources in Control Plane will continue to exist.
</Warning>

### Preventing resource deletion

Deleting a Kubernetes CRD while the operator is running will remove the corresponding resource from Control Plane. To prevent this, add the `cpln.io/resource-policy: keep` annotation:

```yaml theme={null}
kind: gvc
apiVersion: cpln.io/v1
org: your-org-name
metadata:
  name: production
  annotations:
    cpln.io/resource-policy: keep
spec:
  # ...
```

With this annotation, deleting the Kubernetes resource will not delete the Control Plane resource.

## Troubleshooting

<AccordionGroup>
  <Accordion title="Operator pods not starting">
    Check cert-manager is running:

    ```bash theme={null}
    kubectl get pods -n cert-manager
    ```

    Verify webhook certificates:

    ```bash theme={null}
    kubectl get certificates -n controlplane
    ```
  </Accordion>

  <Accordion title="Resources not syncing">
    Check operator logs:

    ```bash theme={null}
    kubectl logs -n controlplane -l app=operator -f
    ```

    Verify the authentication secret exists (look for a secret named after your org):

    ```bash theme={null}
    kubectl get secrets -n controlplane
    ```

    If the secret for your org doesn't exist, run the [`cpln operator install`](#using-the-cli-recommended) command to create it.
  </Accordion>

  <Accordion title="Permission denied errors">
    Ensure the service account has appropriate permissions in Control Plane. Check which group and/or policy the service account belongs to and verify it has the necessary permissions.

    To reconfigure authentication, run the [`cpln operator install`](#using-the-cli-recommended) command which will create a service account and add it to the `superusers` group.
  </Accordion>

  <Accordion title="CRD validation errors">
    View the full resource spec available for each CRD:

    ```bash theme={null}
    kubectl explain gvc.spec
    kubectl explain workload.spec
    ```
  </Accordion>

  <Accordion title="Namespace doesn't exist">
    Create the controlplane namespace:

    ```bash theme={null}
    kubectl create namespace controlplane
    ```
  </Accordion>

  <Accordion title="Secret CRD not syncing">
    Secrets use native Kubernetes Secret objects with special labels, not CRDs. See the [secrets handling](/core/kubernetes-operator#secrets-handling) documentation for the correct format and required labels.
  </Accordion>
</AccordionGroup>

## Next steps

<CardGroup cols={2}>
  <Card title="Kubernetes Operator" href="/core/kubernetes-operator" icon="dharmachakra">
    Learn about CRD structure, secrets handling, and more
  </Card>

  <Card title="GVC Reference" href="/reference/gvc" icon="globe">
    Learn about GVC configuration options
  </Card>

  <Card title="Workload Reference" href="/reference/workload/general" icon="server">
    Explore workload deployment options
  </Card>

  <Card title="Identity Reference" href="/reference/identity" icon="fingerprint">
    Configure cloud access and permissions
  </Card>
</CardGroup>
