Debugging Go with Delve and VSCode

christineoo avatar

christineoo

Photo by Tim Nieland on Unsplash
Photo by Tim Nieland on Unsplash

Debugging is important to figure out what's wrong with your program. However, debugging a program is sometimes not easy and can be overwhelming. This is especially true(maybe it's just me) when your program is running inside a docker container.

This blog post is going to show you how we can setup remote debugging using VSCode. This example will be setup using GoLang and the recommended debugger for Go programming language is Delve.

Part 1: Docker + GoLang

First, let's create a simple web application in Go and setup the Docker container to run the web application.

Step 1: Setup
  1. Create a directory for the project.
$ mkdir go-delve-docker-vscode
$ cd go-delve-docker-vscode
  1. Then, create a file called main.go in your project directory with a simple code below. This is a simple program that renders Hello, world! when you access the url with http://localhost:8080/world .
package main

import (
	"log"
	"net"
	"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")
	listener, err := net.Listen("tcp", ":8080")
	if err != nil {
		log.Fatal(err)
	}

	log.Printf("Start listening: %v", listener.Addr().String())

	if err := http.Serve(listener, nil); err != nil {
		log.Fatal(err)
	}
}
Step 2: Create a Dockerfile

Now, we'll need to setup the Docker. In your project directory, create a file named Dockerfile and paste the following:

FROM golang:1.12

ENV GOPATH /go
ENV PATH $GOPATH/bin:/usr/local/go/bin:$PATH

RUN go get github.com/go-delve/delve/cmd/dlv

# set the working directory to /go/src
WORKDIR $GOPATH/src

# Copy everything from the current directory to the working directory inside the container
# (as set by WORKDIR)
COPY . .

# 8080 is for the web application
EXPOSE 8080
Step 3: Define services in a docker-compose

In your project directory, create a file named docker-compose.yml and paste the following:
(Note: For simplicity, this example only contains one service)

version: "3.0"

services:
  web:
    container_name: go-delve-docker-vscode-example
    build: "./"
    ports:
      - "8080:8080"
    security_opt:
      - "seccomp:unconfined"
    tty: true
    stdin_open: true
    command: go run main.go
Step 4: Build and run the web app with docker compose

Run docker-compose up from the project directory. Compose will build an image and starts the service defined in docker-compose.yml . At the end of the log, you'll see that web server is starting and listening to localhost:8080

Then, proceed to enter http://localhose:8080/world in a browser url and see the application running.

Part 2: Delve + VSCode debugger

Moving on to the debugging section 🐛🐛

Step 1: Add the debugger

Add Delve to our project and expose 2345 port for the debugger.

ENV GOPATH /go
 ENV PATH $GOPATH/bin:/usr/local/go/bin:$PATH

+# add delve debugger
+RUN go get github.com/go-delve/delve/cmd/dlv
+
 # set the working directory to /go/src
 WORKDIR $GOPATH/src

@@ -10,4 +13,5 @@ WORKDIR $GOPATH/src
 COPY . .

 # 8080 is for the web application
-EXPOSE 8080
+# 2345 is for the delve debugger
+EXPOSE 8080 2345
Step 2: Setup docker compose

Then, change the command in docker-compose.yml to start the app in debug mode. security-opt=seccomp:unconfined is needed to run the container insecurely as mentioned in this issue.

build: "./"
     ports:
       - "8080:8080"
+      - "2345:2345"
+    security_opt:
+      - "seccomp:unconfined"
     tty: true
     stdin_open: true
-    command: go run main.go
+    command: dlv debug --headless --listen=:2345 --api-version=2 --log main.go
Step 3: Configure debugging with VSCode

In order to debug with VSCode, we'll need to configure it. Run Debug: Open launch.json command by typing cmd + shift +p. Or you could manually add a new configuration file.

Things to note in this configuration is that we have to make sure that the remotePath is aligned with the path in your docker configuration. In this example, the working directory in the Dockerfile is set to /go/src, thus, the remotePath in launch.json is set to be the same.

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Launch",
      "type": "go",
      "request": "launch",
      "mode": "remote",
      "program": "${workspaceFolder}",
      "remotePath": "/go/src",
      "port": 2345,
      "env": {},
      "args": [],
      "showLog": true,
      "trace": "verbose"
    }
  ]
}

For more configuration settings, you can refer to the list here

Step 4: Run the web app with docker compose

Let's start debugging~ 💪

  1. Add breakpoint(s) in the main.go file in VSCode.
  2. Run docker-compose up
  3. Start debugger in VSCode with cmd + shift +p and type Debug: Start Debugging

Below are some screenshots of the logs for reference.
Run docker-compose up
Start debugger in VSCode

📝 Important note:
  • Make sure the log server is listening to port 8080 and then, head over to the browser and refresh the page.
  • Then, go back to VSCode and the program should be stopping at the breakpoint that you've set.
VSCode stopping at the breakpoint location
VSCode stopping at the breakpoint location

That's it! Hope that you all find this tutorial helpful and you can start debugging your Go code. 💨💨

Finally, the complete code for this blog post can be found in GitHub.

🚗💨 Thanks for stopping by this blog post. 👋

christineoo avatar
Written By

christineoo

A web developer that is fueled by coffee ☕
Enjoyed the post?

Clap to support the author, help others find it, and make your opinion count.