- Published on
Simple way to Automate Deployments with VPS using Ansible
- Authors
- Name
- Dinh Nguyen Truong
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
- 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.
- Run the application using pm2
pm2 start npm --name "helloworld" -- start
You should see your application started
- 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
Basic understanding of Ansible, if you’re not familiar with this tool, then I think this article might help https://medium.com/@wintonjkt/ansible-101-getting-started-1daaff872b64
Ansible installed Installation Guide — Ansible Community Documentation
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:
ansible/inventory.ini:
This file defines the servers Ansible will manage.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