← Back to blog
Automation

Automating Node.js Deployment with Nginx and Ansible Roles on AWS

Mar 25, 202515 min read

In the world of DevOps, consistency is king. Manually configuring servers, installing dependencies, and setting up reverse proxies is not only tedious but prone to human error. This is where Ansible shines.

In this guide, we will walk through how to deploy a Node.js application behind an Nginx reverse proxy on an AWS EC2 instance. We will utilize Ansible Roles to keep our code modular and AWS Dynamic Inventory to automatically target our instances.

What are Ansible Roles?

Before diving into the code, let's understand the structure we are using. In Ansible, Roles are a way to organize and package related tasks, handlers, variables, and files into a reusable unit. They provide a structured approach for creating modular and shareable content.

A typical role contains:

  • Tasks: A set of actions to be executed on the target hosts.
  • Handlers: Actions triggered by specific events (e.g., restarting a service after a config change).
  • Templates: Jinja2 templates used to dynamically generate configuration files.

Step 1: Project Setup and Inventory Configuration

First, we need to organize our directory structure. We will create two roles: nginx and nodejs.

The Playbook

Create a file named `playbook.yml`. This file acts as the entry point that maps your hosts to the roles they should execute.

---
- hosts: nginx
  become: true
  gather_facts: true
  roles: 
    - nginx
    - nodejs
  vars:
    ansible_connection: aws_ssm
    ansible_aws_ssm_region: us-east-1
    ansible_aws_ssm_bucket_name: <s3-bucket-name>

Note: We are using aws_ssm for the connection, which requires the AWS SSM agent to be installed and proper IAM roles attached to your EC2 instance.

Dynamic Inventory

Instead of manually typing IP addresses, we will use the AWS EC2 plugin to find our instances based on tags. Create a file named `aws_ec2.yml`.

---
plugin: aws_ec2
regions:
  - us-east-1
hostnames: 
  - instance-id
groups:
  nginx: "'<group>' in tags.Name"
filters:
   tag:Name: <name-tag-value>
   tag:owner: <owner-tag-value>

This configuration tells Ansible to look for instances in us-east-1 with the specific tag Name: <name-tag-value>.

Step 2: Configuring the Nginx Role

We need Nginx to act as a reverse proxy, forwarding traffic from port 80 to our Node.js application running on port 3000.

Nginx Tasks

4d70f07d-5baf-4b8a-8418-a9b35ae2c4a4.png

Create `roles/nginx/tasks/main.yml`:

# Installing nginx in EC2 instance
- name: Install nginx
  ansible.builtin.apt:
    name: nginx
    state: latest

# Changing configuration nginx file 
- name: Copy configuration file 
  ansible.builtin.template:
    src: nginx.conf.j2
    dest: /etc/nginx/sites-available/default

#Nginx Template

Create the template file `roles/nginx/templates/nginx.conf.j2`. This configuration listens on port 80 and proxies requests to localhost:3000.

server {
    listen 80;
    server_name _;
    location / {
        proxy_pass http://localhost:3000;
        proxy_http_version 1.1;
    }
}

Step 3: Configuring the Node.js Role

Next, we define the tasks to install Node.js, pull our application code, and start it.

Node.js Tasks

Create `roles/nodejs/tasks/main.yml`:

- name: Install nodejs and npm
  ansible.builtin.apt:
    name: 
      - nodejs
      - npm
    state: latest

- name: Install PM2 (Process Manager for Node.js)
  npm:
    name: pm2   

- name: Clone Node.js application repository
  git:
    repo: 'https://github.com/digitalocean/sample-nodejs.git'
    dest: /home/ubuntu/app

- name: Install dependencies
  npm: 
    path: /home/ubuntu/app/

- name: Start the Node.js application with pm2
  command: pm2 start -f app.js
  args:
    chdir: /home/ubuntu/app/
  notify: Restart nginx

Handlers

We noticed a `notify: Restart nginx` in the task above. We need to define this handler so Nginx restarts whenever the application state changes.

Create `roles/nodejs/handlers/main.yml`:

- name: Restart nginx
  service:
    name: nginx
    state: restarted

Step 4: Execution

With our playbook, inventory, and roles set up, we are ready to deploy. Open your terminal and run the following command:

ansible-playbook -i aws_ec2.yml playbook.yml

Ansible will connect to your EC2 instance via AWS Systems Manager (SSM), install the necessary packages, configure Nginx, and launch your Node.js application.

Verification

Once the playbook finishes successfully, copy the Public IP or Public DNS of your EC2 instance and paste it into your browser:

`http://<public_hostname_of_EC2_Instance>`

293323d0-f715-4c07-acc4-b9fd9c58c5eb.png

You should see your Node.js application running successfully behind Nginx!

Conclusion

By using Ansible Roles, we have created a modular deployment strategy. If we need to change how Nginx is configured, we only touch the nginx role. If the application logic changes, we update the nodejs role. This approach scales much better than writing a single massive playbook file.

Happy Automating!

Written by

Sujata Dahal

← Back to all posts