[Backend #23] Build a minimal Golang Docker image with a multistage Dockerfile

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
Hello everyone, and welcome back to the backend master class. So far we have built quite a number of APIs for our simple bank application using Golang. There are still some more advanced features I want to add, but for now I think the app has reached the state where it can be deployed to production. So I will make several videos to show you how to automatically package and deploy the app to Amazon web services (AWS). In this lecture, we will start with the first step: dockerize the application. Since it is a pretty simple thing to do, I will take this opportunity to also show you how to apply a simple git branching flow in your development process. OK let’s start! Here you can see that we’re on the master branch of our simple bank project. A rule of thumb is: never push changes directly to the master branch. When start working on a new feature, we should create a new separate branch from master, and only merge it back after the new code is properly tested and reviewed. To create a new branch, we run: git checkout -b Followed by the name of the branch we want to create. In this case, I’m gonna name it ft/docker. Here, ft stands for feature. Now as you can see, the current git branch has been changed to ft/docker. In visual studio code, you can also see the current branch at the bottom left corner. Recently I’ve updated Golang to the latest version 1.16.3, So before writing the Dockerfile, let’s update our project to make sure it is compatible with this new version. First, in the go.mod file, We should change the go version from 1.15 to 1.16 Then, in the github workflow ci.yaml file, Scroll down to the setup Go step, We have to update this go version to 1.16 as well. OK, now let’s open the terminal and commit this change. First we run git status to see the list of changes. We’ve changed 2 files, but they’re not staged for commit yet, So let’s run git add . to stage them for commit. Then run git commit -m to create a new commit. Finally we push it to Github by running: git push origin ft/docker OK, our changes are pushed to github, And we can use this link to create a new pull request to merge it to master, So let’s copy and paste it to the browser. We can write the name of the pull request in this box, let’s say Add docker. And click Create pull request. After the PR is created, we can see all the changes in this tab. Now back to the Conversation tab, We can see that the CI unit test is triggered and running. You can click on the link to see its progress. The tests are very important to make sure that our code still work well with Go 1.16 OK, the tests finished successfully. But there’s one thing I notice in this ci.yaml file, The golang-migrate version I’m using here, 4.12.2, might be different from the one running on my mac. Let’s run migrate -version to check it. Indeed, the current golang migrate version on my mac is older: version 4.11.0 So let’s try updating it to the latest version. I’m gonna copy the “brew install golang-migrate” command from the README file To update golang-migrate, instead of brew install, we should run brew upgrade. Note that it is upgrade, and not update. The brew update command will update brew itself. Alright, golang migrate has been updated to the latest version: 4.14.1 Let’s run “make migrateup” to make sure it’s still working well. It’s successful! So let’s go back to the ci.yml file, and replace this download link with this latest version. Then let’s add this new change. Commit it. And push the change to Github, just like what we did before. The CI test is running again, so let’s wait a bit for it to finish. OK, the test passed. Now the app is really ready for the first release! Let’s learn how to write a Dockerfile to package it for shipping! I’m gonna create a new file: Dockerfile with a capital D at the root of our project. The first step is to define the base image to build our application. As our app is developed in Golang, we will need a golang base image to build it. So let’s search for it on Docker Hub. OK, this one is the official docker image for golang. If we scroll down a bit, we can see a list of supported tags, corresponding to different version of golang and its base OS. In order to produce a small output image, we should use the alpine version. In the Dockerfile, we use the FROM instruction to specify the base image. In this case we will use golang:1.16-alpine3.13 Next, we use the WORKDIR instruction to declare the current working directory inside the image. To be simple, I’m gonna use /app here. We will then copy all necessary files to this folder Using the COPY instruction. The first dot means that copy everything from current folder, where we run the docker build command to build the image. In this case, we will build from the root of our project, So everything under the simple bank folder will be copied to the image. The second dot is the current working directory inside the image, where the files and folders are being copied to. As we’ve already stated that the current WORKDIR is /app before, that will be the place to store the copied data. Alright, next step, we will build our app to a single binary executable file. We do that with the RUN instruction. So RUN go build -o, which stands for output. Then the name of the output binary file, let’s say main And finally, pass in the main entrypoint file of our application, which is main.go It’s right here, at the root of the project. It’s also a best practice to use the EXPOSE instruction to inform Docker that the container listens on the specified network port at runtime. In our case, it’s port 8080 as we declared in the app.env file. It’s worth noting that the EXPOSE instruction doesn’t actually publish the port. It only functions as a documentation between the person who builds the image and the person who runs the container, about which ports are intended to be published. Alright, now the last step we need to do is to define the default command to run when the container starts We use the CMD instruction for this purpose. It’s an array of command-line arguments. In our case, we just need to run the executable file that we’ve built in the previous step. So there’s only 1 single argument: /app/main And that’s basically it! The Dockerfile is completed. Now to build the image, we will run the docker build command in the terminal. It has a number of options, For example, the file option is used to specify the name of the Dockerfile if it is different from the default name, which is Dockerfile with a capital D. Our Dockerfile already has this default name, so we don’t have to use this option. But we will need another option, the tag. This option is used to specify the name and tag for the output image. OK, let’s run docker build -t The image tag will be simplebank:latest, separated by a colon And finally the path to the Dockerfile is dot, which is the current folder. It will take a while to complete. OK, the image is built. We can run docker images to list all images. Here’s the simple bank image that we’ve just created. Its name is simplebank, and its tag is latest. So it worked! However, this image size is pretty large: 452 MB. It’s 80 times heavier than the alpine linux image. So is it possible to reduce the size of the output image? The answer is yes! We can use multi-stage build to achieve it. The reason for the heavy output image is that it contains golang and all packages that are required by our project. But the only thing we need to be able to run the app is just the output binary file after running go build command. We don’t need anything else, even the original golang code. So if we can produce an image with just the binary file then its size would be very small. To do that, I’m gonna convert this Dockerfile into multistage. The first stage will be the build stage, where we just build the binary file. All we have to do is to add the AS keyword to the end of the FROM instruction, Followed by the name of the stage, let’s say builder in this case. Then after the binary file is produced, we will build the second stage, which is the final Run stage. Just like in the build stage, We use FROM instruction to specify the base image for this run stage. And since we want the output image size to be small, let’s use alpine linux as the base image. Here I use the alpine version 3.13, same as the build stage to make sure everything is compatible. And similar as before, we set the current working directory of the image to be /app. Now we need to copy the executable binary file from the builder stage to this run stage image. How can we do that? Well, we just use the same COPY command as before, But this time, we use the --from argument to tell docker where to copy the file from. In this case, it should copy from the builder stage, so we put the name of that stage here. Then the path to the file we want to copy, which is /app/main And finally the target location in the final image to copy that file to, which should be the current working folder. This dot represents the WORKDIR that we set above: /app And that will be it! The multistage build Dockerfile is completed. Let’s run docker build again to see how it goes! OK, the build finished. Let’s list all docker images. Here we go! The new simple bank latest image has been created. And its new size is just 17.7MB, 25 times smaller than the previous image. Awesome! Now you can see the previous image here, But its repository name and tag have changed to <none>. That’s because we use the same tag simplebank:latest to tag the new output image. So naturally the old image’s tag should be removed to prevent having 2 different images with the same tag. We can use docker rmi command to remove the old image. Now if we list the images again, the old one is gone. So today we have successfully learned how to write a multi-stage Docker file to build a very light-weight docker image for our application. We will continue in the next lecture, Where I’m gonna show you how to run the app using the docker image that we built today and connect it to an existing Postgres container via a user-defined docker network. Thanks a lot for watching, Happy learning, and see you soon in the next lecture!
Info
Channel: TECH SCHOOL
Views: 3,130
Rating: undefined out of 5
Keywords: golang docker, golang dockerfile, multistage dockerfile, golang multistage dockerfile, golang deploy docker, golang small docker image, golang tutorial, golang, docker tutorial, docker, backend master class, backend course, backend development, backend tutorial, coding tutorial, programming tutorial, tech school
Id: p1dwLKAxUxA
Channel Id: undefined
Length: 12min 0sec (720 seconds)
Published: Sun Apr 04 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.