Debugging Dockerized Go Applications with VS Code

Ken Aqshal Bramasta

Ken Aqshal Bramasta / March 07, 2023

7 min read––– views

Ken Aqshal Bramasta

For developers, debugging becomes a crucial and challenging aspect of software development. It's like being a detective, searching for clues and piecing together the story of what went wrong in your code. But, just like any detective story, it's also a challenge. Debugging in a containerized environment, like Docker, adds an extra layer of complexity. However, with the right tools and approach, debugging Go applications in Docker containers can be a straightforward and rewarding experience.

Go, also known as Golang, is a powerful open-source programming language known for its simplicity, efficiency, and reliability. But, when it comes to deploying and managing applications, there's a better solution - Docker containers, especially when microservices continue to gain popularity. Docker containers provide a standardized, isolated environment for your Go applications, making deployment and management a breeze.

In this post, you will explore how to debug Go applications in Docker containers using Visual Studio Code. this post will cover the necessary steps for setting up the debugger and walk through the process of debugging a Go application both with docker and docker-compose. By the end of this post, you should better understand how to debug Go applications in Docker containers using Visual Studio Code.

So, if you're ready to elevate your debugging game and make your life as a developer easier, keep reading! you won't regret it.

Prerequisite

  • Golang 1.16 or higher
  • Docker
  • Visual Studio Code

Setting up

Firstly, ensure that all prerequisites are installed. Once that is done, proceed with setting up the project. In this blog, we will use gofiber for a sample application to debug Go applications. Follow the steps below:

1. Create app folder

1mkdir go-debugger && cd go-debugger

2. Add the following content to Dockerfile file

1# syntax=docker/dockerfile:1
2FROM golang:1.18-alpine
3
4WORKDIR /app
5
6COPY go.mod go.sum ./
7RUN go mod download
8
9COPY . .
10
11RUN go build -o go-debugger .
12
13EXPOSE 3000
14
15CMD ["/app/go-debugger"]

3. Build the image from the code

1docker build --tag go-debugger-image .

4. After finishing the build, we can verify it by running command below in the terminal

1docker images --filter "reference=go-debugger-image"

it will show the information about the local image that you create, and the information should be like this

Search docker image. Image by author
Search docker image. Image by author

5. After we check the image, let's run your image as a container with this command

1docker run -d -p 3000:3000 --name go-debugger-container go-debugger-image

6. Verify if the container is successfully run with the command

1docker ps --filter "name=go-debugger-container"

the display information should be like this

Search docker container. Image by author
Search docker container. Image by author

7. Check by open http://localhost:3000/, the page will show this to you

Test the API. image by author
Test the API. image by author

But how to debug it?

Great work verifying everything is working! Now, let's take it a step further and debug the app inside the Docker container. But before we dive into debugging, we need to create a separate Dockerfile version specifically for debugging purposes. This way, we'll have a "normal" Dockerfile to run the app as usual, without debug mode.

Why do we need to separate the Dockerfile? We're using a third-party package called delve to enable us to debug the Go app inside the Docker container. And because we're using an external package, we need to configure the Docker image differently to ensure that delve works properly.

So, let's get ready to debug our app and unravel any issues lurking inside the container!

1. Let’s create a dockerfile for debug only

1touch Dockerfile.debug

2. Add the following content to Dockerfile.debug file

1FROM golang:1.18-alpine
2
3EXPOSE 3000 4000
4
5WORKDIR /app
6COPY go.mod go.sum ./
7RUN go mod download
8
9COPY . .
10
11RUN CGO_ENABLED=0 go install -ldflags "-s -w -extldflags '-static'" github.com/go-delve/delve/cmd/dlv@latest
12
13RUN CGO_ENABLED=0 go build -gcflags "all=-N -l" -o go-debugger .
14
15
16CMD [ "/go/bin/dlv", "--listen=:4000", "--headless=true", "--log=true", "--accept-multiclient", "--api-version=2", "exec", "/app/go-debugger" ]

there are some commands that you must be aware of:

  • --listen=:4000 this is will be the port that your debugger(delve) listen to. that’s why we expose 2 ports in EXPOSE 3000 4000
  • CGO_ENABLED=0 is required for not making the delve configuration become dynamic compiled
  • -gcflags=all="-N -l" is used for disable inlining and optimizing because it can interfere with the debugging process

3. Next step is you must build a new image with Dockerfile.debug configuration

1docker build --file Dockerfile.debug --tag go-debugger-image . 

4. After finish build the image, the next step is to remove the previous container because we can't have 2 containers with the same name

1docker container rm go-debugger-container

5. After deleting, let's run your image as a container

1docker run -d -p 3000:3000 -p 4000:4000 --name  go-debugger-container go-debugger-image

6. After successfully running your container. now let’s prepare the debugger in VS Code

Create debugger config. image by author
Create debugger config. image by author

7. After that, you will have a launch.json , Add the following content to it

1{
2    "version": "0.2.0",
3    "configurations": [{
4        "name": "Go Containerized Debug",
5        "type": "go",
6        "request": "attach",
7        "mode": "remote",
8        "port": 4000,
9        "host": "127.0.0.1"
10    }]
11}

8. Now, let’s try to set some breakpoints and run the debugger

Set breakpoints and run debugger. Image by author
Set breakpoints and run debugger. Image by author

9. After you run the debugger, let’s verify it with open http://localhost:3000/test . it’s should be automatic redirect to your vs code like this

Debugging process. Image by author
Debugging process. Image by author

Awesome! You've successfully set up your code for debugging in a Docker container. Now, get ready to dive deep into your code and uncover any pesky bugs that have been hiding!

Debugging like a Pro with Docker Compose

But hold on a minute...what if you want to take it to the next level and run your app with Docker Compose? After all, many modern development workflows rely on container management. Well, fear not! Setting it up with Docker Compose is easy as pie, and I'm here to show you how it's done. Let's get started!

1. Make sure that no application will use ports that we expose in Dockerfile.debug. In this case, you expose ports 3000 and 4000. if there’s an application that used the port, stop it

2. let's make the docker-compose.yml with command

1touch docker-compose.yml

3. Add the following content to your docker-compose.yml

1version: "3.9"
2services:
3  app:
4    build: 
5      context: .
6      dockerfile: Dockerfile.debug
7    ports:
8      - "3000:3000"
9      - "4000:4000"

4. Now, let's create and run the container with the command

1docker compose up

5. Now let’s run the debugger in VS code like in step 8 before

Set breakpoints and run debugger. Image by author
Set breakpoints and run debugger. Image by author

6. Verify the debug with an open URL http://localhost:3000/test and it will redirect to VS code like this

Debugging process. Image by author
Debugging process. Image by author

Fantastic! You're ready to take your debugging skills to the next level with Docker Compose. By leveraging this powerful tool, you'll be able to manage your containers more effectively and streamline your development workflow. Whether you're dealing with complex code or simply looking to improve your debugging abilities, Docker Compose is an excellent tool in your arsenal. So, what are you waiting for? It's time to debug like a pro!

Conclusion

here is a wrap-up about this blog post that you've read

  • Debugging is possible in Golang with the third-party package Delve
  • You can debug the app with delve on both docker and docker-compose with some additional.
  • I recommend separating the “normal” Dockerfile and “debug” Dockerfile because there’s a custom configuration to make Delve run smoothly, and changing the environment becomes easier

Reference

Enjoyed this post?

Check out some of my other articles: