Guide to nodejs mongodb angularjs setup and deployment using ansible and vagrant

An average developer's daily life is filled with repetitive and monotous tasks. If you take care of setting up servers, you would want to automate the whole process. Computers are great at handling boring tasks, in this post, we will try to automate the setting up of a typical MEAN stack local, test and production environment using ansible. The post assumes that you know what ansible is, however if your don't what ansible is or does, please read through this tutorial, first..

1.Different development environments


The general workflow with software development process is usually, setting up local environment, version controlled by git, once a feature is implemented, it is pushed to the test server for the tests to run, depending upon the complexity of the appliaction, the dev, test, and staging are same or different machines, on tests successfully being run, the code is then pushed to the production environment. The diagram below summarizes the workflow.

2.Lifecycle of a Request-Response Cycle on a MEAN Stack


Having understood, the different server environments, lets analyse the request-response route. App user, sends a request to a load balancer. The load balancer usually will health check and balance traffic between the app servers.

Nginx works as a front end server, which in this case proxies the requests to a node.js server. Static content is served via nginx, and for serving dynamic content we ping the node process. The node server reads/writes data from/to a database server, mongo db in this case, and the response is returned to the client machine. Further down, we use a tool called pm2 to monitor the node process. PM2 provides an easy way to manage and daemonize applications (run them as a service).

3.Provisioning the Machine

As is clear from the above, to configure a server to run a mean stack, we need three different components:

  1. common system level tools

  2. web tools as nginx, pm2, express, node

  3. db - mongo

Accordingly, if you take a look at the repo. have defined three different roles.

3.1 Installing Common Sytem Requirements

The requirements mentioned over here, assume Ubuntu to be the Operating System.


- name: Ensure bash, OpenSSl, and libssl are the latest versions
  apt: name={{ item }} update_cache={{ update_apt_cache }} state=latest
    - bash
    - openssl
    - libssl-dev
    - libssl-doc
  tags: packages

- name: Install base packages
  apt: name={{ item }} update_cache={{ update_apt_cache }} force=yes state=latest
    - build-essential
    - ntp
    - htop
    - git
    - nodejs
    - npm
  tags: packages

The above snippets, present in (main.yml) updates the cache and installs the basic requirements, that are needed, no matter what your stack is.

3.2 Installing the DB Requirements

- name: MongoDB | Fetch GPG key 
  sudo: yes 
  command: apt-key adv --keyserver hkp:// --recv 7F0CEB10 
  tags: mongodb

- name: MongoDB | Add 10 gen repository 
  sudo: yes 
    echo 'deb dist 10gen' | sudo tee /etc/apt/sources.list.d/10gen.list creates=/etc/apt/sources.list.d/10gen.list

- name: MongoDB | Install latest mongodb 
  sudo: yes 
  apt: pkg=mongodb-10gen update-cache=yes 
  tags: mongodb

- name: MongoDB | Run mongo daemon 
  sudo: yes 
  service: name=mongodb state=started 
  tags: mongodb

We fetch the GPG key, install the latest version of mongo db, and run the daemon.

3.3 Installing and Configuring the Web Requirements

We have defined few of the variables in our env_vars folder. These variables will replace the values in the tasks file.

Web requirements can be classified into 6 different tasks

3.3.1. Creating the user group and folders

- name: Create the application user
  user: name={{ nodeapp_user }} state=present

- name: Create the application group
  group: name={{ nodeapp_group }} system=yes state=present

- name: Add the application user to the application group
  user: name={{ nodeapp_user }} group={{ nodeapp_group }} state=present

The above ansible task, helps us create a user, a group, and add the user to the created group.

3.3.2. Assigning folder permissions

- name: Ensure that the application file permissions are set properly 
  file: path={{ env_path }} 
    recurse=yes owner={{ nodeapp_user }} 
    group={{ nodeapp_group }} 

- name: Create a symlink for legacy node issue 
  command: ln -s /usr/bin/nodejs /usr/bin/node 
  ignore_errors: yes
3.3.3. Setup Git Repository

A sample to do app in mean stack can be found here


- name: Setup the Git repo
  git: repo={{ git_repo }} dest={{ app_path }} accept_hostkey=yes 
  when: setup_git_repo 
  tags: git

We want to pull the codebase from the git repo. In this case, its a public repo, however in most cases it will be a private repo, you will have to ensure the machine has access to the private repository.

3.3.4. Install Express

- name: Express | Install Express 
  sudo: yes 
  npm: name=express global=yes 
  tags: express

The above snippet helps the installation of express.

3.3.5. Install and Configure Nginx

- name: Nginx | Install nginx 
  sudo: yes 
  apt: pkg=nginx 
  tags: nginx

- name: Nginx | Setup reverse proxy 
  sudo: yes 
  template: src=nginx_site_config.j2 dest=/etc/nginx/sites-available/ 
      tags: nginx

- name: Nginx | Start nginx 
  sudo: yes 
  service: name=nginx state=restarted 
  tags: nginx

As outlined, we need to install nginx, configure it to serve the static requests, once installed we need to restart the nginx process. The configuration is saved in a template, and we need to place that template into /etc/nginx/sites-available directory.

3.3.6. Install PM2 and Start the Application

- name: PM2 | Install PM2 
  sudo: yes 
  npm: name=pm2 global=yes 
  tags: pm2

- name: start application with pm2 
  command: pm2 start {{ app_path }}/{{ server_file}} 
  ignore_errors: yes 
  become: yes 
  tags: pm2

PM2 is responsible for daemonizing the node process. Once installed, we need to start the daemon.

4.Setup local environment

For setting up local dev environment, we will create a new ubuntu machine using a virualbox. So we need to first install virtual box, install vagrant.

Clone the repo, we have a vagrant file as follows, you can change the private_network IP to your choice. I have synced my system's folder named mean to /webapps/ansbile folder inside the vagrant machine. You can change it to your choice.

# -*- mode: ruby -*- 
# vi: set ft=ruby :

# Vagrantfile API/syntax version. Don't touch unless you know what you're doing! 

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| = "1432" 
    config.vm.box_url = "" :private_network, ip: ""

    config.vm.provider :virtualbox do |vb| 
        vb.customize ["modifyvm", :id, "--name", "Meansible", "--memory", "512"] 
        # Display the VirtualBox GUI when booting the machine 
        vb.gui = false 
# Shared folder from the host machine to the guest machine.

config.vm.synced_folder "../../mean/", "/webapps/ansible/" 
    # Ansible provisioner. 
    config.vm.provision "ansible" do |ansible| 
        ansible.playbook = "vagrant.yml" 
        ansible.host_key_checking = false 
        ansible.verbose = "v" 
    config.ssh.forward_agent = true 

You are all set now, just run vagrant up, for the first time it will download the ubuntu box and install all the requirements. However, due to network latency or any other issue, the task fails, you can use vagrant provision subsequently and let the magic happen.

5. Setup production environment

The local machine was set using the vagrant.yml, if you want another production/staging machine to setup. Ensure you have sudo access to the remote machine, and you have all the variables mentioned correctly inside the env_vars/production or env_vars/staging and create a file production.yml or staging.yml link to the variables and run the following command

ansible-playbook production.yml -i IP_ADDRESS,. You are all good to go, however if you want to build multiple machines you can specify multiple IP Address in the hosts file, and use that.

Hope the post helped, do let me know, if you have any doubt, happy to resolve them.