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.
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.