Debug it until you make it! Yes, you read it right. In this blog, I am going to dive into my experiences along the way in debugging code, as I came across debugging binary files in a docker container. If you are hearing this for the first time, then it'll be your big day.
Introduction
Debugging a binary in a Docker container can be a challenging task due to the isolation provided by the container. However, it is a crucial step in the development process, as it helps to identify and fix issues in the code. Docker is a popular containerization technology that allows you to package an application and its dependencies into a single container. Containers are lightweight, portable, and provide a consistent environment for running applications. In this blog post, we will explore how to debug a binary in a Docker container using the dlv debugger
. We will cover topics such as adding a binary to a pre-built Docker image, attaching dlv
to a process running in a container, giving your container the correct privileges to enable the debugger to run, and the benefits of having a dlv
init file and how to write one. By the end of this blog post, you will have a better understanding of how to debug a binary in a Docker container using the dlv
debugger.
Before Debug, Let's Code
This following code sets up an HTTP handler that responds with "Hello, World!" to any request it receives and starts a server listening on port 8080
.
package main
import (
"fmt"
"net/http"
)
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Hello, World!")
}
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)
}
Let's Debug the right way
To debug a binary in a Docker container, you first need to add the binary to a pre-built Docker image. You can use a Dockerfile to build an image that includes the necessary tools and libraries for debugging. Here is an example Dockerfile that adds a binary to an Alpine Linux image:
FROM golang:alpine
RUN apk --no-cache add go openssh-client ca-certificates
RUN mkdir -p /go/src /go/bin && chmod -R 777 /go
ENV GOPATH /go
ENV PATH /go/bin:$PATH
WORKDIR /usr
COPY . .
RUN go build -o /usr/bin/myapp .
CMD ["/usr/bin/myapp"]
In this example, we are using the Alpine Linux image as the base image and adding the my-binary executable to the /go/bin
directory. We are also adding the go git bash with an OpenSSH client using the apk
package manager. Finally, we are setting the default command for the container to run go build
with the my-binary executable as the argument.
Once you have added the binary to a Docker image, you can start a container using the new image and attach dlv
to the process running in the container. To do this, you need to start the container with the correct options to enable remote debugging and then attach dlv
to the process. Here is an example command that starts a container and attaches dlv
to the process:
docker run -it --rm --name myc -p 8080:8080 myapp:latest
dlv attach 1 --headless
This command starts a container named myc
using the myapp
image and enables remote debugging on port 8080. The dlv attach
command attaches dlv
to the process with PID 1 in the container and starts it in headless mode.
To enable dlv
to run in your container, you need to give your container the correct privileges. Specifically, you need to give the container the SYS_PTRACE
capability, which allows the container to trace processes outside of its own namespace. Here is an example Dockerfile that gives a container the SYS_PTRACE
capability:
FROM go:alpine
...
RUN setcap 'cap_sys_ptrace=+ep' /go/bin/dlv
This Dockerfile starts with a pre-built image named myapp
, adds the my-app
binary, and gives the dlv
binary the SYS_PTRACE
capability.
The Benefits of Having a dlv
Init File
Having a dlv
init file can make debugging easier and more efficient. An init file is a script that runs when dlv
starts and can set breakpoints, configure the debugger, and execute other commands. Here is an example dlv
init file:
# Set a breakpoint on line 10
break main.go:10
# Enable verbose output
set verbose on
# Start the program
continue
Conclusion
When debugging a binary in a Docker container, it is important to have a good understanding of the container environment and the tools you are using. Additionally, it is important to follow best practices, such as using init files to streamline the debugging process and giving your container the correct privileges to run the debugger.
Overall, the ability to debug a binary in a Docker container is an essential skill for any developer working with containerized applications. By following the tips and techniques outlined in this blog, you can quickly and efficiently identify and fix problems in your containerized applications.