2 giờ sáng. Pipeline GitLab CI của team báo đỏ. Log hiện ra dòng quen thuộc mà mình đã ghét từ lâu:
Cannot connect to the Docker daemon at unix:///var/run/docker.sock.
Is the docker daemon running?
Thật ra đây không phải lần đầu mình gặp cái lỗi này. Hồi deploy microservices cho dự án e-commerce, pipeline build image cứ fail mỗi khi job chạy trong isolated container vì không mount được Docker socket từ host. Lúc đó fix tạm bằng cách mount /var/run/docker.sock vào container CI — nó chạy được, nhưng về sau mới biết đó là quyết định tệ nhất trong tháng về mặt bảo mật.
Tại sao Docker Daemon là vấn đề trong môi trường CI/CD?
Khi dùng docker build trong pipeline, có hai vấn đề cốt lõi:
- Docker cần daemon chạy với quyền root — trong môi trường container hóa như Kubernetes hay GitLab shared runners, không phải lúc nào cũng có Docker daemon sẵn sàng trên host.
- Mount Docker socket là rủi ro bảo mật nghiêm trọng — bất kỳ process nào truy cập được
/var/run/docker.sockđều có thể leo thang đặc quyền lên root của host.
Giải pháp phổ biến là Docker-in-Docker (DinD) — chạy một Docker daemon khác ngay bên trong container CI. Nghe hay, nhưng DinD yêu cầu --privileged mode, vốn tắt gần hết các tính năng bảo mật của container. Với môi trường production, đây là điều tối kị.
Cần một công cụ khác.
Buildah là gì và tại sao nó giải quyết được vấn đề này?
Buildah là tool build OCI (Open Container Initiative) image do Red Hat phát triển, thuộc bộ công cụ container không daemon gồm Buildah + Podman + Skopeo. Điểm mấu chốt:
- Không cần Docker daemon — hoàn toàn standalone
- Hỗ trợ rootless build — chạy với user thường, không cần sudo
- Output là OCI image chuẩn — tương thích hoàn toàn với Docker, Kubernetes, Podman
- Đọc được
DockerfilevàContainerfilenhư bình thường
Cài đặt Buildah trên Linux
RHEL / CentOS / Fedora
sudo dnf install -y buildah
Ubuntu / Debian
sudo apt-get update
sudo apt-get install -y buildah
Kiểm tra cài đặt:
buildah version
# buildah version 1.35.0 (image-spec 1.1.0, runtime-spec 1.2.0)
Để dùng rootless build (không cần sudo), cần cấu hình user namespace mapping:
# Thay "youruser" bằng username thực của bạn
echo "youruser:100000:65536" | sudo tee -a /etc/subuid
echo "youruser:100000:65536" | sudo tee -a /etc/subgid
Các cách build image với Buildah
Cách 1: Dùng Containerfile (đơn giản, quen thuộc)
Tạo file Containerfile — cú pháp hoàn toàn giống Dockerfile:
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
Build:
# Build từ Containerfile trong thư mục hiện tại
buildah build -t my-node-app:latest .
# Hoặc chỉ định file rõ ràng
buildah build -f Containerfile -t my-node-app:latest .
Xong. Không daemon, không root.
Cách 2: Build từng bước bằng shell script (scripted build)
Đây là điểm mạnh độc đáo của Buildah. Bạn có thể kiểm soát từng layer bằng shell script thuần:
#!/bin/bash
set -e
# Tạo container working từ base image
CONTAINER=$(buildah from node:20-alpine)
# Mount filesystem của container ra host
MOUNTPOINT=$(buildah mount $CONTAINER)
# Copy files vào container — dùng cp/rsync của host, không cần RUN layer
mkdir -p $MOUNTPOINT/app
cp -r ./src $MOUNTPOINT/app/
cp package*.json $MOUNTPOINT/app/
# Chạy lệnh bên trong container
buildah run $CONTAINER -- sh -c "cd /app && npm ci --only=production"
# Set metadata cho image
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 và commit thành image cuối
buildah unmount $CONTAINER
buildah commit $CONTAINER my-node-app:v1.0.0
# Dọn dẹp container tạm
buildah rm $CONTAINER
echo "Build xong: my-node-app:v1.0.0"
Cách này cho phép dùng mọi công cụ của host (rsync, sed, jq…) để xử lý file trước khi đóng gói, mà không tốn thêm RUN layer nào — kết quả là image nhỏ hơn và build nhanh hơn.
Rootless Build — Chạy không cần quyền root
Đây là lý do chính mình chuyển toàn bộ CI/CD sang Buildah. Chạy với user thường:
# Không cần sudo
whoami # output: ci-runner
buildah build -t myapp:latest .
# Build thành công — zero root privilege
Ngay cả khi pipeline bị compromise, attacker chỉ có quyền của user ci-runner, không leo thang được lên root host. Đây là thứ DinD không bao giờ đảm bảo được.
Tích hợp Buildah vào CI/CD Pipeline
GitLab CI
stages:
- build
- push
build-image:
stage: build
image: quay.io/buildah/stable:latest
variables:
STORAGE_DRIVER: vfs # Dùng vfs trong môi trường container lồng nhau
BUILDAH_FORMAT: docker # Output format tương thích Docker registry
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"
Không --privileged, không mount Docker socket, không DinD. Job chạy sạch trong image Buildah chính thức của Red Hat.
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 }}
Cách tốt nhất: Buildah + Podman cho workflow hoàn chỉnh
Sau khi chuyển CI/CD của team từ Docker-in-Docker sang Buildah, workflow ổn định nhất mình đúc kết được là:
- Build image: Buildah (rootless, không daemon)
- Test container: Podman để chạy container kiểm tra (
podman runthaydocker run— cũng không cần daemon) - Push lên registry: Buildah push hoặc Skopeo copy
# Bước 1: Build
buildah build -t myapp:test .
# Bước 2: Test (Podman cũng rootless, không daemon)
podman run --rm myapp:test npm test
# Bước 3: Tag và push lên production registry
buildah tag myapp:test registry.example.com/myapp:latest
buildah push registry.example.com/myapp:latest
# Xem danh sách image đã build
buildah images
# Dọn dẹp image cũ
buildah rmi myapp:test
Bộ ba Buildah + Podman + Skopeo thay thế hoàn toàn Docker trong môi trường server mà không cần daemon chạy ngầm, không cần root, và quan trọng nhất — không có /var/run/docker.sock nào để bị khai thác.
Nhìn lại cái đêm e-commerce ngồi debug memory leak trong container, phần lớn thời gian mất đi là do môi trường build không ổn định vì DinD tạo ra quá nhiều layer caching conflict. Chuyển sang Buildah, pipeline sạch hơn, reproducible hơn, và team security không còn nhắc nhở mỗi sprint review nữa.
Nếu bạn đang vận hành CI/CD trên Kubernetes, GitLab shared runners, hay bất kỳ môi trường nào mà Docker daemon là vấn đề — Buildah là câu trả lời thực dụng nhất hiện tại.

