How to Use SSH in Docker

How to Use SSH in Docker

I needed to transfer a docker container that I’d built on one machine, to another machine that I could access via SSH.

I recently had a challenge for one of my side projects. I needed to transfer a docker container that I’d built on one machine, to another machine that I could access via SSH. I didn’t want to push my container to a public docker registry, or go through the trouble of setting up my own private registry.

Using Built-in Tools

It didn’t take long to find an answer on stack overflow

docker save <image> | bzip2 | \
     ssh [email protected] 'bunzip2 | docker load'

Lets break this down:

  1. docker save takes all the image data and serializes it, along with its tag, to a stream of binary data.
  2. docker load takes a stream of binary data and deserializes it into an image with a tag.
  3. bzip2 compresses the stream and bunzip2 decompresses the stream.
  4. ssh [email protected] 'some command' ssh’s into a remote host and runs the specified command.

It turns out that docker load is able to automatically decompress bzip’d content, so you can simplify the command to:

docker save <image> | bzip2 | \
  ssh [email protected] 'docker load'

You can remove the bzip2 but docker images are often big, and the compression from bzip2 saves a massive amount of bandwidth.

I still had a problem though. I was doing all this over a slow 3G internet connection, and the remote host already had most of the layers in the image I wanted to push, I just needed to push the tiny new layer containing my application logic.

Pushing With Layers

A bit more research and I found docker-push-ssh. With this command you can just do:

docker-push-ssh [email protected] <image>

It only transfers the layers that are needed. To do this, it:

  1. sets up a temporary docker registry on the local machine — this is surprisingly easy to do because there’s a docker registry in a docker image called registry:2
  2. Does a docker push to that local registry. This is fast because it’s all over the local machine’s network.
  3. Uses SSH to proxy a port on the remote server so that it connects to the proxy on the local machine.
  4. Does a docker pull on the remote server. This pull is running over the SSH tunnel, but docker pull is smart enough to only pull the layers it doesn’t already have.

Docker Over SSH Diagram

My Own Solution

This was a great idea, but there were three problems I had with it:

  1. It was written in Python and required Python 2.7, which I didn’t want to rely on having installed.
  2. It hasn’t been updated in quite a while, which wouldn’t generally be a problem, but given its reliance on an old version of Python, it was a bit of a concern.
  3. It was unclear the level of privileges it required on the remote machine. I wanted to be able to lock things down so that the user doing the push could not do anything other than push specific named docker images to the remote machine.

To solve all this, I decided to create a CLI in Node.js called docker-over-ssh. In spite of the name, docker-over-ssh is actually completely transport agnostic. It just requires a way to communicate over stdin and stdout with a remote instance of itself. To use it, you install docker-over-ssh on both the local and remote machine. Then you can run:

docker-over-ssh push <image> \
  ssh [email protected] "docker-over-ssh pull <image>"

The diagram looks exactly the same as for the previous solution.

The docker-over-ssh push command starts a local docker registry, pushes the image to it, and then runs the “child command” (in this example that’s ssh [email protected] "docker-over-ssh pull ") and proxies tcp traffic from that child command’s stdio to the local docker registry.

The docker-over-ssh pull command starts a local TCP proxy (written in a few lines of node.js code) and connects that proxy to stdio so it can talk to the local docker registry. It then runs docker pull pointing at the local registry. Only the new layers are transferred, which keeps everything very efficient.

The only permission required by the user is the ability to run docker-over-ssh pull on the remote machine, nothing else is required.

Using with CircleCI

At this point I had a working solution, but I wanted to automate this so that the deployments could be done from CircleCI. Adding an SSH key to CircleCI is fairly easy. The challenge was making this work with their complex docker networking setup:

  • Your local code (i.e. the node.js code terminating the proxy) cannot talk to containers run using docker run.
  • The docker daemon (used to run docker push) cannot talk to containers run as services to the main CircleCI job.

I couldn’t find any practical way to solve either of these problems directly, but I found NGROK which makes it pretty easy to create an internet accessible address that proxies to a local service. With this I was able to tell CircleCI to start a docker registry as a service for my build, and then use ngrok to start a temporary proxy to expose it to the docker daemon. It even supports securing it with a username and password (which I auto-generate on the fly) to keep everything nice and secure.

In the end all I had to do was update the CircleCI config:

docker:
  - image: circleci/node:12
    environment:
      LOCAL_DOCKER_REGISTRY_PORT: '5000'
  - image: registry:2

and set the DOCKER_REGISTRY_NGROK environment variable to my API key for ngrok, which you can get for free.

I then added some code to docker-over-ssh to handle those two environment variables.

Conclusion

The reason I’ve been doing all this is to try and setup a dokku server that I can run lots of side projects in and avoid spending loads of money on heroku for things that are rarely used. Thank you for reading!

Docker Ssh Nodejs DevOps Circleci

Bootstrap 5 Complete Course with Examples

Bootstrap 5 Tutorial - Bootstrap 5 Crash Course for Beginners

Nest.JS Tutorial for Beginners

Hello Vue 3: A First Look at Vue 3 and the Composition API

Building a simple Applications with Vue 3

Deno Crash Course: Explore Deno and Create a full REST API with Deno

How to Build a Real-time Chat App with Deno and WebSockets

Convert HTML to Markdown Online

HTML entity encoder decoder Online

Docker manifest - A peek into image's manifest.json files

The docker manifest command does not work independently to perform any action. In order to work with the docker manifest or manifest list, we use sub-commands along with it. This manifest sub-command can enable us to interact with the image manifests. Furthermore, it also gives information about the OS and the architecture, that a particular image was built for. The image manifest provides a configuration and a set of layers for a container image. This is an experimenta

Docker Explained: Docker Architecture | Docker Registries

Following the second video about Docker basics, in this video, I explain Docker architecture and explain the different building blocks of the docker engine; docker client, API, Docker Daemon. I also explain what a docker registry is and I finish the video with a demo explaining and illustrating how to use Docker hub.

What Is DevOps and Is Enterprise DevOps Any Good?

What is DevOps? How are organizations transitioning to DevOps? Is it possible for organizations to shift to enterprise DevOps? Read more to find out!

DevOps and Docker Live Show (Ep 91)

Join me with guest Docker Captain Elton Stoneman to talk about the state of Docker Desktop and Docker Hub. Support this show on Patreon! It's the #1 way to support me interviewing DevOps and container experts, and doing this Live Q&A.

DevOps Basics: What You Should Know

What is DevOps? What are the goals it helps achieves? What are its benefits? This article has answers!