How to Deploy App Using Docker

Habib Ridho
5 min readApr 30, 2018

--

src: https://spin.atomicobject.com

In this article, I will show you how we can deploy an application using docker. Without further ado, let’s write a simple Golang application for our sample case.

package main

import (
"net/http"
"encoding/json"
"fmt"
)

func main() {
server := &http.Server{
Addr:":8888",
}

http.HandleFunc("/", hello)

fmt.Println("Server started on port 8888")
server.ListenAndServe()
}

func hello(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "application/json")

payload := struct {
Status string `json:"status"`
Message string `json:"message"`
}{
Status:"Success",
Message:"Hello world!",
}

response, err := json.Marshal(payload)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}

w.Write(response)
}

Now, we can build the app in our local machine and transfer the executable file to our server. Or, we can use docker to build it.

Build

To build using docker, we need to create a Dockerfile inside our project. This file consists a set of instructions to build a docker image. In this case, our Dockerfile will be like so.

FROM golang
WORKDIR /go/src/github.com/habibridho/simple-go
ADD . ./
RUN
CGO_ENABLED=0 GOOS=linux go build -a -installsuffix .
EXPOSE 8888
ENTRYPOINT ./simple-go

So what happened here? First, we set our base image. We can use any docker image as our base, but it’s good to use an image that has everything you need to build your application. For example, if you are to build a NodeJS application, you might choose nodejs image for your base.

Next, we set our WORKDIR which will be used as base path for every command that use relative path. Then we ADD all our project files to the docker working directory. With all files already included, now we can RUN the build command. The EXPOSE command tells docker that the container created by this image will listen to the given port. Finally, we set the ENTRYPOINT of our container which is the executable produced by the build script.

$ docker build -t simple-go .
$ docker run -d -p 8888:8888 simple-go

Build your image and try to run it. If you successfully have your app running, then you are ready to deploy it on the server. Or is it?

Multi Stage Build

Let’s see the images that we have by running docker image ls

REPOSITORY          TAG                 IMAGE ID            CREATED              SIZE
simple-go latest d097fdad7838 43 seconds ago 808MB
golang latest 6fe15d4cbc64 4 weeks ago 780MB

Our image is really huge for a simple application. Though, it’s absolutely normal since we use the golang image as our base which by itself has 780MB of size. The question is: do we need all the things inside that image to run our app? No, since we build our app for Linux (see the RUN part of the Dockerfile), we should be able to run the executable file inside a basic linux distribution. And we will achieve that by editing our Dockerfile like so.

FROM golang as builder
WORKDIR /go/src/github.com/habibridho/simple-go/
COPY
. ./
RUN
CGO_ENABLED=0 GOOS=linux go build -a -installsuffix .

FROM alpine:latest
WORKDIR /app/
COPY --
from=builder /go/src/github.com/habibridho/simple-go/simple-go /app/simple-go
EXPOSE 8888
ENTRYPOINT ./simple-go

As we can see, we have two FROM statements which indicates we are not only using multiple base image, but also creating multiple stage of build. We name the first stage as builder which will be used as a referrer in the second stage. What we are doing here is simply copy the executable file created by builder stage, to the second stage. The result is a much smaller image that is ready to use in our server.

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
simple-go latest 26edf327a898 8 seconds ago 10.9MB

Deployment

The next step is transferring our image to the server. We can use Docker Hub which is a docker repository server. We can push our image to the repository and pull it from the server.

-- on local machine
$ docker tag simple-go habibridho/simple-go
$ docker push habibridho/simple-go
-- on server
$ docker pull habibridho/simple-go

You have to note that the default docker repository visibility is public, so if your project is private, you need to change the project visibility from the Docker Hub website.

Alternatively, we can zip our image, transfer the file using scp, and import it in the server.

-- on local machine
$ docker save -o image.zip simple-go
$ scp image.zip <user>@<server-addres>:<target-location>
-- on server
$ docker load -i <path-to-image.zip>

Once we have the image in our server, we can run the app just like what we have done in our local machine.

$ docker run -d -p 8888:8888 simple-go

Swarm

src: https://www.whalefree.com.au

The steps in the previous section is sufficient to make our app up and running, but we can go further. For example, we want to scale our app by creating three instances of it. We can re-run our previous command, use other port mapping, and implement a load balancer. But now, let’s do it the docker way.

First we need a compose file (I name it docker-compose.yml) which will tell docker what is the desired state of our app.

version: "3"
services:
api:
image: simple-go
deploy:
replicas: 3
resources:
limits:
cpus: "0.1"
memory: 50M
ports:
- "8887:8888"
networks:
- host
networks:
host:

So here, we tell docker that we’re creating an api service using image named simple-go. Upon deployment, I want it to have 3 instances of the image and limit the app resources usage to 10% CPU usage and 50MB of memory. I also tell docker the port mapping (host’s 8887 to container’s 8888) and the network to use, in this case we will use the host’s networking stack.

We will utilise Docker’s swarm mode to deploy our app. So let’s init one.

$ docker swarm init

This will initialise a swarm manager that will manage our clustered app and do load balancing across available nodes. Now that we have a node available, let’s deploy our app.

$ docker stack deploy -c docker-compose.yml simple-go

If everything goes well, we should have 3 containers running.

$ docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
mvmxjc1zk2ih simple-go_api replicated 3/3 simple-go:latest *:8887->8888/tcp
$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e6101072bd54 simple-go:latest "/bin/sh -c ./simple…" 10 minutes ago Up 10 minutes 8888/tcp simple-go_api.2.zbthwotfo9i9jixa1dizvzxvg
eef9d8dc9062 simple-go:latest "/bin/sh -c ./simple…" 10 minutes ago Up 10 minutes 8888/tcp simple-go_api.3.aqh10lmx27pku3ld30m9t7a2o
03b5212c878b simple-go:latest "/bin/sh -c ./simple…" 11 minutes ago Up 10 minutes 8888/tcp simple-go_api.1.ovier6c3a6kqbwn62vfdmwwl0

Now, let’s say we want to change the port mapping, or update the resource limit. We can just update the compose file and re-run the docker stack deploy command.

While this article has demonstrated how docker can be used for app deployment, there are still a lot of details that I omit here. Docker’s official documentation provides great explanation of the underlying concept, so I recommend you to check that out. Cheers!

--

--