---
id: docker-buildkit
title: "BuildKit"
shortTitle: "BuildKit"
track: docker
trackTitle: "Docker"
order: 4
level: core
status: published
estimatedMinutes: 40
prerequisites:
  - docker-dockerfile
next: docker-entrypoint
reviewedAgainst: 2026-10-19
sources:
  - title: "KodeKloud — Docker Training Course for the Absolute Beginner"
    url: https://learn.kodekloud.com/learn/courses/docker-training-course-for-the-absolute-beginner
  - title: "BuildKit"
    url: https://docs.docker.com/build/buildkit/
  - title: "Cache mounts"
    url: https://docs.docker.com/build/cache/optimize/
  - title: "Build secrets"
    url: https://docs.docker.com/build/building/secrets/
  - title: "Multi-platform builds"
    url: https://docs.docker.com/build/building/multi-platform/
  - title: "Multi-stage builds"
    url: https://docs.docker.com/build/building/multi-stage/
---

# BuildKit

`docker build` trên Docker Engine từ bản 23.0, và trên Docker Desktop, đã chạy bằng BuildKit. Dòng log `[+] Building` là output của builder đó. Builder cũ tải lại gói mỗi lần layer hỏng, giữ secret trong metadata của image, build đúng kiến trúc của máy đang ngồi, và chạy các stage độc lập nối tiếp nhau.

## Để làm gì

Build lại mà không tải lại cache gói, đưa token vào một lệnh `RUN` mà không để token trong image, và biết khi nào một image cần hai kiến trúc.

## Gồm những gì

- Cache mount cho thư mục cache của npm hoặc pip.
- Secret mount cho file token lúc build.
- `docker buildx build --platform` khi image phải chạy cả máy Intel và máy ARM. Đẩy manifest nhiều kiến trúc thì có `--push`.
- Stage không phụ thuộc nhau được build song song. Stage `COPY --from` đợi stage kia xong.

Bài này chưa nói `CMD` với `ENTRYPOINT`, và chưa nói file `.env` lúc chạy container.

## Dùng khi nào

Dockerfile đã build được, nhưng lần sau vẫn tải gói từ đầu, hoặc CI cần một token để lấy package riêng. Image chỉ để chạy trên laptop thì một nền tảng là đủ. Image trên registry cho cả máy dev ARM và server amd64 thì build hai nền tảng.

Đừng đưa mật khẩu database của app đang chạy vào secret mount. Secret mount chỉ sống trong một lệnh `RUN`. Process lúc `docker run` không thấy nó. Biến lúc chạy nằm ở bài sau.

## Kiến thức cốt lõi

BuildKit hash nội dung file đưa vào layer, rồi dùng lại layer khi nội dung đó không đổi. Bốn việc builder cũ làm kém nằm ngoài cache layer thường.

Cache mount gắn một thư mục cache của builder vào lệnh `RUN`. `npm` hoặc `pip` ghi gói đã tải vào thư mục đó. Layer image không chứa thư mục này. Lần build sau, dù dòng `RUN` phải chạy lại vì lockfile đổi, gói đã tải vẫn còn trên builder. Máy CI khác, hoặc builder khác, không có sẵn thư mục đó.

Secret mount lấy file hoặc biến từ client lúc gọi `docker build --secret`, gắn vào đúng lệnh `RUN`, mặc định ở `/run/secrets/<id>`. Hết lệnh thì mount biến mất. `docker history` không có nội dung file. Docs build secrets nói `ARG` và `ENV` không phải chỗ để secret: giá trị còn trong image. Ai có image thì `docker history` hoặc `docker inspect` đọc được.

<div class="osi">
<div class="osi-row" style="--layer:#dbeafe"><div class="osi-num">1</div><div class="osi-body"><div class="osi-name">Layer được export</div><div class="osi-bits"><span class="osi-bit">FROM</span><span class="osi-bit">COPY</span><span class="osi-bit">RUN</span></div><p class="osi-note">Phần nằm trong image. `docker history` đọc chỉ thị và file đã COPY.</p></div></div>
<div class="osi-row" style="--layer:#fef3c7"><div class="osi-num">2</div><div class="osi-body"><div class="osi-name">Cache mount</div><div class="osi-bits"><span class="osi-bit">type=cache</span><span class="osi-bit">đĩa của builder</span></div><p class="osi-note">Sống giữa các lần build trên cùng builder. Không đi vào image, không theo sang máy CI khác.</p></div></div>
<div class="osi-row" style="--layer:#dcfce7"><div class="osi-num">3</div><div class="osi-body"><div class="osi-name">Secret mount</div><div class="osi-bits"><span class="osi-bit">type=secret</span><span class="osi-bit">/run/secrets</span></div><p class="osi-note">Chỉ có trong lệnh RUN đó. Thiếu `--secret` lúc build thì file trống và lệnh fail.</p></div></div>
</div>

Dòng `# syntax=docker/dockerfile:1` ở đầu Dockerfile bật cú pháp `RUN --mount`. Các Dockerfile ở bài trước đã có dòng này.

Hai stage không `COPY --from` lẫn nhau thì BuildKit chạy cùng lúc. Stage cuối copy từ stage kia thì đợi stage đó xong. Không có cờ riêng để bật việc này.

`docker build --platform linux/amd64` ra một image cho kiến trúc đó. Hai kiến trúc trong một tag là một manifest list. Image store local của `docker images` không nhận manifest list. Lệnh có hai `--platform` cần `--push` tới registry, hoặc chỉ build một nền tảng khi chạy thử trên máy.

## Lệnh và code mẫu

Cache npm. Thư mục `/root/.npm` là cache của npm khi build bằng root. User `node` đặt sau lệnh này, vì `npm ci` cần ghi cache.

```dockerfile
# syntax=docker/dockerfile:1

FROM node:24-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm \
    npm ci --omit=dev
COPY . .
USER node
CMD ["node", "server.js"]
```

Pip cùng kiểu, target là cache của pip:

```dockerfile
RUN --mount=type=cache,target=/root/.cache/pip \
    pip install -r requirements.txt
```

Token để `npm ci` đọc registry riêng. File token nằm trên máy build, không `COPY`.

```dockerfile
# syntax=docker/dockerfile:1

FROM node:24-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
    npm ci
```

```bash
docker build --secret id=npmrc,src="$HOME/.npmrc" -t devops-lab-node:1 .
```

Thiếu `--secret` thì `/root/.npmrc` không có trong lệnh `RUN`. `npm ci` tới registry riêng sẽ fail, và image không giữ token.

Hai kiến trúc, đẩy lên registry đã login. Tag đổi theo repo thật.

```bash
docker buildx build \
  --platform linux/amd64,linux/arm64 \
  --tag ghcr.io/org/devops-lab-node:1 \
  --push \
  .
```

Một nền tảng để chạy trên máy đang ngồi thì `docker build -t devops-lab-node:1 .` như bài trước. BuildKit vẫn là builder phía sau.

## Thực hành

Lab này kiểm tra secret không lọt vào history. Làm trong thư mục trống.

`Dockerfile`:

```dockerfile
# syntax=docker/dockerfile:1

FROM alpine:3.20
RUN --mount=type=secret,id=token \
    sh -c 'test -s /run/secrets/token && echo ok > /seen'
CMD ["cat", "/seen"]
```

```bash
mkdir -p "$HOME/devops-lab/docker/secret"
cd "$HOME/devops-lab/docker/secret"
printf 'lab-token-do-not-bake\n' > token.txt
docker build -t devops-lab-secret:1 .
docker build --secret id=token,src=token.txt -t devops-lab-secret:1 .
docker run --rm devops-lab-secret:1
docker image history --no-trunc devops-lab-secret:1
```

Lần build đầu không có `--secret`. `test -s` fail, không có image. Lần sau có secret thì `docker run` in `ok`. Chuỗi `lab-token-do-not-bake` không xuất hiện trong `docker image history`.

## Kết quả mong đợi

- Build không `--secret` dừng với lỗi của `test`.
- `docker run` in `ok`.
- `docker image history --no-trunc` có dòng `RUN`, không có nội dung `token.txt`.

## Checklist

- [ ] Nhận dòng `[+] Building` là BuildKit, builder mặc định từ Engine 23.0.
- [ ] Gắn cache mount cho thư mục cache của npm hoặc pip, và nhớ mount không nằm trong image.
- [ ] Đưa token lúc build bằng `--secret` và `RUN --mount=type=secret`. Không dùng `ARG` hoặc `ENV` cho token đó.
- [ ] Hai `--platform` thì `--push`. Chạy thử trên một máy thì build một nền tảng.
- [ ] Stage không phụ thuộc nhau chạy song song. `COPY --from` đợi stage nguồn.

## Thẻ ôn

### docker-buildkit-cache
kind: decide
cue: `npm ci` hoặc `pip install` tải lại từ đầu dù gói không đổi: thêm cache mount. Mount nằm trên builder, không nằm trong image.

Q: Mỗi lần sửa một dòng trong lệnh cài dependency, build lại tải toàn bộ gói từ registry. Layer cache không giữ được bản đã tải. Chỗ nào giữ các gói đó giữa hai lần build, và vì sao thư mục đó không có trong image?

A: Cache mount, `RUN --mount=type=cache`, gắn thư mục cache của builder vào đúng lệnh đó. npm dùng `/root/.npm`, pip dùng `/root/.cache/pip`. BuildKit giữ thư mục này trên builder sau khi lệnh xong, nên lần sau lệnh chạy lại vẫn thấy gói đã tải. Thư mục không được commit vào layer. Image pull về máy khác không mang theo cache, và một builder CI khác cũng bắt đầu với thư mục trống.

### docker-buildkit-secret
kind: trap
cue: Token lúc build đi qua `docker build --secret` và `RUN --mount=type=secret`. `ARG` và `ENV` để giá trị trong image.

Q: Dockerfile cần một token để `npm ci` lấy package nội bộ. Bạn định `ARG NPM_TOKEN` rồi `ENV NPM_TOKEN=$NPM_TOKEN` cho lệnh cài đọc. Vì sao image push lên registry vẫn lộ token, và cách đưa token vào đúng lệnh `RUN` là gì?

A: Docs build secrets nói `ARG` và `ENV` còn trong image cuối. `docker history` và `docker inspect` đọc được giá trị đó, kể cả người chỉ có quyền pull. Secret mount lấy file từ `--secret id=...,src=...` trên lệnh build, gắn vào một `RUN`, mặc định tại `/run/secrets/<id>`, rồi bỏ mount khi lệnh kết thúc. Nội dung file không vào layer. Process lúc `docker run` cũng không còn thấy token. Token để app chạy thì đưa lúc start container, không phải lúc build.

### docker-buildkit-platform
kind: decide
cue: Image build trên máy amd64 không chạy trên ARM. Hai kiến trúc trong một tag thì `docker buildx build --platform ... --push`.

Q: Image build trên laptop Intel chạy được local. Cùng tag đó trên máy ARM báo exec format error. Một lệnh build cho cả `linux/amd64` và `linux/arm64` cần thêm gì so với `docker build` một nền tảng?

A: Binary trong image đúng kiến trúc máy đã build. Máy ARM không chạy binary amd64. `docker buildx build --platform linux/amd64,linux/arm64` tạo một manifest list, mỗi nền tảng một image. Image store local không nhận manifest list, nên lệnh hai nền tảng cần `--push` tới registry. Client pull đúng bản của máy mình. Chạy thử trên một laptop thì build một nền tảng, không cần `--push`.

### docker-buildkit-default
kind: default
cue: Từ Docker Engine 23.0, `docker build` dùng BuildKit. Log `[+] Building` là builder đó. `RUN --mount` cần dòng `# syntax=docker/dockerfile:1`.

Q: Một máy cài Docker Engine mới, không đặt `DOCKER_BUILDKIT`. `docker build` có còn dùng builder cũ, kiểu log từng bước `Step 1/4`, hay đã là BuildKit? Muốn `RUN --mount` thì Dockerfile cần dòng gì ở đầu?

A: Engine từ 23.0 và Docker Desktop để BuildKit làm builder mặc định của `docker build`. Log dạng `[+] Building` là dấu hiệu đó. Cú pháp `RUN --mount` thuộc frontend Dockerfile mới. Dòng đầu `# syntax=docker/dockerfile:1` chọn frontend đó. Không có dòng này thì `--mount` không được hiểu, dù builder phía sau là BuildKit.

### docker-buildkit-parallel
kind: distinguish
cue: Hai stage không copy lẫn nhau thì BuildKit build cùng lúc. Stage có `COPY --from` đợi stage nguồn xong.

Q: Dockerfile có stage `test` và stage `prod-deps`, không stage nào copy từ stage kia, rồi stage `runner` `COPY --from` cả hai. BuildKit chạy ba stage theo thứ tự nào?

A: `test` và `prod-deps` không phụ thuộc nhau nên BuildKit chạy song song. `runner` cần file từ cả hai, nên bắt đầu sau khi hai stage kia xong. Không có cờ để bật song song. Stage viết sau trong file vẫn có thể chạy trước nếu nó không chờ stage trên.

## Lỗi thường gặp

**`ENV NPM_TOKEN=...` để `npm ci` đọc registry riêng.** Token nằm trong config của image. Dùng secret mount, file token chỉ có trên máy build.

**Quên `# syntax=docker/dockerfile:1` rồi viết `RUN --mount`.** Frontend không hiểu `--mount`, build fail trước khi cài gói.

**`docker buildx build --platform linux/amd64,linux/arm64 -t app:1 .` rồi tìm image trong `docker images`.** Manifest list không nạp vào image store local. Thêm `--push`, hoặc bỏ bớt một nền tảng khi chỉ chạy local.

**Tưởng cache mount có trên mọi runner CI.** Cache nằm ở builder vừa build. Runner mới vẫn tải gói lần đầu.

## Nguồn

1. [KodeKloud — Docker for the Absolute Beginner](https://learn.kodekloud.com/learn/courses/docker-training-course-for-the-absolute-beginner) — module Docker Images: Problems With Traditional Docker Builds, Modern Builds With BuildKit. Bài viết lại, không chép lab.
2. [BuildKit](https://docs.docker.com/build/buildkit/) — builder mặc định, log `[+] Building`.
3. [Cache mounts](https://docs.docker.com/build/cache/optimize/) — `RUN --mount=type=cache` không vào image.
4. [Build secrets](https://docs.docker.com/build/building/secrets/) — `--secret` và vì sao không dùng `ARG` hay `ENV` cho secret.
5. [Multi-platform builds](https://docs.docker.com/build/building/multi-platform/) — `--platform` và đẩy manifest list.
6. [Multi-stage builds](https://docs.docker.com/build/building/multi-stage/) — stage độc lập chạy song song.

## Tiếp theo

[CMD và ENTRYPOINT](./09-entrypoint.md)
