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!
Top comments (2)
Thanks a lot!
I have a schedule of what I'm going to write, I'm adding that one!