This article describes how to deploy our Go application using Docker and Docker Compose.

Why Docker?

The main goal with Docker is containerization. That is, to provide a consistent environment for your application, independent of the host on which it runs (involving the isolation technique namespace).

Imagine if you also had a scenario where you developed your application locally, it probably had many dependencies or packages, and there were even strict requirements on the specific versions of dependencies, and when the development process was complete, you wanted to deploy the application to a Web server. At this point you must make sure that all dependencies are installed correctly and of the same version, otherwise the application may crash and not work. If you want to deploy the application on another Web server, you must repeat the process from scratch. This scenario is where Docker comes in.

For the host running our application, whether it’s a laptop or a Web server, the only thing we need to do is run a Docker container platform. From now on, you don’t have to worry about whether you’re using MacOS, Ubuntu, Arch or whatever. You only need to define an application once, and it can run anywhere, anytime.

Docker deployment example

Ready to code

I’ll show you how to deploy using Docker using a simple code written using the NET/HTTP library, and then move on to a slightly more complex project deployment case.

package main import ( "fmt" "net/http" ) func main() { http.HandleFunc("/", hello) server := &http.Server{ Addr: ":8888", } fmt.Println("server startup..." ) if err := server.ListenAndServe(); err ! = nil { fmt.Printf("server startup failed, err:%v\n", err) } } func hello(w http.ResponseWriter, _ * HTTP.request) {w.rite ([]byte(" Hello router has no route!" ))}Copy the code

The code above provides a service through port 8888 and returns a string response: Hello router has no route! .

Create a Docker image

An image contains everything you need to run your application — code or binaries, runtimes, dependencies, and any other file system objects you need.

Or simply put, an image is everything that defines the application and how it runs.

Write Dockerfile

To create a Docker image, you must specify the steps in the configuration file. This file is usually called Dockerfile by default. (Although the filename can be named whatever you want, it’s best to use the default Dockerfile.)

Now let’s write a Dockerfile, which looks like this:

Note: Some steps are not unique, and you can change the path of the file, the name of the final executable file, and so on to suit your needs

FROM golang:alpine # Set ENV GO111MODULE=on CGO_ENABLED=0 GOOS= Linux GOARCH=amd64 # Move to working directory: /build WORKDIR /build # COPY the code to the container COPY.. # compile our code to the binary executable app RUN go build -o app. # Move to the /dist directory used to store the generated binaries RUN cp /build/app. RUN cp /build/app.Copy the code

Dockerfile parsing

From

We are using the base image Golang: Alpine to create our image. This is a basic image that we can access stored in the Docker repository, just like the image we’re going to create. This image runs the Alpine Linux distribution, which is small in size and has Go built in, perfect for our use case. There are plenty of publicly available Docker images available at hub.docker.com/_/golang.

Env

Used to set the environment variables that we need to use during compilation.

WORKDIR, COPY, RUN

What these commands do is written in the comments, so it’s easy to understand.

The EXPORT, CMD

Finally, we declare the service port, because this is the port on which our application listens and provides services externally. We also defined CMD [“/dist/app”] to be executed by default when we run the image.

Build the mirror

In the project directory, run the following command to create the image and name it goweb_app:

docker build . -t goweb_app
Copy the code

When the build process is complete, the following message is displayed:

. Successfully built 90d9283286b7 Successfully tagged goweb_app:latestCopy the code

Now we are ready to mirror it, but so far it has done nothing. What we need to do next is run our image so that it can handle our request. A running image is called a container.

Execute the following command to run the image:

docker run -p 8888:8888 goweb_app
Copy the code

The flag bit -p defines the port binding. Since the application in the container is running on port 8888, we bind it to host port 8888 as well. To bind to another port, use -p $HOST_PORT:8888. For example, -p 55:8888.

Now we can test whether our web application is working properly. Open the browser and type http://127.0.0.1:8888 to see the response we defined in advance:

Hello router has no path!Copy the code

Build the example in stages

Our Go program will compile to an executable binary, but we don’t need the Go compiler in the final image, which means we just need a container to run the final binary.

One of Docker’s best practices is to reduce the image size by keeping only binaries, and for this, we’ll use a technique called multi-phase build, which means we’ll build the image in multiple steps.

FROM Golang :alpine AS Builder # Set the necessary environment variable ENV GO111MODULE=on \ CGO_ENABLED=0 \ GOOS= Linux \ GOARCH= AMd64 # Move to working directory: /build WORKDIR /build # COPY code to container COPY.. # Compile our code to binary executable app RUN go build -o app. ################### # Create a small image ################### FROM scratch # COPY /dist/app to the current directory FROM the builder image COPY -- FROM =builder /build/app / # ENTRYPOINT ["/app"]Copy the code

Using this technique, we stripped away the process of compiling the resulting binary executable using Golang: Alpine as a compiled image and generated a simple, very small new image based on Scratch. We copied the binaries from the first image named Builder to the newly created Scratch image. For more information about Scratch mirroring, see hub.docker.com/_/scratch.

Deployment examples with additional files

If you have static files or configuration files in your project, you need to copy them to the final image file.

For example, the helloWord project uses static files and configuration files. The directory structure is as follows:

├─ How to do it? ├─ How to do it? ├─ how to do it Mysql. Go ├ ─ ─ example. PNG ├ ─ ─. Mod ├ ─ ─. Sum ├ ─ ─ main. Go ├ ─ ─ models │ └ ─ ─ todo. Go ├ ─ ─ routers │ └ ─ ─ routers. Go ├ ─ ─ Setting │ └ ─ ─ setting. Go ├ ─ ─ the static │ ├ ─ ─ CSS │ │ ├ ─ ─ app. 8 eeeaf31. CSS │ │ └ ─ ─ the chunk - vendors. 57 db8905. CSS │ ├ ─ ─ fonts │ │ ├ ─ ─ element - the ICONS. 535877 f5. Woff │ │ └ ─ ─ element - the ICONS. 732389 DE. The vera.ttf │ └ ─ ─ js │ ├ ─ ─ app. 007 f9690. Js │ └ ─ ─ Chunk-implant.ddcb6f91. js ├── favicon. Ico ├─ index.htmlCopy the code

You need to copy the templates, static, and conf folders into the final image file. The updated Dockerfile looks like this

FROM Golang :alpine AS Builder # Set the necessary environment variable ENV GO111MODULE=on \ CGO_ENABLED=0 \ GOOS= Linux \ GOARCH= AMd64 # Move to working directory: COPY go.mod. COPY go.sum. RUN go mod download # COPY the code to the container COPY .. # our code compiled into the binary executable file mercifully RUN go build - o mercifully. # # # # # # # # # # # # # # # # # # # # then create a small mirror # # # # # # # # # # # # # # # # # # # the FROM Scratch COPY./templates /templates COPY./static /static COPY./conf /conf # COPY /dist/app from builder to current directory COPY --from=build /build/bubble / #Copy the code

In simple terms, it is more than a few steps of COPY steps, we look at the Dockerfile comments can be.

COPY static files in the upper layer, COPY binary executable files in the lower layer, try to use more cache.

Associating other containers

Since MySQL is used in our project, we can choose to start a MySQL container with the following command, which is called mysql8019; The password of user root is root1234. Mount /var/lib/mysql to /Users/q1mi/docker/mysql; Internal service port 3306 is mapped to external port 13306.

docker run --name mysql8019 -p 13306:3306 -e MYSQL_ROOT_PASSWORD=root1234 -v /Users/q1mi/docker/mysql:/var/lib/mysql -d Mysql: 8.0.19Copy the code

Mysql8019 = mysql8019 = mysql8019 = mysql8019 = mysql8019

[mysql]
user = root
password = root1234
host = mysql8019
port = 3306
db = bubble
Copy the code

Remember to rebuild the Bubble_app image:

docker build . -t bubble_app
Copy the code

Mysql8019: mysql8019: mysql8019: mysql8019: mysql8019: mysql8019: mysql8019: mysql8019: mysql8019: mysql8019: mysql8019

docker run --link=mysql8019:mysql8019 -p 8888:8888 bubble_app
Copy the code

Docker Compose mode

In addition to using –link to associate two containers as above, Docker Compose can also be used to define and run multiple containers.

Compose is a tool for defining and running multi-container Docker applications. With Compose, you can use YML files to configure all the services your application needs. Then, with a single command, all services can be created and started from the YML file configuration.

Using Compose is basically a three-step process:

  1. use**Dockerfile**Define your application environment so that it can be replicated anywhere.
  2. Define the services that make up the application,**docker-compose.yml** So that they can run together in an isolated environment.
  3. perform **docker-compose up**Command to start and run the entire application.

Our project requires two containers to run mysql and Bubble_app respectively. The docker-comemage. yml file we wrote is as follows:

# yaml configuration version: "3.7" services: mysql8019: image: "mysql:8.0.19" ports: - "33061:3306" Command: "--default-authentication-plugin=mysql_native_password --init-file /data/application/init.sql" environment: MYSQL_ROOT_PASSWORD: "root1234" MYSQL_DATABASE: "bubble" MYSQL_PASSWORD: "root1234" volumes: - ./init.sql:/data/application/init.sql bubble_app: build: . command: sh -c "./wait-for.sh mysql8019:3306 -- ./bubble ./conf/config.ini" depends_on: - mysql8019 ports: - "8888:8888"Copy the code

The Compose file defines two services: Bubble_app and mysql8019. Among them:

bubble_app

Construct the image using the Dockerfile file in the current directory and depends_on mysQL8019 service, declare service port 8888 and bind to port 8888.

mysql8019

Mysql8019 service uses Docker Hub’s public mysql:8.0.19 mirror, internal port 3306, external port 33061.

One thing to note here is that our Bubble_app container will have to wait for mysQL8019 to start properly before trying to start because our Web application will initialize the MySQL connection when it starts. There are two changes to be made here. The first is to comment out the last sentence in our Dockerfile:

# Dockerfile ... # ENTRYPOINT ["/bubble", "conf/config.ini"]Copy the code

The second place is to add the following command under bubble_app to check mysQL8019:3306 using the pre-written wait-for.sh script before executing the subsequent command to start the Web application:

command: sh -c "./wait-for.sh mysql8019:3306 -- ./bubble ./conf/config.ini"
Copy the code

Debian: stretch-Slim: netcat: wait-for.sh: netcat: wait-for.sh: netcat: wait-for.sh: netcat: wait-for.sh: netcat: wait-for.sh Finally, don’t forget to COPY the wait-for-. sh script file to the final image and give it executable permission. The updated Dockerfile contains the following contents:

FROM Golang :alpine AS Builder # Set the necessary environment variable ENV GO111MODULE=on \ CGO_ENABLED=0 \ GOOS= Linux \ GOARCH= AMd64 # Move to working directory: COPY go.mod. COPY go.sum. RUN go mod download # COPY the code to the container COPY .. # our code compiled into the binary executable file mercifully RUN go build - o mercifully. # # # # # # # # # # # # # # # # # # # # then create a small mirror # # # # # # # # # # # # # # # # # # # the FROM debian:stretch-slim COPY ./wait-for.sh / COPY ./templates /templates COPY ./static /static COPY ./conf /conf # COPY --from=builder /build/bubble/RUN set -eux; \ apt-get update; \ apt-get install -y \ --no-install-recommends \ netcat; \ chmod 755 wait-for.sh # ENTRYPOINT ["/bubble", "conf/config.ini"]Copy the code

With all the conditions in place, you can run with the following command:

docker-compose up
Copy the code

For a full code example, check out my Github repository: github.com/Q1mi/deploy… .

conclusion

Using Docker containers can greatly simplify our operations in configuring dependent environments, but it also puts higher requirements on our technical reserves. Whether you are familiar with Docker or not, the wheel of technological development is rolling forward. Come on, boy!

The above is about the Docker deployment Go project introduction, if you want to know more, please pay attention to “Go keyboard man” public number, exchange and learn together, ^_^!

Recommended articles:

Several ways to deploy a Go project