Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Buildah: Build OCI Images Without Docker

By Kokil Thapa | Last reviewed: September 2026

Buildah: Build OCI Images Without Docker solves a problem every DevOps engineer hits eventually. Your CI runner is Linux, your app ships as a container, but installing and running the Docker daemon on build agents is slow, privileged, and often blocked by policy. Podman and other daemonless tools changed that model. Buildah focuses on one job: take a base image, run commands, and emit a valid OCI-compliant image you can push to any registry. On production Laravel and WordPress deployments I maintain, that separation keeps build pipelines simple and servers free of Docker entirely.

What is Buildah and why build OCI images without Docker?

Buildah is a Red Hat–backed CLI in the containers/buildah project. It creates and modifies container images on disk. The output follows the Open Container Initiative (OCI) image spec, so Podman, containerd, Kubernetes, and cloud registries accept it without conversion.

Docker bundles image building, runtime, networking, and volumes behind one daemon. That works on a developer laptop. On a shared CI node or a small VPS running Apache and PHP-FPM, a always-on daemon is overhead you do not need. Buildah never starts a long-lived service. Each command runs, writes layers, and exits.

In my experience maintaining Linux production servers, the wins are practical. Builds run rootless where policy allows. CI jobs need fewer privileges. You avoid Docker Desktop licensing questions on macOS build farms by moving image creation to Linux runners. The tool pairs naturally with Podman, which I covered in a separate Podman vs Docker migration guide.

Buildah vs Docker: Image Build ArchitectureDocker ModelClient talks to dockerdDaemon holds imagesBuildah ModelCLI writes OCI layersNo background daemonDockerfileBuildah CLIOCI ImageRegistry: GHCR, ECR, self-hostedPush with buildah push or skopeo
Buildah: Build OCI Images Without Docker using a daemonless CLI that writes standard layers directly to disk or a registry.

Core commands you will use daily

  • buildah bud — build from a Dockerfile (Buildah Using Dockerfile).
  • buildah from — start a working container from a base image tag.
  • buildah run — execute commands inside that working container.
  • buildah commit — freeze the working container into a new image.
  • buildah push — upload the image to a registry.
  • buildah rm — remove working containers and dangling images.

How do you install Buildah on Ubuntu or RHEL?

Buildah ships in most modern Linux repos. Ubuntu 22.04 and 24.04 include it. RHEL, Fedora, and CentOS Stream package it through the containers repository. Install it the same way you would any other container tooling on Ubuntu, but you do not enable dockerd afterward.

Ubuntu 24.04 quick install

sudo apt update
sudo apt install -y buildah podman fuse-overlayfs uidmap
buildah --version
buildah info

Verify rootless mode before wiring CI. Run buildah info as your deploy user. Look for rootless: true in the output. If it says false, configure subuid and subgid maps. That step mirrors what you need for rootless Podman.

Rootless prerequisites

  1. Ensure /etc/subuid and /etc/subgid contain entries for your user.
  2. Install fuse-overlayfs on hosts without native rootless overlay support.
  3. Set export BUILDAH_ISOLATION=chroot when fuse is unavailable in CI sandboxes.
  4. Log out and back in after subuid changes so the kernel applies new mappings.

For teams that prefer scripted server setup, this fits alongside routine support and maintenance work on Ubuntu VPS hosts. I treat Buildah as part of the same toolchain as GitLab CI and Deployer, not a separate platform.

How do you build a container image with Buildah step by step?

Two paths exist. Most teams start with an existing Dockerfile because Laravel, Symfony, and Node frontends already have one. Advanced pipelines use imperative from/run/commit scripts when they need finer layer control than Dockerfile syntax allows.

Path A: Build from a Dockerfile

Assume a Laravel 13 app on PHP 8.3. Your Dockerfile lives at the project root. From the repo checkout:

cd /var/www/myapp
buildah bud -t registry.example.com/myapp:2026.09.1 -f Dockerfile .
buildah images

Flags worth knowing:

  • --layers — cache intermediate layers between CI runs (default on).
  • --target stage — stop at a named multi-stage target, same as Docker.
  • --platform linux/amd64 — cross-build when QEMU binfmt is registered.
  • --build-arg APP_ENV=production — pass build-time variables.

This mirrors multi-stage Docker builds you may already use. A typical PHP production Dockerfile might use a Composer stage, copy vendor into a slim runtime stage, and run php artisan config:cache at build time. Buildah executes those instructions identically.

Path B: Imperative shell workflow

Imperative builds help when you prototype a base image or patch a vendor image without editing a Dockerfile. Example: PHP-FPM runtime with extensions:

container=$(buildah from docker.io/library/php:8.3-fpm-bookworm)
buildah run $container -- apt-get update
buildah run $container -- apt-get install -y libzip-dev
buildah run $container -- docker-php-ext-install pdo_mysql zip opcache
buildah config --label org.opencontainers.image.source=https://example.com/myapp $container
buildah commit $container myapp-php:8.3
buildah rm $container

Each run creates a new layer. Group related commands to avoid image bloat. The same advice applies when you shrink images in Docker; see our guide on reducing container image size.

Buildah OCI Image Build WorkflowBase Imagebuildahfrom / budbuildahrun layersbuildahcommitLocal OCI Image Storecontainers-storage under ~/.localbuildah pushDeploy targetKubernetes, Podman host, or cloud run service
Step-by-step Buildah workflow: pull a base, add layers with run or Dockerfile instructions, commit to OCI format, then push to your registry.

Example Dockerfile for a Laravel API

FROM php:8.3-fpm-bookworm AS vendor
WORKDIR /app
COPY composer.json composer.lock ./
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer \
    && composer install --no-dev --prefer-dist --no-interaction

FROM php:8.3-fpm-bookworm
WORKDIR /var/www/html
COPY --from=vendor /app/vendor ./vendor
COPY . .
RUN docker-php-ext-install pdo_mysql opcache
CMD ["php-fpm"]

Build and tag it:

buildah bud -t ghcr.io/myorg/laravel-api:$(git rev-parse --short HEAD) .

Commit the built frontend assets in CI if your production server has no Node.js. That pattern matches how I deploy several Laravel sites with GitLab CI and Deployer 7. Asset builds run in the pipeline; the server only runs PHP-FPM.

How does Buildah compare to Docker and Podman for CI pipelines?

Buildah builds images. Podman runs containers. Docker does both through one daemon. Pick the tool that matches the job, not the brand on the slide deck.

CriteriaBuildahPodmanDocker Engine
Primary roleImage creation and modificationRun containers daemonlesslyBuild + run + network + volumes
Background daemonNoneNonedockerd required
Rootless buildsYes, with subuid mapsYesRootless mode exists but less common in CI
Dockerfile supportbuildah budpodman build (Buildah backend)docker build
Typical CI fitDedicated build stageBuild + smoke test runAll-in-one if policy allows
Multi-platformWith QEMU binfmtSameBuildx with builders

For GitLab CI, a lean job might use Buildah to produce the image, then Podman to run a quick health check before push. That split keeps privileges minimal. If you already use Docker Buildx for multi-platform builds, Buildah offers a similar --platform flag once binfmt handlers are installed.

Google Cloud Build and other hosted runners often expose Docker. On your own Ubuntu runners, Buildah avoids socket mounting and docker group membership. That matters on shared EC2 hosts where I run multiple client sites. One compromised build job with Docker socket access is a host takeover. Buildah in rootless mode reduces that blast radius.

CI Pipeline: Buildah vs Docker SocketBuildah CI Jobgit pull → buildah budtrivy scan → buildah pushNo docker.sock mountRuns as CI userDocker Socket Jobgit pull → docker buildRequires /var/run/docker.sockOften needs docker groupHigher privilege riskShared GitLab Runner on Ubuntu VPSBuildah job: unprivileged shell executorOutput: OCI tag deployed via SSH or K8s
Buildah in CI avoids mounting the Docker socket while still producing registry-ready OCI images for deploy targets.

Sample GitLab CI job

build-image:
  stage: build
  tags: [linux, buildah]
  script:
    - buildah bud -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
    - buildah push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

Authenticate with buildah login using CI variables. Store registry credentials in GitLab masked variables, not in the repo. This aligns with broader build pipeline automation best practices and the overview in our build automation guide.

How do you push, scan, and sign images built with Buildah?

Building is half the job. Production pipelines scan for CVEs, sign images, and push to a registry you control. Buildah outputs standard OCI artifacts, so the same tools work here as with Docker.

Push to a private registry

buildah login registry.example.com -u deploy -p "$REGISTRY_TOKEN"
buildah push localhost/myapp:2026.09.1 docker://registry.example.com/myapp:2026.09.1

For a self-hosted registry on the same VPS, see our notes on how to self-host a container registry. The push syntax is identical whether the image was built with Docker or Buildah.

Scan with Trivy

Trivy reads OCI layouts and archive tarballs. After build:

buildah push localhost/myapp:2026.09.1 docker-archive:/tmp/myapp.tar
trivy image --severity HIGH,CRITICAL /tmp/myapp.tar

Fail the pipeline on critical findings. That gate pairs well with the workflow in our container image scanning with Trivy article. For supply-chain hardening, add Cosign image signing after scan passes.

Run a smoke test with Podman

podman run --rm localhost/myapp:2026.09.1 php artisan --version

Buildah created the image. Podman runs it. No Docker involved. On a booking platform like Adventure Third Pole Trek, a smoke test might hit php artisan route:list or a health endpoint before deploy.

Buildah Rootless Build GotchasMissing subuidFix: usermod mapsNo fuse-overlayUse chroot isolationCOPY permissionCheck file ownershipSymptom: buildah bud fails in CICheck buildah info and storage driverSet BUILDAH_ISOLATIONchroot for sandboxesPrune stale layersbuildah rm --all
Common rootless Buildah failures—subuid maps, overlay drivers, and CI isolation settings—and the fixes that unblock OCI image builds.

Storage cleanup on small VPS disks

Buildah stores layers under ~/.local/share/containers for rootless users. On a 40 GB VPS hosting PHP apps and MySQL, disk fills fast if CI never prunes. Schedule weekly cleanup:

buildah rm --all --force
buildah rmi --all --force

Monitor free space the same way you would for database dumps. A full disk breaks deploys and container resource limits will not help if the host cannot write layers.

How do you use Buildah with Laravel, WordPress, and enterprise deploys?

Most of my client work still deploys as Git checkouts on PHP-FPM, not Kubernetes. Containers enter when a team wants identical staging and production, or when they move APIs to cloud run services. Buildah fits that transition because you can containerize without adopting Docker on the server.

For a Laravel 12 or 13 app, containerize the PHP-FPM side first. Keep Nginx on the host or in a separate container. Mount .env at runtime, not bake time. Never copy production secrets into an image layer. Use enterprise application development patterns: environment-specific config via orchestrator secrets or host-mounted files.

WordPress containers are trickier. Uploads and wp-content should live on persistent volumes. Buildah can produce a image with core PHP and extensions. Mount the database and media separately. For WooCommerce shops like florist sites in my portfolio, image builds focus on PHP version parity, not the whole media library.

When a team outgrows single-host Podman, the same OCI images deploy to Kubernetes. The build step does not change. Only the runtime target does. That is the point of the OCI standard: build once, run anywhere.

If you need a JSON manifest for your pipeline config, validate it with our JSON formatter tool before committing CI YAML. Small syntax errors waste long build cycles.

Key Takeaways

  • Buildah builds OCI images without a Docker daemon—ideal for rootless CI on Ubuntu and RHEL runners.
  • Use buildah bud for existing Dockerfiles; use from/run/commit when you need scriptable layer control.
  • Pair Buildah with Podman for smoke tests, Trivy for scans, and Cosign for signing before registry push.
  • Configure subuid/subgid and fuse-overlayfs before relying on rootless builds in production pipelines.
  • Prune local container storage regularly on small VPS hosts to avoid failed builds from full disks.
  • Keep secrets out of image layers; inject .env and credentials at deploy time, not at buildah bud time.

People Also Ask

Can Buildah replace Docker completely?

Buildah replaces Docker for image creation. You still need a runtime like Podman or containerd to run containers. Many teams drop Docker entirely by pairing Buildah plus Podman on Linux. Developers on macOS often keep Docker Desktop locally but build release images on Linux CI with Buildah.

Does Buildah support Docker Compose?

Buildah does not run Compose files. Use podman-compose or Podman’s native compose support to orchestrate multi-container stacks. Buildah handles the image half; Compose handles service wiring, networks, and volumes at runtime.

Is Buildah production-ready for GitLab CI and Jenkins?

Yes. Both platforms support shell-based build steps. Install Buildah on the runner, avoid mounting docker.sock, and push to GHCR, GitLab Registry, ECR, or a private registry. Red Hat and the CNCF ecosystem use this stack in production today.

How is Buildah different from Kaniko or Google Cloud Build?

Kaniko also builds without a daemon, mainly inside Kubernetes. Google Cloud Build is a hosted service. Buildah is a local CLI you control on any Linux runner. Choose Buildah when you self-host CI on a VPS or bare metal and want no daemon dependency.

Ship OCI images on your terms

Buildah: Build OCI Images Without Docker gives you a straightforward path off the Docker daemon without leaving the Dockerfile ecosystem you already know. Install it on your Linux runner, wire buildah bud and buildah push into GitLab CI, scan before deploy, and run containers with Podman on the host. That stack matches how I deploy and maintain production apps for clients who need reliable builds on budget-friendly VPS hardware in Nepal and abroad.

If you want help containerizing a Laravel app, hardening CI, or migrating off Docker on Ubuntu servers, contact us to discuss your pipeline. For related reading, explore Google Cloud Build automation, Packer for golden machine images, and more about how I work with PHP, Linux, and deployment automation.

Frequently Asked Questions

Buildah is a Red Hat-backed CLI that creates and modifies OCI-compliant container images on disk. Each command runs and exits—no long-lived daemon required.

No. Buildah writes OCI image layers directly to disk or a registry without starting dockerd, which suits rootless CI on shared Linux runners.

On Ubuntu 22.04 or 24.04, run apt update, then install buildah, podman, fuse-overlayfs, and uidmap. Verify with buildah --version and buildah info. Check that rootless: true appears before wiring CI. If false, configure /etc/subuid and /etc/subgid entries for your deploy user, install fuse-overlayfs where native rootless overlay is unavailable, and log out and back in after subuid changes so the kernel applies new mappings.

The article covers six essentials: buildah bud builds from a Dockerfile, buildah from starts a working container from a base tag, buildah run executes commands inside it, buildah commit freezes that container into a new image, buildah push uploads to a registry, and buildah rm removes working containers and dangling images. Together they support both declarative Dockerfile workflows and imperative shell-driven assembly without any background service running on the host.

From your project root, run buildah bud with a tag and Dockerfile path. Useful flags mirror Docker: --layers caches intermediate layers between CI runs, --target stops at a named multi-stage stage, --platform linux/amd64 supports cross-builds when QEMU binfmt is registered, and --build-arg passes build-time variables like APP_ENV=production. A typical Laravel production Dockerfile might use a Composer vendor stage, copy vendor into a slim PHP-FPM runtime, and run php artisan config:cache at build time—Buildah executes those instructions identically to Docker.

Use the imperative from/run/commit path when prototyping a base image, patching a vendor image without editing a Dockerfile, or needing finer layer control than Dockerfile syntax allows. Pull a base with buildah from, install packages with buildah run, add labels via buildah config, then buildah commit to tag the result and buildah rm to clean up. Each run creates a new layer, so group related commands to avoid image bloat—the same advice applies when shrinking Docker images on production pipelines.

Ensure /etc/subuid and /etc/subgid contain entries for your CI deploy user. Install fuse-overlayfs on hosts without native rootless overlay support. Run buildah info and confirm rootless: true before relying on rootless builds in production. If fuse is unavailable inside CI sandboxes, set export BUILDAH_ISOLATION=chroot. These steps mirror rootless Podman setup. Rootless mode reduces blast radius on shared EC2 hosts where a compromised job with Docker socket access could mean full host takeover.

Buildah builds images, Podman runs containers daemonlessly, and Docker Engine does both through dockerd. For GitLab CI, a lean pattern uses Buildah to produce the image and Podman for a quick smoke test before push—keeping privileges minimal. Buildah supports --platform for multi-platform builds once binfmt handlers are installed, similar to Docker Buildx. On self-hosted Ubuntu runners, Buildah avoids socket mounting and docker group membership that hosted services like Google Cloud Build typically expose through Docker.

Add a build-image stage on a Linux runner tagged for Buildah. Script buildah bud with your registry image and commit SHA, then buildah push. Restrict the job to main or your release branches via rules. Authenticate using buildah login with masked CI variables for registry credentials—never store tokens in the repo. Commit frontend assets in the pipeline if your production server has no Node.js, matching the Deployer 7 pattern where asset builds run in CI and the server only runs PHP-FPM.

Authenticate with buildah login, then push using buildah push with the docker://registry prefix for private registries. For scanning, export to docker-archive and run Trivy with --severity HIGH,CRITICAL, failing the pipeline on critical findings. Add Cosign image signing after scans pass for supply-chain hardening. Run a smoke test with Podman—for example php artisan --version on a Laravel image—before deploy. Buildah created the OCI artifact; Podman validates runtime behavior without Docker involved.

Not entirely. Buildah replaces Docker for image creation. You still need Podman, containerd, or another runtime to run containers. Many Linux teams pair Buildah plus Podman and drop dockerd.

No. Buildah does not run Compose files. Use podman-compose or Podman's native compose support to orchestrate multi-container stacks with service wiring, networks, and volumes at runtime. Buildah handles only the image half of that workflow. On production PHP deployments that still use Git checkouts on Apache and PHP-FPM, Compose may not enter the picture until a team containerizes staging and production for environment parity.

Kaniko also builds without a daemon but targets Kubernetes environments primarily. Google Cloud Build is a hosted managed service. Buildah is a local CLI you control on any Linux runner—VPS or bare metal—with no daemon dependency. Choose Buildah when you self-host CI and want registry-ready OCI images on your terms. On shared EC2 hosts running multiple client sites, that control means avoiding Docker socket mounts while still pushing to GHCR, GitLab Registry, ECR, or a private registry.

Common blockers include missing subuid and subgid maps, overlay driver problems, and CI isolation settings. Verify /etc/subuid and /etc/subgid, install fuse-overlayfs, confirm buildah info reports rootless: true, and set BUILDAH_ISOLATION=chroot when fuse is unavailable in sandboxes. On small VPS disks, Buildah stores rootless layers under ~/.local/share/containers; without pruning, a 40 GB host hosting PHP apps and MySQL fills fast and breaks builds. Schedule weekly buildah rm --all --force and buildah rmi --all --force, and monitor free space like database dump storage.

For Laravel 12 or 13, containerize the PHP-FPM side first, keep Nginx on the host or in a separate container, and mount .env at runtime—never bake production secrets into image layers. Use multi-stage Dockerfiles with Composer vendor stages and php artisan config:cache at build time. WordPress images should include core PHP and extensions while uploads and wp-content live on persistent volumes with the database mounted separately. When a team outgrows single-host Podman, the same OCI images deploy to Kubernetes without changing the build step.

Share this article

0 Comments

Leave a comment

Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

Quick Contact Options
Choose how you want to connect me: