Run Go service in Docker

Of course, you can create Go executable for every system and you can run it where-ever you want. That’s true.

By using Docker, you can create a neat deploy system for yourself. And you do not care, what system actually runs on the final machine. You only want the machine to run Docker. You can easily stop and start multiple programs (multiple services if you like this terminology). You can easily change the port for Go web server without changing the Go code itself. You can run multiple different databases at the same time.

You can do a lot using Docker and I advise you to invest time to learn it.

For those of you not familiar with Docker, I recommend Bret Fisher’s Docker Mastery. This helped me to move from “what the hell is Docker” to “great, what else can I use Docker for”.

And for those of you already familiar with Docker, but want to know more, this talk can give you much more understanding about containers at all.

Open Goland and create new file called Dockerfile in your project directory. Insert code below. Those command will create empty image using FROM scratch and then copy content from css, html, js and linux directory.

But wait, there is no linux directory yet. That reason is, we did not build our software. We did not make any executable.

FROM scratch
COPY /css /css
COPY /html html
COPY /js js
COPY /linux /
CMD [“/medium_service”]

Create Go executable

There are numerous ways, how to build our software… how to create this executable. One way is to use Goland’s built-in functionality.

Create a new configuration by copying the first one. And in this new configuration change four things:

  1. Name… this name will be used for the name of the executable and has to match with CMD [“/medium_service”] , so we use medium_service, Goland will automatically add _linux to filename
  2. Select output directory (linux as we will use linux containers)
  3. Remove tick on Run after build
  4. Set environment to GOOS=linux

Side-note: by defining GOOS you can build executable for any system and any architecture that is available.

Shrinking the executable by 73%

When you run this configuration, you immediately see that a new executable appears in this linux directory. This executable is about 9.6MB and we will do two tricks to make it much more smaller. First add two flags-ldflags=”-s -w” to Go tool arguments, as on screenshot below.

Run this configuration again and you can see, that the executable is about 6.9MB, reduction about 28%. Second trick is to use any executable packers, that are available. I use UPX and I use it also in production with no impact at all. With commandupx medium_service_linux the size of the executable will shrink to about 2.6MB, reduction of 73%.

Create Go executable with script

Another way, I prefer to use, is to use a script, be it a bash or powershell script. As I am using apple device, so below you can see my bash script. This script is called create.sh and is placed in the project directory. It does numerous things.

At first it will get name of the current working directory by using name=${PWD##*/}, then it will update all modules (not necessary, but I like to have latest versions of all modules), then it will build our executable using GOOS=linux go build -ldflags=”-s -w” -o linux/”$name”, then move into that linux folder, pack the executable with UPX and move out.

After all this is done, it will finally do something with docker. By using docker rmi -f petrjahoda/”$name”:latest it will remove any previous image already created before, by using docker build -t petrjahoda/”$name”:latest . it will create image. This dot means it will search for Dockerfile in a directory, where this command is executed. And finally it will push this image to docker hub using docker push petrjahoda/”$name”:latest. To make this final command working you need to have your own repository working and you have to be logged in to Docker on your machine.

Side-note: Don’t forget to chmod +x create.sh and remove that previous executable with _linux at the end.

#!/usr/bin/env bash
name=${PWD##*/}
go get -u all
GOOS=linux go build -ldflags=”-s -w” -o linux/”$name”
cd linux
upx “$name”
cd ..
docker rmi -f petrjahoda/”$name”:latest
docker build -t petrjahoda/”$name”:latest .
docker push petrjahoda/”$name”:latest

Running the script

When you run this script you see numerous things going on (see below). After everything is done, you can find your image in your docker hub repository. In my case it is here.

go: golang.org/x/sys upgrade => v0.0.0–20201223074533–0d417f636930
Ultimate Packer for eXecutables
Copyright © 1996–2020
UPX 3.96 Markus Oberhumer, Laszlo Molnar & John Reiser Jan 23rd 2020File size Ratio Format Name
— — — — — — — — — — — — — — — — — — — — — — — — –
6901760 -> 2630120 38.11% linux/amd64 medium_servicePacked 1 file.
Error: No such image: petrjahoda/medium_service:latest
[+] Building 0.4s (8/8) FINISHED
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 135B 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [internal] load build context 0.2s
=> => transferring context: 5.26MB 0.1s
=> [1/4] COPY /css /css 0.0s
=> [2/4] COPY /html html 0.0s
=> [3/4] COPY /js js 0.0s
=> [4/4] COPY /linux / 0.0s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:1df9b9adbc1167cb1fcb1fc66d74cf33964c35530dc04fa4f5434e917bc50a53 0.0s
=> => naming to docker.io/petrjahoda/medium_service:latest 0.0s
The push refers to repository [docker.io/petrjahoda/medium_service]
bc5cf960a3a7: Pushed
6eec7c2c219c: Pushed
2038e7a8e302: Pushed
95541ba72fa1: Pushed
latest: digest: sha256:4584ba804f99d001a57d84bc85b6624eb8f9ab2b8edc9e226e43e670a0ce51b3 size: 1149

Test running Go service as Docker container

Looks like we did everything right. Time to test it.

Open up your terminal, or console, or use Goland’s built-in terminal and run docker run — name medium_service -p 90:81 petrjahoda/medium_service:latest. This will run a new container with name medium_service, mapping internal port 81 to external port 90 and using already created image with latest tag. When you run the command (in my example I had to use that port 82, because I used it in my application) you see logs from your application.

Open up your browser and navigate to http://localhost:90. Your web page has to be running. If you try to click on Ask for data button, you immediately see, that it was successfully logged.

Make Docker container run in the background

To make this docker container running all the time, end it for now using CTRL+C and remove it using docker rm medium_service. Now run it again, this time with added -d parameter: docker run — name medium_service -p 90:81 -d petrjahoda/medium_service:latest. This will run the container and detach from it. So now your console is free to use or close and you have your web server working in the background.

You can check your container working by using docker ps -a that will show all your not removed containers, or by using docker stats (see image below) that will monitor your running containers in realtime. In my case I have more running containers, medium_service is on the top.

CONGRATULATIONS. You have your own web service running in a Docker container. Every part of the software is running as it should and as a bonus, by using FROM scratch we make our container very secure (but on the other side you cannot from example ping FROM INSIDE the container).

By using docker image ls you can see our image is about 5.26MB whole (Go executable and all other files).

Below is my create.sh file I use in production for every software. There are two different things. At first you see, it runs ./update and at the end there are three more docker commands.

The reason for this is, that I like to have a correct version of my software and do not have to think about it. So in every software in main.go I have a constant const version = “2020.4.3.14” and when the service is starting, i like to use something like logInfo(“MAIN”, serviceName+” [“+version+”] starting…”).

This ./update does two things. Update this constant and update those last three lines of code in create.sh, containing version.

The result is, that the latest docker image is always really the latest and there are more images as my software is built with proper version number. For example, version 2020.4.3.13 is combined like this:

  • 2020 is year
  • 4 is fourth quarter of the year
  • 3 is third month in this quarter
  • 13 is the day of the month

You can see using this approach for example here. Also that update executable is in the project directory.

#!/usr/bin/env bash
./update
name=${PWD##*/}
go get -u all
GOOS=linux go build -ldflags=”-s -w” -o linux/”$name”
cd linux
upx “$name”
cd ..

docker rmi -f petrjahoda/”$name”:latest
docker build -t petrjahoda/”$name”:latest .
docker push petrjahoda/”$name”:latest

docker rmi -f petrjahoda/”$name”:2020.4.2
docker build -t petrjahoda/”$name”:2020.4.2 .
docker push petrjahoda/”$name”:2020.4.2

This article helped you to know, how to …

  • create Go executable using two different methods
  • create Docker image and push it into Docker Hub
  • run Docker image with different parameters
  • run Docker image in the background (and check logs)
  • shrink Go executable by ~ 73%
  • add some automated versioning as bonus feature

Github repository

Leave a Comment

Your email address will not be published. Required fields are marked *