Use Your local SSH Keys Inside a Docker Container
By: David Barral
Tags: Ssh, Docker Compose, Docker Secrets, Ssh Keys, Docker

Photo by Silas Köhler on Unsplash

One of our customers gives us user-by-user access to their systems through a proprietary VPN that we cannot set up on our CI infrastructure.

When we need to deploy their Rails application we rely on mina scripts that connect to the deploy machines using SSH connections (over the VPN). To work properly mina needs you to exchange SSH keys with the server. These are legacy projects, so we use Docker to run our local development environment. So, how can we deploy using Docker?

One key to rule them all

One option would be to create a new shared deploy key for the project using ssh-keygen , exchange the public part with the server, and use it in our Dockerfile .

Let’s create a key and exchange it with the target server:

$ ssh-keygen -t rsa -b 4096 -f ./id_rsa_shared

$ ssh-copy-id -i ./id_rsa_shared remoteuser@remotehost

Write a test Dockerfile :

Build it and test if it works:

$ docker build -t ssh-test .

$ docker run -it --rm ssh-test remoteuser@remotehost

Now you should be logged to remotehost as user remoteuser .

Notice that we disabled StrictHostKeyChecking to automatically add remotehost to the image’s known_hosts . In our scenario we can trust the remotehost identity. Your mileage may vary, and you may need to add the remotehost fingerprint by hand.

Although this solution works, it’s not fine grained. Sharing one key means that if you want to revoke access to one user, you’ll be revoking everybody else’s access. Each change in the SSH key will involve building and sharing a new image. It may be better to use each user local key and manage the allowed users in the remote host individually.

Using local keys by mounting volumes

Easy peasy. Modify a little the Dockerfile and mount your local .ssh folder.

Start the container directly mounting the volume:

$ docker run -it --rm -v ~/.ssh:/home/user/.ssh:ro \

ssh-test remoteuser@remotehost

Or use Docker Compose to mount the volume:

$ docker-compose run test ssh remoteuser@remotehost

Done!

What’s the catch?

You guessed it. There is a catch (and thus, more story below 😉).

In certain environments (like WSL under Windows, older docker versions on Mac, etc.), mounting volumes just don’t work due to different issues. So, let’s try to find some workaround in case you stumble upon one of this environments.

Using local keys at build time

It’s always possible to use the user local keys when building the image. You cannot write this in your Dockerfile , though:

For security reasons, Docker won’t copy any file from outside the folder where you are building the image. You can always copy your SSH keys to the project folder before COPY them.

Although we are now using the local keys, this forces each user to build their own image and move around the SSH keys. If possible, we want to have one shared image published to a private Docker registry that’s easy to pull and use. This also ensures that each developer uses the exact same image.

Docker secrets to the rescue

By using Docker secrets we can have a generic image that uses local SSH keys.

A secret is a blob of data, such as a password, SSH private key, SSL certificate, or another piece of data that should not be transmitted over a network or stored unencrypted in a Dockerfile or in your application’s source code. In Docker 1.13 and higher, you can use Docker secrets to centrally manage this data and securely transmit it to only those containers that need access to it. Secrets are encrypted during transit and at rest in a Docker swarm. A given secret is only accessible to those services which have been granted explicit access to it, and only while those service tasks are running.

Secrets provided to a container will be available as files at /run/secrets/<secret_name> . Let’s modify our Dockerfile to use one of this secrets as the SSH key:

But now… how can we set the secret when using docker run ? Well, we just can’t 😅. Docker secrets are meant to be used with Docker Swarm, not with standalone containers. Fear not. Docker compose does support secrets, so using a compose file similar to this will do the trick:

Run your image through docker compose and you are good to go:

$ docker-compose run test ssh remoteuser@remotehost

Summing up

As we have seen, the most straightforward way to use local SSH keys is to mount a volume. As a workaround, in case you have a problematic development environment, you can use Docker secrets. We have encountered that scenario before and using secrets was the way to go for us.

Using secrets instead of mounting volumes could also add value if you are using Docker Swarm (as we do) and your production images are very similar to the development ones.