Bitbucket Pipelines CI\CD concerns for a Ruby on Rails API and React Frontend in subfolders same repo

Filipe Martins
Runtime Revolution
Published in
5 min readNov 16, 2023

--

The goal of this blog post is to quickly show how can you define your Bitbucket Pipelines if your Ruby on Rails application is inside of a subfolder and not accessible from the root of a repository.

This structure is something here at Runtime Revolution we don’t recommend. We always advise keeping only one application per repository. But sometimes this can be a reality or requirement for some reason and that’s why we are writing about how to solve some concerns with this state.

In this blog post, we won’t enter into details. We will focus on Concerns. For details, I advise you to check “Ruby on Rails CI\CD with Bitbucket Pipelines” which tries to explain all the CI\CD steps for a repository with only one application.

Just one note, this CI\CD will respect the same Git workflow from the mentioned blog post above. Any change in all branches that aren’t the production branch will trigger the CI, and all changes in the production branch main will trigger CD.

Now let’s imagine the following repository structure:

▼ project_name
ᐅ api
ᐅ frontend
- README.md

Inside the api folder, we have an API in Ruby on Rails to be consumed by the React application inside of the folder frontend.

Some concerns with this structure.

Both API and Frontend have their unit tests, if changes only occur on one side there is no reason to consume time running tests from both. The Continuous Integration (CI) should be smart to only validate what has changed.

Since each application is inside of a folder it needs to keep in mind we need sometimes to navigate into that folder before running the required commands.

And for the Continuous Delivery (CD) part the same way, we don’t want to redistribute both applications if only one has changed. Also, you don’t want to push\send code that doesn’t make sense to exist on the cloud platform that makes your applications available on the Web.

Let’s add the required bitbucket-pipelines.yml to our repository, resulting in the following structure:

▼ project_name
ᐅ api
ᐅ frontend
- bitbucket-pipelines.yml
- README.md

The added file bitbucket-pipelines.yml will have all logic for both applications, and both Continuous Integration and Delivery. Here’s the content with some Concern comments that we will discuss after:

image: ruby:3.1.3

definitions:
caches:
# Concern 1
bundler:
key:
files:
- api/Gemfile.lock
path: /usr/local/bundle
# Concern 2
node:
key:
files:
- frontend/package-lock.json
path: frontend/node_modules
services:
postgres:
image: postgres
environment:
POSTGRES_DB: postgres
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres

pipelines:
branches:
main:
- stage:
name: Prepare and Deploy API
# Concern 3
condition:
changesets:
includePaths:
- 'api/**'
steps:
# Concern 4
- step:
name: API Prepare
script:
- cd api
- tar --exclude='.git' -cvzf /tmp/app.tar.gz .
- mv /tmp/app.tar.gz .
artifacts:
- api/app.tar.gz

- step:
name: API Deploy
script:
- pipe: atlassian/heroku-deploy:2.1.0
variables:
HEROKU_API_KEY: $HEROKU_API_KEY
HEROKU_APP_NAME: $HEROKU_API_APP_NAME
ZIP_FILE: api/app.tar.gz
WAIT: 'true'
- stage:
name: Prepare and Deploy Frontend
# Concern 5
condition:
changesets:
includePaths:
- 'frontend/**'
steps:
# Concern 6
- step:
name: Frontend Prepare
script:
- cd frontend
- tar --exclude='.git' -cvzf /tmp/app.tar.gz .
- mv /tmp/app.tar.gz .
artifacts:
- frontend/app.tar.gz

- step:
name: Frontend Deploy
script:
- pipe: atlassian/heroku-deploy:2.1.0
variables:
HEROKU_API_KEY: $HEROKU_API_KEY
HEROKU_APP_NAME: $HEROKU_FRONTEND_APP_NAME
ZIP_FILE: frontend/app.tar.gz
WAIT: 'true'
default:
- stage:
name: Setup and Validate API
condition:
# Concern 7
changesets:
includePaths:
- 'api/**'
steps:
- step:
# Concern 8
name: API Setup
caches:
- bundler
script:
- cd api
- bundle install

- step:
# Concern 9
name: API Tests
caches:
- bundler
services:
- postgres
script:
- cd api
- bundle exec rake
artifacts:
- api/coverage/coverage.json

- step:
# Concern 10
name: API Lint
caches:
- bundler
script:
- cd api
- bundle exec rubocop

- step:
name: API Coverage
script:
- >
if ! command -v jq &> /dev/null; then
apt-get update && apt-get install -y jq
fi
- >
covered_percent=$(cat api/coverage/coverage.json | jq -r '.metrics.covered_percent');
echo "Coverage ($covered_percent%)";
required_coverage=80;
echo "Minimum coverage ($required_coverage%)";
if (( $(echo "$covered_percent < $required_coverage" | bc -l) )); then
echo "Coverage ($covered_percent%) is below the required threshold of $required_coverage%."
exit 1
else
echo "Coverage ($covered_percent%) passed the required threshold of $required_coverage%."
fi
- stage:
name: Setup and Validate Frontend
condition:
# Concern 11
changesets:
includePaths:
- 'frontend/**'
steps:
- step:
# Concern 12
name: Frontend Setup
image: node:18.16.1-slim
caches:
- node
script:
- cd frontend
- npm ci

- step:
# Concern 13
name: Frontend Tests
image: node:18.16.1-slim
caches:
- node
script:
- cd frontend
- npm test --if-present

- step:
# Concern 14
name: Frontend Lint
image: node:18.16.1-slim
caches:
- node
script:
- cd frontend
- >
if ! [ -f frontend/.eslintrc.js ]; then
npm run lint
else
echo "ESLint not configured"
fi

- step:
# Concern 15
name: Frontend Build
image: node:18.16.1-slim
caches:
- node
script:
- cd frontend
- npm run build

Concern 1

In the caches definition for the Ruby on Rails dependencies we use the default folder location bundler uses. And if you want to troubleshoot that location you can add temporally the command bundle config path after the bundle install in the same step.

We don’t change that folder location to a custom folder to simplify the remaining concerns regardless Ruby because it would require us to perform that change\command in each future steps. And what sometimes happens is you forget about that command and steps start failing.

Concern 2

For node, we just need to keep in mind that we are running that project inside of the folder frontend and we need to indicate.

Concerns 3, 5, 7, and 11

In these concerns, we want to make sure the CI\CD only runs if did occur changes for the respective application. If no changes occur on the API side there is no reason to waste CI time on that. The same for distribution we don’t want to re-distribute an application that doesn’t have any changes compared to what is already distributed.

Concerns 4, 6, 8, 9, 10, 12, 13, 14, 15

On all these concerns we just need to remember to navigate into the respective folder to perform the pretended commands.

Concerns 4, 6, and 9

In these concerns note that the artifacts' location is always related to where the applications are. Then when we need to read them they will be in that folder level.

Conclusion

In this blog post, we explored and learned the concepts of Continuous Integration and Continuous Delivery for a repository with multiple applications in different environments. By automating the testing and deployment processes, you can ensure your application is always in a reliable state and ready for release. Bitbucket Pipelines’ flexibility and integration with your existing repositories make it an ideal choice for streamlining your development workflow.

Remember, CI/CD is not just a one-time setup; it’s an ongoing process. As your application evolves, keep iterating and refining your workflows to accommodate new requirements and improve overall efficiency. Happy coding!

--

--