{"slug":"kubernetes-operator","title":"Kubernetes operator","tags":["tailscale","containers"],"agent_summary":"Last validated: Jan 5, 2026","trigger_phrases":[],"runnable":false,"markdown":"\r\n# Kubernetes operator\r\n\r\nLast validated: Jan 5, 2026\r\n\r\nTailscale Kubernetes Operatoris available for [all plans](https://tailscale.com/pricing).\r\n\r\nThe [Tailscale Kubernetes Operator](https://github.com/tailscale/tailscale/blob/main/cmd/k8s-operator/deploy/manifests/operator.yaml) lets you:\r\n\r\n- [Access the Kubernetes control plane using an API server proxy](https://tailscale.com/docs/features/kubernetes-operator/how-to/api-server-proxy)\r\n- [Expose a tailnet service to your Kubernetes cluster (cluster egress)](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-egress)\r\n- [Expose a cluster workload to your tailnet (cluster ingress)](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress)\r\n- [Expose a cluster workload to another cluster (cross-cluster connectivity)](https://tailscale.com/docs/features/kubernetes-operator/how-to/cross-cluster)\r\n- [Expose a cloud service to your tailnet](https://tailscale.com/docs/features/kubernetes-operator/how-to/cloud-services)\r\n- [Deploy exit nodes and subnet routers](https://tailscale.com/docs/features/kubernetes-operator/how-to/connector)\r\n- [Deploy app connector](https://tailscale.com/docs/features/kubernetes-operator/how-to/app-connector)\r\n- [Deploy `tsrecorder`](https://tailscale.com/docs/features/kubernetes-operator/how-to/tsrecorder)\r\n- [Expose multi-cluster applications to internal users](https://tailscale.com/docs/features/kubernetes-operator/how-to/multi-cluster-ingress)\r\n- [Manage multi-cluster deployments with ArgoCD](https://tailscale.com/docs/solutions/manage-multi-cluster-kubernetes-deployments-argocd)\r\n\r\n## [Setting up the Kubernetes operator](https://tailscale.com/docs/features/kubernetes-operator\\#setting-up-the-kubernetes-operator)\r\n\r\n### [Prerequisites](https://tailscale.com/docs/features/kubernetes-operator\\#prerequisites)\r\n\r\nTailscale Kubernetes Operator must be configured with [OAuth client credentials](https://tailscale.com/docs/features/oauth-clients#setting-up-an-oauth-client). The operator uses these credentials to manage devices by using the [Tailscale API](https://tailscale.com/docs/reference/tailscale-api) and to create [auth keys](https://tailscale.com/docs/features/access-control/auth-keys) for itself and the devices it manages.\r\n\r\n1. In your [tailnet policy file](https://tailscale.com/docs/features/access-control/acls), create the [tags](https://tailscale.com/docs/features/tags)`tag:k8s-operator` and `tag:k8s`, and make `tag:k8s-operator` an owner of `tag:k8s`. If you want your `Services` to be exposed with tags other than the default `tag:k8s`, create those as well and make `tag:k8s-operator` an owner.\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n```json\r\n\"tagOwners\": {\r\n     \"tag:k8s-operator\": [],\r\n     \"tag:k8s\": [\"tag:k8s-operator\"],\r\n}\r\n```\r\n\r\n\r\n\r\n\r\n\r\nYou can use the [visual policy editor](https://tailscale.com/docs/features/visual-editor) to manage your tailnet policy file. Refer to the [visual editor reference](https://tailscale.com/docs/reference/visual-editor) for guidance on using the visual editor.\r\n\r\n2. [Create an OAuth client](https://tailscale.com/docs/features/oauth-clients#setting-up-an-oauth-client) in the [Trust credentials](https://login.tailscale.com/admin/settings/trust-credentials) page of the admin console. Create the client with `Devices Core`, `Auth Keys`, `Services` write scopes, and the tag `tag:k8s-operator`.\r\n\r\n\r\n### [Installation](https://tailscale.com/docs/features/kubernetes-operator\\#installation)\r\n\r\nA default operator installation creates:\r\n\r\n- A `\"tailscale\"``Namespace`.\r\n- An `\"operator\"``Deployment`.\r\n- Role-based access control (RBAC) for the operator.\r\n- A `\"tailscale\"``IngressClass`.\r\n- `ProxyClass`, `Connector`, `ProxyGroup`, `DNSConfig`, `Recorder` Custom Resource Definitions (CRDs).\r\n\r\nThere are two ways to install the Tailscale Kubernetes Operator: using [Helm](https://tailscale.com/docs/features/kubernetes-operator#helm) or applying [static manifests with `kubectl`](https://tailscale.com/docs/features/kubernetes-operator#static-manifests).\r\n\r\n#### [Helm](https://tailscale.com/docs/features/kubernetes-operator\\#helm)\r\n\r\nTailscale Kubernetes Operator's Helm charts are available from two chart repositories.\r\n\r\nThe `https://pkgs.tailscale.com/helmcharts` repository contains well-tested charts for [stable Tailscale versions](https://tailscale.com/docs/reference/tailscale-client-versions).\r\n\r\nHelm charts and container images for a new stable Tailscale version are released a few days after the official release. This is done to avoid releasing image\r\nversions with potential bugs in the core Linux client or core libraries.\r\n\r\nThe `https://pkgs.tailscale.com/unstable/helmcharts` repository contains charts with the very latest changes, published in between official releases.\r\n\r\nThe charts in both repositories are different versions of the same chart and you can upgrade from one to the other.\r\n\r\nTo install the latest Kubernetes Tailscale operator from `https://pkgs.tailscale.com/helmcharts` in `tailscale` namespace:\r\n\r\n1. Add `https://pkgs.tailscale.com/helmcharts` to your local Helm repositories:\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n```shell\r\nhelm repo add tailscale https://pkgs.tailscale.com/helmcharts\r\n```\r\n\r\n2. Update your local Helm cache:\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n```shell\r\nhelm repo update\r\n```\r\n\r\n3. Install the operator passing the OAuth client credentials that you created earlier:\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n```shell\r\nhelm upgrade \\\r\n     --install \\\r\n     tailscale-operator \\\r\n     tailscale/tailscale-operator \\\r\n     --namespace=tailscale \\\r\n     --create-namespace \\\r\n     --set-string oauth.clientId=\"<OAauth client ID>\" \\\r\n     --set-string oauth.clientSecret=\"<OAuth client secret>\" \\\r\n     --wait\r\n```\r\n\r\n\r\n#### [Static manifests with `kubectl`](https://tailscale.com/docs/features/kubernetes-operator\\#static-manifests-with-kubectl)\r\n\r\n1. Download the Tailscale Kubernetes Operator [manifest file](https://github.com/tailscale/tailscale/blob/main/cmd/k8s-operator/deploy/manifests/operator.yaml) from the [`tailscale/tailscale`](https://github.com/tailscale/tailscale) repository.\r\n\r\n2. Edit your version of the manifest file:\r\n\r\n\r\n1. Find `# SET CLIENT ID HERE` and replace it with your OAuth client ID.\r\n2. Find `# SET CLIENT SECRET HERE` and replace it with your OAuth client secret. The OAuth client secret is case-sensitive.\r\n\r\nFor both the client ID and secret, quote the value, to avoid any potential YAML misinterpretation of unquoted strings. For example, use:\r\n\r\n```yaml\r\nclient_id: \"k123456CNTRL\"\r\nclient_secret: \"tskey-client-k123456CNTRL-abcdef\"\r\n```\r\n\r\ninstead of:\r\n\r\n```yaml\r\nclient_id: k123456CNTRL\r\nclient_secret: tskey-client-k123456CNTRL-abcdef\r\n```\r\n\r\n3. Apply the edited file to your Kubernetes cluster:\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n```shell\r\nkubectl apply -f manifest.yaml\r\n```\r\n\r\n\r\n### [Installation with workload identity federation](https://tailscale.com/docs/features/kubernetes-operator\\#installation-with-workload-identity-federation)\r\n\r\nWorkload identity federationis currently [in beta](https://tailscale.com/docs/reference/tailscale-release-stages#beta).\r\n\r\nTailscale supports [workload identity federation](https://tailscale.com/docs/features/workload-identity-federation) for authenticating to a tailnet using provider-native identity tokens. The operator can leverage its ServiceAccount token as an OIDC identity and authenticate without requiring a long-lived OAuth client secret.\r\n\r\n#### [Prerequisites](https://tailscale.com/docs/features/kubernetes-operator\\#prerequisites-1)\r\n\r\n- The Kubernetes cluster's OIDC discovery endpoints must be publicly accessible. Bind the `\"unauthenticated\"` group to the `\"system:service-account-issuer-discovery\"``ClusterRole` to allow unauthenticated access:\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n```shell\r\nkubectl create clusterrolebinding oidc-discovery \\\r\n    --clusterrole=system:service-account-issuer-discovery \\\r\n    --group=system:unauthenticated\r\n```\r\n\r\n\r\n#### [Helm](https://tailscale.com/docs/features/kubernetes-operator\\#helm-1)\r\n\r\n1. Get your cluster's issuer:\r\n\r\n\r\n\r\nThe following command has a `jq` dependency. You may need to install `jq` on your device.\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n```shell\r\nISSUER=\"$(kubectl get --raw /.well-known/openid-configuration | jq '.issuer')\"\r\n```\r\n\r\n2. [Configure a federated identity in the admin console](https://tailscale.com/docs/features/workload-identity-federation#configure-federated-identities-in-the-admin-console) using:\r\n   - Set the **Issuer** to **Custom issuer**.\r\n   - Set the **Issuer URL** to the value of `$ISSUER` from the previous step.\r\n   - Set the **Subject** to `system:serviceaccount:tailscale:operator`.\r\n   - Leave the **Audience** field blank.\r\n   - Set `Devices Core` and `Auth Keys` and `Services` write scopes.\r\n   - And the tag `tag:k8s-operator`.\r\n3. Add `https://pkgs.tailscale.com/helmcharts` to your local Helm repositories:\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n```shell\r\nhelm repo add tailscale https://pkgs.tailscale.com/helmcharts\r\n```\r\n\r\n4. Update your local Helm cache:\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n```shell\r\nhelm repo update\r\n```\r\n\r\n5. Install the operator passing the OAuth client details that you created earlier:\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n```shell\r\nhelm upgrade \\\r\n     --install \\\r\n     tailscale-operator \\\r\n     tailscale/tailscale-operator \\\r\n     --namespace=tailscale \\\r\n     --create-namespace \\\r\n     --set-string oauth.clientId=\"<OAauth client ID>\" \\\r\n     --set-string oauth.audience=\"<OAuth client audience>\" \\\r\n     --wait\r\n```\r\n\r\n\r\n### [Validation](https://tailscale.com/docs/features/kubernetes-operator\\#validation)\r\n\r\nVerify that the Tailscale operator has joined your tailnet. Open the [Machines](https://login.tailscale.com/admin/machines) page of the admin console and look for a node named `tailscale-operator` (or your customized hostname) tagged with the `tag:k8s-operator` tag. It may take some time for the operator to join your tailnet as the container image downloads and the Pod starts.\r\n\r\n### [(Optional) Pre-creating a `ProxyGroup`](https://tailscale.com/docs/features/kubernetes-operator\\#optional-pre-creating-a-proxygroup)\r\n\r\nWhen a user configures an [ingress](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress) or [egress](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-egress) proxy, the default mode for the operator is to create a tailnet device deployed as a `StatefulSet` with a single `Pod`.\r\n\r\nThis model has a few caveats:\r\n\r\n- a single `Pod` means that there will be some downtime during proxy upgrades, cluster upgrades, etc\r\n\r\n- a `Pod` per proxy may not be feasible for large installations (high resource consumption)\r\n\r\n\r\nTailscale Kubernetes Operator 1.76 and later provides the ability to pre-create a multi-replica `ProxyGroup`. [Ingress](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress) and [egress](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-egress) can then be exposed redundantly by using the `ProxyGroup`.\r\n\r\nTo create a `ProxyGroup` with three replicas for Tailscale egress `Service`s:\r\n\r\n1. Apply the following manifest:\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n```yaml\r\napiVersion: tailscale.com/v1alpha1\r\nkind: ProxyGroup\r\nmetadata:\r\n     name: ts-proxies\r\nspec:\r\n     type: egress\r\n     replicas: 3\r\n```\r\n\r\n2. (Optional) Wait for the `ProxyGroup` to become ready:\r\n\r\n\r\n```shell\r\nkubectl wait proxygroup ts-proxies --for=condition=ProxyGroupReady=true\r\n```\r\n\r\nFor the above `ProxyGroup` the operator creates a `StatefulSet` with three replicas, each of which is a tailnet device.\r\n\r\nEgress `Service`s can now refer to the newly created `ProxyGroup`. Refer to [Configure an egress `Service` using `ProxyGroup`](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-egress).\r\n\r\nYou can find [all available `ProxyGroup` configuration options on GitHub →](https://github.com/tailscale/tailscale/blob/main/k8s-operator/api.md#proxygroup)\r\n\r\n## [Supported versions](https://tailscale.com/docs/features/kubernetes-operator\\#supported-versions)\r\n\r\n### [Operator and proxies](https://tailscale.com/docs/features/kubernetes-operator\\#operator-and-proxies)\r\n\r\nTailscale recommends that you use the same version for the operator and the proxies, because majority of our tests run against the same versions.\r\n\r\nThe operator supports proxies running a Tailscale version up to four minor versions earlier than the operator's version.\r\nThe operator does not support proxies running a Tailscale version later than the operator's version.\r\n\r\n### [Kubernetes versions](https://tailscale.com/docs/features/kubernetes-operator\\#kubernetes-versions)\r\n\r\nThe earliest supported version of Kubernetes is v1.23.0.\r\n\r\n## [CNI compatibility](https://tailscale.com/docs/features/kubernetes-operator\\#cni-compatibility)\r\n\r\nThe operator creates proxies that configure custom routing and forwarding rules in each proxy `Pod`'s network namespace only.\r\nBecause the proxying is implemented in the proxy `Pod`'s namespace, the routing and firewall configuration on the `Node` (for example, using iptables, eBPF, or any other mechanism) doesn't affect the proxies.\r\nThis means that the proxies work with most CNI configurations out of the box.\r\n\r\n## [TLS certificates and renewal](https://tailscale.com/docs/features/kubernetes-operator\\#tls-certificates-and-renewal)\r\n\r\nThe operator automatically provisions TLS certificates for Tailscale Ingress and API server proxy services. Certificates are valid for 90 days, and typically renew two-thirds through their validity period. Certificates only renew if there is traffic to the service. If a certificate expires, the next request to the service will trigger a renewal.\r\n\r\n## [EKS Fargate](https://tailscale.com/docs/features/kubernetes-operator\\#eks-fargate)\r\n\r\nOn [EKS Fargate](https://docs.aws.amazon.com/eks/latest/userguide/fargate.html), currently the only supported operator features are [Tailscale `Ingress`](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress#ingress-resource) and [Tailscale API server proxy](https://tailscale.com/docs/features/kubernetes-operator/how-to/api-server-proxy).\r\n[Tailscale ingress `Service`s](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress#ingress-service), [Tailscale egress `Service`s](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-egress) and the [`Connector`](https://tailscale.com/docs/features/kubernetes-operator/how-to/connector) configurations currently contain [privileged containers](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/) and containers with [CAP\\_NET\\_ADMIN](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/).\r\nThis is not supported on EKS Fargate.\r\n\r\n### [Cilium in kube-proxy replacement mode](https://tailscale.com/docs/features/kubernetes-operator\\#cilium-in-kube-proxy-replacement-mode)\r\n\r\nYou must enable [bypassing socket load balancer in Pods' namespaces](https://docs.cilium.io/en/stable/network/kubernetes/kubeproxy-free/#socket-loadbalancer-bypass-in-pod-namespace) if you run Cilium in [kube-proxy replacement mode](https://docs.cilium.io/en/stable/network/kubernetes/kubeproxy-free) and want to do one or more of the following:\r\n\r\n- Expose a Kubernetes `Service` to your tailnet as a [Tailscale LoadBalancer `Service`](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress#loadbalancerclass).\r\n- Expose a Kubernetes `Service` to your tailnet using [`tailscale.com/expose` annotation](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress).\r\n- Expose a Service CIDR range by using [`Connector`](https://tailscale.com/docs/features/kubernetes-operator/how-to/connector).\r\n\r\nThis is needed because when Cilium runs in kube-proxy replacement mode with the socket load balancing in `Pod`s' namespaces enabled, connections from `Pod`s to `ClusterIP`s go over a TCP socket (instead of going out via `Pod`s' `veth` devices) and thus bypasses [Tailscale firewall rules](https://tailscale.com/docs/reference/netfilter-modes) that are attached to [netfilter hooks](https://www.netfilter.org/).\r\n\r\nIf you encounter bandwidth issues while using Cilium, use the `--devices` flag to explicitly specify which network interfaces Cilium should monitor for the maximum transmission unit (MTU). This prevents Cilium from defaulting to the MTU of the `tailscale0` interface and instead ensures it uses the MTU of a physical interface on the host.\r\n\r\n## [Customization](https://tailscale.com/docs/features/kubernetes-operator\\#customization)\r\n\r\n[Customize the operator and resources it manages](https://tailscale.com/docs/features/kubernetes-operator/how-to/customize).\r\n\r\n## [Troubleshooting](https://tailscale.com/docs/features/kubernetes-operator\\#troubleshooting)\r\n\r\n[Troubleshoot the operator and resources it manages](https://tailscale.com/docs/reference/troubleshooting/containers/kubernetes-operator).\r\n\r\n## [Limitations](https://tailscale.com/docs/features/kubernetes-operator\\#limitations)\r\n\r\n- There are no dashboards or metrics. We are interested to hear what metrics you would find useful — do [reach out](https://github.com/tailscale/tailscale/issues/new/choose).\r\n- The container images, charts or manifests are not signed. _We are working on this._\r\n- The static manifests are currently only available from the [`tailscale/tailscale`](https://github.com/tailscale/tailscale) codebase. _We are working to improve this flow._\r\n- Using the operator on [OpenShift](https://www.redhat.com/en/technologies/cloud-computing/openshift) is currently not supported.\r\n\r\n## [Glossary](https://tailscale.com/docs/features/kubernetes-operator\\#glossary)\r\n\r\n**Proxy**\r\n\r\nIn the context of this document, a proxy is the Tailscale node deployed for each user-configured component that the operator manages (such as a [`Tailscale Ingress`](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress#ingress-resource) or a [`Connector`](https://tailscale.com/docs/features/kubernetes-operator/how-to/connector)).\r\nThe proxy is deployed as a `StatefulSet` in the operator's namespace (defaults to `tailscale`).\r\nThe `StatefulSet`s name is prefixed by a portion of the configured component's name.\r\n\r\nIf you need to reliably refer to the proxy's `StatefulSet`, you can use label selectors.\r\nFor example, to find `StatefulSet` for a Tailscale `Ingress` resource named `ts-ingress` in `prod` namespace , you can run:\r\n\r\n```shell\r\nkubectl get statefulset \\\r\n  --namespace tailscale \\\r\n  --selector=\"tailscale.com/managed=true,tailscale.com/parent-resource-type=ingress,tailscale.com/parent-resource=ts-ingress,tailscale.com/parent-resource-ns=prod\"\r\n```\r\n\r\nThe `tailscale.com/parent-resource` label is set to `svc` for a `Service` and to `connector` for a `Connector`.\r\nThe `tailscale.com` labels are also propagated to the `Pod`.\r\n\r\n![Project Logo](https://cdn.brandfetch.io/tailscale.com/fallback/lettermark/theme/dark/h/256/w/256/icon?c=1bfwsmEH20zzEfSNTed)\r\n\r\nAsk AI\r\n\r\nreCAPTCHA\r\n\r\nRecaptcha requires verification.\r\n\r\nprotected by **reCAPTCHA**\r\n","html":"<h1>Kubernetes operator</h1>\n<p>Last validated: Jan 5, 2026</p>\n<p>Tailscale Kubernetes Operatoris available for <a href=\"https://tailscale.com/pricing\">all plans</a>.</p>\n<p>The <a href=\"https://github.com/tailscale/tailscale/blob/main/cmd/k8s-operator/deploy/manifests/operator.yaml\">Tailscale Kubernetes Operator</a> lets you:</p>\n<ul>\n<li><a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/api-server-proxy\">Access the Kubernetes control plane using an API server proxy</a></li>\n<li><a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-egress\">Expose a tailnet service to your Kubernetes cluster (cluster egress)</a></li>\n<li><a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress\">Expose a cluster workload to your tailnet (cluster ingress)</a></li>\n<li><a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cross-cluster\">Expose a cluster workload to another cluster (cross-cluster connectivity)</a></li>\n<li><a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cloud-services\">Expose a cloud service to your tailnet</a></li>\n<li><a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/connector\">Deploy exit nodes and subnet routers</a></li>\n<li><a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/app-connector\">Deploy app connector</a></li>\n<li><a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/tsrecorder\">Deploy <code>tsrecorder</code></a></li>\n<li><a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/multi-cluster-ingress\">Expose multi-cluster applications to internal users</a></li>\n<li><a href=\"https://tailscale.com/docs/solutions/manage-multi-cluster-kubernetes-deployments-argocd\">Manage multi-cluster deployments with ArgoCD</a></li>\n</ul>\n<h2><a href=\"https://tailscale.com/docs/features/kubernetes-operator#setting-up-the-kubernetes-operator\">Setting up the Kubernetes operator</a></h2>\n<h3><a href=\"https://tailscale.com/docs/features/kubernetes-operator#prerequisites\">Prerequisites</a></h3>\n<p>Tailscale Kubernetes Operator must be configured with <a href=\"https://tailscale.com/docs/features/oauth-clients#setting-up-an-oauth-client\">OAuth client credentials</a>. The operator uses these credentials to manage devices by using the <a href=\"https://tailscale.com/docs/reference/tailscale-api\">Tailscale API</a> and to create <a href=\"https://tailscale.com/docs/features/access-control/auth-keys\">auth keys</a> for itself and the devices it manages.</p>\n<ol>\n<li>In your <a href=\"https://tailscale.com/docs/features/access-control/acls\">tailnet policy file</a>, create the <a href=\"https://tailscale.com/docs/features/tags\">tags</a><code>tag:k8s-operator</code> and <code>tag:k8s</code>, and make <code>tag:k8s-operator</code> an owner of <code>tag:k8s</code>. If you want your <code>Services</code> to be exposed with tags other than the default <code>tag:k8s</code>, create those as well and make <code>tag:k8s-operator</code> an owner.</li>\n</ol>\n<pre><code class=\"language-json\">\"tagOwners\": {\r\n     \"tag:k8s-operator\": [],\r\n     \"tag:k8s\": [\"tag:k8s-operator\"],\r\n}\n</code></pre>\n<p>You can use the <a href=\"https://tailscale.com/docs/features/visual-editor\">visual policy editor</a> to manage your tailnet policy file. Refer to the <a href=\"https://tailscale.com/docs/reference/visual-editor\">visual editor reference</a> for guidance on using the visual editor.</p>\n<ol start=\"2\">\n<li><a href=\"https://tailscale.com/docs/features/oauth-clients#setting-up-an-oauth-client\">Create an OAuth client</a> in the <a href=\"https://login.tailscale.com/admin/settings/trust-credentials\">Trust credentials</a> page of the admin console. Create the client with <code>Devices Core</code>, <code>Auth Keys</code>, <code>Services</code> write scopes, and the tag <code>tag:k8s-operator</code>.</li>\n</ol>\n<h3><a href=\"https://tailscale.com/docs/features/kubernetes-operator#installation\">Installation</a></h3>\n<p>A default operator installation creates:</p>\n<ul>\n<li>A <code>\"tailscale\"``Namespace</code>.</li>\n<li>An <code>\"operator\"``Deployment</code>.</li>\n<li>Role-based access control (RBAC) for the operator.</li>\n<li>A <code>\"tailscale\"``IngressClass</code>.</li>\n<li><code>ProxyClass</code>, <code>Connector</code>, <code>ProxyGroup</code>, <code>DNSConfig</code>, <code>Recorder</code> Custom Resource Definitions (CRDs).</li>\n</ul>\n<p>There are two ways to install the Tailscale Kubernetes Operator: using <a href=\"https://tailscale.com/docs/features/kubernetes-operator#helm\">Helm</a> or applying <a href=\"https://tailscale.com/docs/features/kubernetes-operator#static-manifests\">static manifests with <code>kubectl</code></a>.</p>\n<h4><a href=\"https://tailscale.com/docs/features/kubernetes-operator#helm\">Helm</a></h4>\n<p>Tailscale Kubernetes Operator's Helm charts are available from two chart repositories.</p>\n<p>The <code>https://pkgs.tailscale.com/helmcharts</code> repository contains well-tested charts for <a href=\"https://tailscale.com/docs/reference/tailscale-client-versions\">stable Tailscale versions</a>.</p>\n<p>Helm charts and container images for a new stable Tailscale version are released a few days after the official release. This is done to avoid releasing image\r\nversions with potential bugs in the core Linux client or core libraries.</p>\n<p>The <code>https://pkgs.tailscale.com/unstable/helmcharts</code> repository contains charts with the very latest changes, published in between official releases.</p>\n<p>The charts in both repositories are different versions of the same chart and you can upgrade from one to the other.</p>\n<p>To install the latest Kubernetes Tailscale operator from <code>https://pkgs.tailscale.com/helmcharts</code> in <code>tailscale</code> namespace:</p>\n<ol>\n<li>Add <code>https://pkgs.tailscale.com/helmcharts</code> to your local Helm repositories:</li>\n</ol>\n<pre><code class=\"language-shell\">helm repo add tailscale https://pkgs.tailscale.com/helmcharts\n</code></pre>\n<ol start=\"2\">\n<li>Update your local Helm cache:</li>\n</ol>\n<pre><code class=\"language-shell\">helm repo update\n</code></pre>\n<ol start=\"3\">\n<li>Install the operator passing the OAuth client credentials that you created earlier:</li>\n</ol>\n<pre><code class=\"language-shell\">helm upgrade \\\r\n     --install \\\r\n     tailscale-operator \\\r\n     tailscale/tailscale-operator \\\r\n     --namespace=tailscale \\\r\n     --create-namespace \\\r\n     --set-string oauth.clientId=\"&#x3C;OAauth client ID>\" \\\r\n     --set-string oauth.clientSecret=\"&#x3C;OAuth client secret>\" \\\r\n     --wait\n</code></pre>\n<h4><a href=\"https://tailscale.com/docs/features/kubernetes-operator#static-manifests-with-kubectl\">Static manifests with <code>kubectl</code></a></h4>\n<ol>\n<li>\n<p>Download the Tailscale Kubernetes Operator <a href=\"https://github.com/tailscale/tailscale/blob/main/cmd/k8s-operator/deploy/manifests/operator.yaml\">manifest file</a> from the <a href=\"https://github.com/tailscale/tailscale\"><code>tailscale/tailscale</code></a> repository.</p>\n</li>\n<li>\n<p>Edit your version of the manifest file:</p>\n</li>\n<li>\n<p>Find <code># SET CLIENT ID HERE</code> and replace it with your OAuth client ID.</p>\n</li>\n<li>\n<p>Find <code># SET CLIENT SECRET HERE</code> and replace it with your OAuth client secret. The OAuth client secret is case-sensitive.</p>\n</li>\n</ol>\n<p>For both the client ID and secret, quote the value, to avoid any potential YAML misinterpretation of unquoted strings. For example, use:</p>\n<pre><code class=\"language-yaml\">client_id: \"k123456CNTRL\"\r\nclient_secret: \"tskey-client-k123456CNTRL-abcdef\"\n</code></pre>\n<p>instead of:</p>\n<pre><code class=\"language-yaml\">client_id: k123456CNTRL\r\nclient_secret: tskey-client-k123456CNTRL-abcdef\n</code></pre>\n<ol start=\"3\">\n<li>Apply the edited file to your Kubernetes cluster:</li>\n</ol>\n<pre><code class=\"language-shell\">kubectl apply -f manifest.yaml\n</code></pre>\n<h3><a href=\"https://tailscale.com/docs/features/kubernetes-operator#installation-with-workload-identity-federation\">Installation with workload identity federation</a></h3>\n<p>Workload identity federationis currently <a href=\"https://tailscale.com/docs/reference/tailscale-release-stages#beta\">in beta</a>.</p>\n<p>Tailscale supports <a href=\"https://tailscale.com/docs/features/workload-identity-federation\">workload identity federation</a> for authenticating to a tailnet using provider-native identity tokens. The operator can leverage its ServiceAccount token as an OIDC identity and authenticate without requiring a long-lived OAuth client secret.</p>\n<h4><a href=\"https://tailscale.com/docs/features/kubernetes-operator#prerequisites-1\">Prerequisites</a></h4>\n<ul>\n<li>The Kubernetes cluster's OIDC discovery endpoints must be publicly accessible. Bind the <code>\"unauthenticated\"</code> group to the <code>\"system:service-account-issuer-discovery\"``ClusterRole</code> to allow unauthenticated access:</li>\n</ul>\n<pre><code class=\"language-shell\">kubectl create clusterrolebinding oidc-discovery \\\r\n    --clusterrole=system:service-account-issuer-discovery \\\r\n    --group=system:unauthenticated\n</code></pre>\n<h4><a href=\"https://tailscale.com/docs/features/kubernetes-operator#helm-1\">Helm</a></h4>\n<ol>\n<li>Get your cluster's issuer:</li>\n</ol>\n<p>The following command has a <code>jq</code> dependency. You may need to install <code>jq</code> on your device.</p>\n<pre><code class=\"language-shell\">ISSUER=\"$(kubectl get --raw /.well-known/openid-configuration | jq '.issuer')\"\n</code></pre>\n<ol start=\"2\">\n<li><a href=\"https://tailscale.com/docs/features/workload-identity-federation#configure-federated-identities-in-the-admin-console\">Configure a federated identity in the admin console</a> using:\n<ul>\n<li>Set the <strong>Issuer</strong> to <strong>Custom issuer</strong>.</li>\n<li>Set the <strong>Issuer URL</strong> to the value of <code>$ISSUER</code> from the previous step.</li>\n<li>Set the <strong>Subject</strong> to <code>system:serviceaccount:tailscale:operator</code>.</li>\n<li>Leave the <strong>Audience</strong> field blank.</li>\n<li>Set <code>Devices Core</code> and <code>Auth Keys</code> and <code>Services</code> write scopes.</li>\n<li>And the tag <code>tag:k8s-operator</code>.</li>\n</ul>\n</li>\n<li>Add <code>https://pkgs.tailscale.com/helmcharts</code> to your local Helm repositories:</li>\n</ol>\n<pre><code class=\"language-shell\">helm repo add tailscale https://pkgs.tailscale.com/helmcharts\n</code></pre>\n<ol start=\"4\">\n<li>Update your local Helm cache:</li>\n</ol>\n<pre><code class=\"language-shell\">helm repo update\n</code></pre>\n<ol start=\"5\">\n<li>Install the operator passing the OAuth client details that you created earlier:</li>\n</ol>\n<pre><code class=\"language-shell\">helm upgrade \\\r\n     --install \\\r\n     tailscale-operator \\\r\n     tailscale/tailscale-operator \\\r\n     --namespace=tailscale \\\r\n     --create-namespace \\\r\n     --set-string oauth.clientId=\"&#x3C;OAauth client ID>\" \\\r\n     --set-string oauth.audience=\"&#x3C;OAuth client audience>\" \\\r\n     --wait\n</code></pre>\n<h3><a href=\"https://tailscale.com/docs/features/kubernetes-operator#validation\">Validation</a></h3>\n<p>Verify that the Tailscale operator has joined your tailnet. Open the <a href=\"https://login.tailscale.com/admin/machines\">Machines</a> page of the admin console and look for a node named <code>tailscale-operator</code> (or your customized hostname) tagged with the <code>tag:k8s-operator</code> tag. It may take some time for the operator to join your tailnet as the container image downloads and the Pod starts.</p>\n<h3><a href=\"https://tailscale.com/docs/features/kubernetes-operator#optional-pre-creating-a-proxygroup\">(Optional) Pre-creating a <code>ProxyGroup</code></a></h3>\n<p>When a user configures an <a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress\">ingress</a> or <a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-egress\">egress</a> proxy, the default mode for the operator is to create a tailnet device deployed as a <code>StatefulSet</code> with a single <code>Pod</code>.</p>\n<p>This model has a few caveats:</p>\n<ul>\n<li>\n<p>a single <code>Pod</code> means that there will be some downtime during proxy upgrades, cluster upgrades, etc</p>\n</li>\n<li>\n<p>a <code>Pod</code> per proxy may not be feasible for large installations (high resource consumption)</p>\n</li>\n</ul>\n<p>Tailscale Kubernetes Operator 1.76 and later provides the ability to pre-create a multi-replica <code>ProxyGroup</code>. <a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress\">Ingress</a> and <a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-egress\">egress</a> can then be exposed redundantly by using the <code>ProxyGroup</code>.</p>\n<p>To create a <code>ProxyGroup</code> with three replicas for Tailscale egress <code>Service</code>s:</p>\n<ol>\n<li>Apply the following manifest:</li>\n</ol>\n<pre><code class=\"language-yaml\">apiVersion: tailscale.com/v1alpha1\r\nkind: ProxyGroup\r\nmetadata:\r\n     name: ts-proxies\r\nspec:\r\n     type: egress\r\n     replicas: 3\n</code></pre>\n<ol start=\"2\">\n<li>(Optional) Wait for the <code>ProxyGroup</code> to become ready:</li>\n</ol>\n<pre><code class=\"language-shell\">kubectl wait proxygroup ts-proxies --for=condition=ProxyGroupReady=true\n</code></pre>\n<p>For the above <code>ProxyGroup</code> the operator creates a <code>StatefulSet</code> with three replicas, each of which is a tailnet device.</p>\n<p>Egress <code>Service</code>s can now refer to the newly created <code>ProxyGroup</code>. Refer to <a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-egress\">Configure an egress <code>Service</code> using <code>ProxyGroup</code></a>.</p>\n<p>You can find <a href=\"https://github.com/tailscale/tailscale/blob/main/k8s-operator/api.md#proxygroup\">all available <code>ProxyGroup</code> configuration options on GitHub →</a></p>\n<h2><a href=\"https://tailscale.com/docs/features/kubernetes-operator#supported-versions\">Supported versions</a></h2>\n<h3><a href=\"https://tailscale.com/docs/features/kubernetes-operator#operator-and-proxies\">Operator and proxies</a></h3>\n<p>Tailscale recommends that you use the same version for the operator and the proxies, because majority of our tests run against the same versions.</p>\n<p>The operator supports proxies running a Tailscale version up to four minor versions earlier than the operator's version.\r\nThe operator does not support proxies running a Tailscale version later than the operator's version.</p>\n<h3><a href=\"https://tailscale.com/docs/features/kubernetes-operator#kubernetes-versions\">Kubernetes versions</a></h3>\n<p>The earliest supported version of Kubernetes is v1.23.0.</p>\n<h2><a href=\"https://tailscale.com/docs/features/kubernetes-operator#cni-compatibility\">CNI compatibility</a></h2>\n<p>The operator creates proxies that configure custom routing and forwarding rules in each proxy <code>Pod</code>'s network namespace only.\r\nBecause the proxying is implemented in the proxy <code>Pod</code>'s namespace, the routing and firewall configuration on the <code>Node</code> (for example, using iptables, eBPF, or any other mechanism) doesn't affect the proxies.\r\nThis means that the proxies work with most CNI configurations out of the box.</p>\n<h2><a href=\"https://tailscale.com/docs/features/kubernetes-operator#tls-certificates-and-renewal\">TLS certificates and renewal</a></h2>\n<p>The operator automatically provisions TLS certificates for Tailscale Ingress and API server proxy services. Certificates are valid for 90 days, and typically renew two-thirds through their validity period. Certificates only renew if there is traffic to the service. If a certificate expires, the next request to the service will trigger a renewal.</p>\n<h2><a href=\"https://tailscale.com/docs/features/kubernetes-operator#eks-fargate\">EKS Fargate</a></h2>\n<p>On <a href=\"https://docs.aws.amazon.com/eks/latest/userguide/fargate.html\">EKS Fargate</a>, currently the only supported operator features are <a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress#ingress-resource\">Tailscale <code>Ingress</code></a> and <a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/api-server-proxy\">Tailscale API server proxy</a>.\r\n<a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress#ingress-service\">Tailscale ingress <code>Service</code>s</a>, <a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-egress\">Tailscale egress <code>Service</code>s</a> and the <a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/connector\"><code>Connector</code></a> configurations currently contain <a href=\"https://kubernetes.io/docs/tasks/configure-pod-container/security-context/\">privileged containers</a> and containers with <a href=\"https://kubernetes.io/docs/tasks/configure-pod-container/security-context/\">CAP_NET_ADMIN</a>.\r\nThis is not supported on EKS Fargate.</p>\n<h3><a href=\"https://tailscale.com/docs/features/kubernetes-operator#cilium-in-kube-proxy-replacement-mode\">Cilium in kube-proxy replacement mode</a></h3>\n<p>You must enable <a href=\"https://docs.cilium.io/en/stable/network/kubernetes/kubeproxy-free/#socket-loadbalancer-bypass-in-pod-namespace\">bypassing socket load balancer in Pods' namespaces</a> if you run Cilium in <a href=\"https://docs.cilium.io/en/stable/network/kubernetes/kubeproxy-free\">kube-proxy replacement mode</a> and want to do one or more of the following:</p>\n<ul>\n<li>Expose a Kubernetes <code>Service</code> to your tailnet as a <a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress#loadbalancerclass\">Tailscale LoadBalancer <code>Service</code></a>.</li>\n<li>Expose a Kubernetes <code>Service</code> to your tailnet using <a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress\"><code>tailscale.com/expose</code> annotation</a>.</li>\n<li>Expose a Service CIDR range by using <a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/connector\"><code>Connector</code></a>.</li>\n</ul>\n<p>This is needed because when Cilium runs in kube-proxy replacement mode with the socket load balancing in <code>Pod</code>s' namespaces enabled, connections from <code>Pod</code>s to <code>ClusterIP</code>s go over a TCP socket (instead of going out via <code>Pod</code>s' <code>veth</code> devices) and thus bypasses <a href=\"https://tailscale.com/docs/reference/netfilter-modes\">Tailscale firewall rules</a> that are attached to <a href=\"https://www.netfilter.org/\">netfilter hooks</a>.</p>\n<p>If you encounter bandwidth issues while using Cilium, use the <code>--devices</code> flag to explicitly specify which network interfaces Cilium should monitor for the maximum transmission unit (MTU). This prevents Cilium from defaulting to the MTU of the <code>tailscale0</code> interface and instead ensures it uses the MTU of a physical interface on the host.</p>\n<h2><a href=\"https://tailscale.com/docs/features/kubernetes-operator#customization\">Customization</a></h2>\n<p><a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/customize\">Customize the operator and resources it manages</a>.</p>\n<h2><a href=\"https://tailscale.com/docs/features/kubernetes-operator#troubleshooting\">Troubleshooting</a></h2>\n<p><a href=\"https://tailscale.com/docs/reference/troubleshooting/containers/kubernetes-operator\">Troubleshoot the operator and resources it manages</a>.</p>\n<h2><a href=\"https://tailscale.com/docs/features/kubernetes-operator#limitations\">Limitations</a></h2>\n<ul>\n<li>There are no dashboards or metrics. We are interested to hear what metrics you would find useful — do <a href=\"https://github.com/tailscale/tailscale/issues/new/choose\">reach out</a>.</li>\n<li>The container images, charts or manifests are not signed. <em>We are working on this.</em></li>\n<li>The static manifests are currently only available from the <a href=\"https://github.com/tailscale/tailscale\"><code>tailscale/tailscale</code></a> codebase. <em>We are working to improve this flow.</em></li>\n<li>Using the operator on <a href=\"https://www.redhat.com/en/technologies/cloud-computing/openshift\">OpenShift</a> is currently not supported.</li>\n</ul>\n<h2><a href=\"https://tailscale.com/docs/features/kubernetes-operator#glossary\">Glossary</a></h2>\n<p><strong>Proxy</strong></p>\n<p>In the context of this document, a proxy is the Tailscale node deployed for each user-configured component that the operator manages (such as a <a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress#ingress-resource\"><code>Tailscale Ingress</code></a> or a <a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/connector\"><code>Connector</code></a>).\r\nThe proxy is deployed as a <code>StatefulSet</code> in the operator's namespace (defaults to <code>tailscale</code>).\r\nThe <code>StatefulSet</code>s name is prefixed by a portion of the configured component's name.</p>\n<p>If you need to reliably refer to the proxy's <code>StatefulSet</code>, you can use label selectors.\r\nFor example, to find <code>StatefulSet</code> for a Tailscale <code>Ingress</code> resource named <code>ts-ingress</code> in <code>prod</code> namespace , you can run:</p>\n<pre><code class=\"language-shell\">kubectl get statefulset \\\r\n  --namespace tailscale \\\r\n  --selector=\"tailscale.com/managed=true,tailscale.com/parent-resource-type=ingress,tailscale.com/parent-resource=ts-ingress,tailscale.com/parent-resource-ns=prod\"\n</code></pre>\n<p>The <code>tailscale.com/parent-resource</code> label is set to <code>svc</code> for a <code>Service</code> and to <code>connector</code> for a <code>Connector</code>.\r\nThe <code>tailscale.com</code> labels are also propagated to the <code>Pod</code>.</p>\n<p><img src=\"https://cdn.brandfetch.io/tailscale.com/fallback/lettermark/theme/dark/h/256/w/256/icon?c=1bfwsmEH20zzEfSNTed\" alt=\"Project Logo\"></p>\n<p>Ask AI</p>\n<p>reCAPTCHA</p>\n<p>Recaptcha requires verification.</p>\n<p>protected by <strong>reCAPTCHA</strong></p>\n"}