Published on

Simple way to Automate Deployments with VPS using Ansible

Authors
  • avatar
    Name
    Dinh Nguyen Truong
    Twitter

Nowadays managing your development workflow is such a crucial part of your working process. An efficient workflow helps you iterate faster, shortens your feedback loop, and gets your product to market quickly.

While many cloud providers provide integrated solutions for continuous deployment (Vercel, Netlify, Render, Railway), how about the virtual machine? Let's say you buy a small droplet on Digital Ocean for $5/month. How could you set up a nice development workflow the same way you did with another hosting provider?

Sure, you can use self-hosted solution like Easypanel, Coolify. The thing is those are slow, and cost a lot of memory, you could never run it on a $5 digital ocean droplet.

So in this post, I’m gonna how to set up an effective development workflow for your virtual machine using Ansible. And you could run it on a $5 droplet if you want to.

My process will be divided into 3 step

  • Setting up your server manually: nginx, nvm, nodejs, pm2.

  • Deploying the application

  • Setting up your development workflow using Ansible. This is my favorite tool to setup automation process on virtual machine.

By the end of this tutorial, you'll have a fully automated deployment process running on your VPS.

Step 1: Install Nginx, Nvm, Nodejs, Pm2

First, SSH to your droplet, follow this guide if you haven’t done so

Install nvm

sudo apt install curl
curl <https://raw.githubusercontent.com/creationix/nvm/master/install.sh> | bash
source ~/.bashrc

Install node, pm2, nginx

nvm install 20
npm i -g pm2
sudo apt-get install nginx

Step 2: Deploy the application

Clone your repository and run your application, for the demo purpose, I’m going to use a simple Hello World express application

  1. Clone the repository:
git clone <https://github.com/compimprove/express-helloworld>
cd express-helloworld && npm install

Note: If your repository is private, you may need to set up SSH keys for authentication.

  1. Run the application using pm2
pm2 start npm --name "helloworld" -- start

You should see your application started

  1. Test the application:

Access to droplet server at port 3000, you should see a legendary message“Hello World!”

Step 3: Setup Ansible for development flow

Prerequisites

Basically, Ansible is an open-source automation tool for complex and boring tasks like application deployment, configuration management, and IT orchestration.

When running Ansible, it will connect to nodes (servers) over SSH and push small programs called "Ansible modules". These modules are executed on the nodes, by a specific sequence, and the results are sent back to the control machine.

It allows you to define the infrastructure as code - this is the main benefit.

So to automate our deployment process, I'll create two main files:

  1. ansible/inventory.ini: This file defines the servers Ansible will manage.

  2. ansible/deploy-backend.yml: This is our main playbook that defines the deployment tasks.

Let's look at each file in detail:

ansible/inventory.ini

[webserver]
server1 ansible_host={your_server_ip_address} ansible_user=root

and the deployment file

ansible/deploy-backend.yml

- name: Deploy Node.js application with Nginx
  hosts: webserver # defined in inventory.ini
  vars:
    GIT_REPO: <https://github.com/compimprove/express-helloworld>
    GIT_BRANCH: main
    USER: compimprove
    PROJECT_NAME: express-helloworld
    NODE_VERSION: v20.16.0
  environment:
      # We installed nodejs using nvm, so we need this to access node and npm excutable file
    PATH: "{{ ansible_env.HOME }}/.nvm/versions/node/{{ NODE_VERSION }}/bin:{{ ansible_env.PATH }}"

  tasks:
    - name: Clone or update repository into {{PROJECT_NAME}} folder
      git:
        repo: "{{ GIT_REPO }}"
        version: "{{ GIT_BRANCH }}"
        dest: "{{ ansible_env.HOME }}/{{ USER }}/{{ PROJECT_NAME }}"
        update: yes
        force: yes

        - name: Install dependencies
      ansible.builtin.shell: "npm install"
      args:
        chdir: "{{ ansible_env.HOME }}/{{ USER }}/{{ PROJECT_NAME }}"


        # My simple application doesn't need this step, but real world
        # application typically does
    # - name: Build the project
    #   ansible.builtin.shell: "npm run build"
    #   args:
    #     chdir: "{{ ansible_env.HOME }}/{{ USER }}/{{ PROJECT_NAME }}"

    - name: Start the project
      ansible.builtin.shell: |
        pm2 stop "{{ PROJECT_NAME }}" || true
        pm2 delete "{{ PROJECT_NAME }}" || true
        pm2 start npm --name "{{ PROJECT_NAME }}" -- start
      args:
        chdir: "{{ ansible_env.HOME }}/{{ USER }}/{{ PROJECT_NAME }}"

Now, go to the ansible directory and run this playbook

ansible-playbook -i inventory.ini deploy-backend.yml

You should see output similar to this, indicating that the deployment was successful:

The result is similar to GitHub actions or any CI/CD tool huh @@

Let's change the “Hello World” message to something else, push it to GitHub and redeploy our application

Recap

We’ve successfully set up an automated deployment for our small VPS

Hope you enjoy my post and find it valuable. Happy coding