Nỗi ám ảnh mang tên “exec format error”
Nếu bạn đang dùng Macbook chip M1/M2 hoặc thuê VPS ARM (như AWS Graviton, Oracle Cloud) để tiết kiệm chi phí, chắc hẳn bạn đã từng gặp tình cảnh trớ trêu này. Bạn build một Docker image cực kỳ tâm huyết trên máy cá nhân, đẩy lên Docker Hub, nhưng khi kéo về server chạy thì nhận ngay thông báo: standard_init_linux.go:211: exec user process caused "exec format error".
Lỗi này xảy ra do sự bất đồng nhất về kiến trúc CPU (Architecture). Máy cá nhân của bạn có thể chạy chip ARM, trong khi server lại dùng chip Intel/AMD (x86_64). Trước đây, mình thường phải build thủ công trên hai máy khác nhau rồi đặt tag kiểu my-app:v1-arm64 và my-app:v1-amd64. Cách làm này rất thủ công, tốn thời gian và cực kỳ dễ nhầm lẫn khi triển khai hệ thống lớn.
Sau nhiều lần “đau thương”, mình đã chuyển hẳn sang dùng Docker Buildx. Đây là một plugin mạnh mẽ giúp tạo ra một image duy nhất nhưng chạy mượt mà trên mọi loại chip. Dưới đây là kinh nghiệm thực tế để setup và sử dụng nó hiệu quả nhất.
Tại sao Buildx lại vượt trội hơn Docker build thông thường?
Lệnh docker build mặc định chỉ tạo image cho kiến trúc của máy hiện tại. Nếu bạn dùng máy Intel, nó build ra x86. Nếu dùng M1, nó sẽ build ra ARM.
Ngược lại, Buildx tận dụng Moby BuildKit và QEMU để giả lập các môi trường phần cứng khác nhau. Điểm mình thích nhất là khả năng tạo ra Manifest List. Khi bạn thực hiện docker pull, Docker sẽ tự động nhận diện phần cứng máy bạn để chọn phiên bản phù hợp nhất. Bạn không còn phải loay hoay chỉ định tag cụ thể cho từng loại CPU nữa.
Cài đặt và chuẩn bị môi trường
Đa số Docker Desktop trên Windows và Mac hiện nay đã tích hợp sẵn Buildx. Để chắc chắn, bạn hãy kiểm tra bằng lệnh: docker buildx version.
Nếu terminal trả về phiên bản, bạn đã có sẵn công cụ. Bước tiếp theo là cài đặt QEMU. Công cụ này giúp máy tính “giả vờ” là một con chip khác để thực thi các tập lệnh trong quá trình build.
docker run --privileged --rm tonistiigi/binfmt --install all
Mình thường chạy lệnh này một lần duy nhất khi setup máy mới. Nó sẽ đăng ký các trình thông dịch binary cho các kiến trúc khác nhau vào nhân Linux.
Cấu hình chi tiết Builder Instance
Driver mặc định của Docker thường không hỗ trợ build nhiều kiến trúc cùng lúc. Vì vậy, chúng ta cần tạo một “máy build ảo” mới (builder instance).
Bước 1: Tạo builder mới
Hãy đặt tên cho nó là mybuilder để dễ quản lý:
docker buildx create --name mybuilder --use
Bước 2: Khởi động builder
Kích hoạt nó bằng lệnh sau:
docker buildx inspect --bootstrap
Danh sách các Platforms hỗ trợ sẽ hiện ra, ví dụ: linux/amd64, linux/arm64, linux/riscv64.... Nếu thấy xuất hiện cả amd64 và arm64, bạn đã sẵn sàng để thực chiến.
Thực hành build image Multi-Arch
Giả sử mình có một project Node.js với Dockerfile cơ bản. Để build cho cả Intel và ARM, mình dùng lệnh:
docker buildx build --platform linux/amd64,linux/arm64 -t your-username/my-app:latest --push .
Lưu ý từ thực tế: Bạn nên dùng flag --push để đẩy thẳng lên Registry. Hiện tại --load chưa hỗ trợ tốt việc lưu trữ multi-arch trực tiếp trên local storage của Docker. Buildx sẽ tự động ghép các phiên bản thành một Manifest duy nhất trước khi đẩy lên Hub.
Trong quá trình chạy, bạn sẽ thấy Buildx xử lý song song hai luồng. Nếu code có các thư viện cần biên dịch (như node-gyp), việc build cho ARM trên máy Intel có thể chậm hơn 5-10 lần do phải qua lớp giả lập QEMU. Bạn hãy kiên nhẫn nhé!
Kiểm tra và xác nhận kết quả
Làm sao để biết chắc chắn image đã hỗ trợ đa nền tảng? Mình thường dùng hai cách sau:
- Cách 1: Kiểm tra UI: Vào Docker Hub, phần Tags. Bạn sẽ thấy tag
latesthiển thị cả hai biểu tượnglinux/amd64vàlinux/arm64. - Cách 2: Dùng Terminal: Chạy lệnh
docker buildx imagetools inspect your-username/my-app:latest. Nó sẽ trả về chi tiết checksum cho từng loại CPU.
Mẹo nhỏ để tránh lỗi vặt
- Base Image: Luôn kiểm tra xem image ở dòng
FROMcó hỗ trợ multi-arch không. Các image chính chủ như node, python, alpine đều đã hỗ trợ rất tốt. - Tốc độ build: Với project lớn, việc giả lập sẽ rất chậm. Ở môi trường doanh nghiệp, mình thường kết nối một máy Intel và một máy ARM thật vào cùng một builder instance để build native (tốc độ nhanh hơn đáng kể).
- Lệnh tải Binary: Tránh dùng
curltải trực tiếp một file cụ thể nhưtool-linux-x64. Thay vào đó, hãy sử dụng biến môi trường${TARGETARCH}để Docker tự chọn bản tải xuống phù hợp.
Làm chủ Docker Buildx giúp quy trình CI/CD chuyên nghiệp hơn rất nhiều. Bạn có thể linh hoạt chuyển đổi giữa các nhà cung cấp cloud để tối ưu chi phí mà không lo ngại vấn đề tương thích. Chúc bạn thực hiện thành công!

