Red Green Repeat Adventures of a Spec Driven Junkie

Can I do better: Connecting to Docker Containers 2

I want to share my debugging process when I run into a problem with code that stops working.

The specific situation is when a solution I made to connect to docker containers without typing out the container SHA identifier suddenly stopped working.

I walk through specific steps I took to debug the issue and create a new solution.

This article will take you less than five minutes to read.

Double-Chambered Vessel source and more information

Introduction

Following up on a previous: “Can I do Better?” article where I improved how I connect to Docker Containers.

All of the sudden, I was running into an issue where the command:

sudo docker exec -it $(sudo docker ps | grep foo | awk '{ print $1 }') bash

Started to give me errors:

sudo docker exec -it $(sudo docker ps | grep foo | awk '{ print $1 }') bash
OCI runtime exec failed: exec failed: container_linux.go:348: starting container process caused "exec: \"574687ed515e\": executable file not found in $PATH": unknown

HUH?? What happened??

Why?

Probably the first place to start is to understand why this command is starting to fail. The command itself is agnostic to the container, specific to the image.

Container not running?

What happens if the foo image isn’t running in a container?

Let’s try the command so it would definitely fail:

$ sudo docker exec -it $(sudo docker ps | grep invalid_image_name | awk '{ print $1 }') bash
"docker exec" requires at least 2 arguments.
See 'docker exec --help'.

Usage:  docker exec [OPTIONS] CONTAINER COMMAND [ARG...]

Run a command in a running container

Ideally, this is not that useful of a message, it’s a quick and dirty test to tell me what the response of the command is when I want to connect to a container with invalid_image_name. This is important.

This isn’t the message I saw earlier, so it seems this isn’t the issue.

What happens with a valid name?

Let’s run the same command and this time, give a valid_image_name so it should work.

This is the response now:

$ sudo docker exec -it $(sudo docker ps | grep foo | awk '{ print $1 }') bash
OCI runtime exec failed: exec failed: container_linux.go:348: starting container process caused "exec: \"574687ed515e\": executable file not found in $PATH": unknown

This isn’t the same response as for an invalid_image_name and it is the message I have been receiving.

Things aren’t working even though the container is running the specified image. What’s going on?

Debug script

Let’s break down the script, run each part individually to validate, and check results.

There’s two parts two the script:

  • getting the docker container SHA for a specific image name, in this case: valid_image_name
  • connecting to the docker container SHA

Getting the Container SHA

The main driver of this script is getting the container SHA given a “valid image name”.

sudo docker ps | grep valid_image_name | awk '{ print $1 }'

What is the output if I run it now?

sudo docker ps | grep foo | awk '{ print $1 }'
2df0bba15bc6
574687ed515e

There are two SHA returned now instead of one?? Are each of these valid?

Validate Outputs

The easiest way to validate: plugin the new SHA values into the next part of the command:

sudo docker exec -it SHA_value bash

and if it works, then something else is wrong with the first part of the command.

Here goes nothing:

sudo docker exec -it 2df0bba15bc6 bash
root@2df0bba15bc6:/foo#

Yup, as expected.

sudo docker exec -it 574687ed515e bash
root@574687ed515e:/foo#

Same here.

So, this is working as expected. Something in the first part of the command changed, it does look the same at the same time, different.

The outputs are correct, SHA values of the container for the image specified. The incorrect part is now there is more than one value.

Investigating higher

Maybe something higher changed at a higher level in the system, outside of the scope of the command.

What does docker ps tell us?

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
b8996065500e        ubuntu              "/bin/bash"              4 seconds ago       Up 2 seconds                            clever_goldstine
5bb3e80c076d        nginx:latest        "nginx -g 'daemon of…"   About a minute ago   Up About a minute   80/tcp              objective_hermann.1.6agqbzmx8qiazhwwncdegknwa
f8ead854815d        registry:latest     "/entrypoint.sh /etc…"   44 seconds ago      Up 42 seconds       5000/tcp            registry.1.4efrmjz58hqjdtmhdqhpg4q0z
2df0bba15bc6        foo               "nginx -g 'daemon ..."   47 seconds ago      Up 46 seconds       80/tcp              sad_boyd
574687ed515e        foo               "nginx -g 'daemon ..."   49 seconds ago      Up 48 seconds       80/tcp              laughing_beaver

There are multiple containers running the foo image!

The command I wrote was for a single container running that image. I did not account for multiple containers of the same image.

Honestly, I just want to connect to the first container running the foo image, it doesn’t really matter to me.

Where are we?

The right image is running one multiple containers. Each container is connect-able.

The one-liner works when there is a single container. When there are more than one container, the result of the container listing command returns multiple items, essentially an array.

To make this command work in a multi-container environment for the same image, there needs to be a change in the first part of the command:

sudo docker ps | grep valid_image_name | awk '{ print $1 }'

So the second part can handle the result.

Potential Solution?

To me, since the image running on the container is the same, whichever one I connect won’t matter. I can just take the first one.

To do that, I just need to take the output from:

sudo docker ps | grep valid_image_name | head -1 | awk '{ print $1 }'

and use the first one.

The easiest way would be to use the head or tail function, which returns the first or last n items specified: head -1 for the first or tail -1 for the last.

Solution

Incorporating head into original command:

sudo docker exec -it $(sudo docker ps | grep foo | head -1 | awk '{ print $1 }') bash

Results in:

sudo docker exec -it $(sudo docker ps | grep foo | head -1 | awk '{ print $1 }') bash
root@2df0bba15bc6:/foo#

The best part about this solution, it’s still works when there’s a single container that matches the container search function:

sudo docker ps | grep foo | head -1 | awk '{ print $1 }'
2df0bba15bc6

Final Solution

The update one-line I will use to connect to docker containers now is:

sudo docker exec -it $(sudo docker ps | grep valid_image_name | head -1 | awk '{ print $1 }') bash

It’ll work wherever multiple containers running image that has name: valid_image_name.

Conclusion

What started off as a solved problem in one specific case evolved into a solution that can handle another case. Best of all, the new solution encapsulates both the new case and the old case.

Now I have a one-liner to connect to any docker container running a specified name and I understand how to use head or tail in array outputs or at least just picking one.