Using Gitlab Pipelines to Jumpstart Your Web App Development

In the modern software development landscape, continuous integration and continuous deployment (CI/CD) are crucial to ensure team collaboration, reduce time-to-market, and maintain deployment consistency. This guide delves into setting up GitLab pipelines for deploying web applications built using Spring Boot and Vue.js onto AWS EC2 instances.


Project Overview

Some time ago, a repository was created for fast prototyping of web applications with Spring and Vue.js. This boilerplate application, ready for AWS EC2 deployment, met basic web application requirements such as SPA UI, a Spring Boot backend, database access, a mailing service, and necessary network configurations.


The project included tools and services like Spring Boot, Vue.js, MongoDB, Docker, Traefik reverse proxy, AWS EC2 configuration, domain setup, Cloudflare, and Mailgun. However, the original application had to be built and deployed manually from a local environment, which posed collaboration challenges.


To address these issues, we can use GitLab CI/CD pipelines, particularly leveraging a GitLab repository seed-spring-vue-aws-ec2. This allows seamless deployment and team collaboration.


Step-by-Step Deployment on EC2

Let's deploy the web application on AWS EC2 in 10 straightforward steps:


  1. Clone the Git repository
  2. Set up your working environment as described in the README
  3. Run the application on localhost using Docker or build tools as per the README
  4. Set up an EC2 instance following instructions in the README
  5. Configure GitLab according to the README
  6. Run the application to make it accessible by IP (refer to the README)
  7. Purchase a domain and configure Cloudflare for redirections
  8. Set up an SMTP server as explained in the README
  9. Run the application to make it accessible by your domain
  10. Enjoy a coffee before you start coding your custom functionalities!

Once deployed, visit your domain to see the application ready for further development. You can see a live example here.


Web Application Overview

The deployed application sends a request to verify communication between the UI and API, visible in the browser's network tab (Dev Tools). Additionally, if SMTP is configured correctly, email functionality can be verified.


Below is a self-explanatory schema of the application's networking setup, which you can find more information about here.


Pipeline Stages and Jobs Explained

Understanding the GitLab pipelines is pivotal for tweaking and expanding the application's deployment process. The pipeline for this project includes several tasks grouped into four stages:

  • Build: Compiles the UI and backend, running tests and generating jar files.
  • Setup: Installs necessary packages and configures the EC2 server.
  • Image: Builds and pushes Docker images for the Spring Boot application and Vue.js UI to AWS ECR.
  • Deploy: Downloads and runs Docker images on the EC2 instance.



Build Stage

The build stage involves two primary tasks: building the API and the UI.


Build: API

built:api
stage: build
image: openjdk:11-jdk-slim
interruptible: true
before_script:
- sed -i s/-all/-bin/ ./seed-spring/gradle/wrapper/gradle-wrapper.properties
script:
- ./seed-spring/gradlew build
after_script:
- '[ -d $GRADLE_USER_HOME/caches ] && find $GRADLE_USER_HOME/caches -name "*.lock" -exec rm -v {} \;'
- '[ -d $GRADLE_USER_HOME/caches ] && find $GRADLE_USER_HOME/caches -name gc.properties -exec rm -v {} \;'
- rm -fr $GRADLE_USER_HOME/caches/*/plugin-resolution/
artifacts:
expire_in: 1 day
paths:
- ./seed-spring/build/libs/
cache:
paths:
- $GRADLE_USER_HOME/caches
- $GRADLE_USER_HOME/dependency-check-data
- $GRADLE_USER_HOME/wrapper

This job compiles the Spring Boot API and produces a jar file. Using the openjdk:11-jdk-slim image ensures the necessary JDK environment.


Build: UI

build:ui
stage: build
image: node:16
interruptible: true
script:
- cd seed-vue
- npm install
- npm run build
artifacts:
paths:
- ./seed-vue/dist
expire_in: 1 day
cache:
key: ${CI_COMMIT_REF_NAME}
paths:
- seed-vue/node_modules/

The Vue.js application is built using the node:16 image. `npm install` and `npm run build` commands ensure production-ready UI artifacts are generated.


Setup Stage

The setup stage prepares the EC2 server, ensuring it is properly configured to run the application.


Setup: EC2

setup-ec2
stage: setup
image: ansible/ansible-runner
before_script:
- mkdir /etc/ansible && echo [ec2-seed]>/etc/ansible/hosts && echo $EC2_IP ansible_user=ubuntu>>/etc/ansible/hosts
&& echo [defaults]>/etc/ansible/ansible.cfg && echo host_key_checking = False>>/etc/ansible/ansible.cfg
- chmod 400 $SSH_KEY_EC2
script:
- ansible-playbook playbook-ec2-configure.yml --private-key $SSH_KEY_EC2 --extra-vars
"seed_hosts=ec2-dev"
rules:
- when: manual

This task installs necessary software like Docker and AWS CLI on the EC2 instance using an Ansible playbook.


Image Stage

The image stage encompasses tasks to build Docker images for both the API and UI and push them to AWS ECR.


Image: API

image:api
variables:
project_directory: seed-spring
docker_image_name: seed-spring-vue/seed-spring:latest
stage: image
image:
name: gcr.io/kaniko-project/executor:debug
entrypoint:
- ''
script:
- mkdir -p /kaniko/.docker
- echo "{\"credsStore\":\"ecr-login\"}" > /kaniko/.docker/config.json
- "/kaniko/executor --context $CI_PROJECT_DIR/${project_directory} --dockerfile
$CI_PROJECT_DIR/${project_directory}/Dockerfile --destination ${ECR_URL}/${docker_image_name}"
when: manual
needs:
- job: build:api

This job uses Kaniko to build and push a Docker image containing the Spring Boot application to ECR, avoiding the need for privileged Docker daemons.


Image: UI

image:ui
variables:
project_directory: seed-vue
docker_image_name: seed-spring-vue/seed-vue:latest
stage: image
image:
name: gcr.io/kaniko-project/executor:debug
entrypoint:
- ''
script:
- mkdir -p /kaniko/.docker
- echo "{\"credsStore\":\"ecr-login\"}" > /kaniko/.docker/config.json
- "/kaniko/executor --context $CI_PROJECT_DIR/${project_directory} --dockerfile
$CI_PROJECT_DIR/${project_directory}/Dockerfile --destination ${ECR_URL}/${docker_image_name}"
when: manual
needs:
- job: build:ui

Similarly, this job builds and pushes the Docker image for the Vue.js UI to AWS ECR.


Deploy Stage

In the deploy stage, Docker images are pulled from AWS ECR and run on the EC2 instance.


Deploy: Domain

deploy:domain
stage: deploy
image: ansible/ansible-runner
needs:
- job: image:api
artifacts: false
- job: image:ui
artifacts: false
variables:
access_mode: env-domain
before_script:
- mkdir /etc/ansible && echo [ec2-seed]>/etc/ansible/hosts && echo $EC2_IP ansible_user=ubuntu>>/etc/ansible/hosts
&& echo [defaults]>/etc/ansible/ansible.cfg && echo host_key_checking = False>>/etc/ansible/ansible.cfg
- chmod 400 $SSH_KEY_EC2
- mkdir -p ~/workspace/secret/${access_mode}
- cp $PB_CONFIG_DOMAIN ~/workspace/secret/${access_mode}/pb-config.yml
script:
- ansible-playbook playbook-run.yml --private-key $SSH_KEY_EC2 --extra-vars "seed_env=${access_mode}
seed_hosts=ec2-seed db_setup=$DB_SETUP"
when: manual

This job runs an Ansible playbook to deploy the application, making it accessible by your domain.


Deploy: IP

deploy:IP
stage: deploy
image: ansible/ansible-runner
needs:
- job: image:api
artifacts: false
- job: image:ui
artifacts: false
variables:
access_mode: env-ip
before_script:
- mkdir /etc/ansible && echo [ec2-seed]>/etc/ansible/hosts && echo $EC2_IP ansible_user=ubuntu>>/etc/ansible/hosts
&& echo [defaults]>/etc/ansible/ansible.cfg && echo host_key_checking = False>>/etc/ansible/ansible.cfg
- chmod 400 $SSH_KEY_EC2
- mkdir -p ~/workspace/secret/${access_mode}
- cp $PB_CONFIG_IP ~/workspace/secret/${access_mode}/pb-config.yml
script:
- ansible-playbook playbook-run.yml --private-key $SSH_KEY_EC2 --extra-vars "seed_env=${access_mode}
seed_hosts=ec2-seed db_setup=$DB_SETUP"
when: manual

This task is akin to the deploy:domain job but deploys the application to be accessible by the EC2 IP address.


Is This Production-Ready?

While the application deployment process is considerably streamlined, it's not fully production-ready yet. Several limitations need addressing, as discussed in my previous articles. However, with this setup, team collaboration is significantly improved, and deployment is no longer tied to individual machines.


Feel free to clone the GitLab repository seed-spring-vue-aws-ec2 and adapt it to your needs. For further details on GitLab pipelines, check out my previous articles here, here, and here.