2 AM. The team’s GitLab CI pipeline is showing red. The logs display that familiar line I’ve grown to hate:
Cannot connect to the Docker daemon at unix:///var/run/docker.sock.
Is the docker daemon running?
This wasn’t my first encounter with this error. Back when deploying microservices for an e-commerce project, the image build pipeline kept failing whenever a job ran inside an isolated container because it couldn’t mount the Docker socket from the host. The quick fix was to mount /var/run/docker.sock into the CI container — it worked, but I later learned that was the worst security decision I made that month.
Why Docker Daemon Is a Problem in CI/CD Environments
When using docker build in a pipeline, there are two core problems:
- Docker requires a daemon running as root — in containerized environments like Kubernetes or GitLab shared runners, a Docker daemon isn’t always available on the host.
- Mounting the Docker socket is a serious security risk — any process with access to
/var/run/docker.sockcan escalate privileges to root on the host.
The popular workaround is Docker-in-Docker (DinD) — running a separate Docker daemon inside the CI container. It sounds clever, but DinD requires --privileged mode, which effectively disables most of the container’s security features. In a production environment, this is a hard no.
A different tool is needed.
What Is Buildah and Why Does It Solve This Problem?
Buildah is an OCI (Open Container Initiative) image build tool developed by Red Hat, part of the daemonless container toolkit that includes Buildah + Podman + Skopeo. The key advantages:
- No Docker daemon required — completely standalone
- Supports rootless builds — runs as a regular user, no sudo needed
- Outputs standard OCI images — fully compatible with Docker, Kubernetes, and Podman
- Reads
DockerfileandContainerfilenatively
Installing Buildah on Linux
RHEL / CentOS / Fedora
sudo dnf install -y buildah
Ubuntu / Debian
sudo apt-get update
sudo apt-get install -y buildah
Verify the installation:
buildah version
# buildah version 1.35.0 (image-spec 1.1.0, runtime-spec 1.2.0)
To use rootless builds (no sudo required), configure the user namespace mapping:
# Replace "youruser" with your actual username
echo "youruser:100000:65536" | sudo tee -a /etc/subuid
echo "youruser:100000:65536" | sudo tee -a /etc/subgid
Ways to Build Images with Buildah
Option 1: Using a Containerfile (simple and familiar)
Create a Containerfile — the syntax is identical to a Dockerfile:
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
Build:
# Build from the Containerfile in the current directory
buildah build -t my-node-app:latest .
# Or specify the file explicitly
buildah build -f Containerfile -t my-node-app:latest .
Done. No daemon, no root.
Option 2: Step-by-step build using a shell script (scripted build)
This is Buildah’s unique strength. You can control every layer using a plain shell script:
#!/bin/bash
set -e
# Create a working container from the base image
CONTAINER=$(buildah from node:20-alpine)
# Mount the container's filesystem to the host
MOUNTPOINT=$(buildah mount $CONTAINER)
# Copy files into the container — using the host's cp/rsync, no extra RUN layer needed
mkdir -p $MOUNTPOINT/app
cp -r ./src $MOUNTPOINT/app/
cp package*.json $MOUNTPOINT/app/
# Run commands inside the container
buildah run $CONTAINER -- sh -c "cd /app && npm ci --only=production"
# Set image metadata
buildah config --cmd '["node", "/app/server.js"]' $CONTAINER
buildah config --port 3000 $CONTAINER
buildah config --label version=1.0.0 $CONTAINER
buildah config --env NODE_ENV=production $CONTAINER
# Unmount and commit as the final image
buildah unmount $CONTAINER
buildah commit $CONTAINER my-node-app:v1.0.0
# Clean up the temporary container
buildah rm $CONTAINER
echo "Build complete: my-node-app:v1.0.0"
This approach lets you use any host-side tool (rsync, sed, jq…) to process files before packaging, without adding extra RUN layers — resulting in smaller images and faster builds.
Rootless Build — Running Without Root Privileges
This is the primary reason I migrated our entire CI/CD stack to Buildah. Running as a regular user:
# No sudo needed
whoami # output: ci-runner
buildah build -t myapp:latest .
# Build succeeded — zero root privilege
Even if the pipeline is compromised, an attacker only gets the permissions of the ci-runner user — no path to escalate to root on the host. This is something DinD can never guarantee.
Integrating Buildah into CI/CD Pipelines
GitLab CI
stages:
- build
- push
build-image:
stage: build
image: quay.io/buildah/stable:latest
variables:
STORAGE_DRIVER: vfs # Use vfs in nested container environments
BUILDAH_FORMAT: docker # Output format compatible with Docker registries
script:
- buildah login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
- buildah build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA .
- buildah push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
rules:
- if: $CI_COMMIT_BRANCH == "main"
No --privileged, no Docker socket mount, no DinD. The job runs cleanly inside Red Hat’s official Buildah image.
GitHub Actions
name: Build with Buildah
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Buildah
run: sudo apt-get install -y buildah
- name: Build image
run: |
buildah build -t ghcr.io/${{ github.repository }}:${{ github.sha }} .
- name: Push image
run: |
echo "${{ secrets.GITHUB_TOKEN }}" | buildah login ghcr.io -u ${{ github.actor }} --password-stdin
buildah push ghcr.io/${{ github.repository }}:${{ github.sha }}
The Best Approach: Buildah + Podman for a Complete Workflow
After migrating the team’s CI/CD from Docker-in-Docker to Buildah, the most stable workflow I’ve settled on is:
- Build the image: Buildah (rootless, no daemon)
- Test the container: Podman to run and verify the container (
podman runinstead ofdocker run— also daemonless) - Push to registry: Buildah push or Skopeo copy
# Step 1: Build
buildah build -t myapp:test .
# Step 2: Test (Podman is also rootless and daemonless)
podman run --rm myapp:test npm test
# Step 3: Tag and push to the production registry
buildah tag myapp:test registry.example.com/myapp:latest
buildah push registry.example.com/myapp:latest
# List built images
buildah images
# Remove old images
buildah rmi myapp:test
The Buildah + Podman + Skopeo trio fully replaces Docker in a server environment — no background daemon, no root access required, and most importantly — no /var/run/docker.sock to be exploited.
Looking back at that e-commerce late-night debugging session, most of the lost time came from an unstable build environment caused by DinD generating too many layer caching conflicts. After switching to Buildah, the pipeline became cleaner, more reproducible, and the security team stopped bringing it up every sprint review.
If you’re running CI/CD on Kubernetes, GitLab shared runners, or any environment where the Docker daemon is a pain point — Buildah is the most practical answer available right now.

