Workflows has led many people to get “job happy” in their config files. One job to ‘build’, another to ‘test’, and another to ‘deploy’? Maybe, that’s not too bad. How about a fan-out, fan-in workflow that comes in at a total of 5 jobs where each uses the same Docker image? There’s going to be redundancy in that config.yml file. We can reduce it, and make YAML do the work for us.

Today, we’re going to cover one of the cooler, lesser known features of the YAML v1.2 spec, Anchors & Aliases. We’re going to use them to reduce repetitive editing of the following example CircleCI config.yml file:

# .circleci/config.yml
version: 2
jobs:
  build:
    docker:
      - image: felicianotech/docker-hugo:0.27.1
    working_directory: ~/project
    steps:
      - checkout
      - run:
          name: "Build Our Statically Generated Docs Website with Hugo"
          command: HUGO_ENV=production hugo -v -s src/
      - persist_to_workspace:
          root: /root/project
          paths:src/public/
  html-testing:
    docker:
      - image: felicianotech/docker-hugo:0.27.1
    working_directory: ~/project
    steps:
      - attach_workspace:
          at: /root/project
      - run:
          name: "Test With HTML Proofer"
          command: echo "This is where we'd do testing with HTML Proofer."
  ui-testing:
    docker:
      - image: felicianotech/docker-hugo:0.27.1
    working_directory: ~/project
    steps:
      - attach_workspace:
          at: /root/project
      - run:
          name: "Test With Selenium or Behat"
          command: echo "This is where we'd run a headless browser and test our site's UI."
  process-testing:
    docker:
      - image: felicianotech/docker-hugo:0.27.1
    working_directory: ~/project
    steps:
      - attach_workspace:
          at: /root/project
      - run:
          name: "Test the commit for org or team processes"
          command: |
            echo "This is where we'd test this commit for things our project wants to enforce"
            echo " such as code formatting, the signing of a CLA, security requirements, etc."
  deploy:
    docker:
      - image: felicianotech/docker-hugo:0.27.1
    working_directory: ~/project
    steps:
      - attach_workspace:
          at: /root/project
      - run:
          name: "Deploy Our Docs Site"
          command: echo "This is where we'd deploy using rsync, awscli, etc."
# Below would be the Workflows specifc config, which we're leaving off for brevity (this example as already lengthy.

YAML Anchors & Aliases

YAML allows declaring a node as an anchor. This means this node will be referred to somewhere later in the YAML. In the config example above, you’ll see that the beginning of each job declaration is repeated for each and every single job. Specifically, we repeat the following lines 5 times in our config file:

...
    docker:
      - image: felicianotech/docker-hugo:0.27.1
    working_directory: ~/project
    steps:
...

Instead, we can use an anchor at the beginning of .circleci/config.yml to set these lines as our default for jobs:

defaults: &defaults
  docker:
    - image: felicianotech/docker-hugo:0.27.1
  working_directory: ~/project
  steps:
...

Then we use an alias with each job to write less lines. Here’s an example using the build job from earlier:

...
  build:
    <<: *defaults
      - checkout
      - run:
          name: "Build Our Statically Generated Docs Website with Hugo"
          command: HUGO_ENV=production hugo -v -s src/
      - persist_to_workspace:
          root: /root/project
          paths:src/public/

The Result

If we repeat this process across the entire config file, we go from 56 lines of config in our initial example to 47 lines.

Maybe, in this example, a 9 line reduction isn’t something to call home about, but we didn’t just reduce the number of lines: we’ve made it easier to maintain the YAML file. If the Docker image we’re using is updated with a new tag, say felicianotech/docker-hugo:0.28, we can update the image tag once, at the top of our config, and be done with it. Each of our 5 jobs in this workflow will now all be using the newer, updated image, with only a single change.

You can continue the discussion on YAML anchors and aliases in CircleCI Discuss. More examples of advanced YAML usage can be found on Wikipedia. You can see the full example config, with changes, below.


Our example config file using a YAML anchor and aliases:

# .circleci/config.yml
defaults: &defaults
  docker:
    - image: felicianotech/docker-hugo:0.27.1
  working_directory: ~/project
  steps:

version: 2
jobs:
  build:
    <<: *defaults
      - checkout
      - run:
          name: "Build Our Statically Generated Docs Website with Hugo"
          command: HUGO_ENV=production hugo -v -s src/
      - persist_to_workspace:
          root: /root/project
          paths:src/public/
  html-testing:
    <<: *defaults
      - attach_workspace:
          at: /root/project
      - run:
          name: "Test With HTML Proofer"
          command: echo "This is where we'd do testing with HTML Proofer."
  ui-testing:
    <<: *defaults
      - attach_workspace:
          at: /root/project
      - run:
          name: "Test With Selenium or Behat"
          command: echo "This is where we'd run a headless browser and test our site's UI."
  process-testing:
    <<: *defaults
      - attach_workspace:
          at: /root/project
      - run:
          name: "Test the commit for org or team processes"
          command: |
            echo "This is where we'd test this commit for things our project wants to enforce"
            echo " such as code formatting, the signing of a CLA, security requirements, etc."
  deploy:
    <<: *defaults
      - attach_workspace:
          at: /root/project
      - run:
          name: "Deploy Our Docs Site"
          command: echo "This is where we'd deploy using rsync, awscli, etc."
# Below would be the Workflows specific config, which we're leaving off for brevity (this example as already lengthy.