Just sometimes, it’s useful to SSH into a Docker Container. While docker exec or docker attach are usually sufficient to run commands in a container, sometimes you specifically need SSH. For example, to connect directly from a remote machine or when an application needs to run commands on your container. Most Docker images don’t come with the SSHd service installed so it is not possible to SSH to them. This post demonstrates how to install and run the SSHd service to an existing image so that you can connect to it.
Is this a good idea?
There are several reasons why you may not want or need to enable SSH on a Docker container. First, you often don’t need to run SSHd in Docker to do stuff you’d normally use SSH for. You can access logs, restart services and browse a container file system with docker attach or docker exec.
Second, it goes against the philosophy of Docker containers. A container should do exactly one thing. An nginx or HTTPd container serves web resources, a Redis or Mongo container provides NoSQL databases. A complex application is composed from several single-purpose containers orchestrated together. A container that that runs nginx and SSHd does two things. It’s not wrong but it’s just not the Docker way.
Finally, it may be a security risk. Exposing an additional means of accessing a container certainly increases the attack surface. You may also need to deal with password / key management.
That said, if you really want to SSH into a Docker Container, it’s not too hard.
In this example, I’ll demonstrate how to enable SSH for an nginx container but the same technique applies to other container types. So let’s start with the default nginx image:
Then we need to install the SSHd service to the container:
RUN apt-get update && apt-get install -y openssh-server
Create a user account
We want a specific user account for the SSH connection. We’ll allow access to this account and no other.
RUN groupadd sshgroup && useradd -ms /bin/bash -g sshgroup sshuser
This creates an account called sshuser which belongs to the sshgroup. It creates a user directory in /home/sshuser and sets the user’s shell to Bash.
Next need to decide how to authenticate the user. It’s possible to allow username / password authentication but this is inadvisable. It bakes the password into the image definition and is generally less secure.
# NOT RECOMMENDED: Set a password on the sshuser account RUN echo 'sshuser:Pa$$word' | chpasswd
This sets a password of Pa$$word on our new account.
Create an SSH key
A better option is to use an SSH key. Plenty of good resources exist to help you do this. So let’s assume you’ve created a public key called id_rsa.pub and the private key called id_rsa. Copy the public key into your container in a file called ~/.ssh/authorized_keys.
ARG home=/home/sshuser RUN mkdir $home/.ssh COPY id_rsa.pub $home/.ssh/authorized_keys RUN chown sshuser:sshgroup $home/.ssh/authorized_keys && \ chmod 600 $home/.ssh/authorized_keys
Be sure to keep the private key private! Anyone with access to the private key will be able to access the container so this should not be shared or copied into the container.
Start the SSH service
This last step is a little fiddly. We can use CMD instruction to run a command when the container starts. An image can include only one CMD instruction but it’s already used to start nginx. The simple solution is to use && to run multiple commands. First we start the SSH service, then we run nginx:
CMD service ssh start && nginx -g 'daemon off;'
SSH into a Docker Container
We now have a container image that will allow SSH access. So let’s start it up and try it. I like using docker-compose to do my build, config and startup with a single simple command. My docker-compose.yml file looks like this:
version: '3' services: nginx-ssh: build: . ports: - "80:80" - "1022:22"
I can run it like this:
docker-compose up -d --build
And then I can SSH into a docker container like this:
ssh sshuser@localhost -p 1022 -i id_rsa
That is, connect to localhost as the sshuser and use the id_rsa private key to authenticate. We specify port 1022 because that’s the port on our machine (localhost, the Docker host) that forwards to port 22 on the container.
After running this command we have a secure shell (Bash) on the nginx Docker container.
The full source code for this example is in GitHub.