Andrey Polischuk

How to start using Gitlab CI

Quick start of testing, building and deploying packages or applications with the built-in CI.

Gitlab has a cool integrated CI/CD service. If you have repositories there, you can automate some infrastructure parts of development workflow out of the box.

For example, you can:

Initial setup

Let’s test and publish some package to npm in CI. To configure it just add .gitlab-ci.yml to the root of a repository and push it. And one more thing you need to do is configure runners that is used to run jobs.

In a .gitlab-ci.yml file you define set of jobs you want run in repository.

image: node:8

test:
  script:
    - npm install
    - npm test

In this example, runner uses Docker image with node from Docker Hub.

Also you can specify stages to manage the ordering of jobs’ execution. Jobs defined at the same stages run in parallel, jobs defined at the next stage run after successful completion of the previous stage.

image: node:8

stages:
  - test
  - publish

before_script: npm install

test:
  stage: test
  script: npm test

publish:
  stage: publish
  script: npm publish

In this example publish job only runs after test succeeds.

For each job you need define a shell script or an array of them that are executed.

test:
  script: npm test

# or

test:
  script:
    - npm run lint
    - npm test

Jobs’ execution policy

Runners execute jobs on each push in all refs by default. You can limit running the specific job by define only or except directives in its configuration.

publish:
  stage: publish
  only:
    - tags
  script: npm publish

Now test job runs on each refs, and publish job only runs when tag is pushed. You can set refs to exectute a job using regular expressions or keywords, such a branches, tags, api, schedules, and something else.

Read more about job policy in Gitlab’s docs.

Secret tokens

You can easy automate publishing or deployment workflow with CI variables. It allows publish npm packages from gitlab repository to the npm registry by run only npm version command on local machine.

  1. Generate the npm auth token locally by logging onto npm with npm login command. This will save auth token to npm configuration file ~/.npmrc:

    //registry.npmjs.org/:_authToken=NPM_AUTH_TOKEN
    
  2. Copy this token and set it in NPM_AUTH_TOKEN variable in project secret variables.

  3. Configure .gitlab-ci.yml to setting authToken:

    image: node:8
    
    stages:
      - test
      - publish
    
    before_script: npm install
    
    test:
      stage: test
      script:
        - npm run lint
        - npm test
    
    publish:
      stage: publish
      only:
        - tags
      script:
        - echo "//registry.npmjs.org/:_authToken=$NPM_AUTH_TOKEN" > ~/.npmrc
        - npm publish
    
  4. Add postversion hook to package.json to push the version commit with tags after increasing version:

    {
      ...
      "scripts": {
        "test": "ava",
        "postversion": "git push --follow-tags"
      },
      ...
    }
    

Now when you’re going to publish new release, run npm version. This will update the package.json version, create commit, push it with tags. When tests succeeds, CI will automatically publish this version to npm.

Also you can use variables to store SSH keys that using for deployment to stage or production.

  1. Generate a new SSH keys locally.

    ssh-keygen -t rsa -b 4096
    
  2. Copy the public key to servers you’re going to deploy from CI.

  3. Copy the private key and add it as SSH_PRIVATE_KEY secret variable to project.

  4. Add the private key to get access to servers in CI:

    image: node:8
    
    stages:
      - test
      - stage
    
    before_script: npm install
    
    test:
      stage: test
      script:
        - npm run lint
        - npm test
    
    deploy_stage:
      stage: stage
      only:
        - tags
      script:
        - mkdir -p ~/.ssh
        - echo "$SSH_PRIVATE_KEY" | tr -d '\r' > ~/.ssh/id_rsa
        - chmod 600 ~/.ssh/id_rsa
        - ssh-keyscan -H stage.awesome.app >> ~/.ssh/known_hosts
        - npm run dist
        - rsync -rpl ./dist/ stage.awesome.app:/dists/$CI_COMMIT_TAG/
    
  5. Also add postversion hook to application package.json as in the previous example.

In this case, when you run npm version command, runner tests the new version of application and then automatically deploys it to stage server.

Read more about using SSH keys with Gitlab CI.

Manual jobs

Next stage jobs run automatically by default after all previous jobs have passed. But you can execute jobs manually by using when directive.

deploy_stage:
  stage: stage
  only:
    - tags
  when: manual
  script:
    - mkdir -p ~/.ssh
    - echo "$SSH_PRIVATE_KEY" | tr -d '\r' > ~/.ssh/id_rsa
    - chmod 600 ~/.ssh/id_rsa
    - ssh-keyscan -H stage.awesome.app >> ~/.ssh/known_hosts
    - npm run dist
    - rsync -rpl ./dist/ stage.awesome.app:/dists/$CI_COMMIT_TAG/

Following this configuration, deploy job needs to be started by a user from pipeline.

Caching

If you build or compile package before publishing, you’ll need install dependencies before running every job. Let’s speed up testing and publishing. You can specify cache directive with list of files which should be cached between jobs. If there are a lot of jobs, you reduce extraneous downloading the dependencies for each job. By default Gitlab doesn’t cache files untracked by git.

Add node_modules directory to caching list:

image: node:8

stages:
  - test
  - publish

cache:
  paths:
    - node_modules/

test:
  stage: test
  script:
    - npm install
    - npm run lint
    - npm test

publish:
  stage: publish
  only:
    - tags
  script:
    - echo "//registry.npmjs.org/:_authToken=$NPM_AUTH_TOKEN" > ~/.npmrc
    - npm publish

Next step, enable per-branch caching by using cache:key directive. It allows cache dependencies solely between jobs with the same ref name. And all jobs run with the same dependencies’ tree.

image: node:8

stages:
  - test
  - publish

test:
  stage: test
  cache:
    key: "$CI_COMMIT_REF_NAME:node_modules"
    paths:
      - node_modules/
    policy: push
  script:
    - npm install
    - npm run lint
    - npm test

publish:
  stage: publish
  cache:
    key: "$CI_COMMIT_REF_NAME:node_modules"
    paths:
      - node_modules/
    policy: pull
  only:
    - tags
  script:
    - echo "//registry.npmjs.org/:_authToken=$NPM_AUTH_TOKEN" > ~/.npmrc
    - npm publish

In this example, test job installs dependencies and pushes them to cache. Publish job pulls dependencies, compiles and publishes results.

Read more about caching in Gitlab’s docs.

Pages for API docs

Another interesting task you can do in CI is a publishing docs for package to Gitlab pages. Add job called pages to .gitlab-ci.yml file with artifacts directive to tell runner that this job creates artifacts.

image: node:8

stages:
  - test
  - publish
  - pages

...

pages:
  stage: docs
  cache:
    key: "$CI_COMMIT_REF_NAME:node_modules"
    paths:
      - node_modules/
    policy: pull
  only:
    - tags
  script:
    - npm run docs
  artifacts:
    paths:
      - public

With this configuration npm run docs command builds the site with API documentations to artifacts ./public directory. This directory will be automatically deployed to Gitlab pages when job completed.

Wrapping up

That’s it. I reviewed the basics of usage integrated Gitlab CI for testing, building or deploying packages or applications. I hope you’ll find something new for yourself.

Read the CI docs to gain a deeper understanding how it works: