DEV Community

Sebastian G. Vinci
Sebastian G. Vinci

Posted on

Deploy using docker swarm

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 
Enter fullscreen mode Exit fullscreen mode

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"] 
Enter fullscreen mode Exit fullscreen mode

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; } 
Enter fullscreen mode Exit fullscreen mode

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; } } 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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); }); }); } 
Enter fullscreen mode Exit fullscreen mode

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: 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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!

Top comments (2)

Collapse
 
svinci profile image
Sebastian G. Vinci

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

Collapse
 
kostjapalovic profile image
Kostja Appliku.com

Thanks a lot!