{"slug":"tailscale-on-flyio","title":"Tailscale on Fly.io","tags":["tailscale","setup"],"agent_summary":"Last validated: Dec 4, 2025","trigger_phrases":[],"runnable":false,"markdown":"\r\n# Tailscale on Fly.io\r\n\r\nLast validated: Dec 4, 2025\r\n\r\n[Fly.io](https://fly.io/) is a popular service to deploy full stack apps and databases all over the world, with Fly handling operations and scaling in each region according to demand. Adding Tailscale to a fly.io application is straightforward, allowing the App on Fly to communicate with other nodes and services in your tailnet.\r\n\r\n## [Step 1: Generate an auth key to authenticate your App on Fly](https://tailscale.com/docs/install/cloud/flydotio\\#step-1-generate-an-auth-key-to-authenticate-your-app-on-fly)\r\n\r\nFirst, we'll [generate an auth key](https://tailscale.com/docs/features/access-control/auth-keys) to allow fly.io to authenticate our app to join our network.\r\n\r\nOpen the [Keys](https://login.tailscale.com/admin/settings/keys) page of the admin console and select **Generate auth key**. We recommend using a reusable and pre-authorized [ephemeral key](https://tailscale.com/docs/features/ephemeral-nodes) for this purpose, since it will automatically clean up devices after they shut down.\r\n\r\n![Tailscale's auth key generation page](https://tailscale.com/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fephemeral-keys.2d3fc94f.png&w=750&q=75)\r\n\r\nThe **Pre-approved** option will only display in the dialog if [device approval](https://tailscale.com/docs/features/access-control/device-management/device-approval) is enabled in your Tailscale network.\r\n\r\nNext, use `flyctl secrets set TAILSCALE_AUTHKEY=\"tskey-<key>\"` to securely store the auth key for the App on Fly to use.\r\n\r\n## [Step 2: Configure your Dockerfile to install Tailscale](https://tailscale.com/docs/install/cloud/flydotio\\#step-2-configure-your-dockerfile-to-install-tailscale)\r\n\r\nNext, we'll use a [multistage Dockerfile](https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds), where the first stage builds your application, and the second stage pulls application code and Tailscale into the final image to be uploaded to Fly.\r\n\r\nIn your `Dockerfile`:\r\n\r\n```docker\r\nFROM alpine:latest as builder\r\nWORKDIR /app\r\nCOPY . ./\r\n# This is where one could build the application code as well.\r\n\r\n# https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds\r\nFROM alpine:latest\r\nRUN apk update && apk add ca-certificates iptables ip6tables && rm -rf /var/cache/apk/*\r\n\r\n# Copy binary to production image.\r\nCOPY --from=builder /app/start.sh /app/start.sh\r\n\r\n# Copy Tailscale binaries from the tailscale image on Docker Hub.\r\nCOPY --from=docker.io/tailscale/tailscale:stable /usr/local/bin/tailscaled /app/tailscaled\r\nCOPY --from=docker.io/tailscale/tailscale:stable /usr/local/bin/tailscale /app/tailscale\r\nRUN mkdir -p /var/run/tailscale /var/cache/tailscale /var/lib/tailscale\r\n\r\n# Run on container startup.\r\nCMD [\"/app/start.sh\"]\r\n```\r\n\r\nThe Dockerfile specifies `/app/start.sh` as the initial process to run. This script needs to bring Tailscale up and then start the application binary. This is where we can use the `TAILSCALE_AUTHKEY` variable we defined earlier.\r\n\r\nThen, create a file named `start.sh` at the root of your app:\r\n\r\n```shell\r\n#!/bin/sh\r\n\r\n/app/tailscaled --state=/var/lib/tailscale/tailscaled.state --socket=/var/run/tailscale/tailscaled.sock &\r\n/app/tailscale up --auth-key=${TAILSCALE_AUTHKEY} --hostname=fly-app\r\n/app/my-app\r\n```\r\n\r\nThe next time your App on Fly deploys, it will be able to connect to your private Tailscale network.\r\n\r\nIf you are using an Alpine base image and an existing Fly machine, you may need to update the machine to ensure that it has kernel support for nftables. For more information, refer to [Fly community post](https://community.fly.io/t/kernel-nftables-support/17669) and [Tailscale issue #10540](https://github.com/tailscale/tailscale/issues/10540).\r\n\r\n## [Remove ephemeral nodes from a tailnet](https://tailscale.com/docs/install/cloud/flydotio\\#remove-ephemeral-nodes-from-a-tailnet)\r\n\r\nWhen an ephemeral node goes offline, it is automatically removed from your tailnet. You can also control ephemeral node removal using the [`tailscale logout`](https://tailscale.com/docs/reference/tailscale-cli#logout) command to either manually force the removal or incorporate the command into the [`tailscaled`](https://tailscale.com/docs/reference/tailscaled) Tailscale daemon. For more information, refer to [Ephemeral nodes](https://tailscale.com/docs/features/ephemeral-nodes#faq).\r\n","html":"<h1>Tailscale on Fly.io</h1>\n<p>Last validated: Dec 4, 2025</p>\n<p><a href=\"https://fly.io/\">Fly.io</a> is a popular service to deploy full stack apps and databases all over the world, with Fly handling operations and scaling in each region according to demand. Adding Tailscale to a fly.io application is straightforward, allowing the App on Fly to communicate with other nodes and services in your tailnet.</p>\n<h2><a href=\"https://tailscale.com/docs/install/cloud/flydotio#step-1-generate-an-auth-key-to-authenticate-your-app-on-fly\">Step 1: Generate an auth key to authenticate your App on Fly</a></h2>\n<p>First, we'll <a href=\"https://tailscale.com/docs/features/access-control/auth-keys\">generate an auth key</a> to allow fly.io to authenticate our app to join our network.</p>\n<p>Open the <a href=\"https://login.tailscale.com/admin/settings/keys\">Keys</a> page of the admin console and select <strong>Generate auth key</strong>. We recommend using a reusable and pre-authorized <a href=\"https://tailscale.com/docs/features/ephemeral-nodes\">ephemeral key</a> for this purpose, since it will automatically clean up devices after they shut down.</p>\n<p><img src=\"https://tailscale.com/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fephemeral-keys.2d3fc94f.png&#x26;w=750&#x26;q=75\" alt=\"Tailscale&#x27;s auth key generation page\"></p>\n<p>The <strong>Pre-approved</strong> option will only display in the dialog if <a href=\"https://tailscale.com/docs/features/access-control/device-management/device-approval\">device approval</a> is enabled in your Tailscale network.</p>\n<p>Next, use <code>flyctl secrets set TAILSCALE_AUTHKEY=\"tskey-&#x3C;key>\"</code> to securely store the auth key for the App on Fly to use.</p>\n<h2><a href=\"https://tailscale.com/docs/install/cloud/flydotio#step-2-configure-your-dockerfile-to-install-tailscale\">Step 2: Configure your Dockerfile to install Tailscale</a></h2>\n<p>Next, we'll use a <a href=\"https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds\">multistage Dockerfile</a>, where the first stage builds your application, and the second stage pulls application code and Tailscale into the final image to be uploaded to Fly.</p>\n<p>In your <code>Dockerfile</code>:</p>\n<pre><code class=\"language-docker\">FROM alpine:latest as builder\r\nWORKDIR /app\r\nCOPY . ./\r\n# This is where one could build the application code as well.\r\n\r\n# https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds\r\nFROM alpine:latest\r\nRUN apk update &#x26;&#x26; apk add ca-certificates iptables ip6tables &#x26;&#x26; rm -rf /var/cache/apk/*\r\n\r\n# Copy binary to production image.\r\nCOPY --from=builder /app/start.sh /app/start.sh\r\n\r\n# Copy Tailscale binaries from the tailscale image on Docker Hub.\r\nCOPY --from=docker.io/tailscale/tailscale:stable /usr/local/bin/tailscaled /app/tailscaled\r\nCOPY --from=docker.io/tailscale/tailscale:stable /usr/local/bin/tailscale /app/tailscale\r\nRUN mkdir -p /var/run/tailscale /var/cache/tailscale /var/lib/tailscale\r\n\r\n# Run on container startup.\r\nCMD [\"/app/start.sh\"]\n</code></pre>\n<p>The Dockerfile specifies <code>/app/start.sh</code> as the initial process to run. This script needs to bring Tailscale up and then start the application binary. This is where we can use the <code>TAILSCALE_AUTHKEY</code> variable we defined earlier.</p>\n<p>Then, create a file named <code>start.sh</code> at the root of your app:</p>\n<pre><code class=\"language-shell\">#!/bin/sh\r\n\r\n/app/tailscaled --state=/var/lib/tailscale/tailscaled.state --socket=/var/run/tailscale/tailscaled.sock &#x26;\r\n/app/tailscale up --auth-key=${TAILSCALE_AUTHKEY} --hostname=fly-app\r\n/app/my-app\n</code></pre>\n<p>The next time your App on Fly deploys, it will be able to connect to your private Tailscale network.</p>\n<p>If you are using an Alpine base image and an existing Fly machine, you may need to update the machine to ensure that it has kernel support for nftables. For more information, refer to <a href=\"https://community.fly.io/t/kernel-nftables-support/17669\">Fly community post</a> and <a href=\"https://github.com/tailscale/tailscale/issues/10540\">Tailscale issue #10540</a>.</p>\n<h2><a href=\"https://tailscale.com/docs/install/cloud/flydotio#remove-ephemeral-nodes-from-a-tailnet\">Remove ephemeral nodes from a tailnet</a></h2>\n<p>When an ephemeral node goes offline, it is automatically removed from your tailnet. You can also control ephemeral node removal using the <a href=\"https://tailscale.com/docs/reference/tailscale-cli#logout\"><code>tailscale logout</code></a> command to either manually force the removal or incorporate the command into the <a href=\"https://tailscale.com/docs/reference/tailscaled\"><code>tailscaled</code></a> Tailscale daemon. For more information, refer to <a href=\"https://tailscale.com/docs/features/ephemeral-nodes#faq\">Ephemeral nodes</a>.</p>\n"}