Setting up Buildah to build OCI images without Docker

Building images with superuser privileges is an unnecessary risk and dependency on a single privileged daemon. We set up turnkey Buildah builds: processes run without excessive privileges, and engineers get a reproducible result. Build release time is reduced, and access is narrowed to the necessary minimum.

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

Buildah: building OCI images without a daemon and superuser privileges

Classic image building relies on a constantly running privileged service: its socket is passed into the build container, and the layer store lives on the host.

On shared or ephemeral build nodes, this breaks immediately — access to the socket equals access to the host, and nested builds require elevated superuser privileges.

Then the cost comes in: the service becomes a single point of failure for the entire pipeline, the layer cache accumulates outside versioning and "drifts" between builds, and parallel tasks queue up behind a single build process. Updating the service itself on a node can halt delivery entirely.

Buildah removes this dependency: building OCI images without Docker runs as separate processes without a long-lived daemon, so there is no need to pass a socket or keep a constantly running privileged service on the node.

In rootless mode, the build runs as a regular user — isolation is based on user namespaces and subordinate ID ranges, and layers are written directly to the image store.

For continuous delivery, this changes the picture: the build becomes a regular pipeline step that runs in parallel on any node, the cache is defined explicitly and transferred between jobs as an artifact, and the result is reproduced identically both locally and in the build environment.

Rollback does not become more complicated — the built image remains immutable, and the previous tag does not go anywhere.

What building images without a privileged daemon provides

Building without a privileged daemon runs as a regular user: Buildah builds the image in an isolated namespace, without access to the socket and without root privileges on the node.

Changes affect only the working directory, so a failure at the build stage does not go beyond its boundaries and does not bring down neighboring jobs.

Speed comes from the layer cache: when one service is edited, only its layer is rebuilt, while dependency installation and the base part are taken ready-made.

The same cache also reduces size — the application is packed into a single layer, debug packages and intermediate files do not end up in the final image. The result is a shorter build and a smaller artifact with the same application composition.

Reproducibility rests on a declarative build description and pinning the base image by an immutable hash.

The same commit produces an identical set of layers on any machine — from a developer's workstation to a build node — so the "works locally" discrepancy stops being a separate class of problems.

Pipeline privileges are narrowed to a minimum: the build account does not need sudo; it only needs a home directory and access to the image registry.

A failed node is recreated and returns to operation in minutes — the finished images are already in the registry, not only on the local disk.

Where Buildah is appropriate: comparison of build modes

Buildah builds OCI images directly, without a constantly running daemon, so the same Containerfile can be run in different modes — on a developer's machine, on a pipeline build node, or inside an isolated environment.

They differ not in syntax but in privileges, network access, and who is responsible for keeping the local store clean. That is why the mode is chosen for the task, not "by eye".

Option When it fits Limitations
Local build Debugging a Containerfile, a fast edit cycle before commit The result depends on the machine and package versions; reproducibility is not guaranteed
Pipeline build, rootless Release images, the same result on every run Requires configuring subordinate UID/GID and store permissions; some privileged instructions will not work
Isolated build Untrusted base image, fixing the environment and network rules The network is closed by default; the build is 15–25% slower
Build with a shared layer cache Long builds with heavy dependencies The cache needs to be cleaned on a schedule, otherwise the disk grows; changing the base layer rebuilds everything from scratch

The guideline is this: if repeatability matters, the Containerfile runs in the pipeline in rootless mode, while local builds remain a debugging tool. Isolation is appropriate where the base image comes from outside or the environment must be fixed before pinning.

The layer cache is mounted as a separate volume and capped by size — then the build fits into minutes instead of rebuilding dependencies on every commit.

How the adoption of Buildah-based building proceeds

Moving image building to Buildah is not replacing one command with another but changing the model: instead of a constantly running daemon, the build runs as a user in an isolated namespace.

Therefore, the order of stages matters more than speed — if you skip the assessment, migrating descriptions will turn into a series of release incidents.

  1. Build assessment. We take inventory: which images are produced, from which base layers, where the build accesses the network, secrets, and root privileges. The output is a dependency map and a list of places that cannot be migrated "as is."
  2. Target design. We determine what stays in one description, what is moved into a multi-stage build, which layers are cached between releases, and which checks are mandatory before publication.
  3. Description migration. We rewrite instructions for the new commands and mounts, and remove steps that depended on the daemon. Each image undergoes composition verification: the same packages, the same user, the same set of files.
  4. Reproducibility check. Two builds of the same source code must match in composition and checksum. If they diverge, we look for implicit dependencies: package updates from the network, layer order, timestamps.
  5. Pipeline run. We connect the build to the release pipeline and review build time, publication to the registry, and rollback to the previous image.
  6. Handover and regulations. We document how to add an image, update the base layer, and what to do when builds diverge.

Each stage ends with an artifact — a map, a description, or a verification report — so the migration proceeds in parts and does not block current releases.

Handover package: build descriptions, roles, regulations

After the environment is handed over, the customer's team retains not only working builds but also a complete set of descriptions by which this environment can be developed without us.

We capture everything that determines the build, access, and update procedure — so a new engineer can deploy and change the process using documents, not chat correspondence.

  • Build profiles for each application type: from a minimal image to services with system dependencies and installation-time compilation.
  • Layer descriptions with comments: why the layer is separated this way, what goes into the cache, and what is rebuilt on every release.
  • Access policies for the registry and build nodes: who signs the image, which keys are used, who is allowed to push to production tags.
  • Base image update regulations: how often updates are checked, how regression is run, and how a problematic base is rolled back.
  • Image flow diagram — where it is built, where it is verified, where it is stored, and how tags relate to releases and rollbacks.
  • Pre-publication checklist: signatures, version labels, verification that there are no secrets or extra files inside layers.
  • Recovery instructions: how to bring up the build environment from scratch if a node is lost or moves to another environment.
  • A set of utility scripts for reproducing the build on a developer's machine — the same result as on the server.

Each document is tied to a specific file in the repository, so a discrepancy between the description and the actual build is visible on the first change. After that, the team edits the profiles themselves, while we stay in touch for disputed cases.

Case study: how image building stopped depending on privileges

Image building went through a privileged service: a permanent root-privileged daemon held the socket, and every build step inherited its permissions.

A full run took about twelve minutes, and every fifth run failed — most often during system package installation, when the service lost access to the working directory. Troubleshooting had to be done manually: only the return code remained in the log.

We moved the build to Buildah and abandoned the privileged service entirely. The image is built as a regular user, in a separate namespace, without a daemon and without access to the system socket — a typical build no longer needs elevated privileges.

The steps are described declaratively, so the same set of commands produces the same result both on an engineer's machine and on a build node.

Before the migration, we ran both schemes in parallel on the same set of descriptions and compared time, failure rate, and checksums of the finished images.

The difference was confirmed over ten consecutive runs, after which the old service was disabled and removed from the node configuration.

Metrics before and after: the full run decreased from about twelve minutes to four — reuse of unchanged layers and parallel builds had an effect.

The share of failed runs went from 18% to almost zero: the main source of failures disappeared — the race for privileges and orphaned service processes. The size of the resulting images decreased by about a quarter because intermediate tools no longer remain in the final layer.

The gain is not only in speed. The build node no longer requires elevated privileges, so the build runs on the same host as other tasks, without a separate hardened machine.

Rollback to the previous image remains a routine operation: tags and layers are preserved, and reproducibility is confirmed by re-running from the same description.

How does daemonless building differ from classic building?

Classic building relies on a constantly running service: a separate process accepts the context, builds layers, and keeps them in a shared cache.

Daemonless building is an ordinary user-space process: it parses the description itself, forms layers, and writes them to the local image store without starting a background service.

The difference shows in privileges and isolation. The service needs a privileged start and a shared entry point — all tasks on the machine share one cache and one store.

Building without the service runs under an unprivileged user, each task gets its own namespace, and a shared socket stops being mandatory for operation.

Option When it fits Limitations
Classic build via a service Local development where a shared layer cache and familiar commands are needed Requires a constantly running process and privileges; the shared access point expands the risk surface
Daemonless build Continuous delivery jobs, unprivileged builds, agents on shared machines There is no familiar service API — build and publication scripts need to be moved to the new tool
Hybrid scheme Gradual migration: daemonless build, publication and local run — with the previous tool Two maintenance paths and different caches: while both live, you need to monitor divergence of base images

The migration does not require rewriting descriptions: the instruction syntax is shared, and the output is the same image manifest.

Buildah reads the same Containerfile and Dockerfile as the classic scheme, so the work comes down to replacing the build command and configuring the store. The image in the registry looks the same for acceptance and deployment — only the way it is obtained on the machine changes.

Engineers' concerns: cache, private registries, compatibility

Daemonless image building removes some concerns but raises new ones: will the tool parse an existing Dockerfile, how does it access a private registry, and will the layer cache survive the next run. Below is what engineers check before starting work.

Will the tool read our build descriptions?

Dockerfile and Containerfile syntax is parsed by a single parser: multi-stage builds, ARG, COPY, heredoc, and platform specification work without rewriting.

The exception is daemon-dependent instructions: for them, we provide a direct replacement in the report and verify the result against the image checksum.

What about private registries?

Credentials are taken from the familiar authorization file; tokens and client certificates are supported. Secrets are mounted only for the duration of the step and do not persist in layers — this is checked by a scanner before publication.

Will the layer cache persist between builds?

The cache lives in the local store and is reused on repeated runs if layer-by-layer mode is enabled.

In a distributed environment, we move it to a shared volume or an intermediate registry: otherwise each agent pulls dependencies again, and build time increases several times over. An edit in the last layer does not reset the previous ones.

Will the image build for multiple architectures?

One command builds images for x86-64 and arm64, and a platform list goes to the registry — the runtime itself selects the required variant. We speed up building for a foreign architecture with emulation and verify both platforms in a single run.

Let's discuss the task: what to clarify before starting build work

Before estimating effort, we need to understand the current build pipeline and where the images end up. Below are the inputs sufficient to come back with an estimate and an implementation plan, rather than general words.

  • Build descriptions and scripts: existing Dockerfile, entrypoint, job steps — they show which instructions stop working in daemonless builds.
  • Target registries and access: store addresses, separate accounts for reading base layers and writing the result, image signing requirements.
  • Platform matrix: which architectures we build for and whether multi-architecture builds are needed — on a single node or distributed.
  • Base images: allowed sources and internal mirrors, requirements for minimal package composition and vulnerability updates.
  • Build environment: rootless runners, network access, CPU and disk limits, layer cache requirements between runs.
  • Tag and retention policy: versioning scheme, registry cleanup rules, rollback depth to previous releases.
  • Deployment environment: cluster or individual hosts, health checks, requirements for rolling back to the previous version.

Describe the task in free form — that will be enough for an initial review. We will come back with an estimate, a stage-by-stage implementation plan, and the handover procedure for the build to your team: configuration, documentation, and a walkthrough with your engineers.