Skip to content

Instantly share code, notes, and snippets.

@bnhf
Last active March 19, 2026 20:19
Show Gist options
  • Select an option

  • Save bnhf/fed4cc3035f32a0f086b1da074a3d50b to your computer and use it in GitHub Desktop.

Select an option

Save bnhf/fed4cc3035f32a0f086b1da074a3d50b to your computer and use it in GitHub Desktop.
Tailscale - Deploying with Docker and Portainer

Just thought I'd put together some detail on deploying Tailscale using Docker and Portainer. These bits-and-pieces are available elsewhere, but not together, so hopefully this will save someone a bit of time if you'd like to add Tailscale to an existing Docker install:

Here's my annotated recommended docker-compose, to use with Portainer-Stacks. Note that I'm not using a pre-made Auth Key. I started that way, but realized it was very easy to simply check the Portainer log for the tailscaled container once the stack is running. In that log you'll see the standard Auth link that you can use to authorize the container. This way you don't need to create a key in advance, or create a reusable key that introduces a security risk:

version: '3.9'
services:
  tailscale:
    image: tailscale/tailscale
    container_name: tailscaled
    cap_add:
      - NET_ADMIN
      - NET_RAW
    environment:
#      - TS_HOSTNAME=${TS_HOSTNAME} # Usually not necessary for your hostname to be the same name on the tailscale network
#      - TS_AUTHKEY=${TS_AUTHKEY} # Generate auth keys here: https://login.tailscale.com/admin/settings/keys
#      - TS_ROUTES=${TS_ROUTES} # Creates a subnet router for Tailscale. Use your subnet's CIDR in the form: 192.168.1.0/24
#      - TS_ACCEPT_DNS=${TS_ACCEPT_DNS} # Set to false for Pi-hole Docker setups
      - TS_SOCKET=${TS_SOCKET} # Specifying the /var/lib/tailscale/tailscaled.sock location allows use of standard Tailscale commands 
      - TS_EXTRA_ARGS=${TS_EXTRA_ARGS} # Add any other supported arguments in the docker commandline style: e.g. --advertise-exit-node
      - TS_STATE_DIR=${TS_STATE_DIR} # Required to create a persistent container state that will survive reboots
    volumes:
      - /data:/var/lib # Creates a tailscale directory under /data for persistence
      - /dev/net/tun:/dev/net/tun
    network_mode: host
    restart: unless-stopped

These are the minimum environment variables you'll want to define in the Portainer-Environment section:

TS_SOCKET=/var/run/tailscale/tailscaled.sock
TS_EXTRA_ARGS=--accept-routes
TS_STATE_DIR=/var/lib/tailscale

With these variables, you'll be able to exec into the container to run commands like "tailscale version" and "tailscale status". Your container will accept routes advertised by a designated node, and your setup (including authorization) will persist across reboots.

@nelsvieira
Copy link
Copy Markdown

Heads up... Recent updates to containerd require moving the TUN mapping over to devices...

Thank you. This was the missing piece I needed to get the tailscale container running again after updating debian (and containerd).

@albaphysic
Copy link
Copy Markdown

albaphysic commented Jan 16, 2025

Hi, how can I use this compose file to have access to the host instead of a particular container. I mean I want to install tailscale in my server with docker and be able to ssh to and from it. Is this even possible?

Yes.

Tailscale Docker uses userspace networking by default. In this situation "tailscale ping" (eg. docker exec -it tailscaled tailscale ping 100.X.X.X) to other tailscale hosts will work but normal ping to tailscale hosts will fail.

TS_USERSPACE: Enable userspace networking, instead of kernel networking enabled by default.

To fix it, you can disable userspace networking by setting env variable TS_USERSPACE to false.

- TS_USERSPACE=false

Source: https://old.reddit.com/r/Tailscale/comments/15f8cbc/tailscale_docker_not_able_to_ping_other_hosts/kaozlvy/

@matrix303
Copy link
Copy Markdown

Hi, thanks for this guide!

I am using Portainer in which I have my Tailscale Stack as well as other stacks/containers that offer services on different ports. I turned on Tailscale HTTPS, and created the certificates as per https://tailscale.com/kb/1153/enabling-https

I can access the sites via http://tailscaleIP:port but not https://tailscaleIP:port

Any suggestions?

@matrix303
Copy link
Copy Markdown

matrix303 commented Jan 21, 2025

Hi, thanks for this guide!

I am using Portainer in which I have my Tailscale Stack as well as other stacks/containers that offer services on different ports. I turned on Tailscale HTTPS, and created the certificates as per https://tailscale.com/kb/1153/enabling-https

I can access the sites via http://tailscaleIP:port but not https://tailscaleIP:port

Any suggestions?

Replying to this for anyone else that has trouble with this. It seems right now that you are not able to do HTTPS for the whole host and all the open ports. I was able to get this functionality working with Tailscale Serve.

docker exec tailscaled tailscale serve --bg --https=[PORT1] http://localhost:[PORT2]
docker exec tailscaled tailscale serve status
  • PORT1 does not have to equal PORT2
  • --bg = run in background
  • your serve settings should be consistent via reboots

To access on another tailscale computer you use your .ts.net domain: https://machine-abcd.ts.net:PORT1

@jpatel207
Copy link
Copy Markdown

I can't get this to work when I have TS_EXTRA_ARGS=--advertise-exit-node. It only works when I have it set to --accept-routes. Is there something else I have to do to get the device to work as an exit node?

@bnhf
Copy link
Copy Markdown
Author

bnhf commented Jun 1, 2025

Is there something else I have to do to get the device to work as an exit node?

Have you authorized it as an exit node in the Tailscale Admin Console?

@jpatel207
Copy link
Copy Markdown

I set the variable with both --advertise-exit-node --accept-routes and it works now. Originally, I had only --advertise-exit-node set and Tailscale wouldn't show the device and the container would constantly restart over and over. I guess I have to set both values for the TS_EXTRA_ARGS variable.

@HackStrix
Copy link
Copy Markdown

Hey! I built a GitHub Action that automates this exact Tailscale + Portainer workflow for CI/CD. It creates an ephemeral Tailscale node, connects to your Portainer instance over the tailnet, deploys/updates your stack, and cleans up automatically.

- name: Install Tailscale
  run: curl -fsSL https://tailscale.com/install.sh | sh
- name: Deploy to Portainer
  uses: hackstrix/portainer-tailscale-deployment-action@v1
  with:
    ts_oauth_client_id: ${{ secrets.TS_OAUTH_CLIENT_ID }}
    ts_oauth_secret: ${{ secrets.TS_OAUTH_SECRET }}
    portainer_url: 'https://my-server.tailnet.ts.net:9443'
    portainer_api_key: ${{ secrets.PORTAINER_API_KEY }}
    stack_name: 'my-app'
    compose_file: './docker-compose.yml'

Features: OAuth-based ephemeral keys (no manual key rotation), endpoint auto-detection, private registry auth (GHCR/DockerHub), retry with backoff, and automatic cleanup.

Repo: https://github.com/HackStrix/Portainer-Tailscale-Deployment-Action

Hope it's useful to anyone deploying to private Portainer instances from GitHub Actions!

@boris1000
Copy link
Copy Markdown

boris1000 commented Mar 19, 2026

Need some help...

I am facing CGNAT:-( since a few weeks and searching for an comfortable way to access some services running on my homeserver
on 192.168.2.100 and also some other local devices/IPs.
I followed the OP additionally activated that line in the compose for - TS_ROUTES=192.168.2.0/24
So far it is running and I also was able to connect my Android phone to my tailnet.
The only change I did was on the phone in "EXIT NODE" I enabled Allow LAN access

Some issues:

  • When enabling Tailscale on the phone I need to deactivate my Android DNS settings (using dnsforge.de) otherwise no data connection at all
    Is that that really the case?
  • I thought now I can just enter e.g. 192.168.2.100:8123 to reach my immich database - but I can't reach any of my IPs
    What do I miss?
  • I just realized that I also could setup tailscale on my OpenWRT router 192.168.2.1 instead on that homeserver 192.168.2.100
    Is there any advantage or disadvantage running tailscale on the homeserver or router?

@boris1000
Copy link
Copy Markdown

OK, problem was that the host wasn't connected to the tailnet - don't really know why but do not fully understand the logic.
Now I am connected, but can it be that the host is disconnecting automatically or does it stay online - what it should.

Still struggling to access my local IPs - what do I enter now the routed IP ot the tailscale IP for the host ...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment