loading...

Deploy using docker swarm

svinci profile image Sebastian G. Vinci ・4 min read

I just finished this tutorial that shows how to build a web site using ReactJS and Redux. For testing purposes I also showed how to build a REST API using nodeJS.

Now, let's deploy those two using docker swarm.

Installation

First of all, you'll need to clone the React tutorial repository from here.

Now, you'll need to install Docker. Installation instructions can be found here.

Once you have docker running, you need to restart the docker daemon to make it run in swarm mode. This can be achieved by running the following command:

$ docker swarm init

Now you're good to go.

Docker Images

Now we'll need to write the docker images for our API and our UI.

API Image

Let's start with the API. Here's the Dockerfile for our NodeJS API.

FROM node:boron

RUN mkdir -p /usr/src/app/config
WORKDIR /usr/src/app

COPY package.json /usr/src/app/
RUN npm install

COPY config/default.json /usr/src/app/config/default.json
COPY index.js /usr/src/app/index.js

EXPOSE 8100

CMD ["npm", "run", "start"]

We are building our image from node:boron (NodeJS 6). Then we're creating a directory in /usr/src/app to deploy the API there, copying the package.json there and installing dependencies.

Then we copy the configuration and our index.js file. We expose port 8100 and start the server.

Now we build the image running docker build -t svinci/todo-api .. Feel free to change the namespace.

UI Image

Now, the UI will be a little more tricky. The container has to deploy an NGINX to work as a web server of the UI, and it has to have the rules to acces the API passing through it.

Let's start by making a directory called conf that will hold NGINX configuration. In it we'll write a file called nginx.conf with the following content.

user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;


events {
    worker_connections 1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '[$remote_addr] [$time_local] [$request] '
                      'status code: $status, response size: $body_bytes_sent, referer: "$http_referer", '
                      'user agent: "$http_user_agent", $request_time seconds';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;

    keepalive_timeout  65;

    gzip on;
    gzip_disable "msie6";

    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_buffers 16 8k;
    gzip_http_version 1.1;
    gzip_min_length 256;
    gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/vnd.ms-fontobject application/x-font-ttf font/opentype image/svg+xml image/x-icon;

    include /etc/nginx/conf.d/*.conf;
}

This basically sets the queue size, mime types, access log format and gzip. Now, in a subdirectory of conf called conf.d, we'll write the following in a file called default.conf.

server {
    listen       80;
    server_name  localhost;

    location /todos {
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_pass http://api:8100;
    }

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }

}

This sets the listening port to 80, configures a proxy pass to the API and sets the site entry point.

Now, with this done, here's the Dockerfile.

FROM nginx

COPY conf /etc/nginx

COPY index.html /usr/share/nginx/html
COPY bundle.js /usr/share/nginx/html

EXPOSE 80

We are building our image from nginx, copying our NGINX configuration, and copying our bundle and our index.html to the default static content location for the web server. Then we expose the port 80.

Now, before actually building the docker image, we have to do a tiny modification. This example was meant to be run locally, so we need to modify the file under todo-site/src/client.js to change http://localhost:8100/todos by just /todos so the UI executes the requests through our NGINX. It should look like this:

import * as superagent from "superagent";

export function get() {

    return new Promise((resolve, reject) => {
        superagent.get("/todos")
            .end((error, result) => {
                error ? reject(error) : resolve(result.body);
            });
    });

}

export function add(text) {

    return new Promise((resolve, reject) => {
        superagent.post("/todos")
            .send({'text': text})
            .end((error, result) => {
                error ? reject(error) : resolve(result.body);
            });
    });

}

export function toggle(id) {

    return new Promise((resolve, reject) => {
        superagent.patch("/todos/" + id)
            .end((error, result) => {
                error ? reject(error) : resolve(result.body);
            });
    });

}

Now we build the image running npm install && npm run build && docker build -t svinci/todo-site ..

Compose

We now have to write our compose file. This file defines all the services we need running, with its dependencies, replication factor, networks and stuff.

We need to write the below content into a file called compose.yml on the root of the repository.

version: "3"

services:
  api:
    image: svinci/todo-api
    networks:
      - todo_network
    ports:
      - 8100
    deploy:
      replicas: 1
      update_config:
        parallelism: 1
      restart_policy:
        condition: on-failure
  site:
    image: svinci/todo-site
    ports:
      - 80:80
    networks:
      - todo_network
    depends_on:
      - api
    deploy:
      replicas: 1
      update_config:
        parallelism: 1
      restart_policy:
        condition: on-failure

networks:
  todo_network:

We are defining two services here:

  • api: The To Do REST API.
  • site: Our NGINX with the static content.

As you see, we are setting the images it needs to use, and the network they belong to.

We are also defining that the site depends on the API to work.

Deployment configuration is being provided too. Notice the replicas under deploy for each service, we could have redundancy here out of the box.

Deploy

Now, let's deploy this. To deploy we need to run the following command:

docker stack deploy --compose-file=compose.yml todo_stack

If you visit http://localhost/ in your browser after a couple seconds (NGINX takes like 15 seconds to start), you'll see the site fully working.

Conclusion

It's fairly easy to dockerize stuff, and I really prefer to work this way as I don't need to install too much software on my computer (NGINX for example). Also, if you deploy like this in production, your local environment will ressemble production a lot, which is awesome.

You can find the source code of this example here on a branch called docker-swarm.

See you in the comments!

Discussion

pic
Editor guide
Collapse
svinci profile image
Sebastian G. Vinci Author

I have a schedule of what I'm going to write, I'm adding that one!

Collapse
kostjapalovic profile image
Kostja

Thanks a lot!