Creating a Dockerfile for your Go Backend
Tags: go, beginners, programming, tutorial
Published: 2023-09-12 18:44:37

This is a step-by-step comprehensive guide on how to create a docker image for your Go backend for absolute beginners. In this article, you will learn to build Docker image from scratch, and deploy and run your Go application. Here is the full source code of the GitHub [repo](https://github.com/Sadeedpv/docker-example)

### PREQUISITES

- Install Go version `1.21.0` or later. Visit the [Download Page for Go](https://go.dev/dl/) to install the toolchain.
- Basic understanding of Golang fundamentals.
- Docker running locally, Install Docker from [here](https://docs.docker.com/desktop/).
- An IDE/Text-editor and a command-line terminal application.

## Creating a Go Backend

In this tutorial, I will be using the `Echo` framework to build the backend. You can learn more about `Echo` [here](https://echo.labstack.com/).
> Feel free to create your own backend with/without frameworks.

Here is an example `main.go` file:

```go
package main

import (
	"net/http"
	"os"

	"github.com/labstack/echo/v4"
	"github.com/labstack/echo/v4/middleware"
)

func main() {
	e := echo.New()

	// Middleware
	e.Use(middleware.Logger())
	e.Use(middleware.Recover())

	e.GET("/", func(c echo.Context) error {
		return c.String(http.StatusOK, "Hello, World!")
	})

	e.GET("/health", func(c echo.Context) error {
		return c.String(http.StatusOK, "Health is OK!!")
	})

	httpPort := os.Getenv("PORT")
	if httpPort == "" {
		httpPort = "8080"
	}

	e.Logger.Fatal(e.Start(":" + httpPort))
}

```
As you can see, we have two endpoints,

- `/` which returns `Hello, World!`
- `/health` which return `Health is OK!!`

> Don't forget to add `PORT` in you `.env` file:

```
PORT=8000
```

You should also initialize new module in current directory using the command:
```
go mod init <your-module-package>
```
To add the dependencies mentioned in the `main.go` file, use the command

```
go mod tidy
```

## Testing your application

Let’s start our application and make sure it’s running properly. 

```
go run main.go
```

Let's try out our application:

```bash
$ curl http://localhost:8080/
$ curl http://localhost:8080/health
```
You should get the following output:
```
Hello, World!
Health is OK!!
```
## Creating the Dockerfile

Here is the full Dockerfile with the explanation

```
# syntax=docker/dockerfile:1

FROM golang:1.21.0

# Set destination for COPY
WORKDIR /app

# Download Go modules
COPY go.mod go.sum ./
RUN go mod download

# Copy the source code. Note the slash at the end, as explained in
# https://docs.docker.com/engine/reference/builder/#copy
COPY *.go ./

# Build
RUN CGO_ENABLED=0 GOOS=linux go build -o /docker-gs-ping

# Optional:
# To bind to a TCP port, runtime parameters must be supplied to the docker command.
# But we can document in the Dockerfile what ports
# the application is going to listen on by default.
# https://docs.docker.com/engine/reference/builder/#expose
EXPOSE 8080

# Run
CMD ["/docker-gs-ping"]
```

## Building the Docker image

After you've created the Dockerfile, now it's time to build a docker image from it with the `build` command. 

The build command optionally takes a `--tag` flag. This flag is used to label the image with a string value, which is easy for humans to read and recognize. If you do not pass a `--tag`, Docker will use `latest` as the default value.

Let's build our docker image!!
Run the following command from the root directory
```
docker build --tag echo .
```

And Voila!! We built a docker image for our backend.
To list the images, use `docker image ls` command or `docker images` command.

## Run the docker image

In order to run the docker image, we can use the `docker run` command:
```
docker run <image-name>
```

In my case, the command will look something like this:
```
docker run echo
```

And Voila!! We got our server up and running; but wait, if you try to access one of your endpoints now, it will give you the following error:

> Failed to connect to localhost port 8080: Connection refused

So, we have to expose port 8080 to port 8080 on the host. To do that, all you have to do is:
```
docker run -p 8080:8080 echo
```

Now let’s rerun the curl command

```
$ curl http://localhost:8080/
Hello, World!
```

Success! We were able to connect to the application running inside of our container on port 8080. Press **Ctrl + C** to stop the container.

To list all the images, you can try the command `docker image ls`, and you will see that our image has enormous size.

As @sibprogrammer mentioned in the [comments](https://dev.to/sibprogrammer/comment/29ae6), one common way to reduce the size is to use multi-stage build.

Thanks to @sibprogrammer for the details:

> The common way of doing that is to use multi-stage build. Go is a compiled language. You don't need Go toolchain to run your services. So the general concept is the following: you build the binary in one stage and use another minimal image to launch it. It bring the huge difference. The latest "golang" image is about 800 Mb (it's based on Debian). The final image of your service can be just 5-10 Mb.
Here is an example:
```
FROM golang:1.15 as builder
ARG CGO_ENABLED=0
WORKDIR /app

COPY go.mod go.sum ./
RUN go mod download
COPY . .

RUN go build

FROM scratch
COPY --from=builder /app/server /server
ENTRYPOINT ["/server"]
```