How to deploy Node.js app on AWS with GitLab
We are going to achieve following things in this article:
- Have GitLab run
lint
andtest
on every branch of our code (Continuous Integration) - If everything passes in previous step, tell GitLab to deploy these changes to AWS EC2 instances only for master branch(Continuous Deployment)
Continuous Integration with GitLab
GitLab has excellent support for CI. To enable this, we need to create .gitlab-ci.yml
file.
Lets understand this file.
Line 2: Tells which Docker image to use
Line 5,6: Run npm install before any job
Line 8–9: We have created only 1 stage with a name test
, could be any name
Line 15–18: We have created a job with a name lint
. Again this could be any name. We want this job to run in a test stage. We want it to run npm run lint
command. lint
script is defined in our package.json
file.
Line 20–23: Same as previous job but this time, run npm run test
command
Since we have not provided any branch, this would run on all the branches whenever a commit is made. Important thing to note is that all the jobs ( lint
and test
) will run in parallel as they are part of the same stage. If you want them to run sequentially, you can write multiple command inside script
tag.
Official documentation has more details about the yml config file.
Once we commit this file, GitLab would start running your script and if everything passes, we would see something like this inside our pipeline.
Continuous Deployment with GitLab
We need to do three steps for deployment
- Update our GitLab config to prepare our docker image
- Enable GitLab container to ssh into our remote servers
- Take the latest changes from GitLab and restart server
Update .gitlab-ci.yml
Lets update our .gitlab-ci.yml
file to reflect our new requirement
We have made three changes in the above file.
- Line 6: We want to make sure our instance has
openssh-client
installed. More on this later. - Line 11: We have added another stage called
deploy
. It will only run after successful completion oftest
stage. - Line 29–34: We would like to run this on master branch only. We want it to run a script located at
deploy/deploy.sh
Enable GitLab container to ssh into AWS EC2 instance
We need to solve for two problems:
- How to give
ssh
key and server information to GitLab docker container so that it can ssh into EC2 instance: If we want to ssh into our AWS EC2 instance from our local machine, we do something like this
ssh -i myPemFile.pem ubuntu@<ip address of the instance>
It’s not a good idea to commit the key in our source code even if its private repo. So we will keep it in GitLab as an environment variable. Go to “Settings => CI/CD” and create two variables:
PRIVATE_KEY <Insert the key you downloaded when creating an EC2 instance>DEPLOY_SERVERS <comma separated ip values of all the servers to which you want to deploy this code>
These variables would be available to us in our docker container as environment variables. We installed ssh-agent
in our GitLab docker container so that we can ssh into the EC2 instance. Lets start the ssh agent and add this key to this docker image.
eval $(ssh-agent -s)
echo "$PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null
GitLab at times add spaces which will fail ssh
into the EC2 instance and hence the tr -d ‘\r'
. Issue here.
- How to disable the prompt for
hostKeyChecking:
Whenever we ssh into a EC2 instance that we have never sshed before, we get a prompt like this
We need to disable this as it would stop our deployment.
To disable this, we need to create a entry in ~/.ssh/config
to looking something like this
Host *
StrictHostKeyChecking no
Make sure this file is executable. We can change the permission with chmod a+x
during commit and during our deploy script.
Now that we have solved both the problems, all we need is a bash script which can loop through the all the ips, ssh into each one of them and execute the script
DEPLOY_SERVERS=$DEPLOY_SERVERS // We defined this in GitLab
ALL_SERVERS=(${DEPLOY_SERVERS//,/ })
for server in "${ALL_SERVERS[@]}"do
ssh ubuntu@${server} 'bash' < ./deploy/updateAndRestart.sh
done
So our full deploy.sh
would look like this
Take the latest changes from GitLab and restart server
This is what we want after we ssh into our box.
- Delete our own repository and clone again: Since we are not using any tool like capistrano etc to manage our deployment, a reliable way to do this is to delete the old code and clone again. If you have a better solution than deleting the code, please leave it in comments.
- npm install:The catch here is when we do non-interactive ssh into an instance. This menas that
~/.bashrc
is not loaded which in turn load ournvm.sh
file which loads our node. So lets load it explicitly. Here is a great article on Configuring your login sessions with dot files - restart server: Next problem we need to tackle is pm2 restart. We cannot run pm2 from the source folder as we are deleting this folder and cloning again. PM2 refer the old folder which is not existent and will give error. This is the reason we do not start pm2 daemon from the source repo.
That’s it!! Now every time a commit/merge is made into the master branch, it would be deployed to all the servers.
The full code is available here
Happy coding!!
If you found this story interesting or useful, please support it by clapping it👏 .