Day 17 - Docker Project for DevOps Engineers

Day 17 - Docker Project for DevOps Engineers

90DaysOfDevOps - Learn how to create a Docker image and DockerHub

ยท

7 min read

Link to project file : github

Today I am going to do a small project of creating a Dockerfile for a REST API I wrote in Golang.

Essentially the task is to create a Docker image of the REST API and push it to DockerHub.


Setting up environment for this REST API in Golang

Installing Go

  1. Install Go version 1.21.6 on linux/amd64

  2. The installation also includes :

    • Downloading the tar.gz file official website.

    • Moving the extracted folder to /usr/local/ folder.

    • Adding the go executable /usr/local/go/bin file to the path variable

  3. Check the go version by typing go version in the shell

Installing dependencies

  1. Initializing the REST API project using go mod init. If you are from React background - this is similar to npm init.

  2. Install a package - gin with the command go get github.com/gin-gonic/gin.


Huh....too much work eyyy!!!๐Ÿ˜ฃ

Feel the need to create some sort of automation to escape setting up the "environment" to run any such program which requires so much to setup?

Steps to make a docker image :

  1. Create a new branch to work on this "feature" - optional but good practice.

  2. Create a Dockerfile.

  3. Push the image to DockerHub - optional.


What is a Dockerfile?

๐Ÿ’ก
A Dockerfile contains instructions for building an image.

There is a command in docker to automate the creation of an image. This is the docker build command which reads instructions written in the Dockerfile.

The most important instructions are as follows :

INSTRUCTIONFUNCTIONALITY
FROMFetch the mentioned base image
WORKDIRSpecify which directory in the file system of image to execute other instructions
COPYCopy files and directories
ADDAdd local or remote files and directories to the image
RUNExecute OS commands in the image
ENVSetting environment variables
EXPOSETo tell docker to start the container on a given port
USERSet user and group ID - the application is gonna be run by this user
CMDThe command to be executed after starting the container
ENTRYPOINTSpecify default executable
SHELLSet the default image of a shell

Create a Dockerfile

  1. Navigate to the directory containing the project.

  2. Let's take it practically and start with the task.

    • First things first, create a branch on git. It often goes neglected and we start working directly on the main branch, but we must follow good practices ๐Ÿค“.

    • I have created a branch named "docker_image" from the main branch using the following command :

        git checkout -b docker_image # create and switch to docker_image branch
      
  3. Create a separate git branch to work on this "task" of creating a Dockerfile and docker image from the main branch.

     git checkout -b docker_image
    
    • NOTE : The docker image won't be created in the same directory, thankfully! We can look into the specifics later though - one thing at a time :)
  4. Create a Dockerfile from cli using touch Dockerfile. The Dockerfile contents are as follows :

     # Use Alpine Linux as the base image
     FROM alpine:3.18
    
     # Set shell to sh
     SHELL ["/bin/sh", "-c"]
    
     # Create a non-root user
     RUN adduser -D -u 1000 definitely_not_root
    
     # Install necessary dependencies
     RUN apk update && \
         apk upgrade && \
         apk add --no-cache \
             curl
    
     # Download and install Go 1.21.6
     RUN curl -sSL https://dl.google.com/go/go1.21.6.linux-amd64.tar.gz | tar -C /usr/local -xz
    
     # Set Go environment variables
     ENV GOPATH=/go
     ENV PATH=$PATH:/usr/local/go/bin:$GOPATH/bin
     ENV API_URL=http://127.0.0.1/
    
     # Create a working directory
     WORKDIR /api
    
     # Copy the necessary files
     COPY . .
    
     # Download Go dependencies mentiond in the go.mod file
     RUN go mod download
    
     # Switch to the non-root user
     USER definitely_not_root
    
     # Expose port 8080 of container
     EXPOSE 80
    
     # Command to start the session with the shell
     CMD ["sh"]
    

The Dockerfile here is pretty self-explanatory with the comments mentioned above each instruction. However, I would like to elaborate on a few points here.

  • The docker file instructs the usage of the base image of Alpine3.18 Linux with a default shell "sh" and this can also accept command line args due to the "-c" flag.

  • Create a user as it is not recommended basically anywhere to carry out tasks as the ROOT user.๐Ÿ˜ตโ€๐Ÿ’ซ

  • Update the local file containing the respository list and upgrade the system (alpine linux). Install curl to download stuff from the web.

  • Download the go package (.tar) file from offical repository and extract it to the desired folder.

  • Set environment variables.

  • Set a working directory for the user - definitely_not_root we created above in the Dockerfile.

  • The REST API Go program uses the port 80 of the container i.e. we made the container to expose it's port 80 to other applications/host. We then map it to any port of the localhost that we want. ๐Ÿ˜

  • Start the image with the application "sh" - the default minimal shell of UNIX.

Building image from Dockerfile

After the Dockerfile is completed, add a .dockerignore file if you do not want to include certain files in the docker image generated from the Dockerfile. If you have used git, this is analogous to .gitignore file.

For reference, the contents of the directory are shown below. The contents of the .dockerignore file are also displayed.

Running the docker image

docker images #list all the images available on your device

You'll be able to see rest_api_golang_todos in the images list.

Now, we have to map the docker container port to some port of the localhost to actually see the API working on our browser. At least to send the "GET" request!

docker run -it -p 9090:80 rest_api_golang_todos

The port flag i.e. "-p" is used to map the ports of container to that of host.

The syntax is as follows : -p host_port:container_port. Thus we can see the API at localhost:9000/ as shown in the images below.

  • After the above command, the container will start.

  • You can now check your username with whoami.

  • Start the REST API with the command :

      go run main.go
    

Final result of Docker image

You can navigate around with the urls :

localhost:9000/todos
localhost:9000/todos/1
localhost:9000/todos/2
localhost:9000/todos/3

You might also wanna play around with Postman API for POST, DELETE and other methods, although I haven't tested the docker image with it. It did work locally with Postman though.

Pushing image to DockerHub

  1. Before pushing to DockerHub, it is recommended to add a tag "1" like the following command :

     docker image tag <image_ID> <image_name>:1
    

  2. Now our latest image points to the image with version tag 1.

  3. Now, before pushing an image to the DockerHub, I prefer having a separate pointer locally. Thus I created a new tag akshaykhoje/rest_api_golang_todos:1 as follows :

  4. Ensure that you have an account on DockerHub.

  5. Login from command line with the credentials using the command :

     docker login
    
  6. Final command to push the image to DockerHub

     docker push akshaykhoje/rest_api_golang_todos:1
    

Pulling from the DockerHub

  1. Link to the docker image : https://hub.docker.com/r/akshaykhoje/rest_api_golang_todos/

  2. To directly use the docker image from the dockerhub, enter the following command :

     docker pull akshaykhoje/rest_api_golang_todos:1
    
    • I am to change the tags, but yeah at the time of writing, I happen to be playing around with tags and other Docker jargon/flags.
  3. Use the docker image as follows :

     docker run -it -p 9090:80 akshaykhoje/rest_api_golang_todos:1
    

    The port flag "-p" means mapping of port 80 of the container to the port 9090 of the host machine. Read more.

  4. Once, the container starts, start the API with the following command :

     go run main.go
    

Happy Learning ;)

ย