Deploying a Web App to a Server with Ansible, Part I: API
During the past few years, I have been developing a math-related web app. Now I wanted to deploy it to my LAMP server that I have discussed in some previous articles. I've decided to use Ansible, and I've described setting it up in WSL in this article.
Preparing the Server
For first, I had to do some preparations on the server. I SSHed to the server and ran the following commands:
sudo apt-get update
sudo apt-get install nodejs
sudo apt-get install npm
Next, I tried to install the PM2 Node process manager globally via NPM. This failed as I wasn't executing the install command with sudo
. However, using sudo
to install NPM packages is a security risk, so I had to use a different approach. I wanted to keep the global packages in my home directory (/home/username
), so I first executed this command:
npm config set prefix ~/.local
Then I created a .bashrc
file in my home directory like this...
nano .bashrc
...and added the following to it:
export PATH=/home/maija/.local/bin/:$PATH
I then ran this command to source the .bashrc
(I was in sh
, not the bash
shell, so the source
command was not working):
. ./.bashrc
Now, I could finally install PM2 with this command:
npm install -g pm2
...and the install was successful.
The API Ansible Playbook Line by Line
My web app consists of a headless Node.js API app, an administration interface written with Node as well, and a static React frontend. Here's a diagram of the app:
I had previously installed MySQL and created a database in it. Now, I wanted to deploy the headless API. I added a directory named ansible
to my app folder and created a playbook file named api.yaml
. Next, I will go through the file line by line.
The first lines just define the name of the playbook and the targeted hosts. I have just one host in my local /etc/ansible/hosts
file (in WSL), so that will be targeted.
---
- hosts: all
name: Math API
Next, I define the tasks included in the playbook. The first tasks create some directories. They set the file permissions to 755
, i.e., readable and executable by anyone but writable only by the owning user. The directories will include the source code from GitHub, the runnable app files, and log files, respectively:
tasks:
- name: Create ts-math directory
ansible.builtin.file:
path: /home/maija/ts-math
state: directory
mode: '0755'
- name: Create app directory
ansible.builtin.file:
path: /home/maija/apps/math-api
state: directory
mode: '0755'
- name: Create log directory
ansible.builtin.file:
path: /home/maija/log
state: directory
mode: '0755'
Next, I'll checkout all the code from GitHub. I added the force
parameter to overwrite files that are edited automatically (like package-lock.json
). The repository is public, so no hassle with keys here:
- name: Checkout code from git repo
ansible.builtin.git:
repo: 'https://github.com/mkkekkonen/TS-Math.git'
dest: /home/maija/ts-math
force: yes
I then upload a file not in source control that includes secrets needed by the app. It will be readable and writable by the owning user, readable by everyone, and executable by no one:
- name: Upload secrets file
ansible.builtin.copy:
src: /mnt/c/path/to/directory/ansible/secrets.json
dest: /home/maija/ts-math/api/src/assets/json/secrets.json
owner: maija
group: maija
mode: '0644'
I then perform some npm
-related tasks. The following tasks will install the API packages, delete the previous build directory if it exists, and build the project.
- name: Install packages
ansible.builtin.shell:
chdir: /home/maija/ts-math/api
cmd: npm install > /home/maija/log/api-install-log.txt
- name: Delete dist directory
ansible.builtin.file:
path: /home/maija/ts-math/api/dist
state: absent
- name: Build project
ansible.builtin.shell:
chdir: /home/maija/ts-math/api
cmd: npm run build > /home/maija/log/api-build-log.txt
Next, I transfer the needed files to the app folder. I first copy the contents of the api/dist
directory (note the slash after the directory name) to the apps/math-api
folder. Then, I copy the package.json
file there. Last, I perform an ugly hack by symlinking the node_modules
directory (ts-math -> apps
) so that I don't need to install the packages again. The app is built with the TypeScript tsc
command so the output does not include the code in node_modules
.
- name: Copy built files
ansible.builtin.copy:
src: /home/maija/ts-math/api/dist/
dest: /home/maija/apps/math-api
remote_src: yes
- name: Copy package.json
ansible.builtin.copy:
src: /home/maija/ts-math/api/package.json
dest: /home/maija/apps/math-api/package.json
remote_src: yes
- name: Link node_modules
ansible.builtin.file:
src: /home/maija/ts-math/api/node_modules
dest: /home/maija/apps/math-api/node_modules
owner: maija
group: maija
state: link
Only the final PM2-related tasks are remaining. I upload the ecosystem file required by PM2 and restart the app. I specify /bin/bash
as the shell executable so that the .bashrc
file with the global npm
packages will be correctly sourced.
- name: Upload pm2 ecosystem file
ansible.builtin.copy:
src: /mnt/c/Users/mkkek/Documents/koodi/js/TS-Math/ansible/mysqlApi.ecosystem.config.js
dest: /home/maija/apps/math-api/ecosystem.config.js
- name: Restart app
ansible.builtin.shell:
executable: /bin/bash
chdir: /home/maija/apps/math-api
cmd: pm2 restart ecosystem.config.js
In the end, I was able to fetch data from the server using the URLs defined in source code:
Sources:
Ansible Documentation: ansible.builtin.file
Ansible Documentation: ansible.builtin.git
Ansible Documentation: ansible.builtin.shell
michaelb: The Right Way™ to do global npm install without sudo
Namecheap: File permissions
PM2 - Home
Stack Overflow: Ansible: copy a directory content to another directory
Stack Overflow: Error message 'source: not found' when running a script