Debugging Go using Delve, Docker and VS Code

Debugging a program can often be frustrating. Particularly so when your program is compiled and running inside a Docker container  - it can seem like you spend more time compiling than actually fixing the bug! Using a debugger can alleviate some of the pain points associated with debugging.

Delve is a debugging tool for the Go programming language. It’s designed to be simple and not get in the way.

In this post we’ll discuss how you could use Delve and Visual Studio Code to debug a Go application running inside a Docker container.

Getting started

To use Visual Studio Code with Delve we’ll need the vscode-go extension. To install this, open the quick command palette (CTRL+P) and enter ext install ms-vscode.Go.

If you’d like to follow along with this article you can download the code from GitHub: https://github.com/kaperys/blog/tree/master/delve-into-docker

Dockerfile

To begin debugging our application we’ll need to first download Delve. The easiest way to download Delve is to go get it. Once we have Delve installed, we can use the dlv debug command to being the debugging process.

The dlv debug command compiles the program with the necessary flags, starts the program and attaches to the running process. Since we’re going to be running the Delve API inside a Docker container and using an external client to connect, we’ll need to start Delve using the --headless flag and tell it which port to listen on  -  we’ll use --listen=:40000 for this.

Here’s an example Dockerfile which does everything we need:

FROM golang
WORKDIR /go/src/github.com/kaperys/delve-into-docker-app
EXPOSE 40000 1541

RUN go get github.com/derekparker/delve/cmd/dlv
ADD main.go .

CMD [ "dlv", "debug", "github.com/kaperys/delve-into-docker-app", "--listen=:40000", "--headless=true", "--api-version=2", "--log" ]

We can build the container using docker build -t delve-into-docker-app ., or, if you’re following along with my code, you can use make build.

Now we’ve built the container we’re ready to start it. We can use the following command to start the container (or make run if you’re following along).

docker run --rm --publish 40000:40000 --publish 1541:1541 --security-opt=seccomp:unconfined --name delve-into-docker delve-into-docker-app

This command tells Docker to run the delve-into-docker-app image and name it delve-into-docker. It publishes ports 40000 (for the Delve API server) and 1541 (for our program) and tells Docker not to use the default seccomp profile, which is necessary to allow dlv to fork processes.

Visual Studio Code

To use Visual Studio Code to debug we’ll need a launch.json file. This file contains all the information Visual Studio Code will need to connect to and control Delve. Visual Studio Code will look for this file in the .vscode directory.

Here’s an example launch.json file which contains the configuration necessary to connect Delve running on our Docker container:

{
      "version": "0.2.0",
      "configurations": [
            {
                  "name": "Delve into Docker",
                  "type": "go",
                  "request": "launch",
                  "mode": "remote",
                  "remotePath": "/go/src/github.com/kaperys/delve-into-docker-app",
                  "port": 40000,
                  "host": "172.17.0.2",
                  "program": "${workspaceRoot}",
                  "showLog": true
            }
      ]
}

Let’s break down what each property does:

Debugging

Now we’re set up we’re ready to begin debugging! Here’s the demo program we’ll use:

package main

import (
	"log"
	"net/http"
	"strings"
)

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		message := r.URL.Path
		message = strings.TrimPrefix(message, "/")
		message = "Hello, " + message + "!"

		w.Write([]byte(message))
	})

	log.Print("starting web server")
	if err := http.ListenAndServe(":1541", nil); err != nil {
		log.Fatal(err)
	}
}

To start with we’ll add a breakpoint at line 11. To add a breakpoint in Visual Studio Code, click just to the left of the line numbers in the editor or press SHIFT + F9 to add one to the current line.

Adding a breakpoint in Visual Studio

It’s important to note that Delve will only allow us to add a breakpoint whilst the application isn’t running. This means we’ll need to add our first breakpoint before starting the debugger. After this, we can add breakpoints whilst the program is paused.

To start debugging either press F5 or go to Debug > Start Debugging in the menu. The blue status bar at the bottom of the editor will go orange once Visual Studio Code is connected to the Delve API server.

If you’re struggling to get Visual Studio Code connected to Delve there’s a helpful “Troubleshooting” section in the vscode-go wiki pages.

Once Visual Studio Code is connected to Delve and you have a breakpoint set you can start debugging by visiting localhost:1541. Visiting the URL will invoke the web server and pause at the first breakpoint.

Stepping through the code in Visual Studio Code

Debugging allows you to step through your program, line by line. Visual Studio Code shows you the call stack and local variables in the left two panels.

You’re able to step through the program using the controls towards the top of the editor. From left to right, the controls are; continue, step over, step into, step out, restart and stop. Here’s what each control does:

Conclusion

In this article we’ve seen how Delve and Visual Studio Code could be used to debug a Go program running inside a Docker container. Although we’ve only discussed a trivial program, it’s easy to see how powerful Delve could be when debugging a complex program.


This article was originally published on Medium - https://medium.com/@kaperys/delve-into-docker-d6c92be2f823 · Published 2019-02-04 00:00:00 +0000