Setting Up Automated Docker Image Builds in CI/CD

Manual deployments slow the team down and depend on who exactly builds the image. We set up automated Docker image builds in the pipeline turnkey: from preparing the repository to handing over the finished process to your team. The time to release a new version is cut several times over, and human errors during builds are eliminated from the process.

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

Automatic Docker image builds: where the process breaks

Automatic Docker image builds don't break at the stage of writing the build file, but later — when a release needs to be repeated a month later or an environment needs to be stood up from scratch.

The most common cause is building on the developer's machine: local environment variables, directories unpacked nearby, dependencies not pinned in the repository.

The resulting image is reproducible exactly once and only for the author — for a colleague it builds differently, on the server differently again.

Then environment drift stops being theoretical. The test environment and production diverge in library versions and settings so much that the defect doesn't reproduce in either one, and the search for the cause proceeds blindly.

Manual release deployment adds the human factor: the image is built manually before rollout, the tag is assigned "by eye," and from the registry record it's no longer possible to tell which exact code is in that image.

Long release cycles are a direct consequence of this scheme. While the build hasn't been launched, edits pile up in the branch, the rollout window shifts, and verification takes hours instead of minutes.

Rollback is also not worked out: the previous image is either not saved, or is tagged so that it doesn't differ from intermediate builds — there's nothing to quickly return to a working state with.

Below we break down how this is closed: building by code revision in a shared pipeline, a single build file for all environments, a tag from the revision hash, and storing a limited number of previous images so that returning to a previous release takes one operation.

What changes after moving builds to a pipeline

The first effect is release time. While builds were launched manually, hours passed between an edit and a ready artifact; after moving to a pipeline, every push of changes to the main branch automatically triggers the build, checks, and publication. A typical cycle takes tens of minutes including the test run.

Build reproducibility no longer depends on who has what installed. The steps are described declaratively, versions of the base layer and dependencies are pinned, intermediate layers are reused — repeated builds run faster, but the result doesn't change. The same set of commands on the build agent and on a local machine produces an identical image.

Image size drops thanks to multi-stage builds: everything unnecessary stays in the intermediate layer, and only what's needed during application runtime gets into the final one.

Such an image rolls out to nodes faster, the registry grows more slowly, and the attack surface for the vulnerability scanner is noticeably smaller.

Release rollback becomes switching to the previous image. Every release is tagged and checksummed, the artifact is immutable, so the previous working release comes up without a rebuild — minutes instead of rerunning the whole cycle.

Environment repeatability is closed by the same mechanism: the test contour, pre-production, and production receive an identical image and differ only in external parameters.

A whole class of failures like "it worked in test" and divergences due to packages manually installed on the server goes away.

Where to run the pipeline: agents, containers, own servers

The choice of build platform determines three things: how much time passes from commit to ready image, how reproducible the build is, and what happens when the hosting hits its limits. Below are the options that are actually chosen between in practice, with their limitations.

Option When it fits Limitations
Provider-managed agents A small team, no own infrastructure, need a quick start Limit on concurrent jobs, per-step timeout, layer cache doesn't survive machine changes
Own build server Large images, private dependencies, heavy checks Maintenance, updates, and monitoring are on us; if it fails, the queue stops entirely
Pipeline in a container Need an isolated build environment on a shared host Nested builds require a separate daemon or extended privileges, disk accounting is harder
Building on the same host as the production service Internal utilities, quick hypothesis testing Resources compete with production, risk of taking down the service along with the build

Most often the result is a hybrid: routine branches go to managed agents, while release images are built on an owned server with a persistent layer cache.

Then the isolated build environment is the same for all branches, hosting limits stop being a point of failure, and if needed the pipeline can be moved to another platform without rewriting the steps.

Pipeline implementation stages: from audit to handover

You can build images manually on a workstation while there's one release per week.

As soon as there are more services and branches, the build moves into a pipeline — and the question is no longer "do we need automation," but in what order to implement it so as not to break current releases.

Below is the sequence we follow on a project: each stage ends with a verifiable result, not a report.

  1. Repository audit. We analyze the branch structure, dependencies between services, and who builds Docker images how right now. We record bottlenecks: manual steps, lack of caching, different base images across teams.
  2. Pipeline scheme. We agree on what starts on a commit to a branch, what starts on a release tag, and where the build stops if tests fail. We fix the scheme before setup so we don't have to redo steps along the way.
  3. Configuring build steps. We enable layer caching, define tagging rules, and separate build from publication to the registry. The output is an identical image from the same commit for any engineer.
  4. Verification in a pre-production environment. We run a real release through the new pipeline, compare the result with a manual build, and check rollback to the previous tag. We catch defects here, not in production.
  5. Regulations. We describe triggers, environment variables, rollback procedure, and access to secrets — so the rules don't live only in one engineer's head.
  6. Documentation handover. We hand over the pipeline scheme, configuration files, and instructions for typical failures, and together go through one release from commit to publication.

After handover, your team runs the pipeline independently: changes to build steps are made without contacting us. Support remains an option, not a condition for operability.

What remains in the repository and with the team after implementation

Implementation ends not with a working pipeline, but with a set of artifacts by which it can be reproduced, handed over, and maintained without the author. Below is exactly what remains in the repository and in the team's documentation after handover.

  • Pipeline configuration files — a declarative description of stages directly in the repository: image build, checks, publication to the registry, deployment by environment. Versioned together with the code, any change goes through review.
  • Access rules and roles — who edits the configuration, who launches deployment to production, where secrets for the registry are stored and how they are rotated, token lifetime.
  • Release flow scheme — from commit to running container: which checks are mandatory, where manual confirmation is needed, which tags are assigned to the image and which of them are considered stable.
  • On-call shift instructions — what to look at when a build fails, how to distinguish a code error from an environment error, to whom escalation goes and what is recorded in the on-call log.
  • Rollback regulation — how many recent images are stored, how return to the previous one is performed, who makes the decision and in what time the environment returns to a working state.
  • Base images and layer cache — pinned base images and layer order, so the build doesn't depend on external updates and fits within a predictable time.
  • Pipeline metrics — build time, share of successful runs, share of paths covered by checks: they show degradation before it reaches release.

Such a kit makes it possible to hand over support to another shift or contractor without oral explanations: the pipeline is reproduced from the repository, and failure behavior is described in advance.

Case: unstable deployments at a service with multiple teams

What was breaking before

Four teams worked in one repository and built images manually, each from their own branch. Deployments went out under one floating tag: two builds minutes apart overwrote each other's artifact, and an image with a neighboring service's dependencies went to prod. Layers weren't cached, packages were pulled again on every build.

Not only speed was half-broken. Roughly every second deployment succeeded on the first try, analyzing a failed release took from half an hour, and rollback was done manually according to instructions from a chat — and it didn't always return the previous working version.

What was changed and what was achieved

We built a unified pipeline in GitLab CI: the build starts on merge to the main branch, each service has its own stage with an isolated layer cache, and the key is calculated from the hash of the dependencies file.

Images are tagged with an immutable commit hash; the floating tag for the environment is set only after checks and approval by the responsible person.

  • Share of deployments without rework: from half to 9 out of 10.
  • Time from merge to ready image: from 25–40 minutes to 6–8.
  • Rollback: one command to the previous tag, about two minutes instead of manual analysis.
  • Image size: reduced threefold thanks to multi-stage builds and package cache cleanup.

The release cycle stopped depending on who pressed build first: teams deploy in parallel and don't block each other.

How layer cache differs from dependency cache

In Docker, layer cache is about build instructions. Each instruction produces an immutable layer, and as long as it itself or any previous one hasn't changed, subsequent runs reuse ready layers in a chain.

That's why cache invalidation is cascading: editing one line at the beginning of the build file resets everything described below.

Dependency cache is a separate storage of already downloaded libraries and modules: a package registry proxy cache or a volume on the build agent.

It isn't tied to instruction order and is shared across all branches — the same library is served from it again, and its lifetime is set by the retention policy.

The difference shows up in step order. If you first copy the dependency manifest and run installation, and copy source code as the last layer, a code edit rebuilds one layer.

If it's the other way around — any change in the code resets dependency installation, and the rebuild pulls the entire package set again.

The speed of rebuild differs by a factor: with a warm layer cache and a filled dependency cache, the agent doesn't go to the external network, and the build takes seconds instead of minutes.

The price for this is versioning and regular cleanup of the second cache: outdated packages and leaked access to private registries happen exactly here.

Bottom line: layer cache is responsible for what's already built, dependency cache for what's already downloaded. The first is managed through instruction order, the second through retention policy and manifest versioning.

Technical doubts before moving to a pipeline

Objections before moving automatic Docker image builds to a pipeline concern not the tool itself, but what's around it: accesses, secrets, and failure behavior. Below are short answers to what engineers ask most often.

What happens if the image registry is unavailable?

The pipeline distinguishes "build failed" from "there's nowhere to put the artifact": in the second case, the tag isn't published, the job is paused, and deployment lives on the previous version. We duplicate the registry with a mirror.

How are release rights separated?

Build and publication are different roles: a developer launches a build of their branch, while the tag in the main registry is set only by the pipeline after checks. It's no longer possible to overwrite a production image from a local machine.

Where are build secrets stored?

Values are injected into the build step from a secret store, don't get into image layers, and are masked in logs. Access to the store is held by the pipeline, not by every developer.

Will deployment stop during the transition?

We go branch by branch: first the pipeline builds exactly what was built manually, and we compare the result by checksum. While verification is underway, manual release remains working — there's no downtime window.

Why did the pipeline often fail for us before?

The cause is usually not the pipeline, but unmanaged dependencies: floating base image tags, different builder versions on agents, reuse of layers between builds not configured. We pin dependencies by checksum and make agents reproducible — failures become isolated.

We'll analyze a doubt that isn't on the list against your configuration.

Let's discuss the task and come back with an implementation plan

The first conversation isn't a presentation, but an analysis of how you currently build and deliver images.

We need inputs about the infrastructure: where the source code lives, which image registry is used, on which machines the builders run, and which environments make up the path to production.

From this data, bottlenecks are visible and it's clear what can be automated immediately and what requires restructuring the scheme.

To come back with an estimate and implementation plan, we usually need the following from the customer:

  • a description of how the build is launched today — manually or by a script on a separate machine;
  • information about the image registry and how artifacts get into it;
  • the composition of environments and mandatory checks before publication;
  • how rights and secrets are arranged: who can build and publish what;
  • the team's operating mode: how many builds per day and what time from a code change to a ready image is considered normal;
  • what has already been tried and why the solution didn't stick.

Then we fix the order of work: describe the target build scheme and tagging policy, configure it on a test branch, then move it to the remaining environments and hand over documentation with a rollback regulation.

We agree on timelines and stages in advance — each stage ends with a working result that can be verified on your tasks. Describe the task and the current build scheme — we'll come back with an implementation plan.