This guide walks you through configuring Tailscale networking for use inside Coder workspaces. With Tailscale networking, you can access services running inside Coder and services running on your tailnet (Tailscale private network).
Creating the image
As part of this tutorial, you'll create an image with the following that you'll use to create new Coder workspaces. The container image will include:
- The Tailscale daemon (
tailscaled
) - A transparent proxy tool (
proxychains4
) - Environment variables to control proxy behavior for outbound connections
- A SOCKS5 proxy included in
tailscaled
to facilitate connections to the tailnet, listening onlocalhost:1080
- An HTTP proxy included in
tailscaled
to facilitate connections to the tailnet, listening onlocalhost:3128
Limitations
This guide describes how to install Tailscale in a Ubuntu base image using the package manager and running it in userspace networking mode. As such:
- The image (which you will create as part of this tutorial) requires
container-based virtual machine workspaces, so that
systemd
can start the Tailscale daemon (tailscaled
) - Users will require
sudo
access to configure the Tailscale tunnel - Inbound connections from other devices on the tailnet to your workspace will
appear to originate from
localhost
- Outbound connections to other devices on the tailnet will require support for
HTTP proxies; otherwise, you'll need to use a wrapper such as
proxychains4
- The example in this article applies to Ubuntu 20.04 LTS (Focal Fossa), so you may need to adapt it for compatibility with your preferred base image
Tailscale does not require root access to operate in userspace networking mode, and the requirement to use container-based virtual machine workspaces applies only to the instructions in this guide. Contact our support team if you are interested in using Tailscale in your Coder workspace without root access.
Step 1: Create the Dockerfile
In Coder, developer workspaces are defined by a Dockerfile that contains the apps, tools, and dependencies that you need to work on the project.
See our custom image docs and Docker’s guide to writing Dockerfiles for more information.
To simplify creating and maintaining the image, we recommend structuring your
source code so that files added or modified in the image match the hierarchy of
the target files, in a subdirectory called files
:
.
├── Dockerfile
└── files
├── etc
│ ├── apt
│ │ ├── preferences.d
│ │ │ └── tailscale
│ │ └── sources.list.d
│ │ └── tailscale.list
│ └── systemd
│ └── system
│ └── tailscaled.service.d
│ └── tailscale.conf
└── usr
└── share
└── keyrings
└── tailscale.gpg
While it is possible to configure everything directly in a single Dockerfile, we recommend using a folder hierarchy, since this makes it easier to create a reproducible image and examine the change history for individual files.
Create the folder hierarchy:
mkdir --parents \
files/etc/apt/preferences.d \
files/etc/apt/sources.list.d \
files/etc/systemd/system/tailscaled.service.d \
files/usr/share/keyrings
Then, create the following files (we'll walk you through the contents of each in the following steps):
touch files/etc/apt/preferences.d/tailscale \
files/etc/apt/sources.list.d/tailscale.list \
files/etc/systemd/system/tailscaled.service.d/tailscale.conf
Step 1a: Add image repository
Add the Tailscale package repository to tailscale.list
in the local path
files/etc/apt/sources.list.d
. This will appear in /etc/apt/sources.list.d
in
the resulting image, with the following contents:
deb [signed-by=/usr/share/keyrings/tailscale.gpg] https://pkgs.tailscale.com/stable/ubuntu focal main
This configures apt
and apt-get
to install packages from the Tailscale
repository and verify package signatures with the specified public key.
Step 1b: (Optional) Configure package pinning
For improved security, you can configure apt
to deny package installation from
a given repository by default and allow specific packages by name. To do this,
create files/etc/apt/preferences.d/tailscale
with the following contents:
# Ignore all packages from this repository by default
Package: *
Pin: origin pkgs.tailscale.com
Pin-Priority: 1
Package: tailscale
Pin: origin pkgs.tailscale.com
Pin-Priority: 500
Step 1c: Add the signing key for Tailscale package repository
Retrieve the signing key from Tailscale, and store the binary (dearmored) key
file in files/usr/share/keyrings/tailscale.gpg
:
curl --silent --show-error --location "https://pkgs.tailscale.com/stable/ubuntu/focal.gpg" | \
gpg --dearmor --yes --output=files/usr/share/keyrings/tailscale.gpg
Step 1d: Override default tailscaled
service settings
By default, tailscaled
will store its internal state in a state file
located at /var/lib/tailscale/tailscaled.state
(this is is ephemeral in
Coder). We will need to modify the service settings to:
- Store the state file in the persistent home volume (
/home/coder
) - Enable userspace networking
- Enable the SOCKS5 proxy (optional)
- Enable the HTTP proxy (optional)
If you do not require outbound connections from the workspace to other services running in the tailnet, you may skip the steps where you configure the proxies.
Override the ExecStart
setting for the tailscaled
service by saving the
following to files/etc/systemd/system/tailscaled.service.d/tailscale.conf
:
[Service]
ExecStart=
ExecStart=-/usr/sbin/tailscaled --state=/home/coder/.config/tailscaled.state --socket=/var/run/tailscale/tailscaled.sock --port $PORT --tun=userspace-networking --socks5-server=localhost:1080 --outbound-http-proxy-listen=localhost:3128 $FLAGS
Step 1e: Create the Dockerfile
Create a Dockerfile
, build it, and push to an external repository, such as
Docker Hub:
FROM codercom/enterprise-base:ubuntu
USER root
# Copy configuration files to appropriate locations
COPY files /
ARG DEBIAN_FRONTEND="noninteractive"
RUN apt-get update && \
apt-get install --no-install-recommends --yes --quiet \
netcat-openbsd \
proxychains4 \
python3 && \
apt-get install --no-install-recommends --yes --quiet \
tailscale && \
# Delete package cache to avoid consuming space in layer
apt-get clean && \
rm -rf /var/lib/apt/lists/*
USER coder
ENV ALL_PROXY=socks5://localhost:1080
ENV http_proxy=http://localhost:3128
Step 2: Authenticate to Tailscale
Create a workspace using the container image. Initially, tailscaled
should
be running, but it will indicate that it requires authentication:
systemctl status tailscaled
Authenticate using sudo tailscale up
, then verify that other network devices
are visible:
tailscale status
You may also use a pre-authentication key with
tailscale up --authkey
to avoid needing to sign in via a web browser.
tailscale
should maintain connectivity across workspace rebuilds, since we
chose to store the state file in a persistent volume.
Step 3: (Optional) Test Tailscale services
By creating two workspaces from the same image, both authenticated to Tailscale, we can verify connectivity works as expected. In one workspace, run the Python web server:
python3 -m http.server 3000
In another workspace, verify that tailscaled
is listening for connections on
the configured proxy ports:
ss -nltp
Check that the http_proxy
environment variable is set to the address of the
local tailscaled
proxy:
env | grep -i proxy
Run curl
(which respects the http_proxy
command) to connect to the webserver
running in the other workspace. Since we proxy the connection through the local
tailscaled
instance, we can use the internal hostname:
curl http://jawnsy-tailscale-1:3000
For applications that do not respect the http_proxy
or ALL_PROXY
environment
variables, consider using a tool like proxychains4
to intercept the socket
system calls and transparently route traffic through the proxy.