{"slug":"gitops-for-tailscale-with-bitbucket","title":"GitOps for Tailscale with Bitbucket","tags":["tailscale"],"agent_summary":"Last validated: Dec 4, 2025","trigger_phrases":[],"runnable":false,"markdown":"\r\n# GitOps for Tailscale with Bitbucket\r\n\r\nLast validated: Dec 4, 2025\r\n\r\n[Access control policies](https://tailscale.com/docs/features/access-control) define what users or devices are permitted to access in your Tailscale network (known as a tailnet). An alternative to managing the access control policy changes in the [Access controls](https://login.tailscale.com/admin/acls) page of the admin console is to use [GitOps](https://tailscale.com/docs/gitops) to manage the access control policy changes. This topic provides details on how to use [Bitbucket Pipelines](https://support.atlassian.com/bitbucket-cloud/docs/get-started-with-bitbucket-pipelines) to automatically apply and test access control policy changes to your tailnet.\r\n\r\nGitOps for Tailscaleis available for [all plans](https://tailscale.com/pricing).\r\n\r\n## [Prerequisites](https://tailscale.com/docs/integrations/bitbucket/gitops\\#prerequisites)\r\n\r\nIn addition to already having your own Tailscale network, you need:\r\n\r\n- A [Bitbucket](https://bitbucket.org/) account.\r\n- Working knowledge of Bitbucket procedures including committing changes, creating pull requests, and merging pull requests.\r\n- A private Bitbucket repository that will contain your tailnet policy file.\r\n\r\nMake sure this repository is private, as tailnet policy files contain personally identifiable information (PII), such as users email addresses.\r\n\r\n- A Tailscale [API access token](https://tailscale.com/docs/reference/tailscale-api) for your tailnet. You can create an API access token in the [Keys](https://login.tailscale.com/admin/settings/keys) page of the admin console.\r\n\r\n## [Set up your GitOps configuration](https://tailscale.com/docs/integrations/bitbucket/gitops\\#set-up-your-gitops-configuration)\r\n\r\n1. In your Bitbucket repository, create a file named `policy.hujson`. Copy your tailnet policy file contents from the [Access controls](https://login.tailscale.com/admin/acls) page of the admin console into this file.\r\n\r\nIf you want to change the tailnet policy file name to something else, you need to modify the `--policy-file` argument used by the `gitops-pusher` binary in the Bitbucket pipeline discussed below.\r\n\r\n1. Commit the tailnet policy file and push it to Bitbucket. For example, if you are using the command line:\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n```shell\r\ngit add .\r\ngit commit -sm \"policy: import from admin console\"\r\ngit push -u origin main\r\n```\r\n\r\n2. Within your repository's **Repository variables** settings, create the following [Bitbucket secured variables](https://support.atlassian.com/bitbucket-cloud/docs/variables-and-secrets/#Secured-variables):\r\n\r\n\r\n   - `TS_API_KEY`: Use your Tailscale API access token as the value.\r\n   - `TS_TAILNET`: Use your tailnet ID as the value. You can find your tailnet ID in the [General](https://login.tailscale.com/admin/settings/general) page of the admin console.\r\n\r\nThese secrets provide the configuration for the pipeline.\r\n\r\n3. Make a new [Bitbucket Pipelines](https://support.atlassian.com/bitbucket-cloud/docs/get-started-with-bitbucket-pipelines) pipeline that uses the [`gitops-pusher`](https://github.com/tailscale/tailscale/tree/main/cmd/gitops-pusher) binary.\r\n\r\nCreate a file named `bitbucket-pipelines.yml` in the `Source` folder and paste in the following:\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n```yaml\r\nimage: golang\r\n\r\npipelines:\r\n     default:\r\n     - step:\r\n         name: Test ACL\r\n         script:\r\n           - go install tailscale.com/cmd/gitops-pusher@latest\r\n           - gitops-pusher --policy-file ./policy.hujson test\r\nbranches:\r\n    main:\r\n      - step:\r\n          name: Deploy ACL\r\n          script:\r\n            - go install tailscale.com/cmd/gitops-pusher@latest\r\n            - gitops-pusher --policy-file ./policy.hujson apply\r\n```\r\n\r\nFor information about Bitbucket Pipelines options and properties, refer to the [Bitbucket Pipelines configuration reference](https://support.atlassian.com/bitbucket-cloud/docs/bitbucket-pipelines-configuration-reference).\r\n\r\n4. Commit and push `bitbucket-pipelines.yml`.\r\n\r\n5. Follow the instructions to [prevent others from accidentally modifying your tailnet policy file](https://tailscale.com/docs/integrations/bitbucket/gitops#prevent-others-from-accidentally-modifying-your-tailnet-policy-file) in the admin console.\r\n\r\n\r\nThe main steps in the pipeline are installing and building the `gitops-pusher` binary and then running the binary. On pull requests, the `Test ACL` step will send your tailnet policy file to Tailscale to determine whether the access control policy is valid and whether all [tests](https://tailscale.com/docs/reference/syntax/policy-file#tests) pass. Tailscale provides your pipeline with the result of those checks, which display in the Bitbucket **Pipelines** page.\r\n\r\nOn push (merge) operations that target the `main` branch, the `Deploy ACL` step will send your tailnet policy file to Tailscale to determine whether the tailnet policy file is valid and whether all [tests](https://tailscale.com/docs/reference/syntax/policy-file#tests) pass. If this portion succeeds, Tailscale then applies your tailnet policy file update to your tailnet. Tailscale provides your pipeline with the result of this step, which displays in the Bitbucket **Pipelines** page.\r\n\r\nWith this setup, you have created a continuous integration (CI) that automatically tests and pushes your tailnet policy file changes to Tailscale!\r\n\r\n## [Push changes to your tailnet policy file](https://tailscale.com/docs/integrations/bitbucket/gitops\\#push-changes-to-your-tailnet-policy-file)\r\n\r\nNow that your configuration has been set up, any time your want to update your tailnet policy file, modify the tailnet policy file in your repository and use the typical Bitbucket authoring/review/merge flow.\r\n\r\n## [Prevent others from accidentally modifying your tailnet policy file](https://tailscale.com/docs/integrations/bitbucket/gitops\\#prevent-others-from-accidentally-modifying-your-tailnet-policy-file)\r\n\r\nOn June 3, 2025, we [announced the deprecation](https://tailscale.com/changelog#2025-06-03) of the GitOps code comment technique for locking the policy file editor in the admin console. Tailnets that were using this method were automatically migrated to use the setting in the admin console. The code comment is no longer effective for locking the policy file editor.\r\n\r\nTo prevent other admins in your organization from accidentally changing your tailnet policy file, you can lock the editor in the [Policy file management](https://login.tailscale.com/admin/settings/policy-file-management) page in the Tailscale admin console.\r\n\r\nYou must be an [Owner, Admin, or Network admin](https://tailscale.com/docs/reference/user-roles) of a tailnet to update tailnet policy file settings.\r\n\r\n1. Open the [Policy file management](https://login.tailscale.com/admin/settings/policy-file-management) page in the Tailscale admin console.\r\n2. If it is not already enabled, select **Prevent edits in the admin console** to prevent editing of the file.\r\n3. For **External reference**, enter the URL of your repository. This lets other admins to know where the source of your policies is located.\r\n4. Select **Save**.\r\n\r\nAny [admin with permissions to edit the tailnet policy file](https://tailscale.com/docs/reference/user-roles#permissions-managed-by-user-roles) can still edit it directly by selecting **Edit anyway**. This lets admins to quickly make changes in case of an emergency.\r\n\r\n![The warning given when trying to edit a tailnet policy file which is using GitOps.](https://tailscale.com/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fgitops-edit-warning.9df07cf9.png&w=1080&q=75)\r\n\r\nThe next time you use the GitOps flow, it overwrites the tailnet policy file changes made in the admin console.\r\n\r\n## [Revert the most recent change to your tailnet policy file](https://tailscale.com/docs/integrations/bitbucket/gitops\\#revert-the-most-recent-change-to-your-tailnet-policy-file)\r\n\r\nIf you need to revert the most recent change, use the Bitbucket **Pull requests** page to find and then revert the merged pull request.\r\n\r\n## [Remove your GitOps configuration](https://tailscale.com/docs/integrations/bitbucket/gitops\\#remove-your-gitops-configuration)\r\n\r\n1. Open the [Policy file management](https://login.tailscale.com/admin/settings/policy-file-management) page in the Tailscale admin console and disable the **Prevent edits in the admin console** toggle.\r\n\r\n2. Optionally, delete the **External reference link** and select **Save**.\r\n\r\n3. [Revoke](https://tailscale.com/docs/features/access-control/auth-keys#revoke-an-auth-key) any associated Tailscale API access tokens for this configuration.\r\n\r\n4. Archive or delete the associated BitBucket repository.\r\n\r\n\r\n## [Additional information](https://tailscale.com/docs/integrations/bitbucket/gitops\\#additional-information)\r\n\r\n- Any manual tailnet policy file changes in the admin console won't be reflected in your Bitbucket version of the tailnet policy file. The next time you use the Bitbucket pipeline, any changes made in the Tailscale admin console will be overwritten.\r\n\r\n- Tailscale [API access tokens expire](https://tailscale.com/docs/reference/tailscale-api#authentication) and currently there is no mechanism to have them automatically renewed. To handle the expiration, create a new API access token and update the Bitbucket `TS_API_KEY` secret to use the new value.\r\n\r\n\r\n\r\nTailscale API access tokens expire after 90 days (or less if the key has a shorter expiry) but updating the Bitbucket secret monthly is a good practice.\r\n\r\n\r\n\r\nWhen you no longer need to use a Tailscale API access token, make sure you revoke it in the [Keys](https://login.tailscale.com/admin/settings/keys) page of the Tailscale admin console.\r\n\r\n- Tailscale tailnet policy files are in [HuJSON](https://nigeltao.github.io/blog/2021/json-with-commas-comments.html), a JSON format with trailing commas and comments. If you don't want to write your tailnet policy files in HuJSON directly, you can use a tool that lets you generate JSON in the same schema as the HuJSON format.\r\n","html":"<h1>GitOps for Tailscale with Bitbucket</h1>\n<p>Last validated: Dec 4, 2025</p>\n<p><a href=\"https://tailscale.com/docs/features/access-control\">Access control policies</a> define what users or devices are permitted to access in your Tailscale network (known as a tailnet). An alternative to managing the access control policy changes in the <a href=\"https://login.tailscale.com/admin/acls\">Access controls</a> page of the admin console is to use <a href=\"https://tailscale.com/docs/gitops\">GitOps</a> to manage the access control policy changes. This topic provides details on how to use <a href=\"https://support.atlassian.com/bitbucket-cloud/docs/get-started-with-bitbucket-pipelines\">Bitbucket Pipelines</a> to automatically apply and test access control policy changes to your tailnet.</p>\n<p>GitOps for Tailscaleis available for <a href=\"https://tailscale.com/pricing\">all plans</a>.</p>\n<h2><a href=\"https://tailscale.com/docs/integrations/bitbucket/gitops#prerequisites\">Prerequisites</a></h2>\n<p>In addition to already having your own Tailscale network, you need:</p>\n<ul>\n<li>A <a href=\"https://bitbucket.org/\">Bitbucket</a> account.</li>\n<li>Working knowledge of Bitbucket procedures including committing changes, creating pull requests, and merging pull requests.</li>\n<li>A private Bitbucket repository that will contain your tailnet policy file.</li>\n</ul>\n<p>Make sure this repository is private, as tailnet policy files contain personally identifiable information (PII), such as users email addresses.</p>\n<ul>\n<li>A Tailscale <a href=\"https://tailscale.com/docs/reference/tailscale-api\">API access token</a> for your tailnet. You can create an API access token in the <a href=\"https://login.tailscale.com/admin/settings/keys\">Keys</a> page of the admin console.</li>\n</ul>\n<h2><a href=\"https://tailscale.com/docs/integrations/bitbucket/gitops#set-up-your-gitops-configuration\">Set up your GitOps configuration</a></h2>\n<ol>\n<li>In your Bitbucket repository, create a file named <code>policy.hujson</code>. Copy your tailnet policy file contents from the <a href=\"https://login.tailscale.com/admin/acls\">Access controls</a> page of the admin console into this file.</li>\n</ol>\n<p>If you want to change the tailnet policy file name to something else, you need to modify the <code>--policy-file</code> argument used by the <code>gitops-pusher</code> binary in the Bitbucket pipeline discussed below.</p>\n<ol>\n<li>Commit the tailnet policy file and push it to Bitbucket. For example, if you are using the command line:</li>\n</ol>\n<pre><code class=\"language-shell\">git add .\r\ngit commit -sm \"policy: import from admin console\"\r\ngit push -u origin main\n</code></pre>\n<ol start=\"2\">\n<li>\n<p>Within your repository's <strong>Repository variables</strong> settings, create the following <a href=\"https://support.atlassian.com/bitbucket-cloud/docs/variables-and-secrets/#Secured-variables\">Bitbucket secured variables</a>:</p>\n<ul>\n<li><code>TS_API_KEY</code>: Use your Tailscale API access token as the value.</li>\n<li><code>TS_TAILNET</code>: Use your tailnet ID as the value. You can find your tailnet ID in the <a href=\"https://login.tailscale.com/admin/settings/general\">General</a> page of the admin console.</li>\n</ul>\n</li>\n</ol>\n<p>These secrets provide the configuration for the pipeline.</p>\n<ol start=\"3\">\n<li>Make a new <a href=\"https://support.atlassian.com/bitbucket-cloud/docs/get-started-with-bitbucket-pipelines\">Bitbucket Pipelines</a> pipeline that uses the <a href=\"https://github.com/tailscale/tailscale/tree/main/cmd/gitops-pusher\"><code>gitops-pusher</code></a> binary.</li>\n</ol>\n<p>Create a file named <code>bitbucket-pipelines.yml</code> in the <code>Source</code> folder and paste in the following:</p>\n<pre><code class=\"language-yaml\">image: golang\r\n\r\npipelines:\r\n     default:\r\n     - step:\r\n         name: Test ACL\r\n         script:\r\n           - go install tailscale.com/cmd/gitops-pusher@latest\r\n           - gitops-pusher --policy-file ./policy.hujson test\r\nbranches:\r\n    main:\r\n      - step:\r\n          name: Deploy ACL\r\n          script:\r\n            - go install tailscale.com/cmd/gitops-pusher@latest\r\n            - gitops-pusher --policy-file ./policy.hujson apply\n</code></pre>\n<p>For information about Bitbucket Pipelines options and properties, refer to the <a href=\"https://support.atlassian.com/bitbucket-cloud/docs/bitbucket-pipelines-configuration-reference\">Bitbucket Pipelines configuration reference</a>.</p>\n<ol start=\"4\">\n<li>\n<p>Commit and push <code>bitbucket-pipelines.yml</code>.</p>\n</li>\n<li>\n<p>Follow the instructions to <a href=\"https://tailscale.com/docs/integrations/bitbucket/gitops#prevent-others-from-accidentally-modifying-your-tailnet-policy-file\">prevent others from accidentally modifying your tailnet policy file</a> in the admin console.</p>\n</li>\n</ol>\n<p>The main steps in the pipeline are installing and building the <code>gitops-pusher</code> binary and then running the binary. On pull requests, the <code>Test ACL</code> step will send your tailnet policy file to Tailscale to determine whether the access control policy is valid and whether all <a href=\"https://tailscale.com/docs/reference/syntax/policy-file#tests\">tests</a> pass. Tailscale provides your pipeline with the result of those checks, which display in the Bitbucket <strong>Pipelines</strong> page.</p>\n<p>On push (merge) operations that target the <code>main</code> branch, the <code>Deploy ACL</code> step will send your tailnet policy file to Tailscale to determine whether the tailnet policy file is valid and whether all <a href=\"https://tailscale.com/docs/reference/syntax/policy-file#tests\">tests</a> pass. If this portion succeeds, Tailscale then applies your tailnet policy file update to your tailnet. Tailscale provides your pipeline with the result of this step, which displays in the Bitbucket <strong>Pipelines</strong> page.</p>\n<p>With this setup, you have created a continuous integration (CI) that automatically tests and pushes your tailnet policy file changes to Tailscale!</p>\n<h2><a href=\"https://tailscale.com/docs/integrations/bitbucket/gitops#push-changes-to-your-tailnet-policy-file\">Push changes to your tailnet policy file</a></h2>\n<p>Now that your configuration has been set up, any time your want to update your tailnet policy file, modify the tailnet policy file in your repository and use the typical Bitbucket authoring/review/merge flow.</p>\n<h2><a href=\"https://tailscale.com/docs/integrations/bitbucket/gitops#prevent-others-from-accidentally-modifying-your-tailnet-policy-file\">Prevent others from accidentally modifying your tailnet policy file</a></h2>\n<p>On June 3, 2025, we <a href=\"https://tailscale.com/changelog#2025-06-03\">announced the deprecation</a> of the GitOps code comment technique for locking the policy file editor in the admin console. Tailnets that were using this method were automatically migrated to use the setting in the admin console. The code comment is no longer effective for locking the policy file editor.</p>\n<p>To prevent other admins in your organization from accidentally changing your tailnet policy file, you can lock the editor in the <a href=\"https://login.tailscale.com/admin/settings/policy-file-management\">Policy file management</a> page in the Tailscale admin console.</p>\n<p>You must be an <a href=\"https://tailscale.com/docs/reference/user-roles\">Owner, Admin, or Network admin</a> of a tailnet to update tailnet policy file settings.</p>\n<ol>\n<li>Open the <a href=\"https://login.tailscale.com/admin/settings/policy-file-management\">Policy file management</a> page in the Tailscale admin console.</li>\n<li>If it is not already enabled, select <strong>Prevent edits in the admin console</strong> to prevent editing of the file.</li>\n<li>For <strong>External reference</strong>, enter the URL of your repository. This lets other admins to know where the source of your policies is located.</li>\n<li>Select <strong>Save</strong>.</li>\n</ol>\n<p>Any <a href=\"https://tailscale.com/docs/reference/user-roles#permissions-managed-by-user-roles\">admin with permissions to edit the tailnet policy file</a> can still edit it directly by selecting <strong>Edit anyway</strong>. This lets admins to quickly make changes in case of an emergency.</p>\n<p><img src=\"https://tailscale.com/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fgitops-edit-warning.9df07cf9.png&#x26;w=1080&#x26;q=75\" alt=\"The warning given when trying to edit a tailnet policy file which is using GitOps.\"></p>\n<p>The next time you use the GitOps flow, it overwrites the tailnet policy file changes made in the admin console.</p>\n<h2><a href=\"https://tailscale.com/docs/integrations/bitbucket/gitops#revert-the-most-recent-change-to-your-tailnet-policy-file\">Revert the most recent change to your tailnet policy file</a></h2>\n<p>If you need to revert the most recent change, use the Bitbucket <strong>Pull requests</strong> page to find and then revert the merged pull request.</p>\n<h2><a href=\"https://tailscale.com/docs/integrations/bitbucket/gitops#remove-your-gitops-configuration\">Remove your GitOps configuration</a></h2>\n<ol>\n<li>\n<p>Open the <a href=\"https://login.tailscale.com/admin/settings/policy-file-management\">Policy file management</a> page in the Tailscale admin console and disable the <strong>Prevent edits in the admin console</strong> toggle.</p>\n</li>\n<li>\n<p>Optionally, delete the <strong>External reference link</strong> and select <strong>Save</strong>.</p>\n</li>\n<li>\n<p><a href=\"https://tailscale.com/docs/features/access-control/auth-keys#revoke-an-auth-key\">Revoke</a> any associated Tailscale API access tokens for this configuration.</p>\n</li>\n<li>\n<p>Archive or delete the associated BitBucket repository.</p>\n</li>\n</ol>\n<h2><a href=\"https://tailscale.com/docs/integrations/bitbucket/gitops#additional-information\">Additional information</a></h2>\n<ul>\n<li>\n<p>Any manual tailnet policy file changes in the admin console won't be reflected in your Bitbucket version of the tailnet policy file. The next time you use the Bitbucket pipeline, any changes made in the Tailscale admin console will be overwritten.</p>\n</li>\n<li>\n<p>Tailscale <a href=\"https://tailscale.com/docs/reference/tailscale-api#authentication\">API access tokens expire</a> and currently there is no mechanism to have them automatically renewed. To handle the expiration, create a new API access token and update the Bitbucket <code>TS_API_KEY</code> secret to use the new value.</p>\n</li>\n</ul>\n<p>Tailscale API access tokens expire after 90 days (or less if the key has a shorter expiry) but updating the Bitbucket secret monthly is a good practice.</p>\n<p>When you no longer need to use a Tailscale API access token, make sure you revoke it in the <a href=\"https://login.tailscale.com/admin/settings/keys\">Keys</a> page of the Tailscale admin console.</p>\n<ul>\n<li>Tailscale tailnet policy files are in <a href=\"https://nigeltao.github.io/blog/2021/json-with-commas-comments.html\">HuJSON</a>, a JSON format with trailing commas and comments. If you don't want to write your tailnet policy files in HuJSON directly, you can use a tool that lets you generate JSON in the same schema as the HuJSON format.</li>\n</ul>\n"}