5 Simple Tips For Debugging Docker Containers
Sometimes Docker containers can be a black box. Whether you built the underlying image or you’re using a public one, a flapping container is frustrating. Figuring out what is going on can be difficult due to the way containers are executed and how they handle logging.
In this article, we’ll explore a few basic commands and parameters you can use to troubleshoot particularly fussy containers. If the container fails to start, intermittently blows up or you just want more insight into the image details, these simple options are real game changers.
1. Better logging and timestamps
The first and simplest example is to use the logging tools that Docker provides already. Most people already know how to look at the logs inside a container:
docker logs <container_id>
But what if this particular container has been running for a long time and has a log the size of Texas? In cases like this, you can simply add the extra --tail
parameter:
docker logs --tail 10 <container_id>
Using the --tail
option you can see just the last n
lines of the log. Passing in the number of lines you want to see allows you to jump right to the most relevant and recent info.
If your log output from within the container doesn’t contain timestamps by default, you can add those too. Docker allows you to pass the -t
flag to log
which will prepend each line with a timestamp:
docker logs -t <container_id>
These options can also be combined to form a precise troubleshooting instrument. Now you’ll be able to tell exactly when something happened without having to alter anything inside the container.
2. Executing commands as root
If you’re using an image that runs as the default root user, then this isn’t an issue. When you do not run as root and instead use an unprivileged user, this is a great troubleshooting tool.
If you run:
docker exec -it <container_id> /bin/sh
This will always run as the user defined in the underlying image. If this user doesn’t have root privileges then it can be difficult to try and exec into a running container to troubleshoot (especially if you need to install anything).
If you want to jump into the container as the root user, all you have to do is pass the following instead:
docker exec -u 0 -it <container_id> /bin/sh
This will tell Docker to use the user who has ID 0
. This is root. Now when you enter the container you’ll be ready to debug with full privileges.
3. Committing a container as an image
This is an often overlooked feature of Docker. You can actually create a new image from an existing container. This means if you’ve been fiddling with a container and make a few changes to fix some bugs, you can spin up new containers from it right away. You don’t even have to go rebuild the Dockerfile.
The following command will commit a new image from the existing container:
docker commit <container_name> <new_image>
This will create a new image with whatever name you specify and you can immediately use it to spin up new containers.
Another added benefit of the commit
command is that you can actually pass Dockerfile syntax to it during the commit process. If you wanted to commit and existing container but change one of the environment variables in it, you could use the --change
flag to pass that in:
docker commit --change="ENV foo=bar" <container_name> <new_image>
You can pass in multiple different changes
to the commit
command to facilitate impressively granular image creation on the command-line.
4. Matching image hashes
If you’re troubleshooting a container that has been around for a while, you might not know what particular version of an image it was built with. If you use a container registry like Docker Hub or Elastic Container Registry, you can easily get the hash of the image to compare it against your container.
A quick way to grab all the metadata about a container is using the inspect
command. This is fine, but it gives you a ton of info. If all you’re after is the image hash, you can get it using a little formatting magic like this:
docker inspect --format "{{ .Image }}" <container_id>
This should output the sha256 hash of the image the container is running. The hash can be compared to the one in your registry to determine when it was built.
Now you can be absolutely sure what version is running where.
5. Skipping the build cache
If you’re really struggling to understand why a build is failing, being buggy or just not including some changes you made then it might be time to drop the cache. Although Docker should recognize changes to layers and rebuild as needed, sometimes you need the peace of mind of starting from scratch.
If you want to build an image without leveraging any existing build cache, you can run the following command:
docker build --tag <tag> --no-cache .
This will ignore any previously built items in the cache and force everything to be built from zero. Handy if you’re working through several iterations of an image and want to be sure you include very subtle changes to some layers.