Docker Desktop

Yesterday, Docker announced that they will start charging per-seat fees for Docker Desktop. Docker Desktop is the user friendly application for Mac and Windows that sets up a VM running Linux with docker so the developer can use docker “natively” and “seamlessly”. Linux desktop users are not impacted by this change.

How to avoid using docker desktop

Docker has a client-server model. It’s possible and effective to run docker on any remote host. I recommend individual users get a VM somewhere or a beefy machine in a closet for remote dev. This is basically what Docker Desktop does anyways, except with a local VM instead of a remote host. It’s possible to duplicate the docker desktop model of the docker host being local to the developer machine, but why do that? Heavy docker desktop users know the resources it requires.

Connecting to a remote docker host

See instructions at the end of the article for how to set up the remote host.

There are a few ways to do this but to get started very quickly:

$ docker context create docker-on-nuc --docker "host=ssh://nibz@nuc"
$ docker context use docker-on-nuc
$ docker ps
$ docker run hello-world

Assuming ssh, ssh keys, and an ssh agent are all set up, you’re done.

There are two pieces here. The first is docker’s built in context machinery, which I hope is self-explanatory.

The second is the DOCKER_HOST setting. The above case is equivalent to DOCKER_HOST=ssh://nibz@nuc docker ps.

You can also do it via ssh tunnel as well (in two commands):

# leave this running in a separate terminal or manage it how you manage other tunnels
# note the -L syntax but we're forwarding unix sockets
ssh -L /home/nibz/docker.sock:/var/run/docker.sock nuc 

# run ps
DOCKER_HOST=unix:///home/nibz/docker.sock docker ps

There are other tutorials out there for running the remote docker engine in tcp mode but I prefer ssh tunnels.

Docker Build

Docker build “just works”. You can issue docker build on your local dev machine and the tags and images appear on the remote host. I suppose that means the ‘build’ is being done on the remote host but I haven’t dug in.

Port Forwarding

The biggest drawback is that you have to think more about port forwarding. In local dev I often use e.g. -p 5000:5000 and that doesn’t work for remote docker hosts. In fact, it’s worse than not working because the port is now exposed to whatever network the remote host is on.

Two step ssh tunnel solution:

# this assumes the service we're building listens on port 5678, change it if needed.

# set up ssh tunnel
# leave this running or manage it however you like
$ ssh -L 5678:localhost:5678 nibz@nuc 

# another terminal
$ DOCKER_HOST=unix:///home/nibz/docker.sock docker run -it --rm -p 127.0.0.1:5678:5678 hashicorp/http-echo -text hi


# another terminal
$ curl localhost:5678
hi

The extra argument to -p prevents the container from getting requests from the outside world. The ssh tunnel forwards 5678 back to your local system so you can hit your app. See SSH Tunnel Online Management below for tips on doing it all with one terminal.

Bind mounting in volumes

This is where things start to break down. Any binds you make will be local to the remote host. You can’t easily bind mount in your working code directory. You can docker build for every test run though. You could also write something that watches for osx’s version of inotify events and triggers a new docker build.

Setting up the remote Docker Host

Checklist for setting up docker host:

  • Standard linux cloud host
  • SSH key only auth
  • Docker engine installed (hopefully from packages)
  • Unattended upgrades enabled
  • General firewall off everything except port 22 (this is especially important because an accidental -p or -P is easy to do)
  • Maybe a once a month reboot cron?

SSH Tunnel Management

Did you know ssh tunnels and port forwarding can be changed while running?

In an open ssh terminal session issue the following: <enter> ~ ?

A help menu should pop up:

nibz@nuc:~$ ~?
Supported escape sequences:
 ~.   - terminate connection (and any multiplexed sessions)
 ~B   - send a BREAK to the remote system
 ~C   - open a command line
 ~R   - request rekey
 ~V/v - decrease/increase verbosity (LogLevel)
 ~^Z  - suspend ssh
 ~#   - list forwarded connections
 ~&   - background ssh (when waiting for connections to terminate)
 ~?   - this message
 ~~   - send the escape character by typing it twice
(Note that escapes are only recognized immediately after newline.)

To set up a new port forward:

<enter> ~ C

ssh> -L5678:localhost:5678
Forwarding port.
<enter>