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

# Deploy Cilium CNI

> Learn how to install Cilium CNI on Talos Linux using one of several supported methods.

export const k8s_release = '1.36.1';

Cilium CNI can be installed on Talos Linux using several methods, ranging from a fully declarative GitOps-friendly approach to a quick one-command install for development clusters.

The right method depends on whether you're using Omni, what your production requirements are, and how much you want the installation to persist across reboots.

Each method supports both the default kube-proxy mode and [kube-proxy-free](https://docs.cilium.io/en/stable/network/kubernetes/kubeproxy-free/) operation, covered by tabs in each section.

| Method                                             | Recommended for          | Notes                                                                     |
| -------------------------------------------------- | ------------------------ | ------------------------------------------------------------------------- |
| [Omni manifest sync](#method-1-omni-manifest-sync) | Omni users               | Recommended path for Omni clusters                                        |
| [Helm](#method-2-helm)                             | All other Talos clusters | Four variants — inline, apply, hosted, CLI; choose based on your workflow |
| [Cilium CLI](#method-3-cilium-cli)                 | Development / testing    | Quickest path, least declarative                                          |

## Prerequisites

Talos Linux has specific requirements that apply regardless of which installation method you choose:

* IPAM mode must be set to `kubernetes`
* Talos already provides `cgroupv2` and `bpffs` mounts — do not let Cilium attempt to mount these
* Talos does not allow Kubernetes workloads to load kernel modules, so the `SYS_MODULE` capability must be dropped from Cilium's default capability set
* This guide assumes [KubePrism](../advanced-guides/kubeprism) is enabled and configured on port `7445`

### Machine configuration prerequisites

In addition to the above prerequisites, the Cilium CLI and Helm methods all require that you first configure your machine with CNI set to `none`. Set this before bootstrapping.

To do that:

<Tabs>
  <Tab title="With kube-proxy">
    **1. Create a patch file** that sets the CNI to `none`:

    ```bash theme={null}
    cat <<EOF > patch.yaml
    cluster:
      network:
        cni:
          name: none
    EOF
    ```

    **2. Generate the machine config** with the patch applied:

    ```bash theme={null}
    talosctl gen config \
        my-cluster https://mycluster.local:6443 \
        --config-patch @patch.yaml
    ```
  </Tab>

  <Tab title="Without kube-proxy">
    **1. Create a patch file** that sets the CNI to `none` and disables kube-proxy:

    ```bash theme={null}
    cat <<EOF > patch.yaml
    cluster:
      network:
        cni:
          name: none
      proxy:
        disabled: true
    EOF
    ```

    **2. Generate the machine config** with the patch applied:

    ```bash theme={null}
    talosctl gen config \
        my-cluster https://mycluster.local:6443 \
        --config-patch @patch.yaml
    ```
  </Tab>
</Tabs>

<Note> After applying the machine config with CNI set to `none` and bootstrapping, Talos will appear to hang at phase 18/19 with the message `retrying error: node not ready`. This is expected, nodes are only marked ready once a CNI is running. You have approximately 10 minutes to apply Cilium before the node reboots to retry.</Note>

## Method 1: Omni manifest sync

If you are using [Omni](https://www.siderolabs.com/platform/saas-for-kubernetes/), the recommended approach is to deploy Cilium using the [manifest sync](../../omni/cluster-management/sync-kubernetes-manifests) feature in a cluster template. Omni will wait until the Kubernetes API is available and the cluster is healthy before applying the Cilium manifests.

**Step 1.** Create a `values.yaml` file with the Cilium Helm values by running:

<Tabs>
  <Tab title="With kube-proxy">
    ```bash theme={null}
    cat <<EOF > values.yaml
    ipam:
      mode: kubernetes
    kubeProxyReplacement: false
    securityContext:
      capabilities:
        ciliumAgent:
          - CHOWN
          - KILL
          - NET_ADMIN
          - NET_RAW
          - IPC_LOCK
          - SYS_ADMIN
          - SYS_RESOURCE
          - DAC_OVERRIDE
          - FOWNER
          - SETGID
          - SETUID
        cleanCiliumState:
          - NET_ADMIN
          - SYS_ADMIN
          - SYS_RESOURCE
    cgroup:
      autoMount:
        enabled: false
      hostRoot: /sys/fs/cgroup
    EOF
    ```
  </Tab>

  <Tab title="Without kube-proxy">
    ```bash theme={null}
    cat <<EOF > values.yaml
    ipam:
      mode: kubernetes
    kubeProxyReplacement: true
    k8sServiceHost: localhost
    k8sServicePort: 7445
    securityContext:
      capabilities:
        ciliumAgent:
          - CHOWN
          - KILL
          - NET_ADMIN
          - NET_RAW
          - IPC_LOCK
          - SYS_ADMIN
          - SYS_RESOURCE
          - DAC_OVERRIDE
          - FOWNER
          - SETGID
          - SETUID
        cleanCiliumState:
          - NET_ADMIN
          - SYS_ADMIN
          - SYS_RESOURCE
    cgroup:
      autoMount:
        enabled: false
      hostRoot: /sys/fs/cgroup
    EOF
    ```
  </Tab>
</Tabs>

**Step 2.** Render the Cilium manifests using Helm:

```bash theme={null}
helm repo add cilium https://helm.cilium.io/
helm repo update
helm template \
    cilium \
    cilium/cilium \
    --version 1.18.0 \
    --namespace kube-system \
    --values values.yaml > cilium.yaml
```

**Step 3.** Reference the rendered manifest in your Omni cluster template using the `file` field:

<Tabs>
  <Tab title="With kube-proxy">
    <CodeBlock lang="yaml">
      {`kind: Cluster\nname: my-cluster\nkubernetes:\n  version: ${k8s_release}\n  manifests:\n    - name: cilium\n      file: cilium.yaml\n      mode: one-time\npatches:\n  - name: disable-default-cni\n    inline:\n      cluster:\n        network:\n          cni:\n            name: none\n...\n# Include machines for template`}
    </CodeBlock>
  </Tab>

  <Tab title="Without kube-proxy">
    When deploying without kube-proxy, set `proxy.disabled: true` in the `patches` block:

    <CodeBlock lang="yaml">
      {`kind: Cluster\nname: my-cluster\nkubernetes:\n  version: ${k8s_release}\n  manifests:\n    - name: cilium\n      file: cilium.yaml\n      mode: one-time\npatches:\n  - name: disable-default-cni-and-proxy\n    inline:\n      cluster:\n        network:\n          cni:\n            name: none\n        proxy:\n          disabled: true\n...\n# Include machines for template`}
    </CodeBlock>
  </Tab>
</Tabs>

**Step 4.** Apply the cluster template:

```bash theme={null}
omnictl cluster template sync --file cluster-template.yaml
```

See [Sync Kubernetes Manifests](../../omni/cluster-management/sync-kubernetes-manifests) for more on manifest sync modes and status monitoring.

## Method 2: Helm

Helm is the recommended approach for Talos clusters not managed by Omni. There are four variants depending on how you want to manage and apply the Cilium manifest. Choose the one that fits your workflow:

* **CLI install**: Installs Cilium directly with `helm install` without generating a manifest file. Simplest Helm path but least declarative.
* **Inline manifest**: Embeds the manifest directly in the machine configuration; applied automatically on bootstrap and reapplied on control plane reboots. Recommended for production.
* **Manifest apply**: Generates the manifest with `helm template` and applies it manually with `kubectl`. Useful for one-off installs or when you want to inspect the manifest first.
* **Hosted manifest**: Generates the manifest, hosts it at a URL, and patches the machine config to fetch it automatically during bootstrap. Requires secure internal hosting.

<Tabs>
  <Tab title="CLI install">
    Install Cilium directly using `helm install` during the bootstrap window.

    <Tabs>
      <Tab title="With kube-proxy">
        ```bash theme={null}
        helm repo add cilium https://helm.cilium.io/
        helm repo update

        helm install \
            cilium \
            cilium/cilium \
            --version 1.18.0 \
            --namespace kube-system \
            --set ipam.mode=kubernetes \
            --set kubeProxyReplacement=false \
            --set securityContext.capabilities.ciliumAgent="{CHOWN,KILL,NET_ADMIN,NET_RAW,IPC_LOCK,SYS_ADMIN,SYS_RESOURCE,DAC_OVERRIDE,FOWNER,SETGID,SETUID}" \
            --set securityContext.capabilities.cleanCiliumState="{NET_ADMIN,SYS_ADMIN,SYS_RESOURCE}" \
            --set cgroup.autoMount.enabled=false \
            --set cgroup.hostRoot=/sys/fs/cgroup
        ```
      </Tab>

      <Tab title="Without kube-proxy">
        ```bash theme={null}
        helm repo add cilium https://helm.cilium.io/
        helm repo update

        helm install \
            cilium \
            cilium/cilium \
            --version 1.18.0 \
            --namespace kube-system \
            --set ipam.mode=kubernetes \
            --set kubeProxyReplacement=true \
            --set securityContext.capabilities.ciliumAgent="{CHOWN,KILL,NET_ADMIN,NET_RAW,IPC_LOCK,SYS_ADMIN,SYS_RESOURCE,DAC_OVERRIDE,FOWNER,SETGID,SETUID}" \
            --set securityContext.capabilities.cleanCiliumState="{NET_ADMIN,SYS_ADMIN,SYS_RESOURCE}" \
            --set cgroup.autoMount.enabled=false \
            --set cgroup.hostRoot=/sys/fs/cgroup \
            --set k8sServiceHost=localhost \
            --set k8sServicePort=7445
        ```
      </Tab>

      <Tab title="Without kube-proxy + Gateway API">
        Install [Gateway API CRDs](https://docs.cilium.io/en/stable/network/servicemesh/gateway-api/gateway-api/#prerequisites) first, then:

        ```bash theme={null}
        helm repo add cilium https://helm.cilium.io/
        helm repo update

        helm install \
            cilium \
            cilium/cilium \
            --version 1.18.0 \
            --namespace kube-system \
            --set ipam.mode=kubernetes \
            --set kubeProxyReplacement=true \
            --set securityContext.capabilities.ciliumAgent="{CHOWN,KILL,NET_ADMIN,NET_RAW,IPC_LOCK,SYS_ADMIN,SYS_RESOURCE,DAC_OVERRIDE,FOWNER,SETGID,SETUID}" \
            --set securityContext.capabilities.cleanCiliumState="{NET_ADMIN,SYS_ADMIN,SYS_RESOURCE}" \
            --set cgroup.autoMount.enabled=false \
            --set cgroup.hostRoot=/sys/fs/cgroup \
            --set k8sServiceHost=localhost \
            --set k8sServicePort=7445 \
            --set gatewayAPI.enabled=true \
            --set gatewayAPI.enableAlpn=true \
            --set gatewayAPI.enableAppProtocol=true
        ```
      </Tab>
    </Tabs>

    After Cilium is installed successfully, the bootstrap process will continue and complete.
  </Tab>

  <Tab title="Inline manifest">
    The Cilium manifest is embedded directly in the machine configuration and applied automatically during bootstrap.

    > **Before you start:**
    >
    > * Only add the Cilium inline manifest to control plane node machine configurations, and make sure all control plane nodes have an identical configuration.
    > * Changing the namespace when templating with Helm does not generate a manifest that creates that namespace. If you change the namespace, manually prepend the namespace YAML at the top of the inline manifest.
    > * Talos only creates missing resources from inline manifests — it never deletes or updates them. To update the manifest, edit all control plane machine configurations and run `talosctl upgrade-k8s`.

    **Step 1.** Generate the Cilium manifest:

    <Tabs>
      <Tab title="With kube-proxy">
        ```bash theme={null}
        helm repo add cilium https://helm.cilium.io/
        helm repo update

        helm template \
            cilium \
            cilium/cilium \
            --version 1.18.0 \
            --namespace kube-system \
            --set ipam.mode=kubernetes \
            --set kubeProxyReplacement=false \
            --set securityContext.capabilities.ciliumAgent="{CHOWN,KILL,NET_ADMIN,NET_RAW,IPC_LOCK,SYS_ADMIN,SYS_RESOURCE,DAC_OVERRIDE,FOWNER,SETGID,SETUID}" \
            --set securityContext.capabilities.cleanCiliumState="{NET_ADMIN,SYS_ADMIN,SYS_RESOURCE}" \
            --set cgroup.autoMount.enabled=false \
            --set cgroup.hostRoot=/sys/fs/cgroup > cilium.yaml
        ```
      </Tab>

      <Tab title="Without kube-proxy">
        ```bash theme={null}
        helm repo add cilium https://helm.cilium.io/
        helm repo update

        helm template \
            cilium \
            cilium/cilium \
            --version 1.18.0 \
            --namespace kube-system \
            --set ipam.mode=kubernetes \
            --set kubeProxyReplacement=true \
            --set securityContext.capabilities.ciliumAgent="{CHOWN,KILL,NET_ADMIN,NET_RAW,IPC_LOCK,SYS_ADMIN,SYS_RESOURCE,DAC_OVERRIDE,FOWNER,SETGID,SETUID}" \
            --set securityContext.capabilities.cleanCiliumState="{NET_ADMIN,SYS_ADMIN,SYS_RESOURCE}" \
            --set cgroup.autoMount.enabled=false \
            --set cgroup.hostRoot=/sys/fs/cgroup \
            --set k8sServiceHost=localhost \
            --set k8sServicePort=7445 > cilium.yaml
        ```
      </Tab>
    </Tabs>

    **Step 2.** Embed the generated manifest in your machine configuration:

    ```yaml theme={null}
    cluster:
      inlineManifests:
        - name: cilium
          contents: |
            ---
            # Source: cilium/templates/cilium-agent/serviceaccount.yaml
            apiVersion: v1
            kind: ServiceAccount
            metadata:
              name: "cilium"
              namespace: kube-system
            ---
            # Paste the full contents of cilium.yaml here
    ```
  </Tab>

  <Tab title="Manifest apply">
    Generate the manifest with `helm template` and apply it manually with `kubectl` during the bootstrap window.

    <Tabs>
      <Tab title="With kube-proxy">
        ```bash theme={null}
        helm repo add cilium https://helm.cilium.io/
        helm repo update

        helm template \
            cilium \
            cilium/cilium \
            --version 1.18.0 \
            --namespace kube-system \
            --set ipam.mode=kubernetes \
            --set kubeProxyReplacement=false \
            --set securityContext.capabilities.ciliumAgent="{CHOWN,KILL,NET_ADMIN,NET_RAW,IPC_LOCK,SYS_ADMIN,SYS_RESOURCE,DAC_OVERRIDE,FOWNER,SETGID,SETUID}" \
            --set securityContext.capabilities.cleanCiliumState="{NET_ADMIN,SYS_ADMIN,SYS_RESOURCE}" \
            --set cgroup.autoMount.enabled=false \
            --set cgroup.hostRoot=/sys/fs/cgroup > cilium.yaml

        kubectl apply -f cilium.yaml
        ```
      </Tab>

      <Tab title="Without kube-proxy">
        ```bash theme={null}
        helm repo add cilium https://helm.cilium.io/
        helm repo update

        helm template \
            cilium \
            cilium/cilium \
            --version 1.18.0 \
            --namespace kube-system \
            --set ipam.mode=kubernetes \
            --set kubeProxyReplacement=true \
            --set securityContext.capabilities.ciliumAgent="{CHOWN,KILL,NET_ADMIN,NET_RAW,IPC_LOCK,SYS_ADMIN,SYS_RESOURCE,DAC_OVERRIDE,FOWNER,SETGID,SETUID}" \
            --set securityContext.capabilities.cleanCiliumState="{NET_ADMIN,SYS_ADMIN,SYS_RESOURCE}" \
            --set cgroup.autoMount.enabled=false \
            --set cgroup.hostRoot=/sys/fs/cgroup \
            --set k8sServiceHost=localhost \
            --set k8sServicePort=7445 > cilium.yaml

        kubectl apply -f cilium.yaml
        ```
      </Tab>
    </Tabs>
  </Tab>

  <Tab title="Hosted manifest">
    Generate the manifest, host it at a secure internal URL, and patch the machine config to fetch it automatically during bootstrap.

    <Warning>The Helm-generated Cilium manifest contains sensitive key material. Do not host it somewhere publicly accessible.</Warning>

    **Step 1.** Generate `cilium.yaml` using `helm template` as shown in the Manifest apply tab above.

    **Step 2.** Host the file at a secure internal URL, then patch your machine configuration:

    ```bash theme={null}
    cat <<EOF > patch.yaml
    cluster:
      network:
        cni:
          name: custom
          urls:
            - https://server.yourdomain.tld/some/path/cilium.yaml
    EOF
    ```

    ```bash theme={null}
    talosctl gen config \
      my-cluster https://mycluster.local:6443 \
      --config-patch @patch.yaml
    ```
  </Tab>
</Tabs>

## Method 3: Cilium CLI

Install the [Cilium CLI](https://docs.cilium.io/en/stable/gettingstarted/k8s-install-default/#install-the-cilium-cli) following the upstream instructions, then run:

<Tabs>
  <Tab title="With kube-proxy">
    ```bash theme={null}
    cilium install \
        --set ipam.mode=kubernetes \
        --set kubeProxyReplacement=false \
        --set securityContext.capabilities.ciliumAgent="{CHOWN,KILL,NET_ADMIN,NET_RAW,IPC_LOCK,SYS_ADMIN,SYS_RESOURCE,DAC_OVERRIDE,FOWNER,SETGID,SETUID}" \
        --set securityContext.capabilities.cleanCiliumState="{NET_ADMIN,SYS_ADMIN,SYS_RESOURCE}" \
        --set cgroup.autoMount.enabled=false \
        --set cgroup.hostRoot=/sys/fs/cgroup
    ```
  </Tab>

  <Tab title="Without kube-proxy">
    ```bash theme={null}
    cilium install \
        --set ipam.mode=kubernetes \
        --set kubeProxyReplacement=true \
        --set securityContext.capabilities.ciliumAgent="{CHOWN,KILL,NET_ADMIN,NET_RAW,IPC_LOCK,SYS_ADMIN,SYS_RESOURCE,DAC_OVERRIDE,FOWNER,SETGID,SETUID}" \
        --set securityContext.capabilities.cleanCiliumState="{NET_ADMIN,SYS_ADMIN,SYS_RESOURCE}" \
        --set cgroup.autoMount.enabled=false \
        --set cgroup.hostRoot=/sys/fs/cgroup \
        --set k8sServiceHost=localhost \
        --set k8sServicePort=7445
    ```
  </Tab>

  <Tab title="Without kube-proxy + Gateway API">
    Install [Gateway API CRDs](https://docs.cilium.io/en/stable/network/servicemesh/gateway-api/gateway-api/#prerequisites) first, then:

    <Note>If you plan to use gRPC and GRPCRoutes with TLS, you must enable ALPN (`gatewayAPI.enableAlpn=true`). gRPC relies on HTTP/2, and ALPN is required to negotiate HTTP/2 support between client and server.</Note>

    ```bash theme={null}
    cilium install \
        --set ipam.mode=kubernetes \
        --set kubeProxyReplacement=true \
        --set securityContext.capabilities.ciliumAgent="{CHOWN,KILL,NET_ADMIN,NET_RAW,IPC_LOCK,SYS_ADMIN,SYS_RESOURCE,DAC_OVERRIDE,FOWNER,SETGID,SETUID}" \
        --set securityContext.capabilities.cleanCiliumState="{NET_ADMIN,SYS_ADMIN,SYS_RESOURCE}" \
        --set cgroup.autoMount.enabled=false \
        --set cgroup.hostRoot=/sys/fs/cgroup \
        --set k8sServiceHost=localhost \
        --set k8sServicePort=7445 \
        --set gatewayAPI.enabled=true \
        --set gatewayAPI.enableAlpn=true \
        --set gatewayAPI.enableAppProtocol=true
    ```
  </Tab>
</Tabs>

## Known issues

The following are known issues when running Cilium on Talos Linux. Each entry includes a workaround where one exists.

* **GCP internal load balancers**: There are known issues when using Talos and Cilium on Google Cloud Platform with internal load balancers. See [GCP ILB support / support scope local routes to be configured](https://github.com/siderolabs/talos/issues/4109) for details.

* **CoreDNS not working with `forwardKubeDNSToHost` and `bpf.masquerade`**: When using Talos `forwardKubeDNSToHost=true` (enabled by default) together with Cilium `bpf.masquerade=true`, CoreDNS may not work correctly. Setting `forwardKubeDNSToHost=false` resolves the issue. See [the discussion here](https://github.com/siderolabs/talos/pull/9200) for more context.

* **`cilium connectivity test` fails with PodSecurity errors**: After installing Cilium, `cilium connectivity test` may hang or fail with an error like:

  ```text theme={null}
  Error creating: pods "client-69748f45d8-9b9jg" is forbidden: violates PodSecurity "baseline:latest":
  non-default capabilities (container "client" must not include "NET_RAW" in securityContext.capabilities.add)
  ```

  This is expected. Work around it by adding the `pod-security.kubernetes.io/enforce=privileged` [label on the namespace level](../security/pod-security).

## Additional resources

Talos has full kernel module support for eBPF. See [Cilium System Requirements](https://docs.cilium.io/en/stable/operations/system_requirements/), [Talos Kernel Config AMD64](https://github.com/siderolabs/pkgs/blob/main/kernel/build/config-amd64), and [Talos Kernel Config ARM64](https://github.com/siderolabs/pkgs/blob/main/kernel/build/config-arm64).
