Skip to main content
The cpln convert command transforms Kubernetes manifests into Control Plane resources, enabling seamless migration from Kubernetes environments.

When to use this

Kubernetes migration

Migrate existing K8s workloads, secrets, and storage to Control Plane

Reuse existing manifests

Leverage your existing Kubernetes YAML files without rewriting them

Preview conversions

See how K8s resources translate to Control Plane before applying

CI/CD integration

Convert and apply K8s manifests in automated pipelines

Supported resources

Workload conversions

How workload type is determined

The converter analyzes your Kubernetes spec to automatically select the appropriate Control Plane workload type:
Set the cpln.io/workload-type annotation on the Deployment, StatefulSet, DaemonSet, ReplicaSet, or its pod template to pin the type explicitly:
The pin overrides the automatic inference below. A workload that mounts a dedicated volumeset can only be pinned to stateful, and pinning serverless on a workload whose shape conflicts with serverless requirements (e.g., multiple ports) emits a warning and is validated by the API. Jobs and CronJobs always convert to cron and ignore the annotation.
Job and CronJob resources always become cron workloads.
  • CronJobs preserve their schedule from spec.schedule
  • Jobs use a default schedule of * * * * * (every minute)
The workload becomes stateful if any container mounts a dedicated volumeset (from a PersistentVolumeClaim or volumeClaimTemplates with single-node access modes such as ReadWriteOnce).Claims with multi-node access modes (ReadWriteMany, ReadOnlyMany) convert to shared volumesets, which any workload type can mount — they do not force the stateful type.Stateful takes precedence over standard when both conditions are met.
All non-cron workloads start as standard. The type remains standard unless a stateful condition is detected. Conditions that explicitly confirm standard:
  • A container has no ports or more than one port
  • A container uses gRPC health probes (liveness or readiness)
  • The workload has rollout options (from K8s strategy, updateStrategy, minReadySeconds, or podManagementPolicy)
The workload type is immutable after creation. When a converted workload already exists with a different type, cpln apply --k8s fails with Workload type may not be changed (405). Pin the type with the cpln.io/workload-type annotation to keep conversions stable across CLI versions. CLI versions before 3.9.0 inferred serverless for some Deployments; from 3.9.0 on, the default is standard.

Conversion annotations

The converter honors these annotations on the workload object metadata (Deployment, StatefulSet, DaemonSet, ReplicaSet, ReplicationController, Job, CronJob) or on its pod template metadata. Object-level annotations take precedence over template-level ones.
cron and vm cannot be pinned. Cron workloads carry a job schedule that only Jobs and CronJobs provide (they always convert to cron). VM workloads boot from a disk image instead of running containers, so no container-based Kubernetes kind can convert to one.
Example
With kustomize, apply annotations across a whole stack using commonAnnotations, or per-resource with a patch.

Environment variables

Container environment variables convert as follows: envFrom expansion follows Kubernetes semantics:
  • An explicit env entry overrides an envFrom variable with the same name.
  • A key present in multiple envFrom sources takes the value of the last source.
  • A prefix on the source is prepended to every expanded variable name.
  • ConfigMap binaryData keys are not expanded (Kubernetes does not expose them as environment variables either).
The converter needs the referenced ConfigMap or Secret in the conversion input to know its keys:
  • A missing non-optional configMapRef fails the conversion (the pod could not start on Kubernetes either).
  • A missing optional: true configMapRef is skipped with a warning.
  • A missing secretRef (e.g., a Secret that already exists on Control Plane) produces a warning because its keys cannot be enumerated — include the Secret manifest in the input or add the variables to env manually.
  • A secretRef to a Secret that converts to a non-dictionary type (a payload key becomes opaque, basic-auth becomes userpass) is skipped with a warning, since its keys are not individually addressable.
  • Expanded variable names starting with CPLN_ are skipped with a warning; that prefix is reserved.

Secret and config conversions

Storage conversions

Domain conversions

How secrets are converted

Kubernetes Secrets are converted based on their type field:
ConfigMaps are always converted to dictionary secrets, with all key-value pairs preserved. If a secret has both data and stringData fields, it is always converted to dictionary with both merged.

How volumesets are configured

PersistentVolumeClaims are converted to volumesets with these settings: Shared volumesets from multi-node access modes: A PVC whose accessModes include ReadWriteMany or ReadOnlyMany converts to a shared volumeset (fileSystemType: shared, performanceClass: shared). Shared volumesets can be mounted by workloads of any type and by multiple workloads at once — matching how RWX claims behave on Kubernetes. All other claims convert to dedicated block-storage volumesets, which only stateful (or vm) workloads can mount, one workload at a time. High-performance SSD detection: If the StorageClass parameters contain any of these values, the volumeset uses high-throughput-ssd:
  • AWS: io1, io2
  • GCP: pd-extreme
  • Azure: UltraSSD_LRS
  • VMware: thick
  • Other: fast, persistent_1

How domains are converted

Kubernetes Ingresses are converted to Control Plane Domains. The converter maps Ingress rules (host + paths) to domain routes, resolving backend Services to workloads.

Field mapping

Path type handling

Service-to-workload resolution

The converter resolves each Ingress backend to a Control Plane workload through the following steps:
1

Find the Service

The converter locates the Kubernetes Service referenced by the Ingress backend’s service.name.
2

Match the workload

The Service’s selector labels are matched against workload pod template labels to find the target workload.
3

Resolve the port

The Service port’s targetPort is resolved to the actual container port:
  • Numeric targetPort — used directly as the container port
  • Named targetPort — resolved by matching the port name in the container spec
  • No targetPort — falls back to the Service port number

Domain defaults

All converted domains use these fixed settings:

Wildcard and apex domain merging

When an Ingress contains rules for both example.com and *.example.com, the converter merges them into a single domain named example.com with acceptAllSubdomains: true. Routes from both rules are combined and deduplicated.
Domains with acceptAllSubdomains enabled require a Dedicated Load Balancer on the target GVC.

GVC context

If a --gvc is provided, workload links use it directly (e.g., //gvc/my-gvc/workload/api). Otherwise, the converter inserts a {{GVC}} placeholder that you must replace before applying.

Auto-generated resources

When workloads reference secrets (via environment variables or volume mounts), the converter automatically creates:
  1. Identity - Named identity-{workload-name}, linked to the workload
  2. Policy - Named policy-{workload-name}, granting reveal permission on referenced secrets
This ensures workloads can access their secrets without manual policy configuration.

Firewall and public exposure

The converter analyzes your Kubernetes Services and Ingresses to determine how each workload should be reachable: On Kubernetes, any pod can reach a Service inside the cluster. The converter preserves that reachability: a workload backed by a Service converts with firewallConfig.internal.inboundAllowType: same-gvc, so dependent workloads (databases, message brokers, internal APIs) keep talking to each other after migration. Use the cpln.io/internal-inbound-allow-type annotation to override the inference per workload — for example, none to lock a workload down even though a Service selects it.
All workloads have external outbound traffic allowed by default (0.0.0.0/0).
When a workload’s internal firewall is none, calls from other workloads in the GVC fail in ways that look like application errors on the caller side (e.g., connection reset by peer); the target workload’s access log shows rbac_access_denied_matched_policy[none]. If intra-GVC calls fail after a conversion done with an older CLI, set same-gvc on the target: cpln workload update <name> --set spec.firewallConfig.internal.inboundAllowType=same-gvc.

Informational resources

These Kubernetes resources are not directly converted but inform the conversion:

Basic usage

This outputs the converted Control Plane resources to stdout.

Options

How port protocol is inferred

When you don’t specify --protocol, the converter automatically infers the protocol for each container port using a multi-level strategy (in priority order):
1

Service appProtocol (highest priority)

If a Kubernetes Service explicitly declares appProtocol on a port that targets this container, that protocol is used.
2

Service port name prefix

The converter checks if any Service port targeting this container has a name with a protocol prefix.
3

Container port name prefix

The container’s own port name is checked for protocol prefixes.
4

Health probe type

If the port has a liveness or readiness probe, the probe type determines the protocol:
  • grpc probe → gRPC
  • httpGet probe → HTTP
  • tcpSocket probe → TCP
5

Well-known port number (lowest priority)

Common port numbers are mapped to their typical protocols. See the tables below.

Recognized protocol prefixes

Port names starting with these prefixes are automatically mapped:

Well-known port mappings

These port numbers are automatically assigned protocols when no other signal is available:
If no protocol can be inferred, the converter defaults to tcp.

Example conversion

1

Create a Kubernetes manifest

Create a file named k8s.yaml:
k8s.yaml
2

Convert the manifest

3

Apply the converted resources

Convert and apply in one command:
Or pipe the conversion output:

More conversion examples

A simple Deployment with one HTTP port becomes standard (the default type):
k8s-deployment.yaml
Converts to a standard workload (the default type for all non-cron, non-stateful workloads).

Override protocol inference

To bypass automatic protocol inference and set a specific protocol for all ports, use the --protocol flag:

Verbose mode

Use --verbose to see which K8s properties were converted and which were ignored:
Ignored properties are highlighted in yellow and marked with (ignored).

Delete converted resources

To remove resources that were applied from a K8s manifest:

Troubleshooting

The workload type is immutable. This error means the type inferred by the current conversion differs from the type of the already-existing workload — commonly after upgrading across CLI v3.9.0, which changed the inferred default from a serverless/standard mix to standard.Pin the type in the K8s manifest so it stays stable across CLI versions:
To intentionally change a workload’s type, create a new workload with the target type and migrate traffic to it — the type cannot be changed in place.
Workloads not selected by any Kubernetes Service convert with the internal firewall closed (inboundAllowType: none). Callers see errors like connection reset by peer while the target’s access log shows rbac_access_denied_matched_policy[none].Include the Services in the conversion input so the converter infers same-gvc, set the cpln.io/internal-inbound-allow-type annotation, or open the firewall after the fact with cpln workload update <name> --set spec.firewallConfig.internal.inboundAllowType=same-gvc.
envFrom sources expand only when the referenced ConfigMap or Secret is part of the conversion input, because the converter needs the keys. Check the conversion warnings on stderr: a Secret that lives only on Control Plane cannot be expanded — include its manifest in the input or add the variables to env manually.
Some Kubernetes resources don’t have direct Control Plane equivalents. Use --verbose to see which properties are ignored during conversion.
ConfigMaps are converted to dictionary secrets. Reference them in your workload using environment variables or volume mounts pointing to the secret.
HorizontalPodAutoscaler settings inform the workload’s autoscaling configuration. Check the converted workload’s defaultOptions.autoscaling section.
Services are used to inform port mappings but don’t create standalone resources. Ports are configured directly on the workload’s container spec.
PersistentVolumeClaims are converted to volumesets. Ensure the PVC is referenced by a StatefulSet’s volumeClaimTemplates for proper conversion.
Ingress conversion requires the referenced Services and their matching workloads (Deployments, StatefulSets, etc.) to be present in the same file. The converter uses the Service selector to find the target workload by matching pod template labels.
Ingress backend Services must have a selector defined so the converter can resolve which workload the Service targets. Services without selectors (e.g., ExternalName services) are not supported for domain conversion.

Next steps

Apply YAML Manifests

Learn more about applying resources

Delete Resources

Remove resources from Control Plane

Workload Reference

Understand workload configuration

Convert Command Reference

Convert command reference