Custom private CircleCI Orbs development for CI/CD

We develop turnkey private CircleCI Orbs: we turn repetitive build steps into reusable modules with clear versions. Build time is reduced severalfold, configuration duplicates disappear, and launching new projects speeds up.

Our competencies:

Frequently Asked Questions

Latest works

  • Development of a web application for FEEDME
    Development of a web application for FEEDME
    1365
  • Development of an online store for the company FURNORO
    Development of an online store for the company FURNORO
    1330
  • B2B Advance company logo design
    B2B Advance company logo design
    776
  • Development of a web application for Enviok
    Development of a web application for Enviok
    1077
  • AIDER company logo development
    AIDER company logo development
    1022
  • CRM development for Chasseurs
    CRM development for Chasseurs
    1113

CircleCI Orbs: why build configuration grows

In any repository, the build lives in one configuration file — .circleci/config.yml: dependency installation, test runs, image build, publication, deployment. When projects become dozens, these steps start being copied from repository to repository.

This is exactly where CircleCI Orbs appear — an extracted and versioned shared part of the configuration — but before teams get to them, they usually manage to accumulate several generations of copies of the same scenario.

The divergence is almost invisible at the start. Later it turns out that in one project the dependency cache is keyed by branch name, in another by the lockfile checksum, and in a third the key has not changed since creation.

Somewhere a flag was added to the test step, somewhere not, and somewhere an outdated executor image remains. A change to one thing — a cache key or step order — has to be made in thirty files, and half of them remain unnoticed.

The same commit builds differently, and a green build stops proving anything: reproducibility drops exactly where it is relied upon during deployment.

The second source of growth is conditional logic inside steps. Branches like “if tag — publish, if main branch — deploy to staging, otherwise tests only,” separate sets of steps for different environments, run matrices.

The file grows to hundreds of lines, reviews stop, and maintaining build scenarios turns into manual synchronization of copies — each new edit increases divergence instead of reducing it.

Next we break down how to extract the shared part into reusable commands and images, version it, and keep build configuration in one place — so that updates reach all projects deliberately, not by rewriting thirty files.

Effect of reusable build modules

Reusable build modules in CircleCI are not “a tidy configuration structure,” but measurable effects that are visible in build statistics already in the first month of work.

Build time on repeated steps

Environment preparation, dependency installation, checks, and image publication are described once in a shared module, and the project only attaches the required version.

Repeated steps stop being performed anew in every repository: a build that took 11–12 minutes fits into 6–8, and waiting in the queue for a shared executor is reduced.

Share of manual edits in configurations

Previously, the same logic lived its own edition in dozens of files, and every fix had to be repeated manually. Now a change is made in the module, and projects only bump its version. The number of places where the standard has to be edited separately is reduced to one.

Eliminated class of failures due to environment discrepancies

The module fixes environment versions, step order, and the set of checks before publication — a single build standard becomes mandatory for all services.

Failures of the “works here, not in the build” kind disappear from statistics: a failure now means a problem in code, not in the environment.

Speed of onboarding a new repository

Onboarding comes down to specifying the module and its version — checks, build, and publication are already described inside. A new service gets a working standard in an hour instead of a day, and the engineer does not need to understand the internals of the build.

Build reuse options: what fits and when

Build logic can be reused in four ways, and the choice between them is determined not by taste, but by the number of teams on the shared pipeline and the frequency of changes.

Below is a comparison by scenarios and maintenance load: each approach has its own class of limitations, and it is clear which one will hit limits first as the number of repositories grows.

Option When it fits Approach limitations
Shared build template in a repository One or two teams, configuration changes rarely, full control over every line is needed Edits do not reach neighboring repositories: versions diverge, templates live their own lives
Private build module in a shared registry Several teams with common stages and different access rights, code closed to outsiders A module registry and versioning rule are needed: an update must remain backward compatible
Shared external module Typical steps — style checks, artifact publication, notifications Dependence on an external maintainer: their changes arrive outside your schedule and break the build not at your site
Configuration generation from project metadata Many similar repositories, differences described by parameters An intermediate layer appears: without the generator, the configuration cannot be read or edited manually

In practice, approaches are combined: a private module covers common stages, generation covers typical repositories, and a template remains for rare services.

The transition is done without stopping delivery — the new module is attached alongside old steps, and duplicates are removed as migration proceeds.

How private module registry implementation proceeds

Implementing a private module registry is not installing a service, but rebuilding how teams assemble and reuse configuration. The work order is structured so that each step relies on a proven previous one, and builds of working services do not stop for a single day.

  1. Build configuration audit. We take inventory: how many build files there are, where steps are duplicated, which secrets are stored in plaintext, how long a typical run takes. The output is a dependency map and a list of fragments that are actually worth extracting into modules.
  2. Module scheme and versioning. We mark out modules by responsibility zones, agree on a tag scheme and compatibility ranges so that updating one module does not break other builds.
  3. Registry access setup. We connect the private Orbs registry, issue teams tokens with the minimum required rights, separate reading and publication, and set up caching so that fetching a module does not slow down startup.
  4. Pilot on one service. We migrate the most representative service, compare build time and the number of manual edits before and after, and fix module interfaces based on actual inconveniences.
  5. Build rollback checks. We run scenarios: module revoked, tag rewritten, registry unavailable. The build must fail predictably or come up on a pinned version — without manual config edits during an incident.
  6. Handover and further workflow. We hand over the publication policy, new module template, and deprecation rules, and train on-call engineers to handle common failures.

After that, the remaining services are migrated along the proven route — one per sprint, without stopping releases.

What remains with you: configurations, roles, policies

Private Orbs and the tooling around them remain in your environment — together with the package by which modules are maintained without an external team. We transfer everything to your repository and support it until engineers begin making changes themselves.

  • Build configurations — parameterized task definitions: steps, caching, run conditions. They reproduce the same result as on the reference environment.
  • Dependency scheme — which module relies on what and what an edit to a shared block will affect. It is clear where a change is isolated and where it will affect consumers.
  • Access policies and roles — who releases a new version of a shared module, and who only attaches it to their project. Secrets are separated by levels and are not inherited automatically.
  • Versioning description — an agreement on breaking and compatible changes: the former goes on a separate branch, a new parameter comes with a default value.
  • Module update policy — frequency of reviewing base images and dependencies and the trial procedure on a test project before migrating the rest.
  • Documentation — a card for each module: purpose, parameters, an attachment example, known limitations, and common failures.
  • Engineer instructions — how to add a step, how to roll back a release, and where to look on failure.

Policies are not static: when processes change, we update documents in the same change as the code, otherwise they diverge from reality in a couple of months.

Case: build time decreased, configuration duplicates eliminated

What it was

Fourteen services, each with its own configuration file, copied from a neighbor and then edited manually. Build and publication steps diverged into nine variants of the same action, and for three services checks did not run at all.

The cost of divergence: a full run took about forty minutes — there was no shared dependency cache, and each service pulled and built everything anew.

Roughly every fifth production failure was not due to code but to an incorrect configuration step that had already been fixed in one service but not in the others.

What it became

We built private CircleCI Orbs — a shared module with commands for image build, dependency caching, check runs, and publication by tag. The service config shrank to fifteen lines: specify the image and call the shared command. Editing a step is now done in one place and picked up by all services.

The run was reduced from forty minutes to thirteen due to caching and parallel check execution.

There is now one copy of the build step instead of nine, and failures due to configuration divergence are two per quarter instead of five to six per month, with both caught at the preparation stage, not in production.

Rollback also became manageable: the module version is pinned with a tag, and returning to the previous branch of the shared module restores the previous behavior for all services at once, without going through repositories.

Next we will look at how such a module is structured inside and what is worth moving into it versus leaving in the service.

How private CircleCI Orbs differ from templates

A private Orb is a packaged module: commands, jobs, and executors with parameters and default values, published in the CircleCI registry under your namespace.

In a project configuration, it is attached by a reference with a version specified, meaning it works as an external dependency, not as a copied file.

A template in a repository is just text duplicated across projects: the connection between copies is lost at the moment of copying.

Module versioning is the main difference. Updating a private Orb is one publication of a new version: projects pinned to the previous one continue building as before, while switching to the new one is a separate commit in the configuration, and rollback fits in the same line.

With a template, any edit turns into walking dozens of repositories, and some of them inevitably lag or diverge in behavior.

Rights to a shared module are divided by roles: the platform team releases versions, while product teams only attach the Orb and pass their parameters.

Changes are reviewed in one place and are not rewritten on the fly, unlike a template, which anyone can adjust for themselves and break shared behavior. Change isolation rests on the version, not on discipline.

One or two repositories, a single stack, no external consumers — a local template is more than enough: it requires no publication and is easier to maintain.

Private Orbs pay off when build scenarios repeat across several repositories, different teams must build identically, and the shared module needs to be changed centrally and predictably.

Common technical doubts before starting work

Five questions are usually raised before the start, and all of them are about operations, not the code itself. Below are short answers; if something is set up differently for you, tell us during discussion — we will adapt the configuration to your current environment.

How to update private modules without breaking builds?

We version according to semantic versioning rules: backward-incompatible changes go only to the major branch, the rest to minor ones. Projects are pinned to a major version, and an update is first run on a test branch — divergence is visible before merging into main.

How are accesses to the registry and build environment issued?

By least privilege: separate read and write tokens, limited lifetime, binding to environment contexts. Each change leaves an entry in the log — you can see who published what.

Who owns the registry after handover?

We register the registry to your organization, and owner rights remain with you. We connect as a contractor with publication rights; after handover you change accesses in one step and work with modules independently.

We have several stacks — will one module work?

Yes, through parameterization: language, runtime, step set, and image are passed as arguments with default values. One module covers several stacks; we do not create duplicate copies for each.

We already have our own templates — rewrite everything from scratch?

No. We wrap existing templates into a private module and migrate one service at a time: old builds continue to work until the new environment covers all projects. Rollback of any step is a return to the previous build description.

Let's discuss your build scheme and come back with a plan

For the implementation estimate to rely on facts, not guesses, we need a short exchange of inputs. Send what you already have — that is enough to see bottlenecks and propose a scheme.

If part of the builds are already moved into shared Orbs and they work, tell us: we will not redo what is not in the way.

  • List of repositories and services that need to be moved to shared builds, marking which of them are critical for releases.
  • Current build and deployment configuration files — as is, including temporary workarounds.
  • Branching scheme and release rules: what counts as a release, who confirms it and how.
  • Access requirements: which roles engineers need, for which environments, image registries, and secrets.
  • Constraints that must not be violated: closed-source code, isolated environments, windows for heavy builds.
  • Target metrics: build time, share of automated checks, release frequency.
  • Contact of the person responsible for infrastructure — we will coordinate technical details with them.

The order is: review the inputs, come back with an implementation estimate and a staged work plan — from the pilot repository to rollout to the rest.

Separately, we fix the handover order: what remains in your environment, which rights and documentation transfer to your team, and how the result is maintained after launch. Describe the task — we will come back with an estimate and plan.