{"slug":"deploy-tsrecorder-on-kubernetes-using-the-operator","title":"Deploy tsrecorder on Kubernetes using the operator","tags":["tailscale","containers"],"agent_summary":"Last validated: Dec 8, 2025","trigger_phrases":[],"runnable":false,"markdown":"\r\n# Deploy tsrecorder on Kubernetes using the operator\r\n\r\nLast validated: Dec 8, 2025\r\n\r\nIn Tailscale 1.74 and later, you can deploy [`tsrecorder`](https://tailscale.com/docs/features/tailscale-ssh/tailscale-ssh-session-recording) to your Kubernetes cluster using the operator and its [`Recorder` Custom Resource Definition](https://github.com/tailscale/tailscale/blob/main/k8s-operator/api.md#recorder).\r\nYou can use this as a [recording destination for SSH sessions](https://tailscale.com/docs/features/tailscale-ssh/tailscale-ssh-session-recording).\r\nThe recorder will connect to your tailnet and receive end-to-end-encrypted recordings using WireGuard just like any other `tsrecorder` devices running in your tailnet.\r\n\r\nTailscale SSH session recordingis available for [the Personal and Enterprise plans](https://tailscale.com/pricing).\r\n\r\n## [Prerequisites](https://tailscale.com/docs/features/kubernetes-operator/how-to/tsrecorder\\#prerequisites)\r\n\r\n- Set up the [Kubernetes operator](https://tailscale.com/docs/features/kubernetes-operator).\r\n\r\n- (Optional) create a [tag](https://tailscale.com/docs/features/tags), for example `tag:k8s-recorder`, to attach to the `tsrecorder` device.\r\nThis might be a useful way to refer to the recorder when configuring the [access control policies](https://tailscale.com/docs/features/access-control) for SSH session recording. Make sure the operator is one of the [tag owners](https://tailscale.com/docs/reference/syntax/policy-file#tag-owners) for your recorder tag:\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n```json\r\n\"tagOwners\": {\r\n   \t\"tag:k8s-operator\":   [],\r\n   \t\"tag:k8s-recorder\":   [\"tag:k8s-operator\"],\r\n}\r\n```\r\n\r\n- (Optional) [enable HTTPS](https://tailscale.com/docs/how-to/set-up-https-certificates) for your tailnet if you want to use the recorder's UI.\r\n\r\n\r\n## [Deploy](https://tailscale.com/docs/features/kubernetes-operator/how-to/tsrecorder\\#deploy)\r\n\r\n1. Create your `Recorder` resource based on the below. Use the [reference documentation](https://github.com/tailscale/tailscale/blob/main/k8s-operator/api.md#recorder)\r\nto update it as required and then save it as `recorder.yaml`.\r\n\r\nThe following spec will save recordings to a local temporary directory, and only persist for the lifetime of the pod.\r\nUse the `s3` storage option shown below for durable storage.\r\n\r\n```yaml\r\napiVersion: tailscale.com/v1alpha1\r\nkind: Recorder\r\nmetadata:\r\n  name: recorder\r\nspec:\r\n  enableUI: true\r\n```\r\n\r\n2. Apply the file:\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n```shell\r\nkubectl apply -f recorder.yaml\r\n```\r\n\r\n3. (Optional) wait for the recorder to become ready:\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n```shell\r\nkubectl wait --for condition=RecorderReady=true Recorder recorder\r\n```\r\n\r\n\r\n\r\nFor example, this command will return information similar to the following:\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n```shell\r\nrecorder.tailscale.com/recorder condition met\r\n```\r\n\r\n4. (Optional) find the URL for the recorder's UI:\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n```shell\r\nkubectl get Recorder recorder\r\n```\r\n\r\n\r\n\r\nFor example, this command will return information similar to the following:\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n```shell\r\nNAME       STATUS            URL\r\nrecorder   RecorderCreated   https://recorder-0.tails-scales.ts.net\r\n```\r\n\r\n\r\n## [Examples](https://tailscale.com/docs/features/kubernetes-operator/how-to/tsrecorder\\#examples)\r\n\r\n### [Static S3 credentials](https://tailscale.com/docs/features/kubernetes-operator/how-to/tsrecorder\\#static-s3-credentials)\r\n\r\nThe following example:\r\n\r\n- Creates a `Secret` named \"s3-auth\" with AWS credentials for writing to a pre-existing S3 bucket named \"tsrecorder-bucket\".\r\n- Deploys a `StatefulSet` with a single replica of `tsrecorder`.\r\n- Assigns the tag `tag:k8s-recorder` to the recorder tailnet device.\r\n- Enables the recorder's UI.\r\n- Stores the recordings in an S3 bucket called \"tsrecorder-bucket\".\r\n- Gets S3 credentials as environment variables from a `Secret` called \"s3-auth\" in\r\nthe same namespace as the Tailscale operator.\r\n\r\n```yaml\r\napiVersion: v1\r\nkind: Secret\r\nmetadata:\r\n  name: s3-auth\r\n  namespace: tailscale\r\nstringData:\r\n  AWS_ACCESS_KEY_ID: ABC...\r\n  AWS_SECRET_ACCESS_KEY: xyz123...\r\n---\r\napiVersion: tailscale.com/v1alpha1\r\nkind: Recorder\r\nmetadata:\r\n  name: recorder\r\nspec:\r\n  enableUI: true\r\n  tags:\r\n    - \"tag:k8s-recorder\"\r\n  storage:\r\n    s3:\r\n      endpoint: s3.us-east-1.amazonaws.com\r\n      bucket: tsrecorder-bucket\r\n      credentials:\r\n        secret:\r\n          name: s3-auth\r\n```\r\n\r\n### [IAM roles for service accounts (IRSA)](https://tailscale.com/docs/features/kubernetes-operator/how-to/tsrecorder\\#iam-roles-for-service-accounts-irsa)\r\n\r\nYou can modify the above example to use IRSA for S3 credentials instead of static credentials stored in a `Secret`.\r\n\r\n1. Follow the [AWS instructions for IRSA](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html). When you select the name and namespace of the `ServiceAccount` to attach your policy to, use the Tailscale Kubernetes Operator's namespace (usually `tailscale`) and a name of your choosing (the default `ServiceAccount` name is the same as the `Recorder` resource's name). This will be reflected in the trust policy document, for example:\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n```json\r\n{\r\n     \"Version\": \"2012-10-17\",\r\n     \"Statement\": [\\\r\n       {\\\r\n         \"Effect\": \"Allow\",\\\r\n         \"Principal\": {\\\r\n           \"Federated\": \"arn:aws:iam::111122223333:oidc-provider/oidc.eks.region-code.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE\"\\\r\n         },\\\r\n         \"Action\": \"sts:AssumeRoleWithWebIdentity\",\\\r\n         \"Condition\": {\\\r\n           \"StringEquals\": {\\\r\n             \"oidc.eks.region-code.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE:sub\": \"system:serviceaccount:tailscale:my-recorder\",\\\r\n             \"oidc.eks.region-code.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE:aud\": \"sts.amazonaws.com\"\\\r\n           }\\\r\n         }\\\r\n       }\\\r\n     ]\r\n}\r\n```\r\n\r\n2. Use the `Recorder` field `.spec.statefulSet.pod.serviceAccount` to annotate its service account with the correct AWS role.\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n```yaml\r\napiVersion: tailscale.com/v1alpha1\r\nkind: Recorder\r\nmetadata:\r\n     name: recorder\r\nspec:\r\n     enableUI: true\r\n     tags:\r\n    - \"tag:k8s-recorder\"\r\nstorage:\r\n    s3:\r\n      endpoint: s3.us-east-1.amazonaws.com\r\n      bucket: tsrecorder-bucket\r\nstatefulSet:\r\n    pod:\r\n      serviceAccount:\r\n        name: my-recorder\r\n        annotations:\r\n          eks.amazonaws.com/role-arn: arn:aws:iam::111122223333:role/my-s3-role\r\n```\r\n\r\n## [Known issues and limitations](https://tailscale.com/docs/features/kubernetes-operator/how-to/tsrecorder\\#known-issues-and-limitations)\r\n\r\n- The only durable storage method currently available is `s3`. If `s3` storage isn't configured, recordings will be saved to a local temporary directory, and will only persist for the lifetime of 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>Deploy tsrecorder on Kubernetes using the operator</h1>\n<p>Last validated: Dec 8, 2025</p>\n<p>In Tailscale 1.74 and later, you can deploy <a href=\"https://tailscale.com/docs/features/tailscale-ssh/tailscale-ssh-session-recording\"><code>tsrecorder</code></a> to your Kubernetes cluster using the operator and its <a href=\"https://github.com/tailscale/tailscale/blob/main/k8s-operator/api.md#recorder\"><code>Recorder</code> Custom Resource Definition</a>.\r\nYou can use this as a <a href=\"https://tailscale.com/docs/features/tailscale-ssh/tailscale-ssh-session-recording\">recording destination for SSH sessions</a>.\r\nThe recorder will connect to your tailnet and receive end-to-end-encrypted recordings using WireGuard just like any other <code>tsrecorder</code> devices running in your tailnet.</p>\n<p>Tailscale SSH session recordingis available for <a href=\"https://tailscale.com/pricing\">the Personal and Enterprise plans</a>.</p>\n<h2><a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/tsrecorder#prerequisites\">Prerequisites</a></h2>\n<ul>\n<li>\n<p>Set up the <a href=\"https://tailscale.com/docs/features/kubernetes-operator\">Kubernetes operator</a>.</p>\n</li>\n<li>\n<p>(Optional) create a <a href=\"https://tailscale.com/docs/features/tags\">tag</a>, for example <code>tag:k8s-recorder</code>, to attach to the <code>tsrecorder</code> device.\r\nThis might be a useful way to refer to the recorder when configuring the <a href=\"https://tailscale.com/docs/features/access-control\">access control policies</a> for SSH session recording. Make sure the operator is one of the <a href=\"https://tailscale.com/docs/reference/syntax/policy-file#tag-owners\">tag owners</a> for your recorder tag:</p>\n</li>\n</ul>\n<pre><code class=\"language-json\">\"tagOwners\": {\r\n   \t\"tag:k8s-operator\":   [],\r\n   \t\"tag:k8s-recorder\":   [\"tag:k8s-operator\"],\r\n}\n</code></pre>\n<ul>\n<li>(Optional) <a href=\"https://tailscale.com/docs/how-to/set-up-https-certificates\">enable HTTPS</a> for your tailnet if you want to use the recorder's UI.</li>\n</ul>\n<h2><a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/tsrecorder#deploy\">Deploy</a></h2>\n<ol>\n<li>Create your <code>Recorder</code> resource based on the below. Use the <a href=\"https://github.com/tailscale/tailscale/blob/main/k8s-operator/api.md#recorder\">reference documentation</a>\r\nto update it as required and then save it as <code>recorder.yaml</code>.</li>\n</ol>\n<p>The following spec will save recordings to a local temporary directory, and only persist for the lifetime of the pod.\r\nUse the <code>s3</code> storage option shown below for durable storage.</p>\n<pre><code class=\"language-yaml\">apiVersion: tailscale.com/v1alpha1\r\nkind: Recorder\r\nmetadata:\r\n  name: recorder\r\nspec:\r\n  enableUI: true\n</code></pre>\n<ol start=\"2\">\n<li>Apply the file:</li>\n</ol>\n<pre><code class=\"language-shell\">kubectl apply -f recorder.yaml\n</code></pre>\n<ol start=\"3\">\n<li>(Optional) wait for the recorder to become ready:</li>\n</ol>\n<pre><code class=\"language-shell\">kubectl wait --for condition=RecorderReady=true Recorder recorder\n</code></pre>\n<p>For example, this command will return information similar to the following:</p>\n<pre><code class=\"language-shell\">recorder.tailscale.com/recorder condition met\n</code></pre>\n<ol start=\"4\">\n<li>(Optional) find the URL for the recorder's UI:</li>\n</ol>\n<pre><code class=\"language-shell\">kubectl get Recorder recorder\n</code></pre>\n<p>For example, this command will return information similar to the following:</p>\n<pre><code class=\"language-shell\">NAME       STATUS            URL\r\nrecorder   RecorderCreated   https://recorder-0.tails-scales.ts.net\n</code></pre>\n<h2><a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/tsrecorder#examples\">Examples</a></h2>\n<h3><a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/tsrecorder#static-s3-credentials\">Static S3 credentials</a></h3>\n<p>The following example:</p>\n<ul>\n<li>Creates a <code>Secret</code> named \"s3-auth\" with AWS credentials for writing to a pre-existing S3 bucket named \"tsrecorder-bucket\".</li>\n<li>Deploys a <code>StatefulSet</code> with a single replica of <code>tsrecorder</code>.</li>\n<li>Assigns the tag <code>tag:k8s-recorder</code> to the recorder tailnet device.</li>\n<li>Enables the recorder's UI.</li>\n<li>Stores the recordings in an S3 bucket called \"tsrecorder-bucket\".</li>\n<li>Gets S3 credentials as environment variables from a <code>Secret</code> called \"s3-auth\" in\r\nthe same namespace as the Tailscale operator.</li>\n</ul>\n<pre><code class=\"language-yaml\">apiVersion: v1\r\nkind: Secret\r\nmetadata:\r\n  name: s3-auth\r\n  namespace: tailscale\r\nstringData:\r\n  AWS_ACCESS_KEY_ID: ABC...\r\n  AWS_SECRET_ACCESS_KEY: xyz123...\r\n---\r\napiVersion: tailscale.com/v1alpha1\r\nkind: Recorder\r\nmetadata:\r\n  name: recorder\r\nspec:\r\n  enableUI: true\r\n  tags:\r\n    - \"tag:k8s-recorder\"\r\n  storage:\r\n    s3:\r\n      endpoint: s3.us-east-1.amazonaws.com\r\n      bucket: tsrecorder-bucket\r\n      credentials:\r\n        secret:\r\n          name: s3-auth\n</code></pre>\n<h3><a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/tsrecorder#iam-roles-for-service-accounts-irsa\">IAM roles for service accounts (IRSA)</a></h3>\n<p>You can modify the above example to use IRSA for S3 credentials instead of static credentials stored in a <code>Secret</code>.</p>\n<ol>\n<li>Follow the <a href=\"https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html\">AWS instructions for IRSA</a>. When you select the name and namespace of the <code>ServiceAccount</code> to attach your policy to, use the Tailscale Kubernetes Operator's namespace (usually <code>tailscale</code>) and a name of your choosing (the default <code>ServiceAccount</code> name is the same as the <code>Recorder</code> resource's name). This will be reflected in the trust policy document, for example:</li>\n</ol>\n<pre><code class=\"language-json\">{\r\n     \"Version\": \"2012-10-17\",\r\n     \"Statement\": [\\\r\n       {\\\r\n         \"Effect\": \"Allow\",\\\r\n         \"Principal\": {\\\r\n           \"Federated\": \"arn:aws:iam::111122223333:oidc-provider/oidc.eks.region-code.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE\"\\\r\n         },\\\r\n         \"Action\": \"sts:AssumeRoleWithWebIdentity\",\\\r\n         \"Condition\": {\\\r\n           \"StringEquals\": {\\\r\n             \"oidc.eks.region-code.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE:sub\": \"system:serviceaccount:tailscale:my-recorder\",\\\r\n             \"oidc.eks.region-code.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE:aud\": \"sts.amazonaws.com\"\\\r\n           }\\\r\n         }\\\r\n       }\\\r\n     ]\r\n}\n</code></pre>\n<ol start=\"2\">\n<li>Use the <code>Recorder</code> field <code>.spec.statefulSet.pod.serviceAccount</code> to annotate its service account with the correct AWS role.</li>\n</ol>\n<pre><code class=\"language-yaml\">apiVersion: tailscale.com/v1alpha1\r\nkind: Recorder\r\nmetadata:\r\n     name: recorder\r\nspec:\r\n     enableUI: true\r\n     tags:\r\n    - \"tag:k8s-recorder\"\r\nstorage:\r\n    s3:\r\n      endpoint: s3.us-east-1.amazonaws.com\r\n      bucket: tsrecorder-bucket\r\nstatefulSet:\r\n    pod:\r\n      serviceAccount:\r\n        name: my-recorder\r\n        annotations:\r\n          eks.amazonaws.com/role-arn: arn:aws:iam::111122223333:role/my-s3-role\n</code></pre>\n<h2><a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/tsrecorder#known-issues-and-limitations\">Known issues and limitations</a></h2>\n<ul>\n<li>The only durable storage method currently available is <code>s3</code>. If <code>s3</code> storage isn't configured, recordings will be saved to a local temporary directory, and will only persist for the lifetime of the <code>Pod</code>.</li>\n</ul>\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"}