How to SSH into a Docker Container

How do you use SSH to enter a Docker container? The traditional approach consists of two steps:

Step 1: SSH into your remote Linux server (if you are running the container in a remote system).‌

ssh user_name@server_ip_address

Step 2: And then you enter the shell of your running Docker container in interactive mode like this:

docker exec -it container_ID_or_name /bin/bash

With that, you can run Linux command or do some maintenance of the service running inside the container.

There is nothing wrong with the above method. This is the traditional and recommended way of effortlessly entering containers.

However, with some efforts, you can actually SSH into a running container directly, without logging into the host system first.

SSH into a Docker container: But why?

This is kind of weird, isn't it? Logging into a container, through SSH. Though it sounds non-traditional, it might still be useful to you, according to your use cases.

Here are a few things you can achieve with the ability to SSH into a container:

  1. You can set up a fake host for any potential attacker. By using a non-standard port for your host's SSH daemon, and serving an SSH connection at port 22 for the attackers.
  2. A totally separate authorization level, i.e. password logins or different ssh keys all up to you and separate from whatever your host is currently using.
  3. Running any automated remote process, without using the same ssh keys that are used to log in by your team's individuals.

Before I show you how to do all the above things, I'll walk you through the idea of how this actually works.

Using ssh login for existing container' is not recommended. That kills the whole point of host isolation.

Setting up SSH access for Docker containers [Intermediate to Expert]

If you're not interested in the workings of this, you can safely ignore this section. I am going to show you with a dummy container. You may follow the steps to practice.

Run a container

First, you need to start a Docker container. I'll use the extremely small alpine:latest image for now. Start the container with this command:

docker run --rm --name ssh-test -it -p 7655:22 alpine:latest ash 

Some noticeable points regarding the command line options are as follows

  • With the --rm option, you don't have to explicitly remove the container afterwards.
  • The -it options are there so that you can have a working, interactive shell of the container.
  • Finally, you are binding the container's port 22 to the host's port number 7655 (or any other port number that is not already used by SSH daemon on your host system). Keep in mind which port you are using.

Set up the SSH daemon in the container

Now you need to install the ssh server inside the container. In Alpine Linux, you can use these commands:‌

apk update; apk add openssh-server

Next, you need to change a configuration parameter quickly to allow root logins. You can do it by manually editing the /etc/ssh/sshd_config file or using this command:

sed -E 's/^#(PermitRootLogin )no/\1yes/' /etc/ssh/sshd_config -i 

Generate the host keys with:

ssh-keygen -A

Finally, start the ssh server, run /usr/sbin/sshd &. Check if it's running by ps aux.

Set a password for your container's root account

By default, your container's root account doesn't have a password. If you are opening SSH access to it, you must set the password for the root account.

You can use the passwd command without any option and follow the instructions on the screen:

passwd

Log into the container via SSH

From another host, try to log into the container now.

ssh root@IP_address_of_host_server -p port_number

You don't need the -p option if you bound to port 22 previously. For IP use the host server's IP address (not the container's).

When you run the command, you should see an output similar to this:

debdut@shinchan:/mnt/data/documents/Linux Handbook/container-ssh$ ssh root@domain.com
   root@domain.com's password: 
   Welcome to Alpine!
   
   The Alpine Wiki contains a large amount of how-to guides and general
   information about administrating Alpine systems.
   See <http://wiki.alpinelinux.org/>.
   
   You can setup the system with the command: setup-alpine
   
   You may change this message by editing /etc/motd.
   
   c4585d951883:~#

How does this work?

This can be understood better visually. Take a look at the following diagram:‌

Think of the containers as a virtual machine whose port 22 is glued together with the host's port 7655 (or the one you chose). This enables you to have two different ssh processes running on the same machine bound to different ports.

Let's say you use some other port for SSH on the host system and glue the port 22 with the container's port. Now, if somebody tries to connect to the host server using SSH default port 22, he/she will be inside the container's root file system.

Setting up SSH for containers using Docker Compose [Experts]

It wouldn't be fair if I leave you at this spot without providing some reliable option for an SSH server container.

If you wanted to take advantage of having a different, isolated ssh server with a separate root file system running on your remote system, that could be done, but not by following the previous walk-through, i.e. installing and configuring sshd on a running base container.

Simply because it's not easily reproducible, every change you make on the running container, are not persistent, a container restart and everything is gone.

So, here I give you a much simpler, easily reproducible, configurable way of deploying an SSH server container on your remote host.

Prerequisites

You need to have docker compose installed obviously. Basic knowledge of docker compose is a must here.

Since you will be accessing the server via SSH keys, you need to add the public SSH key of your local system to your host Linux server's directory where docker-compose file is located and keep the name "id_rsa.pub" just to be sure.

Prepare the compose file

I suggest using the linuxserver/openssh-server image. This is a very lightweight image with good enough configuration options through environment variables.

Here the whole compose file is going to be pasted. Copy this over to some location on your server and name the file docker-compose.yaml.‌

version: "3.7"

services:
    ssh:
        image: "linuxserver/openssh-server"
        ports:
            - "22:2222"
        volumes:
            - "./id_rsa.pub:/pubkey:ro"
        environment:
            PUID: ${ID}
            PGID: ${ID}
            TZ: ${TZ}
            PUBLIC_KEY_FILE: "/pubkey"
            SUDO_ACCESS: "false"
            PASSWORD_ACCESS: "false"
            USER_NAME: ${USER_NAME}
        restart: "always"

Quite a small compose file, isn't it? Let me explain different parts of this compose file.

Volumes: You only have a single bind mount, which mounts the public key within the container as pubkey. It's also read only.

Ports: The sshd process inside the container runs on port 2222. That's why I have bound that port to my host's port 22. Change 22 according to your needs but remember that as you'll need it to log into the container through SSH later.

Environment Variables:

  • USER_NAME :The in-container user, you'll be logging in as from your local machine.
  • PUID & PGID: USER_NAME's UID and GID. This is optional, the container will assign a pair of non-root IDs automatically if the environment variables are not set.
  • TZ: Your current time zone. You can get that by cat /etc/timezone.
  • PUBLIC_KEY_FILE: The location of the public key file
  • SUDO_ACCESS & PASSWORD_ACCESS: Self explanatory.

Restart policy: I have set the "always" restart policy which will restart the container even if the daemon is reloaded.

Docker Restart Policy [Explained With Examples]
Using a restart policy can be extremely helpful in restarting containers automatically in certain events or failures.

Deploy the service

To make it easy for you, I have made a Bash script that will ask you a couple of questions, and based on the answer it'll deploy the service.

#! /usr/bin/env bash

vars=("ID" "USER_NAME")
defaults=("991" "dummy")
questions=("What'd be your preferred UID & GID for the username of your choice? [default] " "Your preferred username for the container? [dummy] ")

put()
{
    echo "$1" >> .env
}

for i in {1..0}; do
    read -p "${questions[$i]}" ans
    case $ans in
        ""|"default")
            put "${vars[$i]}=${defaults[$i]}" ;;
        *)
            put "${vars[$i]}=$ans" ;;
    esac
done

put "TZ=$(cat /etc/timezone)"

docker-compose up -d

I saved the bash script as deploy.sh in the same directory where docker-compose file was located.

Now, if you run this script, it will ask you some questions and then run the docker container:

bash deploy.sh

Once it's done, try logging into the server:

ssh user@ip -p port 

That concludes this article on ssh with docker containers. If you liked it, or have anything else to mention, feel free to comment down below or tweet to me @imdebdut.

If you'd like me to write some other article feel free to let me know.