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
PREQUISITES
- Install Go version
1.21.0
or later. Visit the Download Page for Go to install the toolchain. - Basic understanding of Golang fundamentals.
- Docker running locally, Install Docker from here.
- 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.
Feel free to create your own backend with/without frameworks.
Here is an example main.go
file:
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 returnsHello, World!
-
/health
which returnHealth 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:
$ 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, 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"]
Top comments (3)
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.
Here is an example:
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.
I was about to explain that, But I accidentally published the post instead of saving to draft; I just saw it today
It's also pretty easy to create a multi-stage dockerfile for building an oci image that can be put into a registry.