CI/CD Setup for Rust Applications (Cargo)

Manual builds of Rust applications eat up team time and break on small issues before release. We set up CI/CD turnkey: builds, checks, and delivery run on their own, with no manual steps. Time from a change to a ready release is cut many times over.

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

Why Rust CI/CD builds break and slow the team down

A build that is green on the developer's machine and fails on the pipeline is the most expensive category of failures: the cause is not in the code, but in environment mismatch.

The compiler version, set of system libraries, and linking method differ between the build server and the local machine, so the error reproduces intermittently and consumes hours of investigation.

A non-reproducible build environment is more dangerous than an obvious breakage: as long as it exists, no release can be considered verified.

The second bottleneck is dependency cache. If only the directory with library sources is carried over between runs, and compilation results are not, each run rebuilds the entire dependency tree: tens of minutes instead of tens of seconds.

The opposite extreme is a cache key tied to a volatile file: the cache is never reused, and slow Rust builds become the norm the team gets used to.

At the same time, a full rebuild of the project on every change in a single module is a consequence of the same thing: tasks are not split into parts, and intermediate results are not saved between stages.

The third source is manual steps during release. Editing the version number in the manifest, locally building the release artifact, tagging, pushing to the registry — each step depends on a person.

A skipped step produces an artifact that cannot be unambiguously linked to a commit, and rollback turns into digging through developers' machines.

Tools like Cargo cover reproducibility at the dependency level but are not responsible for the environment, cache, and release steps — that is the pipeline's domain.

Below we discuss how this is covered: pinning the build image, layered cache, splitting the graph into parts, and automatic release.

What a well-established build and application delivery pipeline provides

In a clean project, every pipeline run pulls dependencies anew and compiles them from scratch.

Cargo artifact cache by lockfile hash and incremental builds compress the edit verification cycle from 12–15 minutes to 3–4, while parallel jobs distribute tests and linters across agents — the merge queue stops being a bottleneck.

A reproducible build gives more than predictable time.

A pinned lockfile, a fixed toolchain, and an isolated environment remove the "works locally, fails in the pipeline" mismatch: one commit produces the same artifact, so rollback to the previous release happens without a rebuild.

Manual release operations — versioning, tagging, changelog, artifact upload, migration application, traffic switching — move into the pipeline, which is triggered by a tag. Change delivery time drops to minutes and stops depending on who is on duty today.

The main effect is not speed itself, but the class of failures that stops reaching the customer.

Incompatible dependencies, uncommitted migrations, version mismatches, and "it built on my machine" are cut off before release: only an artifact that has passed build, tests, and vulnerability scanning reaches the staging environment.

Practical result: a release fits into a single pipeline pass, and the only manual point where it can break remains merging into the main branch.

Options for organizing builds: agents, containers, self-hosted machines

Building a Rust application runs up against not the compiler but the environment: Cargo pulls dependencies, the target directory grows quickly, and the same code has to be built for different platforms.

That is why the first question when setting up CI/CD is where the build physically runs and how artifacts are reused between runs. There are four options, and they are not mutually exclusive.

Cache is handled separately. An isolated build cache keyed by the lockfile and target platform enables dependency reuse between runs and prevents one job from corrupting another's state.

Without it, every change in dependencies means a full rebuild, and verification time grows by an order of magnitude.

Comparison of build organization options

Option When it fits Limitations
Self-hosted build machines Need access to the internal network, private package registries, special hardware for load builds Operation is on us: toolchain updates, job isolation, disk and queue control
Containerized builds Need reproducibility: the same image for local machine and CI, predictable dependencies Image startup adds time to each run; cache must be externalized from the container, otherwise it is lost
Managed agents from an external service Small team without its own infrastructure, public code, starting with minimal investment Resource and access limits, harder to connect internal networks, cost grows with the number of parallel jobs
Build matrix by platform Product ships for several systems or architectures, artifacts needed for each Number of jobs multiplies, total verification time grows; without parallelization and separate cache per platform, the queue stalls

In practice, a hybrid is most often used: containerized builds with an external isolated cache, a platform matrix for release branches, and self-hosted machines where access to a closed environment is needed.

We describe such a set explicitly in configuration — which option on which branch and with which cache, so that build queue behavior is predictable.

How we set up CI/CD for Rust applications: stages of work

We start with measurement, not with configuration edits: in Rust projects, most time is consumed by cold compilation of dependencies and rebuilding the tree on every change.

If you do not first record how long verification takes until the first result and which steps reuse artifacts, any optimization turns into guesswork. The sequence is as follows.

  1. Build pipeline audit. We look at what is already described in the repository: which checks run, on which branches, where secrets are stored, and how manual interventions are organized. We identify points where the result depends on a specific engineer's environment.
  2. Bottleneck measurement. We break the build down by steps: how long dependency fetching, own code compilation, and tests take. Separately, we count how many jobs rebuild the same tree from scratch.
  3. Environment and cache scheme. We determine which branches and tags lead to test and production environments, what we separate by build profiles, where the dependency cache and compiled artifact cache live, and by what criterion it is invalidated. We tie the cache key to manifest files and the set of target platforms.
  4. Build pipeline setup. We assemble steps in ascending order of cost: formatting and static checks, then tests, then release build for the required targets. We parallelize independent jobs and connect the cache before compilation so it is not invalidated by a change in a single module.
  5. Pre-handover build verification. We run the pipeline on real changes, not an empty commit: break a test, add a dependency with an incompatible version, revert a commit. We look at where verification fails quickly and where it drags the entire environment. Cost calculated after audit. Handover of the pipeline to the customer's team. We describe the scheme in the repository, set up rights and access, and go through typical scenarios with engineers: how to add a step, how to update a dependency, how to roll back a build.
  6. Change management. We agree on who amends the configuration and by what procedure, and we leave a check that prevents silently breaking the cache or bypassing tests.

What we hand over: configurations, diagrams, policies

We finish the work not with a report "everything is set up", but with a set of artifacts that your team reads, edits, and passes on without us. Each item below is checked at acceptance — it also shows where our responsibility ends and your operation begins.

  • Build and cache configuration for Cargo: target matrix, profile separation, cache of the build directory and package registry by the hash of the dependency lock file — a rebuild takes minutes instead of tens, and when dependencies change, the cache is invalidated precisely.
  • Service interaction diagram: pipeline, image registry, artifact storage, runtime, and notifications — with failure points and steps that run in parallel.
  • Secret access policies: where keys are stored, what their scopes and lifetimes are, and which roles can read them. Secrets are issued to a specific step, not to the entire build environment as a whole.
  • Pipeline documentation: stages, variables, launch conditions, and checks that block merging. A new engineer can understand the cause of a failure without reading the configuration line by line.
  • Release policy: from the tag in the repository to the artifact in the environment — who initiates, which checks are mandatory, how the version gets into the changelog.
  • Release rollback instructions: criteria for "roll back or fix forward", commands, checkpoints, and the responsible person. We rehearse the scenario in staging before handover.
  • Monitoring map: build and deployment metrics, alert thresholds, and signal recipients.

Case study: reduced build time and removed manual steps

The customer is a team that develops a Rust service from a dozen libraries in a single repository.

Before every release, a full build took about 40 minutes, and a noticeable share of failures was not due to code but to environment mismatches: the developer had updated dependencies, while the build machine did not.

We moved the project to a shared GitLab CI pipeline and broke it into independent stages: format check, tests, release artifact build.

Dependencies and intermediate results are cached between runs, and the compiler and set of libraries are pinned in a single image — it works the same on the engineer's machine and in the pipeline.

Metrics before and after

  • Full build duration: from 40 minutes to 11 — due to cache and stage parallelization.
  • Share of failures caused by the environment: was about a third of all failures, now only isolated cases remain.
  • Manual operations before release: from nine to one — only the release trigger remains.
  • Recovery after a build failure: from several hours to 15 minutes, because the failure reproduces locally in the same way.

Steps like tagging the version and uploading artifacts moved into the pipeline, and the build became reproducible: the same set of sources produces the same result. Rollback to the previous release is a single job run, not a rushed manual rebuild.

How does dependency cache differ from build artifact cache?

Confusion arises because both caches speed up the same build command but lie at different levels. Dependency cache is downloaded and compiled external libraries: it saves network traffic and the most expensive part of the work, compiling third-party code.

Build artifact cache is the compilation results of your project: object files and ready binaries that incremental project builds rely on.

What we cache What is inside When it is reused Limitations
Dependencies sources and compiled external libraries when the version lock file is unchanged and the build profile is the same grows linearly, eviction policy needed
Build artifacts object files, intermediate and final binaries on partial source changes tied to branch, toolchain, and environment
Check results binaries and test reports when the code fingerprint matches cannot be moved between environments

The cache key for dependencies is Cargo.lock, toolchain version, and build profile; for artifacts, it is the source fingerprint and the previous state of the build directory.

Updating one library resets the first layer and barely touches the second, while editing one module does the opposite.

Reusing build results between jobs works only when layers are separated: dependency cache is carried between branches, artifact cache is not — it is read-only and written only on the main branch.

If the entire build directory is cached, false hits return a binary from someone else's commit, and debugging turns into hunting for a floating defect.

The result is two keys, two eviction policies, and different write permissions. Then a build with no changes runs in seconds instead of a full cycle.

Common technical concerns: cache, secrets, target platforms

Five questions most often come up at the assessment stage: will strict checks lengthen the build, what to do with settings from the previous team, how to survive an agent failure, how to build for multiple platforms, and where to store keys.

Will strict checks noticeably lengthen the build?

Formatting, static analysis, and tests run as separate steps and in parallel. Cache by Cargo dependency graph reuses unchanged layers — a repeat run takes seconds, not minutes.

We still have manual settings from the previous team

We describe the pipeline declaratively in the repository, and the environment is reproduced from the description, not from the state of a specific machine. We move scripts and parameters into secure storage — after that, the build comes up from scratch on a clean agent.

What happens if the build machine fails mid-work

The build is split into steps with result persistence: a repeated run lands on a free agent and skips what is already complete. Artifacts are stored separately from the agent, and only the full set of verified builds goes to the release channel.

Need to build for multiple target platforms

A matrix works: each platform is a separate cell, builds run in parallel and share a common cache. Cross-compilation is used where it is reliable, a native agent where it is not; a partial set of platforms is not published to release.

Where are build keys and secrets stored

Secrets are stored in secure storage and issued to a step only for the duration of execution. Logs, cache, and artifacts are automatically checked for leaks, and access to changing secrets is role-based and logged.

Let's discuss the task: we'll return with an estimate and work plan

To come back with a timeline estimate and work plan for the build, we need a minimal set of inputs. Below is what we usually request: with this set of initial data, the estimate becomes substantive rather than "plus or minus a quarter".

  • Repository and Cargo workspace structure: how many crates, whether there is a workspace, which dependencies are pulled from internal registries or directly from git.
  • Target platforms: which systems and architectures need a binary, whether cross-compilation, static linking, and separate builds for embedded targets are required.
  • Current pipeline: where the build currently lives, what triggers it, how long the full cycle from commit to artifact takes, and at which step it most often breaks.
  • Requirements for cache and build agents: acceptable build time, dependency cache size, resource and parallelism limits.
  • Pre-release checks: which tests must pass, whether static analysis, dependency license control, and vulnerability scanning are needed.
  • Release rules: versioning scheme, where artifacts are published, whether signing, build reproducibility, and automatic rollback to the previous release are needed.
  • Access and environments: where agents are deployed, whether there is a closed environment, who owns secrets and images.
  • Who on your side makes decisions on the release process and is available for short clarifications.

Describe the task and send what you already have — we will come back with a timeline estimate, pipeline diagram, and stage-by-stage work plan: from basic build to release and rollback.