Get started
Login
© 2024

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!