Search code examples
dockerdevopsgitlab-cimonoreponomachine-nx

How to build Nx monorepo apps in Gitlab CI Runner


I am trying to have a gitlab CI that performs the following actions:

  • Install yarn dependencies and cache them in order to don't have to yarn install in every jobs
  • Test all of my modified apps with the nx affected command
  • Build all of my modified apps with the nx affected command
  • Build my docker images with my modified apps

I tried many ways to do it in my CI and no one of them worked. I'm very stuck actually.

This is my actual CI :

default:
  image: registry.gitlab.com/xxxx/xxxx/xxxx

stages:
  - setup
  - test
  - build
  - forge

.distributed:
  interruptible: true
  only:
    - main
    - develop
  cache:
    key:
      files:
        - yarn.lock
    paths:
      - node_modules
      - .yarn
  before_script:
    - yarn install --cache-folder .yarn-cache --immutable --immutable-cache --check-cache
    - NX_HEAD=$CI_COMMIT_SHA
    - NX_BASE=${CI_MERGE_REQUEST_DIFF_BASE_SHA:-$CI_COMMIT_BEFORE_SHA}
  artifacts:
    paths:
      - node_modules

test:
  stage: test
  extends: .distributed
  script:
    - yarn nx affected --base=$NX_BASE --head=$NX_HEAD --target=test --parallel=3 --ci --code-coverage

build:
  stage: build
  extends: .distributed
  script:
    - yarn nx affected --base=$NX_BASE --head=$NX_HEAD --target=build --parallel=3

forge-docker-landing-staging:
  stage: forge
  services:
    - docker:20.10.16-dind
  rules:
    - if: $CI_COMMIT_BRANCH == "develop"
      allow_failure: true
    - exists:
        - "dist/apps/landing/*"
      allow_failure: true
  script: 
    - docker build -f Dockerfile.landing -t landing:staging .

Currently here is what works and what doesn't :

  • ❌ Caching don't work, it's doing yarn install in every jobs that got extends: .distributed
  • ✅ Nx affected commands work as expected (test and build)
  • ❌ Building the apps with docker is not working, i have some trouble with docker in docker.

Solution

  • Problem #1: You don't cache your .yarn-cache directory, while you explicitly set in in your yarn install in before_script section. So solution is simple - add .yarn-cache to your cache.paths section

    Regarding

    it's doing yarn install in every jobs that got extends: .distributed

    It is intended behavior in your pipeline, since "extends" basically merges sections of your gitlab-ci config, so test stage basically uses the following bash script in runner image:

    yarn install --cache-folder .yarn-cache --immutable --immutable-cache --check-cache
    NX_HEAD=$CI_COMMIT_SHA
    NX_BASE=${CI_MERGE_REQUEST_DIFF_BASE_SHA:-$CI_COMMIT_BEFORE_SHA}
    yarn nx affected --base=$NX_BASE --head=$NX_HEAD --target=test --parallel=3 --ci --code-coverage
    

    and build stage differs only in one last line

    When you'll cache your build folder - install phase will be way faster.

    Also in this case

      artifacts:
        paths:
          - node_modules
    

    is not needed, since it will come from cache. Removing it from artifacts will also ease the load on your gitlab instance, node_modules is usually huge and doesn't really make sense as an artifact.

    Problem #2: What is your artifact?

    You haven't provided your dockerfile or any clue on what is exactly produced by your build steps, so i assume your build stage produces something in dist directory. If you want to use that in your docker build stage - you should specify it in artifacts section of your build job:

    build:
      stage: build
      extends: .distributed
      script:
        - yarn nx affected --base=$NX_BASE --head=$NX_HEAD --target=build --parallel=3
      artifacts:
        paths:
          - dist
    

    After that, your forge-docker-landing-staging job will have an access to your build artifacts.

    Problem #3: Docker is not working!

    Without any logs from your CI system, it's impossible to help you, and also violates SO "one question per question" policy. If your other stages are running fine - consider using kaniko instead of docker in docker, since using DinD is actually a security nightmare (you are basically giving root rights on your builder machine to anyone, who can edit .gitlab-ci.yml file). See https://docs.gitlab.com/ee/ci/docker/using_kaniko.html , and in your case something like job below (not tested) should work:

    forge-docker-landing-staging:
      stage: forge
      image:
        name: gcr.io/kaniko-project/executor:v1.9.0-debug
        entrypoint: [""]
      rules:
        - if: $CI_COMMIT_BRANCH == "develop"
          allow_failure: true
        - exists:
            - "dist/apps/landing/*"
          allow_failure: true
      script: 
        - /kaniko/executor
          --context "${CI_PROJECT_DIR}"
          --dockerfile "${CI_PROJECT_DIR}/Dockerfile.landing"
          --destination "${CI_REGISTRY_IMAGE}:landing:staging"