Dissatisfied with the screen share method of remote pairing, I've used Docker and a variety of other tools to make my coding environment portable and repeatable. Here's how.
I'm part of an entirely remote team. I'm mobile, and so my most reliable Internet access is tethering to my phone. It's not always fast, and I pay by the gigabyte. I pair frequently, and traditional screen sharing has been consistently awful: bandwidth hungry, lagged, and hard on the eyes. I addressed these problems using a combination of cloud servers, Docker, SSH, and tmux. Aside from an improved pairing experience, there were unexpected additional benefits.
Let's get this out of the way early: my method requires a text-based editor to realize all of the benefits. Emacs, nano, vim, any of them will work. It's possible to containerize GUI applications, so what I describe may still be applicable to you – particularly if you have bandwidth to spare, or you intend to automate an environment that you'll run locally.
This post is not a technical dive into my particular environment, but an overview of how and why I used a container-based development environment, and particularly where Docker helped. I do link to the actual code that I use, so all of the gritty details are available to you if you're inclined to look further.
Laura Frank previously published a blog post on containerizing your development environment. If you're new to Docker, that post explains basics that I will omit. Her focus is on running your application in a container, but I will expand that to include all of the tools you use to author the code, too.
If it took you weeks or months to configure your local machine just right, it might seem daunting to automate. My very first attempt at this was an Amazon EC2 instance and a long shell script. It was awful in every way: difficult to author, fragile, and time-consuming to run. What you want is a solution that is easy to build, repeatable, and quick to deploy. Docker arrived at the right time. It provides a combination of power and simplicity that are perfect for this task. Containers are easy to spin up, test, and tear down. Dockerfiles have a low learning curve, and the layer caching feature means you can iterate repeatedly from scratch until everything works correctly.
I split my development Docker images into two parts: a base of cross-language tools that I always need(shell, vim, git, etc.) with language-specific tools like a Ruby interpreter or Go compiler on top of that. As an example, here are the sources for my base and Go development images.
The gist is that the base image is built on Ubuntu, installs software I always need, and starts an SSH server. You've probably read warnings that suggest keeping SSH out of your images, but that's not applicable in this case. The point of this image is to host a shell from which you can develop. Using SSH instead of just
docker run /bin/sh is important because it allows you to use SSH agent forwarding to identify yourself. That way you won't need to copy your private key into the image. SSH will also come in handy later when you want to allow access to others.
The Go image descends from the base image, adding Go-specific software like a compiler and development packages. Source code should exist on the host machine and be volume mounted so that it outlives any one container, as mentioned in that README.
Do It Yourself
This setup didn't happen overnight. Docker is flexible enough to let you work on your automation in stages. You can
docker run /bin/sh an Ubuntu container, manually run some shell commands, then
docker commit the result. You don't have to stop everything and make it work perfectly from the outset, but try and be disciplined enough to jot down the manual steps so that you can automate them later when you have the time. You can use my Dockerfile as your starting point if you'd like, but that's _my_ config and you might hate it. If you look at the contents, what I've done just isn't that complicated. It's a dozen shell commands. You can do this yourself and make it yours.
At this point you have something you can use locally, but it's only a containerized version of what you already had. This isn't terribly exciting until you think about the side-effects: everything you need to get work done is backed up, and it's divorced from the vagaries of your host operating system. If you've ever lost a laptop, or held on to an old one or an old version of an OS solely because you were terrified of breaking your development environment, this is good news. You can also fearlessly experiment with your environment, because anything you do can be undone by deleting the container.
Your environment is now a portable Docker image that you access via SSH. Guess where this is going?
How Cloud Coding Facilitates Remote Pairing
Let's back up for a moment and talk about specific problems with screen share remote pairing. First, it's a bandwidth hog. You're transferring pictures of text, so the person on the remote end of the session is trading their usual crisp text for lagged, compressed images. The host is unaffected by all of this, while the remote client is at a permanent disadvantage. Second, if the host needs to take lunch or leaves for the day, the session is over and the remote either waits or pulls code down locally – unless the host forgot to push before they left, in which case you're stuck.
Hosting the development environment we've just built on a cloud server offers a neutral site to hold your pairing session. Both people are on an equal footing. You're only transferring text, so there are no readability issues and significantly better response times. You can also use a dedicated VOIP client like Skype to communicate with one another. The bandwidth usage is minimal compared to screen sharing.
So what does a pairing session with wemux look like? Here's a contrived side-by-side demo.
For the host instance, I have lately chosen CoreOS. Many cloud providers offer pre-built CoreOS machines, and it comes with Docker and git, which are all I need to get the code and start the development container. Wemux provides an easy-to-use wrapper around tmux's session sharing feature, so that both pairs are seeing the same thing. Tmux's persistent sessions mean that pairs can detach from the session, then later return with everything exactly as they'd left it.
This setup has great flexibility. When I started at CenturyLink and was waiting for my laptop to arrive, I worked from the only computer I had handy, a five-year-old Windows desktop. Last year when I had an equipment failure and was left with only an iPad, I was able to continue pairing. Any platform with Skype and an SSH client can be my development box. I've swarmed three developers on the same problem, and hosted multiple pairing sessions from the same cloud instance simultaneously. The sky's the limit, as long as it's text.
I've glossed over some details about my specific image setup, but feel free to dive into the source code of my base and Go development images. These are exactly what I use every day to work, so if you're curious about specifics like how I version my dotfiles, or how I deal with SSH keys, it's all there. You can even try it out for yourself. It's reasonably well-documented, and if you already have Docker then you're a single command away. Better yet, spin up a Docker-ready server on a cloud provider, follow the instructions in my
README, and code with someone else!