Smaller binaries for embedded devices
Tailscale provides downloads for a variety of operating systems and architectures on our downloads page. However, there are cases where you may want to build an "extra-small" Tailscale binary—one that takes up a minimal amount of disk space. One common use case is building for an embedded device like an OpenWrt router.
Prerequisites
Before you begin this guide, you'll need to have a Go development environment already set up and working.
Step 1: Building Tailscale
First, you can build a version of Tailscale that embeds both the client (the
tailscale
binary) and the daemon (the tailscaled
binary) into a single
binary. This is similar to how tools like busybox
work, where the behavior
of the binary depends on how it's called (specifically, the value of
argv[0]
). Essentially: if the file on-disk is named tailscale
, then the
binary will behave like the tailscale
binary, and if it's named tailscaled
,
then it'll behave like tailscaled
.
In the Tailscale repository:
$ go build -o tailscale.combined -tags ts_include_cli ./cmd/tailscaled
$ du -hs tailscale.combined
23M tailscale.combined
The actual size of the output binary will depend on the version of Tailscale and the operating system and architecture it's being built for. Exact sizes are only for illustrative purposes.
By creating symlinks to the combined binary, you can see how it can run as both the daemon and the CLI:
$ ln -s tailscale.combined tailscale
$ ln -s tailscale.combined tailscaled
$ ./tailscale --help
USAGE
tailscale [flags] <subcommand> [command flags]
For help on subcommands, add --help after: "tailscale status --help".
# ... omitted ...
$ ./tailscaled --help
Usage of ./tailscaled:
-bird-socket string
path of the bird unix socket
-cleanup
clean up system state and exit
# ... omitted ...
Step 2: Building a smaller Tailscale binary
In addition to combining both the Tailscale client and daemon into the same
binary, you can also use the --extra-small
flag to omit things like debug information and lesser
used features from the built binary.
build_dist.sh --extra-small
Step 3: Compressing Tailscale
In cases where disk space is a premium, one of the more effective options is to use compression. There's a class of tools called "packers", which compress a binary on-disk and restore the original uncompressed version of the binary when it is run.
Using a packer can be dangerous! These tools fundamentally change how a binary interacts with the operating system in ways that can reduce that binary's security. It's beyond the scope of this guide to go into the full details, but as a specific example, most packers require that W^X protection be turned off or an executable temporary directory in order to function.
Also, some antivirus software will treat packed binaries as malicious, since this technique is often used by malware to obfuscate what it's doing.
In the case where it's necessary, though, the resulting space savings can be impressive. Perhaps the most well-known packer is UPX, and it can be used to compress the Tailscale combined binary that we built above.
$ go build -o tailscale.combined -tags ts_include_cli -ldflags="-s -w" ./cmd/tailscaled
$ du -hs tailscale.combined
18M tailscale.combined
$ upx --lzma --best ./tailscale.combined
$ du -hs tailscale.combined
4.5M tailscale.combined
After UPX compression, we've reduced the size of the (combined) Tailscale binary from the original size of 23MiB down to only 4.5MiB, about 20% of the original size!