Rancher is an open source, self-hosted, and complete platform to run and easily manage containers in production. Being a Docker image itself, a Rancher server will work on any Linux host where Docker is available.
Some of the key features that make Rancher an attractive solution are:
In this guide, you will build a Rancher cluster to deploy a load-balanced Node.js application, with support for data storage using MongoDB.
At the end of this tutorial, you’ll have four load-balanced instances of a simple Node.js application and a MongoDB server with a separated data container for persistent storage.
Note: As of December 15, 2022, DigitalOcean no longer supports the creation of new RancherOS Droplets through the Control Panel or API. However, any existing RancherOS Droplets created prior to December 15, 2022, will be functional despite the change in offerings. Additionally, you can still spin up RancherOS Droplets using a custom image. Learn how to import a custom image to DigitalOcean by following our product documentation.
You should also have basic knowledge of Docker concepts like containers, images, and Dockerfiles. See How To Install and Use Docker on Ubuntu 16.04.
For this tutorial, we’ll use a simple Node.js application based on the Hapi.js framework that receives a message, records it, and lists all the messages previously submitted. Let’s explore how the application works and how it receives configuration values so we can set those values with Docker when we create our image.
You’ll prepare the application and the Docker image on your local development machine, rather than a server.
Clone the example application to your local machine using the following command:
- git clone https://github.com/do-community/hapi-example
Then navigate into the project directory:
- cd hapi-example
Let’s look at the main file of the application, server.js
. It contains the MongoDB connection and the code to initialize the server. Open this file in your local text editor and you’ll see the following contents:
const Hapi = require('hapi');
const mongojs = require('mongojs');
// Loads environment variables
// Used only in development
require('dotenv').config({silent: true});
const server = new Hapi.Server();
server.connection({ port: process.env.PORT || 3000 });
// Connect with the database
server.app.db = mongojs(process.env.MONGO_HOST + '/api');
// Add the routes
server.register(require('./routes'), (err) => {
if (err) {
console.error('Failed to load plugin:', err);
}
// Start the server
server.start((err) => {
if (err) {
throw err;
}
console.log('Server running at:', server.info.uri);
});
});
The code for the routes is encapsulated as a Hapi.js plugin to save space in this tutorial, but if you’re curious, you can look in the file routes.js
.
The critical parts of the server.js
file are as follows:
require('dotenv').config({silent: true});
This uses the dotenv
Node.js package to load our environment variables from a .env
file. You can review the documentation for the dotenv
package in its Github repository if you’re interested in how it works. We save the variables in this file only for the development process; it’s easier than manually writing the variables in the terminal. In production, we are going to get the variables from Docker, via Rancher.
Next we set the port for the server, using an environment variable called PORT
, with a fallback value of 3000
, in case the variable isn’t defined:
server.connection({ port: process.env.PORT || 3000 });
Port 3000
is a common convention for Node.js applications. This value can be changed if necessary; the only requirement is that it should be above 1023
and below 65535
.
Finally, before loading the routes and starting the server, we connect to the MongoDB server, using an environment variable called MONGO_HOST
:
server.app.db = mongojs(process.env.MONGO_HOST + '/api');
This environment value will be defined through Rancher with the host name of the MongoDB server once we create the MongoDB containers. The value api
is the name of the database we are going to connect to, and it will be set up automatically if it doesn’t exist.
Now that you have some background on what the app is looking for, and how we configure its port and database connection, let’s bring Docker into the picture.
Rancher uses Docker images to deploy applications to servers, so let’s create a Docker image for our application. In order to build a Docker image for our app, we need a file called Dockerfile
which contains a series of steps that Docker will follow when building the image. This file is already included in the application repository that you cloned. Let’s look at its contents to understand how it works. Open it in your text editor and you’ll see the following code:
FROM node:6
MAINTAINER James Kolce <contact@jameskolce.com>
RUN mkdir -p /usr/api
COPY . /usr/api
WORKDIR /usr/api
RUN npm install --production
ENV PORT 3000
EXPOSE $PORT
CMD ["npm", "start"]
Let’s look at each step in detail. First, we see this line:
FROM node:6
This line declares that our image is built on top of the official Node.js image from Docker Hub, and we are selecting version 6 of Node.js since our app makes use of some ES6 features only available in that version or higher. It’s recommended practice to choose a particular version instead of just using latest in production so you avoid any changes that may break your app.
After that line, we set up our working directory:
RUN mkdir -p /usr/api
COPY . /usr/api
WORKDIR /usr/api
First we run the mkdir
command to create a new directory called /usr/api
, which is where our application will live. The -p
flag means that mkdir
will create intermediate directories as required. Then we copy the contents of the image to that directory. Then we set this new directory as our working directory so subsequent commands will be run from that directory.
The next line runs the npm
command and installs the production dependencies for our app.
RUN npm install --production
Next, we see these two lines:
ENV PORT 3000
EXPOSE $PORT
The first line defines an environment variable called PORT
which our application will use for its listening port. Just in case this variable isn’t defined, we set 3000
as the default value. Then we expose that port so we can have access to it from outside of the container. Using an environment variable makes this easier to change without having to rewrite our application code. And remember, our application is designed to use these environment variables.
The last step in our Dockerfile runs our Node.js server:
CMD ["npm", "start"]
To create the Docker image of our application from this file, ensure you are in the hapi-example
folder in your terminal and execute the following command:
- docker build -t your_dockerhub_username/hapi-example .
This command creates a Docker image using our Dockerfile
. Note the dot at the end of the command. This specifies the path to the Dockerfile
, which is in the current folder. The -t
flag sets a tag for our image, and we’ll use your_dockerhub_username/hapi-example
for the tag, which is a label we apply to the image so we can use it to create container instances from the image. We use the Docker Hub username as a prefix, as we are preparing to publish this image once we test it, and the local tag of the Docker image must match the repository name on Docker Hub.
If you receive the message Cannot connect to the Docker daemon. Is the docker daemon running on this host?
when you run this command, ensure the Docker app is running and that Docker is started. Then run the command again.
Now let’s test the image we just built so we can be sure that everything is working as expected. As you saw earlier, our application relies on MongoDB, so let’s create a MongoDB container our app can use to store its data. Run the following command to create and start a MongoDB container based on the official MongoDB Docker image:
- docker run --name testdb -p 27017:27017 -d mongo:3
We assign a temporary name to the container with the --name
option; we will use that name to stop the server when we finish testing the application. We also bind the host port 27017
to the port exposed by the container so we can test that MongoDB is running by using our local web browser. Finally, we specify the image we want to use. It’s a good idea to use the same version of MongoDB that the application was developed with to assure that everything works as expected, so in this case we specify version 3
.
After executing that command, visit http://localhost:27017
in your browser and you’ll see the message: It looks like you are trying to access MongoDB over HTTP on the native driver port
which means that MongoDB is running.
Now run your application container and link it to the MongoDB container by running this command:
- docker run --name hapi-app -p 3000:3000 -d -e MONGO_HOST=testdb --link testdb your_dockerhub_username/hapi-example
This command is similar to the command we used to start the MongoDB container, but this time we use our application image (your_dockerhub_username/hapi-example
) and map port 3000
of our host with the port exposed by the container. This is the same port we used when we created the Dockerfile
. Also, we add an environment variable called MONGO_HOST
that specifies the name of our MongoDB container which will be used by our application to connect to the database server. The --link testdb
parameter lets us use the name of the database container as a host inside of our application’s container.
After running the command, test the application by visiting http://localhost:3000
in your browser. It should show an empty page without any errors.
You might also see an empty array ([ ]
) or the JSON view on Firefox when you visit http://localhost:3000
. Either of these outcomes is also okay.
Now that we’ve proven the Docker image works locally, let’s stop and delete our local containers. Keep in mind that deleting a container is not the same as deleting an image. Our image is going to remain intact so we can recreate the container later, or push the image to Rancher, which is what we’ll do after we clean up our local environment.
First, stop the database container by using its name you defined previously:
- docker stop testdb
Now that the container is stopped, you can delete it from your machine since you won’t need it anymore:
- docker rm testdb
Repeat the same steps for your application container. First stop it, then remove it.
- docker stop hapi-app && docker rm hapi-app
Now let’s publish the working image so we can use it with Rancher.
To deploy containers using Rancher we need access to a Docker registry, where we can create a repository to store our Docker image. We’ll use Docker Hub which is the official registry for Docker. Docker Hub is free to use for public repositories.
Log in to Docker Hub with your username and password. Once logged in, click the Create Repository button at the right side of the screen. Fill in the fields as follows:
hapi-example
.If you set your repository as private, you will have to add your Docker Hub credentials in the Infrastructure -> Registries page in the Rancher UI.
After all the required fields are filled in, click the Create button. When the process is done you will be redirected to your new repository site.
In order to upload your Docker image, you must log in to Docker Hub through the docker
command. Back in your terminal, execute the following command:
- docker login
You’ll be prompted to enter your username and password. Like most CLI tools, you won’t see your password as you type it.
Once you’re logged in, upload the image to Docker Hub with the following command which uploads all the files to the repository:
- docker push your_dockerhub_username/hapi-example
Pushing an image may take several minutes depending on your local internet connection. Once the image is successfully published, we can set up our hosts using Rancher.
Let’s use Rancher to create all of the hosts we’ll need to deploy our services. We’ll need two for the Node.js application, one for the MongoDB server, and one for the load balancer. We’ll do all of this within the Rancher UI using DigitalOcean’s API.
Visit your Rancher interface in your browser by visiting http://your_rancher_ip_address
and follow these steps to create the four hosts we’ll need:
host
, which will automatically generate names from host1
to host4
.Once Rancher finishes creating all of the hosts, we’ll add a label to each one of them to classify their type so we can organize where we are going to put each of our components. Labeling hosts also lets us scale our servers depending of their type. For example, if we are getting too much demand for our application, we can increase the number of servers of that type and Rancher will automatically deploy the appropriate Docker container for us. The labels that we are going to create are: loadbalancer
, application
and database
.
Let’s create the first label, loadbalancer
.
host1
.type
, and then enter loadbalancer
in the Value input.Next, label the application hosts. Repeat the previous process with the next two hosts but this time use application
in the Value input.
For the last host, repeat the process again but use database
in the Value input.
All four hosts should now have labels, so let’s set up the services. We’ll start with the database.
We’ll use the official MongoDB Docker image on Docker Hub to deploy our database server. The MongoDB container will also have a sidekick container to store all of our data. Both containers will be deployed on the host labeled as database
.
To do that, follow these steps in the Rancher user interface:
MongoDB
.mongo:3
.Data
. This container will be used as a volume to store the MongoDB data.busybox
image./data/db
into the text field that appears. This is the default path where MongoDB stores data.The Host must have a host label of type = database
. Use the dropdowns to help you create this rule.The Host must have a host label of type = database
Now let’s configure the application service.
We’ll use a similar approach to deploy the Node.js application we previously prepared. The image we stored on Docker Hub will be deployed on the hosts labeled application
, and will be linked to the MongoDB service to store and access data. So, follow these steps in the Rancher user interface:
NodeJS
.your_dockerhub_username/hapi-example
.db
, so our NodeJS
service can have access to the MongoDB service.MONGO_HOST
with the value of db
, which maps to the destination service name we used in the previous step. Remember that our application relies on this environment variable to locate the database server.The Host must have a host label of type = application
.In a short time, you’ll see that both hosts labeled application
are now running this new service. Since there’s more than one, let’s set up a load balancer so we can use both of these hosts effectively.
Our load balancer is going to be linked to our NodeJS
services to balance the workload between all the containers across the application hosts.
LoadBalancer
.80
, and the Default Target Port to 3000
which is the port our NodeJS
containers are exposing.The Host must have a host label of type = loadbalancer
.Each time we’ve created a service, we’ve used the labels we created to determine how the service gets deployed. This makes managing additional hosts easy in the future. Now let’s make sure things work.
To test our application, we need to get the address of the load balancer host. Select the LoadBalancer service and you’ll see the IP address in the Ports tab.
To test that our application is working, execute the following command in a terminal:
curl LoadBalancerIP
This command sends a GET request to the server. You’ll see a response that contains an empty array ([]
) because our database is empty.
Now add a message to the database to ensure that the application can save data. Execute the following command:
curl -i -X POST -H "Content-Type:application/json" LoadBalancerIP -d '{"message":"This is a test"}'
This command sends a POST request to the server with a JSON object which contains a message
key with the value of This is a test. This application will only accept a message
key, so any other name will be discarded. After sending the request, you should receive the same message you sent as a response, along with a _id
from MongoDB. This means that the connection with the MongoDB server is working and the application saved your data.
Now, to double check that the application functions properly, execute the first command again and you should get the message you added in the previous step.
curl LoadBalancerIP
The output will look like this:
HTTP/1.1 200 OK
content-type: application/json; charset=utf-8
cache-control: no-cache
content-length: 61
Date: Tue, 12 Jul 2016 20:07:02 GMT
Connection: keep-alive
{"message":"This is a test","_id":"e64d85579aee7d1000b075a2"}
This example application is insecure; anyone who knows the address and the API can add messages to the system. When you’re done with this tutorial, you may want to disable this application by deleting the service in Rancher.
At this point you now have two application servers, a database, and a load balancer configured and ready for use. Let’s look at how to scale our services to handle more traffic.
When your application starts getting a lot of demand and your servers can’t handle the load, you can increase the amount of Node.js servers and the load will be distributed automatically between the containers across the application hosts. Follow these steps to scale your application:
host5
as the name for the first new host, since the last host we created was host4
. Because we are going to create two new hosts, Rancher will name the next one automatically to host6
.type
, and then enter application
in the Value input.After the new hosts come online, because they are labeled as application hosts, new instances of the NodeJS
application will be configured and deployed automatically, and the load balancer will distribute the workload between four containers across four hosts.
In this tutorial you learned how to prepare, deploy, and scale a functional Node.js application with support for data storage with MongoDB. As you can see, with Rancher and its GUI the process is pretty intuitive, and it’s easy to scale a complete application. And thanks to Rancher’s scheduling features, when your app hits the big time, you’ll be able to handle the load with ease.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!
I am very new to Rancher and I am referring to this article in 2019. Rancher had a lot of changes since this article was written. But despite every hurdle, I somehow got everything to work. It took me a total nearly 4 hours to complete everything that has been said in this article which would have been done in just 30 mins if no changes were there in rancher. Anyways, thanks!
It will be very useful to see article about making continuous deployment work with Rancher. Do I need to manually upload new image on docker hub every time when the new version of the app is ready?
Well explained article. It helped me to understand a lot on the local/in-house server setup environments (where I need to setup all the hardware/software environment, to have a website up and running on our office residing server commercially). Can any one assist me to solve my problem please.
I want to deploy a CMS application developed in React, Redux, Mongo DB. I am new so I am wondering how can I setup this. Any guidelines will be helpful. I have following questions in my mind:
What are pros and corn of having **Linux **or **Windows **based server. (also which versions are relevant)
How to setup an SSL on local hosting server. What are options.
What are the security precautions to be made.
An internet line with dedicated IP from ISP can be connected, but do need to have any security hardware in middle in the network?
How to setup/connect a purchased domain name (www.mydomain.com) to an internal hosting server.
How to have multiple IPs to an internal hosting server, so if one server fails or one network fails the other keeps working with the purchased domain name. Also how floating IP helps and how this can be setup.
How to log IP(visitors) access log in hardware level to keep server secure. How Ranchet is helpful and what is the purpose of setting up Ranchet. Is it not right to simply install NodeJS on the machine and run application on it (but as I’m new so I need some guidelines on this please).
Is it simple to start application just like I do in my local development environment and everything will be accessible on the DNS (if setup).
How to setup internal code version control system (using any local version control system and also GitHub), so if one deployment fails or creates any trouble; we can then restore to older code version.
How to setup a mailing server to send and receive emails and also how can we setup different emails on local hosting server.
Any application crash and monitoring tools I need to setup? How Nginx is helpful and any comparison with Ranchet?
What is the purpose of Containers and Dockers? and is it a feature or a service or what exactly it serves. As I’m new so I may ask: Is it ok to go for deployment without Containers and Docker :) Dont’ be angry please.
Your how-to posts are really good. But It would be helpful if you included screenshots. Many of the options in rancher are now labeled differently, and it’s not always clear how to follow the instructions.
a high level diagram would be nice :)
Is there a way to do this with docker’s container networks rather than linking containers? Links (–link) are considered legacy.
I had been researching using Rancher to manage containers as well as hosting on DigitalOcean so thanks for the article!