Use Your local SSH Keys Inside a Docker Container

David Barral
Oct 5 · 4 min read
Image for post
Image for post
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

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

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?

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

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

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

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.

Trabe

We are a development studio.

Thanks to Ceci García García

David Barral

Written by

Co-founder @Trabe. Developer drowning in a sea of pointless code.

Trabe

Trabe

We are a development studio. We use Java, Rails, and JavaScript. This is where we write about the technologies we use at Trabe.

David Barral

Written by

Co-founder @Trabe. Developer drowning in a sea of pointless code.

Trabe

Trabe

We are a development studio. We use Java, Rails, and JavaScript. This is where we write about the technologies we use at Trabe.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store