CircleCI’s core magic is its ability to transform your Git commits into deployed applications with zero manual intervention.

Let’s see it in action. Imagine a simple Node.js app.

circleci/config.yml

version: 2.1

jobs:
  build-and-test:
    docker:
      - image: cimg/node:18.17.0
    steps:
      - checkout
      - run:
          name: Install Dependencies
          command: npm ci
      - run:
          name: Run Tests
          command: npm test
      - run:
          name: Build Application
          command: npm run build

  deploy-to-staging:
    docker:
      - image: cimg/node:18.17.0
    steps:
      - checkout
      - run:
          name: Deploy to Staging
          command: ./scripts/deploy_staging.sh

workflows:
  version: 2
  build-deploy-workflow:
    jobs:
      - build-and-test
      - deploy-to-staging:
          requires:
            - build-and-test
          filters:
            branches:
              only: main

When you push a commit to the main branch of your repository, CircleCI automatically picks it up. It spins up a Docker container based on cimg/node:18.17.0. The checkout step pulls your code into the container. Then, npm ci installs your project’s dependencies. npm test runs your unit tests, and npm run build creates your production-ready artifacts. If all these steps in build-and-test succeed, the deploy-to-staging job kicks off, again in a fresh Node.js container. This job executes a script (./scripts/deploy_staging.sh) that handles the actual deployment to your staging environment. The requires key ensures deploy-to-staging only runs after build-and-test completes successfully, and the filters ensure this deployment only happens for commits to the main branch.

The problem CircleCI solves is the inherent friction and error-proneness of manual build and deployment processes. Think about the steps: cloning the repo, installing dependencies, running tests, building assets, packaging, uploading, configuring servers – each a potential point of failure or a time sink. CircleCI orchestrates all of this, making it repeatable, auditable, and fast.

Internally, CircleCI uses a distributed execution engine. When a build is triggered, it grabs a container from its pool (or provisions a new one), assigns the job to it, and streams your commands to that container. The output, including logs and artifacts, is collected and presented in your CircleCI dashboard. You control the entire process through the .circleci/config.yml file, which defines jobs (units of work), steps within those jobs, and workflows (how jobs are ordered and triggered). The docker key specifies the execution environment, steps are the commands or actions to perform, and workflows define the orchestration logic.

One of the most powerful, yet often overlooked, aspects of CircleCI is its caching mechanism. By default, CircleCI doesn’t persist anything between builds. However, you can define caches for specific directories using the restore_cache and save_cache steps. For example, if you want to cache your node_modules directory to speed up npm ci, you’d add something like this:

      - restore_cache:
          keys:

            - v1-dependencies-{{ checksum "package-lock.json" }}

            - v1-dependencies-
      - run: npm ci
      - save_cache:

          key: v1-dependencies-{{ checksum "package-lock.json" }}

          paths:
            - node_modules

This tells CircleCI to look for a cache matching v1-dependencies-<checksum_of_package-lock.json>. If found, it restores the node_modules directory from that cache, skipping the potentially lengthy npm ci download. If not found, it proceeds with npm ci and then saves the newly created node_modules directory under the specified key, ready for the next build. The fallback key v1-dependencies- ensures that if the exact checksum cache isn’t found, it still tries to restore any v1-dependencies cache, which can be a lifesaver when a dependency changes slightly without altering the package-lock.json checksum in a way you expect.

The next step in mastering CircleCI is understanding how to manage secrets and environment variables securely for your deployment jobs.

Want structured learning?

Take the full DevOps & Platform Engineering course →