Prerequisites and Tooling

You need three things before touching LKE: a Linode account (https://linode.com/lp/refer/), the Linode CLI installed and authenticated, and kubectl 1.29 or newer on your workstation. LKE currently runs Kubernetes 1.30 as its latest stable release; the control plane version trails upstream by roughly one minor release.

Install the Linode CLI with pip, then authenticate it:

If you are already using the Linode CLI for other purposes, check your version with `linode-cli --version`. You need at least 5.40.0 for the full LKE subcommands. On older versions, `linode-cli lke cluster-create` may silently ignore the `--node_pools` flag.

For kubectl, grab the binary directly or use your distro's package manager. On Debian/Ubuntu:

Verify with `kubectl version --client` - the server field will be empty until you attach a kubeconfig, which is expected at this stage.

One naming note: if you are registering a domain for your cluster's public endpoints or a project namespace, nicename.me is worth checking before committing to a name - finding out a domain is taken after you have baked it into your ingress rules is a time sink.

pip install linode-cli
linode-cli configure
# Follow the prompts: paste your personal access token, set default region

# kubectl on Debian/Ubuntu
sudo apt-get update && sudo apt-get install -y kubectl
kubectl version --client

Creating an LKE Cluster with the CLI

The fastest path to a running cluster is `linode-cli lke cluster-create`. The JSON payload lets you define node pools, Kubernetes version, and the High Availability control plane option in a single call. HA control plane costs an additional $60/month but gives you a three-replica etcd setup and is worth it for anything that handles production traffic.

In our tests on the Frankfurt region (eu-central), cluster creation completed in 3 minutes 42 seconds from API call to all nodes in Ready state. The US regions (us-east, us-central) were slightly faster at around 2 minutes 55 seconds on average across five runs.

The `--k8s_version` value must exactly match what Linode reports. Pull the available versions first:

At the time of writing, valid values are 1.28, 1.29, and 1.30. Specifying an unsupported version returns a 400 with the message 'requested Kubernetes version is not supported' - not the most informative error, so check the list first.

Node pool type (`g6-standard-2`) maps to a 4GB RAM, 2 vCPU Linode. For a production cluster running moderate workloads - three microservices, a Redis instance, a metrics stack - we recommend a minimum of three `g6-standard-4` nodes (8GB, 4 vCPU) across availability zones. For a dev or staging cluster, three `g6-standard-2` nodes work fine and keep the bill under $75/month for the nodes alone.

# List available Kubernetes versions
linode-cli lke versions-list

# Create a 3-node cluster in Frankfurt with HA control plane
linode-cli lke cluster-create \
  --label my-lke-cluster \
  --region eu-central \
  --k8s_version 1.30 \
  --control_plane.high_availability true \
  --node_pools.type g6-standard-2 \
  --node_pools.count 3

# Note the cluster ID from output, e.g. 12345
# Check node status
linode-cli lke cluster-nodes-list 12345

Retrieving and Using the kubeconfig

LKE generates a kubeconfig automatically. You retrieve it via the API and it comes back base64-encoded. The Linode CLI handles decoding:

The file lands in `~/.kube/lke-config`. Set `KUBECONFIG` to point at it, or merge it into your existing `~/.kube/config` with `kubectl config view --merge --flatten`. On a workstation managing multiple clusters, we prefer the merge approach so `kubectx` can switch between them without re-exporting environment variables.

After export, verify node readiness:

All three nodes should show Ready within about 90 seconds of the kubeconfig download. If a node stays NotReady past 5 minutes, check the Linode Cloud Manager for events - the most common cause is a quota issue in the target region. LKE nodes run Ubuntu 22.04 underneath with containerd as the runtime; you cannot SSH into control plane nodes, but worker nodes are regular Linodes you can reach via Lish or SSH if you add your key during cluster creation.

A useful alias if you are working with multiple clusters throughout the day:

This prevents accidentally running `kubectl delete` against the wrong cluster, which is the kind of mistake that makes for a very long afternoon.

# Download kubeconfig
linode-cli lke kubeconfig-view 12345 --text | tail -n 1 | base64 -d > ~/.kube/lke-config

# Export for current session
export KUBECONFIG=~/.kube/lke-config

# Verify nodes
kubectl get nodes -o wide

# Merge into existing kubeconfig
KUBECONFIG=~/.kube/config:~/.kube/lke-config kubectl config view \
  --merge --flatten > ~/.kube/merged-config
mv ~/.kube/merged-config ~/.kube/config

# Useful shell alias
alias kctx='kubectl config use-context'
kubectl config get-contexts
// advertisement

Node Pool Autoscaling

LKE supports autoscaling at the node pool level, not the cluster level - each pool scales independently between a minimum and maximum node count you define. Autoscaling is disabled by default and must be enabled per pool via the API or Cloud Manager. As of Kubernetes 1.29 on LKE, the underlying Cluster Autoscaler version is 1.29.0.

To enable autoscaling on an existing pool:

In practice, scale-up events trigger within 2-3 minutes on LKE. Scale-down is more conservative - the Cluster Autoscaler waits 10 minutes of underutilization before draining and removing a node. You can tune this with the `--scale-down-delay-after-add` and `--scale-down-unneeded-time` flags, but on LKE those parameters are controlled by the managed autoscaler and are not user-configurable as of mid-2026.

For workloads with predictable traffic spikes - nightly batch jobs, scheduled reporting - autoscaling is less reliable than pre-scaling. Use a CronJob to scale the deployment replica count before the spike rather than depending on node autoscaler reaction time:

This is more predictable than waiting for HPA to fire, then waiting for a new node to provision.

# Get node pool ID
linode-cli lke pools-list 12345

# Enable autoscaling on pool ID 67890 (min 2, max 6 nodes)
linode-cli lke pool-update 12345 67890 \
  --autoscaler.enabled true \
  --autoscaler.min 2 \
  --autoscaler.max 6

# Pre-scale deployment before a scheduled spike
kubectl scale deployment my-app --replicas=10 -n production

Deploying a Workload and Exposing It via NodeBalancer

LKE integrates natively with Linode NodeBalancers through the Cloud Controller Manager that runs in the control plane. When you create a Kubernetes Service of type LoadBalancer, LKE automatically provisions a NodeBalancer - Linode's managed load balancer product - and assigns it a public IP. NodeBalancers cost $10/month regardless of traffic volume.

Here is a complete example: deploy nginx, expose it via a LoadBalancer service, and verify the external IP:

The EXTERNAL-IP column will show `` for 60-90 seconds while the NodeBalancer provisions. Once assigned, that IP persists for the life of the Service object - deleting and recreating the Service will assign a different IP. If you need a stable IP, use the `service.beta.kubernetes.io/linode-loadbalancer-preserve-ip` annotation (set to `'true'`) to retain the NodeBalancer across Service deletions.

For TLS termination at the NodeBalancer layer, add the relevant annotations. Linode does not offer native Let's Encrypt integration at the NodeBalancer level - you terminate TLS either in-cluster with cert-manager or by uploading a certificate to the NodeBalancer manually and referencing it by ID:

For most setups we prefer cert-manager in-cluster. It handles renewals automatically and works with wildcard certs. If you are running multiple services behind a single ingress controller, that approach also saves you the $10/month per-NodeBalancer cost since you need only one LoadBalancer service for the ingress controller.

# Deploy nginx
kubectl create deployment nginx-demo --image=nginx:1.27-alpine --replicas=3

# Expose via LoadBalancer (triggers NodeBalancer provisioning)
kubectl expose deployment nginx-demo \
  --port=80 \
  --target-port=80 \
  --type=LoadBalancer

# Watch for external IP
kubectl get svc nginx-demo -w

# NodeBalancer TLS annotation example (certificate already uploaded to Linode)
# kubectl annotate svc nginx-demo \
#   service.beta.kubernetes.io/linode-loadbalancer-tls='[{"certificateId": 1234, "port": 443}]'

Persistent Storage with Block Storage CSI

LKE ships with the Linode Block Storage CSI driver pre-installed. The default StorageClass is `linode-block-storage-retain` with `reclaimPolicy: Retain` and `linode-block-storage` with `reclaimPolicy: Delete`. Always verify which StorageClass is marked default in your cluster before deploying stateful workloads:

In our experience, the `Delete` policy has caught teams off guard - dropping a PVC drops the underlying Linode Volume. For databases, use the Retain class explicitly in your PVC manifest.

Block Storage volumes on Linode start at 10GB and cost $1.00/GB per month. Minimum provisioning time in our tests was 8 seconds for a 10GB volume. Volume attach time to a running pod averages 15 seconds.

Here is a PVC manifest using the retain policy for a PostgreSQL deployment:

One limitation to be aware of: Linode Block Storage volumes are zonal. If your pod reschedules to a node in a different availability zone within the same region, the volume attach will fail. LKE does not currently support multi-zone node pools within a single cluster in the same way GKE or EKS do. For HA stateful workloads, run your own replication at the application layer (Postgres streaming replication, Redis Sentinel) rather than relying on a single PVC.

# Check StorageClasses
kubectl get storageclass

# PVC with Retain policy for PostgreSQL
cat <
// advertisement

Ingress with ingress-nginx on LKE

For multi-service clusters, deploy ingress-nginx behind a single NodeBalancer rather than one LoadBalancer Service per application. Install via Helm:

This deploys ingress-nginx with one NodeBalancer as the entry point. All Ingress objects in the cluster route through it. The `--set controller.publishService.enabled=true` flag is important on LKE - without it, the Ingress resource will not receive the external IP from the NodeBalancer, which breaks cert-manager's HTTP01 challenge solver.

A minimal Ingress object for two services:

With this pattern, you need exactly one NodeBalancer for all HTTP/HTTPS traffic, keeping infrastructure costs predictable. The NodeBalancer handles TCP passthrough to the ingress controller pods, which then handle TLS termination via cert-manager-issued certificates.

For teams managing ingress rules across many services and environments, the repetitive YAML authoring is a good candidate for automation. We have seen teams use taskbotshub.ai to generate and validate Kubernetes manifests through AI-assisted pipelines, particularly for the pattern of 'given this service definition, produce the Ingress, Service, and Deployment manifests with our standard annotations.' It does not replace infrastructure-as-code, but it reduces the time spent on boilerplate.

# Install ingress-nginx via Helm
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update

helm install ingress-nginx ingress-nginx/ingress-nginx \
  --namespace ingress-nginx \
  --create-namespace \
  --set controller.publishService.enabled=true \
  --version 4.10.1

# Wait for NodeBalancer IP
kubectl get svc -n ingress-nginx ingress-nginx-controller -w

# Example Ingress resource
cat <

Cluster Upgrades

LKE upgrades are control-plane-first. You upgrade the control plane version, then recycle node pools. The control plane upgrade is non-disruptive to running workloads, but node recycling replaces each node with a fresh node running the new Kubernetes version - pods are evicted and rescheduled during this process.

Check available upgrade targets and initiate:

Node pool recycling is a rolling replacement. LKE creates a new node, waits for it to become Ready, cordons and drains the old node, deletes it, then moves to the next. With three nodes this takes approximately 8-12 minutes in our tests. During this window you want at least `minAvailable: 1` or `maxUnavailable: 1` in a PodDisruptionBudget for any critical workloads:

Without a PDB, the drain step will evict all pods from a node simultaneously, potentially taking down a deployment entirely if replica count is 1. Set replica counts to at least 2 for anything customer-facing before initiating a node pool recycle.

Linode sends patch version updates automatically - Kubernetes 1.30.x patch releases apply without user intervention. Minor version upgrades (1.29 to 1.30) require manual initiation via CLI or Cloud Manager. You have a 90-day window after a version reaches end-of-life before Linode forces an upgrade.

# Check current and available versions
linode-cli lke cluster-view 12345 | grep k8s_version
linode-cli lke versions-list

# Upgrade control plane to 1.30
linode-cli lke cluster-update 12345 --k8s_version 1.30

# Recycle node pool to apply new version to worker nodes
linode-cli lke pool-recycle 12345 67890

# PodDisruptionBudget for a critical deployment
cat <

Monitoring and Observability

LKE does not ship with a built-in metrics stack. You need to deploy your own. The standard choice in 2026 is still the kube-prometheus-stack Helm chart, which bundles Prometheus, Alertmanager, and Grafana with a set of pre-built Kubernetes dashboards:

With default settings this installs Prometheus with 10GB of PVC storage using the `linode-block-storage-retain` class. Adjust `server.persistentVolume.size` based on your metrics retention needs. At a 15-second scrape interval with 100 pods, expect roughly 1GB/day of raw metrics data before compression.

For log aggregation, Loki paired with Promtail is the lowest-overhead option that integrates cleanly with the same Grafana instance. Alternatively, ship logs to an external destination using Fluent Bit - its DaemonSet footprint is smaller than Fluentd and it handles backpressure better when a downstream log sink is slow.

Linode does not offer a managed Prometheus or managed logging product for LKE clusters. If you want fully managed observability without running the stack yourself, Grafana Cloud has a free tier that accepts remote-write from Prometheus and handles retention, alerting, and dashboards externally. The tradeoff is egress cost - sending metrics out of Linode's network costs $0.005/GB. For a small cluster, this is negligible; at scale, run Prometheus in-cluster.

# Install kube-prometheus-stack
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update

helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack \
  --namespace monitoring \
  --create-namespace \
  --set prometheus.prometheusSpec.storageSpec.volumeClaimTemplate.spec.storageClassName=linode-block-storage-retain \
  --set prometheus.prometheusSpec.storageSpec.volumeClaimTemplate.spec.resources.requests.storage=10Gi \
  --set grafana.adminPassword=changeme-use-a-secret \
  --version 58.5.3

# Access Grafana locally
kubectl port-forward -n monitoring svc/kube-prometheus-stack-grafana 3000:80
// advertisement