Deploying a Vue + AdonisJS monorepo to Heroku via Gitlab CI/CD

Dana Janoskova
ITNEXT
Published in
6 min readJul 7, 2019

--

Such a long headline, right? It’s hard to say everything this is in just a few words!

Why monorepo?

We use monorepos in my workplace a lot. It’s easier to keep all the projects up to date if you’re using micro-service architecture. They’re usually bundled with docker so they’re easy to run and you don’t have to worry about downloading and npm installing new repos all the time. I’m not there yet with my personal monorepo (I’m a Docker noob, or DevOps noob in general), so I have two projects inside.

Step by step

My repository structure looks like this

/my-app
.gitlab-ci.yml
/frontend
.gitlab-ci.yml
/api
.gitlab-ci.yml

Where “my-app” is the root folder, the “frontend” folder is a generated Vue.js app with Vue CLI and the “api” folder is a generated AdonisJS (Node.js) app with AdonisJS CLI. I’m going to show you the code in all the required files below.

This was my first time trying a monorepo approach and writing CI/CD files, so let me alert you: The way the configs are written may not be the best practice. I went step by step trying to get everything to work, collecting from various resources.

Before I walk you through my .gitlab-ci.yml configs, I’d like to mention all the sources that led me to where I am:

Reading those might be a good starting point for those of you who have no idea what they’re doing (much like myself 😁). We all have to start somewhere, right?

https://memegenerator.net/instance/45801509/chemistry-dog-2-i-have-no-idea-what-im-doing

Root .gitlab-ci.yml

I called my app root my-app. This is also the folder I have run git init inside and pushed to my Gitlab account.

My root .gitlab-ci.yml file looks like this

stages:
- dependencies
- build
- deploy

include:
- '/frontend/.gitlab-ci.yml'
- '/api/.gitlab-ci.yml'

Quite simple, right?

What we’re saying here is: “Hey, we will do this: we will install dependencies, we will run build and then we’ll deploy what’s left”.
Those are the stages that the other linked files will run.

We’re also saying: “You’re not done yet! Check out /frontend/.gitlab-ci.yml and /api/.gitlab-ci.yml.”

/frontend .gitlab-ci.yml

'UI dependencies':
image: node:8.15-alpine
stage: dependencies
only:
changes:
- frontend/**/*
- .gitlab-ci.yml
artifacts:
paths:
- frontend/node_modules/
dependencies: []
script:
- cd frontend
- npm install

'UI build':
image: node:8.15-alpine
stage: build
only:
changes:
- frontend/**/*
- .gitlab-ci.yml
artifacts:
paths:
- frontend/dist/
dependencies:
- 'UI dependencies'
script
:
- cd frontend
- npm run build

'UI deploy':
stage: deploy
image: "ruby:2.5"
script
:
- apt-get update -qy
- apt-get install rubygems ruby-dev -y
- gem install dpl
- cd frontend
- dpl --provider=heroku --app=$HEROKU_FRONTEND --api-key=$HEROKU_API_KEY --skip_cleanup
only:
changes:
- frontend/**/*
- .gitlab-ci.yml

This one’s a little longer. We’re utilizing all the 3 stages that we have defined: dependencies, build and deploy.

  • In the dependencies part, we’re basically running npm install. We’re also defining when to trigger said stage (the “changes” part). Notice the cd frontend. As we’re not working with a single folder, we have to be very careful to set the paths correctly.
  • In the build part, we’re almost doing the same, but we’re running npm run build instead. We have to cd again to have our script working. Notice the artifacts part, which is kind of a magic for me — as I said I followed articles of people who’re most likely more experienced in this than me. It’s there to tell the config where exactly our (built) app is located.
  • The last part is the deploy. As you can see, we’re doing actual heroku deploy here which is dependent on two external variables — $HEROKU_FRONTEND and $HEROKU_API_KEY.

$HEROKU_FRONTEND is the name of my Heroku application where I’m serving the front-end application. The variable value is defined in my Gitlab -> Settings -> CI/CD -> Variables. So is my $HEROKU_API_KEY.

Notice the steps are also called in a nice way, such as “UI dependencies” or “UI build”. That’s so we’re able to see clearly what’s happening in Gitlab:

People often don’t see the 100 fails behind 1 success

To serve the application, I also have a server.js file in the root of my /frontend directory that looks like this (you can read about this in more detail in the article that describes how to deploy Vue apps to Heroku). To my knowledge, this (server.js) is the file that Heroku looks for automatically when using Node.

const express = require('express')
const serveStatic = require('serve-static')
const path = require('path')

const app = express()
app.use('/', serveStatic(path.join(__dirname, '/dist')))

const port = process.env.PORT || 5000
app.listen(port)

console.log('Server started on port ' + port)

You need both the server.js and .gitlab-ci.yml files in order for your Vue.js app to run.

/api .gitlab-ci.yml

This one’s a little less scary:

'API deploy':
stage: deploy
image: "ruby:2.5"
script
:
- apt-get update -qy
- apt-get install rubygems ruby-dev -y
- gem install dpl
- cd api
- dpl --provider=heroku --app=$HEROKU_API --api-key=$HEROKU_API_KEY --skip_cleanup
only:
changes:
- api/**/*
- .gitlab-ci.yml

The only stage we need to run here is the deploy stage. My understanding is that Heroku does npm install and looks for the server.js file on its own (I might be wrong, as I said getting this to work was more about experiments and less about knowledge 😅)

Notice the cd api again. We have to make sure we’re in the right folder.

We’re using also another variable that’s called $HEROKU_API and it’s again defined in my Gitlab -> Settings -> CI/CD -> Variables. This is the API application name that’s independent from the front-end application.

My server.js is a traditional file generated by AdonisJS CLI, with the addition of WebSockets. This should be located in your directory automatically.

'use strict'

const
{ Ignitor } = require('@adonisjs/ignitor')

new Ignitor(require('@adonisjs/fold'))
.appRoot(__dirname)
.wsServer()
.fireHttpServer()
.catch(console.error)

There’s also a Procfile in my /api folder with the following contents:

// Procfile

release: ENV_SILENT=true node ace migration:run --force
web: ENV_SILENT=true npm start

You can read more about this in the Deploying AdonisJS Apps to Heroku article.

Epilogue

In case it’s not clear, I’m running 2 Heroku applications — one for the API and one for the frontend. Their names are the variables $HEROKU_FRONTEND and $HEROKU_API. You can find the $HEROKU_API_KEY in your Heroku Account -> Settings -> API key. As this is unique per account, you use the same key if your apps are under the same account.

You can find the configs bundled at https://gitlab.com/djanoskova/my-app.

Setting something like this up is a lot of work and a lot of waiting for pipelines to succeed (or fail?). In case you have a monorepo with a similar stack like mine and you’re also a beginner, I hope that I was able to ease some of your load.

In case something’s not working for you, don’t hesitate to ask me and I’ll try to help. Reading those articles over can also give you some clarity. In the end, don’t forget to code what you read — the hands-on experience is the most important.

--

--

My current stack consists of JS, TS, Node.js and Dart, coding Vue.js, React or Flutter applications. I focus on delivering good UX and writing clean code.