Let's debug docker container in the right way

Let's debug docker container in the right way

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.