{"id":2103,"date":"2016-12-15T14:33:11","date_gmt":"2016-12-15T14:33:11","guid":{"rendered":"https:\/\/intelligentbee.com\/blog\/?p=2103"},"modified":"2024-11-28T13:59:05","modified_gmt":"2024-11-28T13:59:05","slug":"debugging-golang-apps-in-docker-with-visual-studio-code","status":"publish","type":"post","link":"https:\/\/intelligentbee.com\/blog\/debugging-golang-apps-in-docker-with-visual-studio-code\/","title":{"rendered":"Debugging Golang apps in Docker with Visual Studio Code"},"content":{"rendered":"<div id=\"ez-toc-container\" class=\"ez-toc-v2_0_68_1 counter-hierarchy ez-toc-counter ez-toc-grey ez-toc-container-direction\">\n<div class=\"ez-toc-title-container\">\n<p class=\"ez-toc-title \" >Table of Contents<\/p>\n<span class=\"ez-toc-title-toggle\"><a href=\"#\" class=\"ez-toc-pull-right ez-toc-btn ez-toc-btn-xs ez-toc-btn-default ez-toc-toggle\" aria-label=\"Toggle Table of Content\"><span class=\"ez-toc-js-icon-con\"><span class=\"\"><span class=\"eztoc-hide\" style=\"display:none;\">Toggle<\/span><span class=\"ez-toc-icon-toggle-span\"><svg style=\"fill: #999;color:#999\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" class=\"list-377408\" width=\"20px\" height=\"20px\" viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M6 6H4v2h2V6zm14 0H8v2h12V6zM4 11h2v2H4v-2zm16 0H8v2h12v-2zM4 16h2v2H4v-2zm16 0H8v2h12v-2z\" fill=\"currentColor\"><\/path><\/svg><svg style=\"fill: #999;color:#999\" class=\"arrow-unsorted-368013\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"10px\" height=\"10px\" viewBox=\"0 0 24 24\" version=\"1.2\" baseProfile=\"tiny\"><path d=\"M18.2 9.3l-6.2-6.3-6.2 6.3c-.2.2-.3.4-.3.7s.1.5.3.7c.2.2.4.3.7.3h11c.3 0 .5-.1.7-.3.2-.2.3-.5.3-.7s-.1-.5-.3-.7zM5.8 14.7l6.2 6.3 6.2-6.3c.2-.2.3-.5.3-.7s-.1-.5-.3-.7c-.2-.2-.4-.3-.7-.3h-11c-.3 0-.5.1-.7.3-.2.2-.3.5-.3.7s.1.5.3.7z\"\/><\/svg><\/span><\/span><\/span><\/a><\/span><\/div>\n<nav><ul class='ez-toc-list ez-toc-list-level-1 ' ><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-1\" href=\"https:\/\/intelligentbee.com\/blog\/debugging-golang-apps-in-docker-with-visual-studio-code\/#Context\" title=\"Context\">Context<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/intelligentbee.com\/blog\/debugging-golang-apps-in-docker-with-visual-studio-code\/#The_application\" title=\"The application\">The application<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/intelligentbee.com\/blog\/debugging-golang-apps-in-docker-with-visual-studio-code\/#The_Dockerfile\" title=\"The Dockerfile\">The Dockerfile<\/a><\/li><\/ul><\/nav><\/div>\n<h2><span class=\"ez-toc-section\" id=\"Context\"><\/span>Context<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>We\u2019ve recently had some problems with a Go application that was running inside a Docker container in a very big Docker Compose setup.<\/p>\n<p>After getting fed up with writing console prints and rebuilding the Docker image for that container and spinning up all the containers to debug things, we started investigating how we could speed up our debugging process.<\/p>\n<p>Enter <a href=\"https:\/\/code.visualstudio.com\/\">Visual Studio Code<\/a> and its wonderful <a href=\"https:\/\/github.com\/Microsoft\/vscode-go\">Go extension<\/a> which supports <a href=\"https:\/\/github.com\/derekparker\/delve\">Delve<\/a>.<\/p>\n<p>Now if you read through the pages linked above you will find out how to install and setup all these things. It\u2019s pretty straight forward. The Docker part, however, is not. As such, I will show you a basic Go application which mimics what we had to deal with and how to set up debugging for it.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"The_application\"><\/span>The application<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>The following is the <strong>main.go<\/strong> of our app. It will connect to a Redis server, <em>set<\/em> and <em>get<\/em> a value.<\/p>\n<pre class=\"lang:go decode:true\" title=\"main.go\">package main\r\n\r\n\r\nimport (\r\n    \"fmt\"\r\n\r\n\r\n    \"github.com\/garyburd\/redigo\/redis\"\r\n)\r\n\r\n\r\nfunc main() {\r\n    client, err := redis.Dial(\"tcp\", \"redis:6379\")\r\n    if err != nil {\r\n        panic(err)\r\n    }\r\n    defer client.Close()\r\n\r\n\r\n    result, err := client.Do(\"SET\", \"key1\", \"value1\")\r\n    if err != nil {\r\n        panic(err)\r\n    }\r\n    fmt.Printf(\"%v\\n\", result)\r\n\r\n\r\n    result, err = client.Do(\"GET\", \"key1\")\r\n    if err != nil {\r\n        panic(err)\r\n    }\r\n    fmt.Printf(\"%v\\n\", result)\r\n}\r\n<\/pre>\n<p>&nbsp;<\/p>\n<p>As you can see, it relies on the <a href=\"https:\/\/github.com\/garyburd\/redigo\">Redigo<\/a> package, so make sure you get it and place it in your <strong>vendor<\/strong> folder.<\/p>\n<p>To make sure you have everything setup the right way, go ahead and build it locally by running :<\/p>\n<pre class=\"lang:default decode:true\">go build -o main main.go<\/pre>\n<p>If you run the application built this way, it will fail of course, because you need to connect to Redis. I\u2019ve set the <em>hostname<\/em> for the server to <strong>redis<\/strong> which will point to an IP on the <strong>docker-machine<\/strong> when we <strong>docker-compose up<\/strong>.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"The_Dockerfile\"><\/span>The Dockerfile<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Now we have to build the image for this application.<\/p>\n<pre class=\"lang:default decode:true\" title=\"Dockerfile\">FROM golang\r\n\r\n\r\nENV GOPATH \/opt\/go:$GOPATH\r\nENV PATH \/opt\/go\/bin:$PATH\r\nADD . \/opt\/go\/src\/local\/myorg\/myapp\r\nWORKDIR \/opt\/go\/src\/local\/myorg\/myapp\r\n\r\n\r\nRUN go get github.com\/derekparker\/delve\/cmd\/dlv\r\nRUN go build -o main main.go\r\nCMD [\".\/main\"]\r\n<\/pre>\n<p>&nbsp;<\/p>\n<p>When this image will be built, it will basically copy the application code, set up the environment and build the Go application. The application\u2019s <strong>entrypoint<\/strong> will be the <strong>main<\/strong> executable that will be built. We also install the Delve command line tool but we won\u2019t use it if we run a container from this image directly (i.e. <em>docker run<\/em>).<\/p>\n<p>Note the <strong>GOPATH<\/strong> variable and the path to which we copy our code. This path is very important for Delve and our debug configuration.<\/p>\n<h3>The Docker Compose file<\/h3>\n<p>Now that we have the <strong>Dockerfile<\/strong> to build the image, we have to define the<strong> docker-compose.yml<\/strong> file. Here, however we will overwrite the <strong>entrypoint<\/strong> for the container to launch <strong>Delve<\/strong>. Also the code that we copied will be replaced with a <strong>volume<\/strong> that will point to the code on the host machine, and we will also remove some <strong>security constraints<\/strong> that prevent Delve from forking the process.<\/p>\n<p>Essentially, for the context I mentioned above we try <strong>not<\/strong> to touch the base image for the application since it might get accidentally pushed to the Docker Hub with debugging parameters. So in order to avoid that we have our Docker Compose process override the image with what we need to go about debugging.<\/p>\n<p>Here\u2019s the <strong>docker-compose.yml<\/strong> file :<\/p>\n<pre class=\"lang:default decode:true \" title=\"docker-compose.yml\">version: '2'\r\nservices:\r\n  redis:\r\n    image: redis\r\n    ports:\r\n      - \"6379:6379\"\r\n    expose:\r\n      - \"6379\"\r\n  myapp:\r\n    build: .\r\n    security_opt:\r\n      - seccomp:unconfined\r\n    entrypoint: dlv debug local\/myorg\/myapp -l 0.0.0.0:2345 --headless=true --log=true -- server\r\n    volumes:\r\n      - .:\/opt\/go\/src\/local\/myorg\/myapp\r\n    ports:\r\n      - \"2345:2345\"\r\n    expose:\r\n      - \"2345\"\r\n<\/pre>\n<p>&nbsp;<\/p>\n<p>It&#8217;s here that we introduce the Redis server dependency we have.\u00a0 Note that for the <strong>myapp<\/strong> container we\u2019ve exposed the ports that the Delve command line tool listens to.<\/p>\n<p>So to see that everything is working, you can now run :<\/p>\n<pre class=\"lang:default decode:true \">docker-compose up --build<\/pre>\n<p>This will build the image and start up the <strong>redis<\/strong> and <strong>myapp<\/strong> containers.<\/p>\n<p>You should see the following output coming from the <strong>myapp<\/strong> container:<\/p>\n<pre class=\"lang:default decode:true\" title=\"myapp startup\">myapp_1  | 2016\/12\/15 08:50:39 server.go:71: Using API v1\r\nmyapp_1  | 2016\/12\/15 08:50:39 debugger.go:65: launching process with args: [\/opt\/go\/src\/local\/myorg\/myapp\/debug server]\r\nmyapp_1  | API server listening at: [::]:2345<\/pre>\n<p>Which means that the Delve command line tool compiled our Go code into a <strong>debug<\/strong> executable, started it, and it\u2019s listening for remote connections to the debugger on port 2345.<\/p>\n<p>Now we just have to set up our <strong>launch.json<\/strong> config in the <strong>.vscode<\/strong> folder of our project.<\/p>\n<h3>The launch configuration<\/h3>\n<p>Here\u2019s how our <strong>launch.json<\/strong> should look like:<\/p>\n<pre class=\"lang:default decode:true\" title=\"launch.json\">{\r\n    \"version\": \"0.2.0\",\r\n    \"configurations\": [\r\n       {\r\n           \"name\": \"Remote Docker\",\r\n           \"type\": \"go\",\r\n           \"request\": \"launch\",\r\n           \"mode\": \"remote\",\r\n           \"remotePath\": \"\/opt\/go\/src\/local\/myorg\/myapp\",\r\n           \"port\": 2345,\r\n           \"host\": \"192.168.99.100\",\r\n           \"program\": \"${workspaceRoot}\",\r\n           \"env\": {},\r\n           \"args\": []\r\n       }\r\n    ]\r\n}<\/pre>\n<p>You might have to change the host IP\u00a0 to what your <strong>docker-machine ip<\/strong> output is.<\/p>\n<p>Now all we have to do is set up a few breakpoints and start the debugger using the <strong>Remote Docker<\/strong> configuration.<\/p>\n<p>Our docker compose terminal should print something like this from the <strong>myapp<\/strong> container :<\/p>\n<pre class=\"lang:default decode:true\" title=\"breakpoints output\">myapp_1  | 2016\/12\/15 08:50:45 debugger.go:242: created breakpoint: &amp;api.Breakpoint{ID:1, Name:\"\", Addr:0x4010af, File:\"\/opt\/go\/src\/local\/myorg\/myapp\/main.go\", Line:11, FunctionName:\"main.main\", Cond:\"\", Tracepoint:false, Goroutine:false, Stacktrace:0, Variables:[]string(nil), LoadArgs:(*api.LoadConfig)(nil), LoadLocals:(*api.LoadConfig)(nil), HitCount:map[string]uint64{}, TotalHitCount:0x0}\r\nmyapp_1  | 2016\/12\/15 08:50:45 debugger.go:242: created breakpoint: &amp;api.Breakpoint{ID:2, Name:\"\", Addr:0x401116, File:\"\/opt\/go\/src\/local\/myorg\/myapp\/main.go\", Line:16, FunctionName:\"main.main\", Cond:\"\", Tracepoint:false, Goroutine:false, Stacktrace:0, Variables:[]string(nil), LoadArgs:(*api.LoadConfig)(nil), LoadLocals:(*api.LoadConfig)(nil), HitCount:map[string]uint64{}, TotalHitCount:0x0}\r\nmyapp_1  | 2016\/12\/15 08:50:45 debugger.go:242: created breakpoint: &amp;api.Breakpoint{ID:3, Name:\"\", Addr:0x4013d1, File:\"\/opt\/go\/src\/local\/myorg\/myapp\/main.go\", Line:22, FunctionName:\"main.main\", Cond:\"\", Tracepoint:false, Goroutine:false, Stacktrace:0, Variables:[]string(nil), LoadArgs:(*api.LoadConfig)(nil), LoadLocals:(*api.LoadConfig)(nil), HitCount:map[string]uint64{}, TotalHitCount:0x0}\r\nmyapp_1  | 2016\/12\/15 08:50:45 debugger.go:397: continuing<\/pre>\n<p>You can <strong>Next<\/strong> and <strong>Continue<\/strong>, look at the <strong>callstack<\/strong>, see the <strong>locals<\/strong>, view contents of specific variables, etc.<\/p>\n<h3>Final thoughts<\/h3>\n<p>I hope this proves to be as useful to you as it did for us. The tools mentioned in this post really save us a heap of trouble.<\/p>\n<p>We really have to thank the open source community that brought us these tools. They are the real heroes.<\/p>\n<p>Happy debugging!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Context We\u2019ve recently had some problems with a Go application that was running inside a Docker container in a very [&hellip;]<\/p>\n","protected":false},"author":28,"featured_media":2105,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[73,86],"tags":[122,147,258],"yst_prominent_words":[384,394,413,483,1007,1011,1121,1321],"post_mailing_queue_ids":[],"_links":{"self":[{"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/posts\/2103"}],"collection":[{"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/users\/28"}],"replies":[{"embeddable":true,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/comments?post=2103"}],"version-history":[{"count":3,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/posts\/2103\/revisions"}],"predecessor-version":[{"id":133333,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/posts\/2103\/revisions\/133333"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/media\/2105"}],"wp:attachment":[{"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/media?parent=2103"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/categories?post=2103"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/tags?post=2103"},{"taxonomy":"yst_prominent_words","embeddable":true,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/yst_prominent_words?post=2103"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}