GitLab CI monorepo setup
GitLab CI/CD is a powerful continuous integration/delivery tool. It currently offers 400 free pipeline minutes per month on public and private repositories.
Recently, I've split one of my projects into multiple modules (client, server, docs) using the monorepo architecture. In this article, I'll explain how to adjust the GitLab CI configuration to support such workflows.
Let's start with a basic
yarn, but the principle can be applied to any other use case:
image: node:10 stages: - test before_script: - yarn install --pure-lockfile --prefer-offline --cache-folder .yarn test: stage: test script: - yarn lint - yarn test - yarn build cache: paths: - node_modules/ - .yarn
The CI pipeline consists of a single
test stage that runs a linter, checks unit tests and makes sure a build doesn't fail. You might consider splitting those tasks into multiple jobs and/or stages. However, while
build can be run in parallel, the CI setup time may lead to an overall slower pipeline execution.
node_modules and the yarn cache are stored between jobs. Notice that I'm passing the yarn cache folder directly to the
install command (instead of using
yarn config set cache-folder .yarn) because of an open yarn defect.
A basic multirepo has the following structure:
├─ 📁 client | ├─ 📁 src | └─ 📄 package.json ├─ 📁 server | ├─ 📁 src | └─ 📄 package.json └─ 📄 .gitlab-ci.yml
We still have only a single
.gitlab-ci.yml file in the project root, but all sources are split into multiple directories.
To run the CI for both modules, we'll create two independent jobs:
image: node:10 stages: - test client: stage: test before_script: - cd client - yarn install --pure-lockfile --prefer-offline --cache-folder .yarn script: - yarn test - yarn build cache: key: client paths: - client/.yarn - client/node_modules/ server: stage: test before_script: - cd server - yarn install --pure-lockfile --prefer-offline --cache-folder .yarn script: - yarn test - yarn build cache: key: server paths: - server/.yarn - server/node_modules/
Some things to consider:
- All paths are relative to the project root, not the subfolder.
- Before every job we have to
cdinto the corresponding subdirectory.
- Every job defines a custom cache by using different
The config file works fine, but there's room for improvement:
- Remove code duplicates with extend and variables.
- Split config into multiple files with include.
- Run every module with different node versions to ensure compatibility. I'm using dedicated jobs per node version, but as of GitLab 13.3, a job matrix is supported natively.
stages: - test .job: image: node:10 stage: test before_script: - cd $DIR - yarn install --pure-lockfile --prefer-offline --cache-folder .yarn script: - yarn test - yarn build cache: key: $DIR paths: - $DIR/.yarn - $DIR/node_modules/ include: - local: "/client/.gitlab-ci.yml" - local: "/server/.gitlab-ci.yml"
client-node-10: image: node:10 variables: DIR: client extends: .job client-node-12: image: node:12 variables: DIR: client extends: .job
server-node-10: image: node:10 variables: DIR: server extends: .job server-node-12: image: node:12 variables: DIR: server extends: .job
Keep in mind that the configuration is always relative to project root, even if it's defined in a subfolder.
After running the pipeline, we'll see 4 different jobs:
In the end, there's nothing special about a monorepo when using GitLab CI, except for executing all commands from subdirectories instead of the project root.
For the full code and demo, see this GitLab repository.