> ## Documentation Index
> Fetch the complete documentation index at: https://www.latitude.sh/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Kubernetes

> Run production-ready Kubernetes clusters on dedicated bare metal infrastructure

export const ReactFlowDiagram = ({nodes = [], edges = [], height = 'md', className, interactive = false, showControls = interactive, showMiniMap = false, showBackground = true, fitView = true, fitViewPadding = 0.25, orientation = 'horizontal'}) => {
  const reactFlowVersion = '12.10.2';
  const reactVersion = '18.3.1';
  const reactUrl = `https://unpkg.com/react@${reactVersion}/umd/react.production.min.js`;
  const reactDomUrl = `https://unpkg.com/react-dom@${reactVersion}/umd/react-dom.production.min.js`;
  const reactFlowUrl = `https://unpkg.com/@xyflow/react@${reactFlowVersion}/dist/umd/index.js`;
  const reactFlowCssUrl = `https://unpkg.com/@xyflow/react@${reactFlowVersion}/dist/style.css`;
  const heightClassNames = {
    sm: 'h-64',
    md: 'h-96'
  };
  const heightStyles = {
    lg: {
      height: '560px'
    },
    xl: {
      height: '820px'
    }
  };
  const nodeToneClassNames = {
    default: 'border-zinc-950/20 bg-white text-zinc-950 dark:border-white/20 dark:bg-zinc-950 dark:text-white',
    accent: 'border-violet-500/30 bg-violet-50 text-zinc-950 dark:border-violet-400/30 dark:bg-violet-950/40 dark:text-white',
    success: 'border-emerald-500/30 bg-emerald-50 text-zinc-950 dark:border-emerald-400/30 dark:bg-emerald-950/40 dark:text-white',
    warning: 'border-amber-500/30 bg-amber-50 text-zinc-950 dark:border-amber-400/30 dark:bg-amber-950/40 dark:text-white'
  };
  const classNames = (...values) => values.filter(Boolean).join(' ');
  const containerRef = useRef(null);
  const rootRef = useRef(null);
  const [errorMessage, setErrorMessage] = useState(null);
  const heightStyle = heightStyles[height];
  const heightClassName = heightClassNames[height] || (heightStyle ? null : height);
  const isVertical = orientation === 'vertical';
  const loadScript = src => {
    if (document.querySelector(`script[src="${src}"]`)) return Promise.resolve();
    return new Promise((resolve, reject) => {
      const script = document.createElement('script');
      script.src = src;
      script.async = true;
      script.onload = resolve;
      script.onerror = () => reject(new Error(`Failed to load ${src}`));
      document.head.appendChild(script);
    });
  };
  const loadStylesheet = href => {
    if (document.querySelector(`link[href="${href}"]`)) return;
    const link = document.createElement('link');
    link.rel = 'stylesheet';
    link.href = href;
    document.head.appendChild(link);
  };
  const ensureJsxRuntime = () => {
    if (window.jsxRuntime || !window.React) return;
    const createElement = (type, props = {}, key) => {
      const elementProps = key === undefined ? props : {
        ...props,
        key
      };
      return window.React.createElement(type, elementProps);
    };
    window.jsxRuntime = {
      Fragment: window.React.Fragment,
      jsx: createElement,
      jsxs: createElement
    };
  };
  const loadReactFlow = async () => {
    if (window.ReactFlow) return window.ReactFlow;
    if (!window.__latitudeReactFlowLoader) {
      window.__latitudeReactFlowLoader = Promise.resolve().then(() => loadStylesheet(reactFlowCssUrl)).then(() => window.React ? undefined : loadScript(reactUrl)).then(() => ensureJsxRuntime()).then(() => window.ReactDOM ? undefined : loadScript(reactDomUrl)).then(() => loadScript(reactFlowUrl)).then(() => window.ReactFlow).catch(err => {
        window.__latitudeReactFlowLoader = null;
        return Promise.reject(err);
      });
    }
    return window.__latitudeReactFlowLoader;
  };
  const createDiagramNode = (React, Handle, Position) => {
    const DiagramNode = ({data}) => {
      const positions = {
        top: Position.Top,
        right: Position.Right,
        bottom: Position.Bottom,
        left: Position.Left
      };
      const targetPosition = positions[data?.targetPosition] || (isVertical ? Position.Top : Position.Left);
      const sourcePosition = positions[data?.sourcePosition] || (isVertical ? Position.Bottom : Position.Right);
      if (data?.kind === 'label') {
        const isBadge = data?.tone === 'badge';
        return React.createElement('div', {
          className: isBadge ? 'rounded border px-1.5 py-0.5 text-xs font-semibold leading-none' : 'text-sm font-semibold leading-5 text-zinc-900 dark:text-zinc-100',
          style: {
            backgroundColor: isBadge ? 'var(--latitude-diagram-label-bg)' : undefined,
            borderColor: isBadge ? 'hsl(var(--latitude-diagram-border-strong))' : undefined,
            color: 'hsl(var(--latitude-diagram-node-text))',
            textAlign: data?.align || 'left',
            width: data?.width || 320
          }
        }, data?.title);
      }
      if (data?.kind === 'boundary') {
        return React.createElement('div', {
          className: 'rounded-xl border border-dashed border-zinc-950/70 bg-transparent dark:border-white/70',
          style: {
            width: data?.width || 640,
            height: data?.height || 520
          }
        }, React.createElement(Handle, {
          type: 'target',
          position: targetPosition,
          isConnectable: false,
          className: 'opacity-0'
        }), React.createElement('div', {
          className: 'px-4 py-3 text-sm font-semibold leading-5 text-zinc-800 dark:text-zinc-100'
        }, data?.title), React.createElement(Handle, {
          type: 'source',
          position: sourcePosition,
          isConnectable: false,
          className: 'opacity-0'
        }));
      }
      if (data?.kind === 'zone') {
        return React.createElement('div', {
          className: 'rounded-xl border border-violet-300/70 bg-violet-100/50 dark:border-violet-400/30 dark:bg-violet-950/30',
          style: {
            width: data?.width || 520,
            height: data?.height || 300
          }
        }, React.createElement(Handle, {
          type: 'target',
          position: targetPosition,
          isConnectable: false,
          className: 'opacity-0'
        }), React.createElement('div', {
          className: 'px-4 py-3 text-sm font-medium leading-5 text-zinc-700 dark:text-zinc-200'
        }, data?.title), React.createElement(Handle, {
          type: 'source',
          position: sourcePosition,
          isConnectable: false,
          className: 'opacity-0'
        }));
      }
      if (data?.kind === 'container') {
        const containerHandles = [{
          type: 'target',
          position: Position.Top,
          id: 'target-top'
        }, {
          type: 'source',
          position: Position.Top,
          id: 'source-top'
        }, {
          type: 'target',
          position: Position.Right,
          id: 'target-right'
        }, {
          type: 'source',
          position: Position.Right,
          id: 'source-right'
        }, {
          type: 'target',
          position: Position.Bottom,
          id: 'target-bottom'
        }, {
          type: 'source',
          position: Position.Bottom,
          id: 'source-bottom'
        }, {
          type: 'target',
          position: Position.Left,
          id: 'target-left'
        }, {
          type: 'source',
          position: Position.Left,
          id: 'source-left'
        }];
        return React.createElement('div', {
          style: {
            width: data?.width || 480,
            height: data?.height || 200,
            border: data?.border || '1px solid hsl(var(--latitude-diagram-brand-uv) / 0.35)',
            backgroundColor: data?.backgroundColor || 'transparent',
            borderRadius: data?.borderRadius ?? 12,
            boxSizing: 'border-box'
          }
        }, ...containerHandles.map(handle => React.createElement(Handle, {
          key: `${handle.type}-${handle.id}`,
          type: handle.type,
          position: handle.position,
          id: handle.id,
          isConnectable: false,
          className: 'opacity-0'
        })));
      }
      if (data?.kind === 'resource') {
        const icon = data?.icon || data?.badge || '';
        const hexColor = data?.hexColor || '#2563eb';
        return React.createElement('div', {
          className: 'flex w-32 flex-col items-center text-center text-zinc-800 dark:text-zinc-100'
        }, React.createElement(Handle, {
          type: 'target',
          position: targetPosition,
          isConnectable: false,
          className: 'opacity-0'
        }), React.createElement('div', {
          className: 'flex h-20 w-20 items-center justify-center text-lg font-bold tracking-wide text-white shadow-sm',
          style: {
            backgroundColor: hexColor,
            clipPath: 'polygon(30% 0%, 70% 0%, 100% 30%, 100% 70%, 70% 100%, 30% 100%, 0% 70%, 0% 30%)'
          }
        }, icon), React.createElement('div', {
          className: 'mt-2 text-sm font-semibold leading-5'
        }, data?.title), data?.subtitle ? React.createElement('div', {
          className: 'mt-0.5 text-xs leading-4 text-zinc-600 dark:text-zinc-300'
        }, data.subtitle) : null, React.createElement(Handle, {
          type: 'source',
          position: sourcePosition,
          isConnectable: false,
          className: 'opacity-0'
        }));
      }
      const tone = nodeToneClassNames[data?.tone] || nodeToneClassNames.default;
      const width = data?.width || (data?.size === 'wide' ? 320 : 256);
      const blockColumns = data?.blockColumns || 3;
      return React.createElement('div', {
        className: classNames('rounded-lg border px-4 py-3 shadow-sm', tone),
        style: {
          width
        }
      }, React.createElement(Handle, {
        type: 'target',
        position: targetPosition,
        isConnectable: false,
        className: 'opacity-0'
      }), React.createElement('div', {
        className: 'flex items-start justify-between gap-3'
      }, React.createElement('div', {
        className: 'min-w-0'
      }, React.createElement('div', {
        className: 'truncate text-base font-semibold leading-6'
      }, data?.title), data?.subtitle ? React.createElement('div', {
        className: 'mt-1 text-sm leading-5 text-zinc-600 dark:text-zinc-300'
      }, data.subtitle) : null), data?.badge ? React.createElement('span', {
        className: 'shrink-0 rounded-full border border-zinc-950/10 px-1.5 py-0.5 text-xs font-medium leading-none text-zinc-600 dark:border-white/20 dark:text-zinc-300'
      }, data.badge) : null), data?.blocks?.length ? React.createElement('div', {
        className: 'mt-3',
        style: {
          display: 'grid',
          gap: '0.5rem',
          gridTemplateColumns: `repeat(${blockColumns}, minmax(0, 1fr))`
        }
      }, ...data.blocks.map((block, index) => {
        const blockData = typeof block === 'string' ? {
          label: block
        } : block;
        return React.createElement('div', {
          key: `${blockData.label}-${index}`,
          className: 'flex min-h-14 items-center justify-center rounded-md border border-zinc-950/10 bg-zinc-950/[0.03] px-2 text-center text-xs font-medium leading-4 text-zinc-700 dark:border-white/15 dark:bg-white/10 dark:text-zinc-200'
        }, blockData.label);
      })) : null, React.createElement(Handle, {
        type: 'source',
        position: sourcePosition,
        isConnectable: false,
        className: 'opacity-0'
      }));
    };
    return React.memo(DiagramNode);
  };
  const normalizeNodes = (inputNodes, Position) => inputNodes.map(node => {
    const positions = {
      top: Position.Top,
      right: Position.Right,
      bottom: Position.Bottom,
      left: Position.Left
    };
    return {
      draggable: false,
      selectable: false,
      ...node,
      type: node.type || 'diagramNode',
      sourcePosition: positions[node.sourcePosition] || positions[node.data?.sourcePosition] || (isVertical ? Position.Bottom : Position.Right),
      targetPosition: positions[node.targetPosition] || positions[node.data?.targetPosition] || (isVertical ? Position.Top : Position.Left),
      data: {
        title: node.label || node.id,
        ...node.data || ({})
      }
    };
  });
  const normalizeEdges = inputEdges => inputEdges.map(edge => ({
    type: 'smoothstep',
    animated: true,
    ...edge,
    style: {
      stroke: 'hsl(var(--latitude-diagram-border-strong))',
      strokeWidth: 1.5,
      strokeDasharray: '5 7',
      ...edge.style || ({})
    },
    ...edge.markerEnd ? {
      markerEnd: edge.markerEnd
    } : {}
  }));
  const renderDiagram = ({container}) => {
    const React = window.React;
    const ReactDOM = window.ReactDOM;
    const {Background, Controls, Handle, MiniMap, Position, ReactFlow} = window.ReactFlow;
    const nodeTypes = {
      diagramNode: createDiagramNode(React, Handle, Position)
    };
    const children = [showBackground ? React.createElement(Background, {
      key: 'background'
    }) : null, showMiniMap ? React.createElement(MiniMap, {
      key: 'minimap',
      pannable: interactive,
      zoomable: interactive
    }) : null, showControls ? React.createElement(Controls, {
      key: 'controls',
      showInteractive: false
    }) : null].filter(Boolean);
    const flowElement = React.createElement(ReactFlow, {
      nodes: normalizeNodes(nodes, Position),
      edges: normalizeEdges(edges),
      nodeTypes,
      fitView,
      fitViewOptions: {
        padding: fitViewPadding
      },
      nodesDraggable: interactive,
      nodesConnectable: false,
      elementsSelectable: interactive,
      panOnDrag: interactive,
      panOnScroll: false,
      zoomOnScroll: false,
      zoomOnPinch: interactive,
      zoomOnDoubleClick: interactive,
      preventScrolling: false
    }, ...children);
    const root = ReactDOM.createRoot ? ReactDOM.createRoot(container) : {
      render: element => ReactDOM.render(element, container),
      unmount: () => ReactDOM.unmountComponentAtNode(container)
    };
    root.render(flowElement);
    return root;
  };
  useEffect(() => {
    let isMounted = true;
    loadReactFlow().then(() => {
      if (!isMounted || !containerRef.current) return;
      rootRef.current?.unmount();
      rootRef.current = renderDiagram({
        container: containerRef.current
      });
    }).catch(error => {
      if (!isMounted) return;
      setErrorMessage(error.message);
    });
    return () => {
      isMounted = false;
      rootRef.current?.unmount();
      rootRef.current = null;
    };
  }, [nodes, edges, interactive, showControls, showMiniMap, showBackground, fitView, fitViewPadding, orientation]);
  return <div className={classNames('latitude-react-flow-diagram not-prose my-6 overflow-hidden rounded-xl border border-zinc-950/10 bg-zinc-50 dark:border-white/10 dark:bg-zinc-950/50', heightClassName, className)} data-interactive={interactive ? 'true' : 'false'} style={heightStyle}>
      <style>
        {`
          .latitude-react-flow-diagram {
            --latitude-diagram-brand-uv: var(--brand-uv, 241 87% 61%);
            --latitude-diagram-border: var(--hsl-border, 0 0% 92%);
            --latitude-diagram-border-strong: 210 4% 53%;
            --latitude-diagram-node-bg: 0 0% 100%;
            --latitude-diagram-node-text: 210 4% 20%;
            --latitude-diagram-node-border: 210 4% 28%;
            --latitude-diagram-label-bg: #ffffff;
            --latitude-diagram-surface: 0 0% 100%;
          }

          .dark .latitude-react-flow-diagram {
            --latitude-diagram-brand-uv: var(--brand-uv, 71 99% 67%);
            --latitude-diagram-border: var(--hsl-border, 71 2% 18%);
            --latitude-diagram-border-strong: 71 1.5% 50%;
            --latitude-diagram-node-bg: 71 1.5% 10%;
            --latitude-diagram-node-text: 71 1.5% 90%;
            --latitude-diagram-node-border: 71 1.5% 38%;
            --latitude-diagram-label-bg: hsl(71 1.5% 7%);
            --latitude-diagram-surface: 71 1.5% 7%;
          }

          .latitude-react-flow-diagram[data-interactive="false"] .react-flow__handle {
            opacity: 0;
          }

          .latitude-react-flow-diagram .react-flow__node-default {
            background: hsl(var(--latitude-diagram-node-bg));
            border: 1px solid hsl(var(--latitude-diagram-node-border));
            border-radius: 6px;
            box-shadow: 0 1px 2px hsl(0 0% 0% / 0.06);
            color: hsl(var(--latitude-diagram-node-text));
            font-size: 13px;
            line-height: 1.2;
            padding: 10px 14px;
            white-space: nowrap;
          }

          .latitude-react-flow-diagram .react-flow__edge.animated path {
            animation-duration: 1.7s;
          }

          .dark .latitude-react-flow-diagram .react-flow__node-default {
            background: hsl(var(--latitude-diagram-node-bg));
            border-color: hsl(var(--latitude-diagram-brand-uv) / 0.35);
            box-shadow: 0 0 0 1px hsl(0 0% 0% / 0.2);
            color: hsl(var(--latitude-diagram-node-text));
          }
        `}
      </style>
      {errorMessage ? <div className="flex h-full items-center justify-center px-4 text-sm text-red-600 dark:text-red-400">
          React Flow failed to load: {errorMessage}
        </div> : <div ref={containerRef} className="h-full w-full" />}
    </div>;
};

export const kubernetesArchitectureNodes = [{
  id: 'lks-label',
  position: {
    x: 90,
    y: 0
  },
  type: 'diagramNode',
  data: {
    kind: 'label',
    title: 'Latitude Kubernetes Service (LKS)',
    width: 540,
    align: 'center'
  }
}, {
  id: 'lks-sublabel',
  position: {
    x: 90,
    y: 22
  },
  type: 'diagramNode',
  data: {
    kind: 'label',
    tone: 'badge',
    title: 'Customer cluster — RKE2 on dedicated bare metal',
    width: 540,
    align: 'center'
  }
}, {
  id: 'customer',
  position: {
    x: 10,
    y: 70
  },
  type: 'default',
  data: {
    label: 'Customer (kubectl)'
  },
  style: {
    width: 170
  },
  sourcePosition: 'bottom'
}, {
  id: 'end-users',
  position: {
    x: 520,
    y: 70
  },
  type: 'default',
  data: {
    label: 'End users / Internet'
  },
  style: {
    width: 210
  },
  sourcePosition: 'bottom'
}, {
  id: 'control-plane-label',
  position: {
    x: 10,
    y: 150
  },
  type: 'diagramNode',
  data: {
    kind: 'label',
    title: 'Control Plane (RKE2 HA)',
    width: 460,
    align: 'center'
  }
}, {
  id: 'control-plane-zone',
  position: {
    x: 0,
    y: 175
  },
  type: 'diagramNode',
  data: {
    kind: 'container',
    width: 480,
    height: 140,
    backgroundColor: 'hsl(var(--latitude-diagram-brand-uv) / 0.08)'
  }
}, {
  id: 'cp-1',
  position: {
    x: 20,
    y: 45
  },
  type: 'default',
  parentId: 'control-plane-zone',
  extent: 'parent',
  data: {
    label: 'Control plane 1'
  },
  style: {
    width: 130
  },
  targetPosition: 'top',
  sourcePosition: 'bottom'
}, {
  id: 'cp-2',
  position: {
    x: 175,
    y: 45
  },
  type: 'default',
  parentId: 'control-plane-zone',
  extent: 'parent',
  data: {
    label: 'Control plane 2'
  },
  style: {
    width: 130
  },
  targetPosition: 'top',
  sourcePosition: 'bottom'
}, {
  id: 'cp-3',
  position: {
    x: 330,
    y: 45
  },
  type: 'default',
  parentId: 'control-plane-zone',
  extent: 'parent',
  data: {
    label: 'Control plane 3'
  },
  style: {
    width: 130
  },
  targetPosition: 'top',
  sourcePosition: 'bottom'
}, {
  id: 'control-plane-subtitle',
  position: {
    x: 10,
    y: 322
  },
  type: 'diagramNode',
  data: {
    kind: 'label',
    tone: 'badge',
    title: 'etcd · kube-apiserver · scheduler · controller-manager',
    width: 460,
    align: 'center'
  }
}, {
  id: 'worker-plane-label',
  position: {
    x: 10,
    y: 370
  },
  type: 'diagramNode',
  data: {
    kind: 'label',
    title: 'Worker Plane',
    width: 460,
    align: 'center'
  }
}, {
  id: 'worker-zone',
  position: {
    x: 0,
    y: 395
  },
  type: 'diagramNode',
  data: {
    kind: 'container',
    width: 480,
    height: 140,
    backgroundColor: 'hsl(var(--latitude-diagram-brand-uv) / 0.05)'
  }
}, {
  id: 'worker-1',
  position: {
    x: 20,
    y: 45
  },
  type: 'default',
  parentId: 'worker-zone',
  extent: 'parent',
  data: {
    label: 'Worker 1'
  },
  style: {
    width: 130
  },
  targetPosition: 'top'
}, {
  id: 'worker-2',
  position: {
    x: 175,
    y: 45
  },
  type: 'default',
  parentId: 'worker-zone',
  extent: 'parent',
  data: {
    label: 'Worker 2'
  },
  style: {
    width: 130
  },
  targetPosition: 'top'
}, {
  id: 'worker-3',
  position: {
    x: 330,
    y: 45
  },
  type: 'default',
  parentId: 'worker-zone',
  extent: 'parent',
  data: {
    label: 'Worker 3'
  },
  style: {
    width: 130
  },
  targetPosition: 'right'
}, {
  id: 'worker-plane-subtitle',
  position: {
    x: 10,
    y: 542
  },
  type: 'diagramNode',
  data: {
    kind: 'label',
    tone: 'badge',
    title: 'customer workloads · ingress controllers · CNI',
    width: 460,
    align: 'center'
  }
}, {
  id: 'network-label',
  position: {
    x: 520,
    y: 150
  },
  type: 'diagramNode',
  data: {
    kind: 'label',
    title: 'Network',
    width: 200,
    align: 'center'
  }
}, {
  id: 'network-zone',
  position: {
    x: 510,
    y: 175
  },
  type: 'diagramNode',
  data: {
    kind: 'container',
    width: 220,
    height: 400,
    backgroundColor: 'hsl(var(--latitude-diagram-brand-uv) / 0.05)'
  }
}, {
  id: 'lb-ips',
  position: {
    x: 30,
    y: 40
  },
  type: 'default',
  parentId: 'network-zone',
  extent: 'parent',
  data: {
    label: 'LoadBalancer IPs'
  },
  style: {
    width: 160
  },
  targetPosition: 'top',
  sourcePosition: 'left'
}, {
  id: 'bgp',
  position: {
    x: 30,
    y: 170
  },
  type: 'default',
  parentId: 'network-zone',
  extent: 'parent',
  data: {
    label: 'BGP peering'
  },
  style: {
    width: 160
  }
}, {
  id: 'metallb',
  position: {
    x: 30,
    y: 300
  },
  type: 'default',
  parentId: 'network-zone',
  extent: 'parent',
  data: {
    label: 'MetalLB'
  },
  style: {
    width: 160
  }
}];

export const kubernetesArchitectureEdges = [{
  id: 'customer-cp-zone',
  source: 'customer',
  target: 'control-plane-zone',
  targetHandle: 'target-top'
}, {
  id: 'end-users-network-zone',
  source: 'end-users',
  target: 'network-zone',
  targetHandle: 'target-top'
}, {
  id: 'cp-zone-worker-zone',
  source: 'control-plane-zone',
  target: 'worker-zone',
  sourceHandle: 'source-bottom',
  targetHandle: 'target-top'
}, {
  id: 'network-zone-worker-zone',
  source: 'network-zone',
  target: 'worker-zone',
  sourceHandle: 'source-left',
  targetHandle: 'target-right'
}];

Latitude.sh Kubernetes provisions RKE2 clusters on dedicated bare metal. We bring the control plane and worker nodes up, join them to the cluster, and hand you the kubeconfig. You connect and run your workloads.

<ReactFlowDiagram nodes={kubernetesArchitectureNodes} edges={kubernetesArchitectureEdges} height="lg" fitViewPadding={0.04} />

## What's included

* **Cluster provisioning**: Latitude.sh deploys the control plane and worker nodes on dedicated hardware and joins them into a working RKE2 cluster.
* **Bare metal performance**: Workloads run on dedicated servers for maximum performance and predictable latency.
* **Kubeconfig access**: Download a kubeconfig and connect with standard Kubernetes tooling.
* **LoadBalancer IPs out of the box**: Each cluster ships with LoadBalancer IPs announced via BGP using MetalLB.
* **Scale on demand**: Resize control plane (1-3 nodes) and workers (0-10 nodes), and upgrade Kubernetes versions, from the dashboard or API.

## Shared responsibility

Latitude.sh delivers the running cluster, including the control plane, worker nodes, kubeconfig, and a BGP-announced LoadBalancer IP. You handle day-2 operations: workloads, Kubernetes version upgrades, ingress, storage classes, monitoring, backups, and node OS patching.

For the full breakdown, see [Shared responsibility](/kubernetes/shared-responsibility).

## Creating a cluster

Create Kubernetes clusters from the dashboard. Clusters are deployed with Rancher RKE2.

<Note>
  Before creating a cluster, make sure you have a verified account with a payment method, at least
  one SSH key added to your team or project, and a project selected.
</Note>

<Steps>
  <Step title="Access cluster creation">
    <a href="https://www.latitude.sh/dashboard">Log in to the dashboard</a>, select a project, navigate to **Kubernetes** in the sidebar, and click **Create cluster**.
  </Step>

  <Step title="Select a location">
    Choose a data center region for your cluster. Plan availability and pricing may vary by location.
  </Step>

  <Step title="Select a Kubernetes version">
    Choose which Kubernetes version to run on your cluster. The newest available version is selected
    by default.
  </Step>

  <Step title="Configure control plane">
    Choose a control plane plan best suited for your workload. Use the node count selector to
    configure 1-3 control plane nodes.
  </Step>

  <Step title="Add worker nodes (optional)">
    Click **Add worker node** to add compute capacity for your applications. Select a plan and
    configure the node count (up to 10). All worker nodes share the same plan.
  </Step>

  <Step title="Configure access and details">
    Select one or more SSH keys for node access. These keys let you SSH into the underlying bare metal servers.

    Enter a name for your cluster (3-63 lowercase characters, numbers, or hyphens).
  </Step>

  <Step title="Create the cluster">
    Review the pricing summary and click **Create cluster**.
  </Step>
</Steps>

After creation, you'll be redirected to the cluster overview page. Provisioning typically takes several minutes.

## Viewing your clusters

Navigate to **Kubernetes** in the sidebar to view your clusters. The cluster list displays:

| Column        | Description                                                            |
| ------------- | ---------------------------------------------------------------------- |
| Name          | The cluster identifier                                                 |
| Version       | The Kubernetes version running on the cluster                          |
| Control Plane | Number of control plane nodes                                          |
| Workers       | Number of worker nodes                                                 |
| Location      | The data center region where the cluster is deployed                   |
| Endpoint      | The API server endpoint URL for connecting to the cluster              |
| Status        | Current cluster state (Pending, Deploying, Ready, Deleting, or Failed) |

## Managing your cluster

Click a cluster in the list to view its details and access configuration options.

### Cluster access

From the overview page, you can download your kubeconfig file to connect to the cluster using kubectl:

* **Download kubeconfig**: Save the file to your machine
* **Copy to clipboard**: Copy the kubeconfig contents directly
* **View**: Preview the kubeconfig in the dashboard

Connect to your cluster:

```bash theme={null}
kubectl --kubeconfig=./my-cluster-kubeconfig.yaml get nodes
```

The kubeconfig is available once the cluster reaches Ready status.

### Nodes summary

View a summary of your control plane and worker nodes, including the total count and plan used for each node type.

### LoadBalancer IPs

Kubernetes clusters include LoadBalancer IPs announced via BGP using MetalLB. When you create a LoadBalancer service, an IP from this pool is assigned automatically.

## Viewing cluster nodes

Navigate to the **Nodes** tab to view information about your cluster's servers.

The page displays two sections:

* **Control Plane Nodes**: Servers running Kubernetes core services that maintain cluster state
* **Worker Nodes**: Servers running your application workloads (pods and services)

Each section shows replica and ready counts in the header, with a table listing each node's server name (linked to the server details page), IP address, and status.

Click any row to open a details panel with additional information including the node type, internal IP, and external IP.

## Deleting a cluster

To delete a cluster, go to the **Settings** tab and click **Delete cluster** in the **Danger Zone** section. You'll need to type the cluster name to confirm.

<Warning>
  This permanently destroys all nodes, releases all LoadBalancer IPs, and deletes all data and
  workloads.
</Warning>
