Go logo

GoLand

A cross-platform Go IDE with extended support for JavaScript, TypeScript, and databases

Features GoLand Tutorials

How to use Docker to compile and run Go code from GoLand

Up until now, when you wanted to test or run your shiny new code, you had only the local machine to do so.

Many of our users have told us that they’d prefer to use Docker to run their code since that’s where they will ultimately test, build, and deploy the code with their CI/CD pipeline.

Today I’m happy to introduce to you our latest feature that allows you to seamlessly use Docker containers for your workflows from the comfort of your IDE.

We call this feature “Run targets”, and it doesn’t extend to Docker support only. We currently support Docker, WSL 2, and SSH remotes.

You may be asking: What are all these terms and technologies? Why is this interesting to me? If you’d like to learn more about these, here’s an overview of this feature, explaining it in more detail.

In this article, I’ll focus on the Docker side of this feature.

If you’d prefer a video version of this article, then please head to YouTube:

While Docker is not the only container technology, it’s by far the most popular one, which is why we’ll focus on this for now. If you’d like us to support other technologies, please let us know via our usual channels (see the end of this post).

The Code

Docker helps isolate the environment we run our code in, whether it’s tests or client-facing code.

Let’s take a look at the following example:

package main

import (
    "fmt"
    "log"
    "net/http"
    "os"
    "runtime"
)

func homeHandler(w http.ResponseWriter, r *http.Request) {
    myOS, myArch := runtime.GOOS, runtime.GOARCH
    inContainer := "inside"
    if _, err := os.Lstat("/.dockerenv"); err != nil && os.IsNotExist(err) {
        inContainer = "outside"
    }

    w.Header().Set("Content-Type", "text/plain")
    w.WriteHeader(http.StatusOK)

    _, _ = fmt.Fprintf(w, "Hello, %s!\n", r.UserAgent())
    _, _ = fmt.Fprintf(w, "I'm running on %s/%s.\n", myOS, myArch)
    _, _ = fmt.Fprintf(w, "I'm running %s of a container.\n", inContainer)
}

func main() {
    http.HandleFunc("/", homeHandler)

    err := http.ListenAndServe(":38000", nil)
    if err != nil {
        log.Fatalln(err)
    }
}

We are running a web server that listens on port 38000 and replies with some information about the running environment, such as the operating system and system architecture. It also performs a trivial check to see if it’s running inside a Docker container or not.

When using a run configuration with default values, this sample application would launch on the local machine. If you are on Windows like I am, then the output would read as:

Hello, Apache-HttpClient/4.5.13 (Java/11.0.10)!
I'm running on windows/amd64.
I'm running outside a Docker container.

Run a regular Run Configuration

Enter the Docker Container

While running on the local machine is possible for such a simple application, what about a more complex application deployed in a Docker container? How could you replicate that environment?

Here is where the IDE steps in and can automate everything using a Docker container via the Run Targets feature.

Using a container from a Registry

To make use of this feature, we first need to create a new target.

Head over to Run | Manage Targets... | + | Docker and select the Docker type from the list.

You can select whether you want to use an existing container image or pull one from a repository.

Docker Pull Run Target

If you need to expose any ports or use any other flags for the container runtime, you can add them under Run options. By default, the IDE adds –rm to remove the container after it stops, making cleanup easier.

Clicking Next will pull the container and allow the IDE to do an introspection of the build/run environment to detect where the go binary is, the GOPATH, and the Go version.

Finally, you can configure the container directory in which the IDE will place the sources and adjust any previously auto-detected features.

Create a Docker Run Target

There are two ways to use this feature once the target is available: either edit the existing run configuration or create a new one dedicated to running against the new target.

Let’s change our existing run configuration and tell it to run our code using the Docker container.

To do this, go to Run | Edit Configurations... and select the desired configuration.

After changing the Run on field to match our newly created Target, we can then click OK and launch our configuration.

The IDE will compile our binary on the local machine and run it via the Docker container.

Pro tip: If you want to build the binary inside the Docker container, you can enable Build on remote target.

Use a Run Target in a Run Configuration

That’s it! Rerunning this configuration now works against our Docker container.

Pro tip: You can disable the Run after build option to allow the configuration to run only the compilation step.

Using a Dockerfile

We’ve seen this feature using a container that’s pulled from a registry. What if we don’t have a registry available?

This is where the Build option from the Docker target type comes in.

You’ll need to provide a Dockerfile that contains a Go installation.
Below, you can find an example of such a Dockerfile.

FROM debian:buster

RUN apt update && \
    apt install -y curl

# Work inside the /tmp directory
WORKDIR /tmp

RUN curl https://storage.googleapis.com/golang/go1.16.2.linux-amd64.tar.gz -o go.tar.gz && \
    tar -zxf go.tar.gz && \
    rm -rf go.tar.gz && \
    mv go /go

ENV GOPATH /go

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

# If you enable this, then gcc is needed to debug your app
ENV CGO_ENABLED 0

# TODO: Add other dependencies and stuff here

Note: I can’t stress enough that this Dockerfile should not be used to run anything in a production environment as it omits a lot of the best practices for how to configure a Docker container correctly. Go is not a scripting language, so it shouldn’t be included in any deployed image.

Note: Multi-stage Dockerfiles are supported as long as the final image contains the Go SDK and the GOPATH is correctly set. This is a requirement for the IDE to perform code compilation inside the container.

Now, let’s create a new Docker Run Target, and this time select the Build option.

We can specify additional arguments under the Optional section, such as the Image tag, Build options, Build arguments, and Run options. For now, I’ll include the port in the Run options to create a container that listens to the port that our application runs on.

After a bit of introspection, we will have a similar screen to review/customize any potential settings that we might want to, and then we are done.

Let’s clone the run configuration and switch the Run on field to our newly created target.

Create a Docker Build Run Target type

Debugging using a Docker Run Target

Everything we’ve seen so far works with the debugger too.

Let’s see this in action.

Debug a Go app in a Docker container using a Run Target

Running tests and benchmarks

Running applications means that all our tests pass, and our benchmarks run well too.

Go Test configuration types support Run targets too. Tests and benchmarks can run natively in the same environment as the application would by adjusting their target.

For example, let’s create a new Go Test run configuration and set the Run on field to the same Docker container that we have for our application.

As soon as we use the Run feature, it will start working in the same way as the Go Build configuration type, and our code will test inside the container.

Running tests with coverage or profiling benchmarks is similar to this.

Run a Test with Coverage in Docker using a Run Target

And that’s it for today. We learned how to use Docker transparently to run our applications, tests, and benchmarks.

Please keep in mind that this feature is still in its early days. We’d like to hear from you about any improvements we can make to transform this feature into another tool in your toolbelt.

As always, you can reach out to us by commenting below this post, raise an issue on our issue tracker, ping us on Gophers Slack in the #goland channel, or via @GoLandIDE on Twitter.

image description