Continuous deployment of Node.js to Azure VM
Fullstack Developer and Tech Author
Virtual machines (VM) offer great flexibility for hosting web applications. As a developer or engineer, you can configure and control every piece of software and every setting that the application needs to run. Azure, one of the largest cloud hosting platforms, has virtual machine offerings for both Linux and Windows-based operating systems.
By implementing continuous integration and continuous deployment (CI/CD), you can fully leverage the scalability and agility of cloud-based virtual machines, automatically updating your applications in response to changes in the codebase without manual intervention. In this tutorial, you will learn how to set up a continuous deployment pipeline to deploy a Node.js application to an Azure virtual machine.
Prerequisites
To follow this post, a few things are required:
- Node.js installed on your system (version >= 10.3)
- An Azure account
- A CircleCI account
- A GitHub account
- Azure CLI installed on your system
With all these installed and set up, you can begin the tutorial.
Cloning the Node.js project
First you need to clone the project that you will be deploying to the Azure VM. This project is a basic Node.js API with a single endpoint for returning an array of todo
objects. Go to the location where you want to store the project and clone it:
git clone --single-branch --branch base-project https://github.com/CIRCLECI-GWP/cd-node-azure-vm.git
Once the project has been cloned, go to the root of the project and install dependencies:
cd cd-node-azure-vm
npm install
Run the application using the npm run dev
command. The application will start up at the address http://localhost:3000
. When the application is up and running, enter http://localhost:3000/todos
in your browser to review the list of todos
.
Now, go to the package.json
file of your project and add these scripts in the scripts
sections:
"scripts": {
.....,
"stop": "pm2 kill",
"start": "pm2 start server.js"
}
The start
and stop
scripts will use the pm2 process manager to start and stop the Node.js application on the VM. The pm2
script will be installed globally on the VM when it has been set up.
At the root of the project, run the rm -rf .git
command to remove any .git
history. Then push the project to GitHub. Make sure that this is the GitHub account connected to your CircleCI account.
Setting up a virtual machine on Azure to run Node.js
Next, create a new VM on Azure and set its environment up for hosting the Node.js application. These are the steps:
- Create a new VM instance.
- Install nginx.
- Configure
nginx
to act as a proxy server. Route all traffic to port80
on your VM to the running instance of the Node.js application on port3000
. - Install Node.js on the VM and clone the app from the GitHub repo into a folder in the VM.
- Install
pm2
globally.
Do not be intimidated by the complexity of these steps! You can complete all five with one command. At the root of your project, create a new file named cloud-init-github.txt
; it is a cloud-init file. Cloud-init is an industry-standard method for cloud instance initialization.
In the cloud-init file, enter:
#cloud-config
package_upgrade: true
packages:
- nginx
write_files:
- owner: www-data:www-data
path: /etc/nginx/sites-available/default
content: |
server {
listen 80;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection keep-alive;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
runcmd:
# install Node.js
- 'curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -'
- 'sudo apt-get install -y nodejs'
# clone GitHub Repo into myapp directory
- 'cd /home/azureuser'
- git clone "https://github.com/CIRCLECI-GWP/nodejs-azure-vm" myapp
# Install pm2
- 'sudo npm install pm2 -g'
# restart NGINX
- service nginx restart
All five of the steps I listed earlier are included in this file. Make sure you replace the sample GitHub repo with the name of your repository in the git clone
command. Note that you are cloning the project in a myapp
folder inside the home directory: /home/azureuser
.
Next, use the configuration in the file above to create a new VM instance on Azure. Make sure that you are logged in to Azure on your CLI (run az login
to log in):
First, you need a resource group. Run the following command to create one:
az group create --name Demos-Group --location eastus
Note: You don’t have to create a new resource group for this. You can use an existing one if you prefer.
Next, run this command to create the VM instance:
az vm create --resource-group Demos-Group --name node-vm --image Ubuntu2204 --admin-username azureuser --custom-data cloud-init-github.txt --generate-ssh-keys
Note the value of each parameter set in the command:
Demos-Group
is the Azure resource group you are creating the VM in.node-vm
is the name of your VM.eastus
is the region you are creating the VM in.Ubuntu2204
is the VM operating system.azureuser
is the admin user for the VM; it will be used to connect via SSH into the VM.cloud-init-github.txt
is the cloud VM configuration file you just wrote.
When the command is done running, a response object is printed to the screen. Make sure you save the privateIpAddress
property of the object. privateIpAddress
is the IP address you will use to access your application in the browser and via ssh
.
Go to your resources page in the Azure portal. Click Virtual Machines to review your VM instance.
Web port 80
is not open by default on the VM. You need to open this port explicitly to allow web requests to hit the server. To open port 80
, run:
az vm open-port --port 80 --resource-group Demos-Group --name node-vm
Note that the VM name and resource group are passed in this command. When the command is done, a big chunk of JSON is printed on your CLI. We are not using it in the tutorial, so feel free to ignore it.
Generating SSH keys on the server
Your next step is to generate ssh
keys on the server to give the continuous deployment pipeline script access to the VM.
SSH into your server, making sure to replace YOUR-PUBLIC-IP-ADDRESS
with what was returned when you set up the VM:
ssh azureuser@YOUR-PUBLIC-IP-ADDRESS
This command gets you into the home directory. Go to the .ssh
folder by running cd .ssh
. Generate the ssh keys:
ssh-keygen
Press Enter to accept the default location with id_rsa
as the file name. CircleCI requires an empty passphrase for access, so press Enter twice in response to the passphrase
and confirm passphrase
prompts.
Next, append the contents of the public key to the authorized_keys
file by running:
cat id_rsa.pub >> authorized_keys
Then, print out the contents of the private key:
cat id_rsa
Copy this information and save it in a safe location. You will be adding it to CircleCI later on.
Assigning permissions to the VM user
When the project folder was cloned during the set-up process, the root
user created the myapp
folder. That means that your VM’s admin user, azureuser
, cannot update the contents of the myapp
folder. You need to give your VM’s admin user the permissions to update the myapp
folder.
To begin, make sure that you have moved out of the .ssh
folder back to the home directory containing myapp
. Change azureuser
to have root
as its default group using the command:
sudo usermod -g root azureuser
Next, you need to change the owner and group of all of the files in myapp
to azureuser
and root
. Use the command:
sudo chown -R azureuser:root myapp
Now, if you run ls -l myapp
, you will see the output indicating that azureuser
has ownership of myapp
and its content.
You can now set the privileges on all folders and files to read/write/execute for owner
and group
and to nothing for public
. Enter:
sudo chmod -R 770 myapp
Configure the deployment pipeline
At the root of your project, create a folder and name it .circleci
. In that folder, create a file named config.yml
. Inside config.yml
, enter this code:
version: 2.1
jobs:
build:
working_directory: ~/repo
docker:
- image: cimg/node:18.10.0
steps:
- checkout
- add_ssh_keys:
fingerprints:
- $AZURE_VM_SSH_FINGERPRINT
- run:
name: Copy updated files to VM
command: scp -o StrictHostKeyChecking=no -r ./* $AZURE_VM_USER@$AZURE_VM_IP:~/myapp
deploy:
machine:
enabled: true
steps:
- run:
name: Deploy Over SSH
command: |
ssh $AZURE_VM_USER@$AZURE_VM_IP "cd myapp && sudo npm run-script stop && sudo npm install && sudo npm start"
workflows:
version: 2
build:
jobs:
- build:
filters:
branches:
only: main
- deploy:
requires:
- build
filters:
branches:
only: main
Your new config.yml
file contains two jobs.
The build
job checks out the code and uses the SSH key to securely copy the updated files from the application to the Azure VM myapp
folder, using the scp
command. The StrictHostKeyChecking=no
part of the command suppresses the prompt asking for a confirmation check for the host key. This keeps the prompt from blocking the automated process.
The deploy
job then deploys the application over SSH. It goes into the myapp
folder, stops the app, installs dependencies, and restarts the application.
The config.yml
file contains a workflow
definition that makes sure the build
job completes successfully before deploy
is run. The workflow also makes sure that deployments take place only when code is pushed to the main
branch. That prevents deploying the application when team members are pushing to feature branches.
The next step is to update the repository with the new update. Review Pushing a project to GitHub for instructions.
Add the project to CircleCI
To begin, make sure that you have pushed the latest updates to your project to GitHub. Next, log in to your CircleCI account. If you signed up with your GitHub account, all your repositories will be available on your project’s dashboard.
Locate your nodejs-azure-vm
project and click Set Up Project.
Enter main
as the name of the GitHub branch containing your CircleCI configuration when prompted, then click Set Up Project.
CircleCI will then start the pipeline, which will run the tests but fail to deploy. This build failed because you have not yet set up the configuration file with the variables for your virtual machine on Azure.
Create pipeline configuration environment variables
To fix this, start by adding the SSH key you saved earlier (the private key you copied to a secure location). Go to the Project Settings page of your project. From the side menu, click SSH Keys . Scroll down to the Additional SSH Keys section and click Add SSH Key. When prompted, enter your public IP in the Hostname field and your private key in the Private Key field. Click Add SSH Key to save the information.
After adding the key, it will be shown in a Hostname/Fingerprint pair on a table in the Additional SSH Keys section. Copy this fingerprint. It will be required in the pipeline configuration file.
Because the VM user and public IP will be used in the pipeline configuration, it is good practice to make them environment variables. From the side menu, click Environment Variables and enter the following information:
- For
AZURE_VM_SSH_FINGERPRINT
, enter the fingerprint that was generated after you added the SSH key. - For
AZURE_VM_USER
enter the VM admin,azureuser
. - In
AZURE_VM_IP
enter your VM public IP address.
Go back to the dashboard. Click Rerun Workflow from Failed. This will trigger the workflow, which should build successfully this time.
Click the build
job to review the details.
Next, click the deploy
job to view its details.
So the workflow is running smoothly. That is great, but a more convincing test of the process is to open the deployed application in the browser. In your browser, load the endpoint http://[YOUR_PUBLIC_IP]/todos
. Make sure you replace the placeholder YOUR_PUBLIC_IP
with the correct information.
Now, that is convincing.
Next, add some more todo
objects to your todos.js
file:
module.exports = [
...,
{
id: 4,
task: "Make Dinner"
},
{
id: 5,
task: "Take out the trash"
}
];
Commit and push your updates to your repository to run the pipeline again. When the workflow is complete, reload your browser to check on your changes.
Conclusion
Virtual machines come with great power and flexibility, and Azure provides one of the most reliable VM offerings in the market. Automating the deployment of your applications to an Azure virtual machine, as demonstrated in this tutorial, combines the reliability of Azure with the ease-of-use of CircleCI. Your team will benefit from having reliable web applications that are easier to deploy.
Happy coding!