[PR #41] [MERGED] feat(build): BuildKit/buildx support via an LLB solver on LXC #42

Closed
opened 2026-10-15 12:51:32 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/games-on-whales/LXC2Docker/pull/41
Author: @JBailes
Created: 5/31/2026
Status: ✅ Merged
Merged: 5/31/2026
Merged by: @JBailes

Base: main ← Head: feat/buildkit-llb-on-lxc


📝 Commits (3)

  • f2b71b2 feat(build): BuildKit/buildx support via an LLB solver on LXC
  • 3d5ee39 feat(build): persistent content cache for fully content-addressed LLB ops
  • 8c11673 feat(build): run executor RUN steps in a real LXC container, not a chroot

📊 Changes

19 files changed (+2503 additions, -10 deletions)

View changed files

📝 go.mod (+58 -2)
📝 go.sum (+164 -2)
📝 internal/api/build.go (+24 -2)
➕ internal/api/buildkit_cache.go (+26 -0)
➕ internal/api/buildkit_cache_store.go (+124 -0)
➕ internal/api/buildkit_cache_store_test.go (+47 -0)
➕ internal/api/buildkit_cache_test.go (+42 -0)
➕ internal/api/buildkit_exec.go (+541 -0)
➕ internal/api/buildkit_exec_test.go (+154 -0)
➕ internal/api/buildkit_grpc.go (+199 -0)
➕ internal/api/buildkit_grpc_test.go (+116 -0)
➕ internal/api/buildkit_import.go (+170 -0)
➕ internal/api/buildkit_llb.go (+82 -0)
➕ internal/api/buildkit_llb_test.go (+79 -0)
➕ internal/api/buildkit_solve.go (+338 -0)
➕ internal/api/buildkit_solve_test.go (+248 -0)
📝 internal/api/router.go (+6 -0)
📝 internal/api/system.go (+8 -4)
➕ internal/lxc/build_run.go (+77 -0)

📄 Description

What

Makes docker buildx build work against docker-lxc-daemon by speaking the BuildKit Control gRPC API and executing the LLB op DAG directly on LXC — a real LLB solver, not a shim.

buildx → /grpc → Session/Solve/Status → FileSync context
      → dockerfile2llb (real pb.Op DAG) → LLB executor on LXC
      → image in store  (or --output type=tar|local to the client)

How

  • /grpc Control endpoint (h2c hijack, buildkit_grpc.go): Info, ListWorkers, Session, Solve, Status, DiskUsage, Prune. Ping now advertises Builder-Version: 2 so buildx uses the docker driver.
  • Solve (buildkit_solve.go) drives the dockerfile.v0 frontend: pulls context+Dockerfile over the session FileSync, converts to LLB via dockerfile2llb, executes, imports the result, streams vertex/log status.
  • LLB executor (buildkit_exec.go) materialises the pb.Op DAG with directory snapshots: Source (docker-image via the daemon's existing image resolution; local://context), Exec (RUN in a chroot), File (COPY/ADD/mkdir/rm/symlink with action-chaining).
  • RUN --mount=type=secret resolved from the client session, written to the rootfs only for the RUN and deleted afterwards (never persists in the image).
  • --output type=tar|local stream the result back to the client over FileSend.
  • Classic POST /build is unchanged — its core was extracted as buildFromContext and is shared.

Bug fixes (found while validating)

  • statusRecorder (the request-logging middleware) now forwards Hijack(). It previously masked the http.Hijacker, which silently broke /grpc and exec/attach through the middleware.
  • The build-context FileSync no longer mirror-deletes context files (context and Dockerfile now sync to separate dirs).

Validation

Live-tested on a Proxmox host (isolated daemon, production untouched):

  • FROM scratch + COPY → correct image in store
  • FROM busybox + RUN + COPY + multi-RUN → RUN output streamed, image rootfs has both RUN- and COPY-produced files
  • --output type=tar → correct client-side tarball

Unit tests cover the gRPC handshake, Solve/Status correlation, frontend-attr parsing, LLB conversion, File-op chaining, secret staging, and DiskUsage/Prune.

⚠️ Integration notes for the maintainer

  • This branch is stacked on feat/deb-packaging (the chosen base) so the diff is buildx-only.
  • origin/main has since merged #40 "run RUN steps in a real LXC container", which rewrites build.go's RUN path (plus manager.go/network.go). This branch predates #40, so it will need a rebase onto main and reconciliation: notably, this executor runs RUN via chroot, while #40 moved classic builds to real LXC containers — we should align on one execution model.
  • --push and oci/docker image-tarball exporters are intentionally out of scope (the daemon has no registry push).
  • Remaining follow-up: a content-addressable build cache (would also give du/prune real data).

🤖 Generated with Claude Code


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/games-on-whales/LXC2Docker/pull/41 **Author:** [@JBailes](https://github.com/JBailes) **Created:** 5/31/2026 **Status:** ✅ Merged **Merged:** 5/31/2026 **Merged by:** [@JBailes](https://github.com/JBailes) **Base:** `main` ← **Head:** `feat/buildkit-llb-on-lxc` --- ### 📝 Commits (3) - [`f2b71b2`](https://github.com/games-on-whales/LXC2Docker/commit/f2b71b29028f598b65cb0857c69b76e87634ed18) feat(build): BuildKit/buildx support via an LLB solver on LXC - [`3d5ee39`](https://github.com/games-on-whales/LXC2Docker/commit/3d5ee39c71145c78ef3117fb5cdf4507cc8395e6) feat(build): persistent content cache for fully content-addressed LLB ops - [`8c11673`](https://github.com/games-on-whales/LXC2Docker/commit/8c1167386bbfa31216664b5444a897bec8758b7a) feat(build): run executor RUN steps in a real LXC container, not a chroot ### 📊 Changes **19 files changed** (+2503 additions, -10 deletions) <details> <summary>View changed files</summary> 📝 `go.mod` (+58 -2) 📝 `go.sum` (+164 -2) 📝 `internal/api/build.go` (+24 -2) ➕ `internal/api/buildkit_cache.go` (+26 -0) ➕ `internal/api/buildkit_cache_store.go` (+124 -0) ➕ `internal/api/buildkit_cache_store_test.go` (+47 -0) ➕ `internal/api/buildkit_cache_test.go` (+42 -0) ➕ `internal/api/buildkit_exec.go` (+541 -0) ➕ `internal/api/buildkit_exec_test.go` (+154 -0) ➕ `internal/api/buildkit_grpc.go` (+199 -0) ➕ `internal/api/buildkit_grpc_test.go` (+116 -0) ➕ `internal/api/buildkit_import.go` (+170 -0) ➕ `internal/api/buildkit_llb.go` (+82 -0) ➕ `internal/api/buildkit_llb_test.go` (+79 -0) ➕ `internal/api/buildkit_solve.go` (+338 -0) ➕ `internal/api/buildkit_solve_test.go` (+248 -0) 📝 `internal/api/router.go` (+6 -0) 📝 `internal/api/system.go` (+8 -4) ➕ `internal/lxc/build_run.go` (+77 -0) </details> ### 📄 Description ## What Makes `docker buildx build` work against docker-lxc-daemon by speaking the **BuildKit Control gRPC API** and executing the **LLB op DAG directly on LXC** — a real LLB solver, not a shim. ``` buildx → /grpc → Session/Solve/Status → FileSync context → dockerfile2llb (real pb.Op DAG) → LLB executor on LXC → image in store (or --output type=tar|local to the client) ``` ## How - **`/grpc` Control endpoint** (h2c hijack, `buildkit_grpc.go`): `Info`, `ListWorkers`, `Session`, `Solve`, `Status`, `DiskUsage`, `Prune`. Ping now advertises `Builder-Version: 2` so buildx uses the docker driver. - **`Solve`** (`buildkit_solve.go`) drives the `dockerfile.v0` frontend: pulls context+Dockerfile over the session FileSync, converts to LLB via `dockerfile2llb`, executes, imports the result, streams vertex/log status. - **LLB executor** (`buildkit_exec.go`) materialises the `pb.Op` DAG with directory snapshots: `Source` (docker-image via the daemon's existing image resolution; `local://context`), `Exec` (RUN in a chroot), `File` (COPY/ADD/mkdir/rm/symlink with action-chaining). - **`RUN --mount=type=secret`** resolved from the client session, written to the rootfs only for the RUN and deleted afterwards (never persists in the image). - **`--output type=tar|local`** stream the result back to the client over FileSend. - Classic `POST /build` is unchanged — its core was extracted as `buildFromContext` and is shared. ## Bug fixes (found while validating) - `statusRecorder` (the request-logging middleware) now forwards `Hijack()`. It previously masked the `http.Hijacker`, which silently broke `/grpc` **and** `exec`/`attach` through the middleware. - The build-context FileSync no longer mirror-deletes context files (context and Dockerfile now sync to separate dirs). ## Validation Live-tested on a Proxmox host (isolated daemon, production untouched): - `FROM scratch` + `COPY` → correct image in store - `FROM busybox` + `RUN` + `COPY` + multi-`RUN` → RUN output streamed, image rootfs has both RUN- and COPY-produced files - `--output type=tar` → correct client-side tarball Unit tests cover the gRPC handshake, Solve/Status correlation, frontend-attr parsing, LLB conversion, File-op chaining, secret staging, and DiskUsage/Prune. ## ⚠️ Integration notes for the maintainer - This branch is **stacked on `feat/deb-packaging`** (the chosen base) so the diff is buildx-only. - `origin/main` has since merged **#40 "run RUN steps in a real LXC container"**, which rewrites `build.go`'s RUN path (plus `manager.go`/`network.go`). This branch predates #40, so it will need a **rebase onto `main` and reconciliation**: notably, this executor runs `RUN` via `chroot`, while #40 moved classic builds to real LXC containers — we should align on one execution model. - `--push` and oci/docker image-tarball exporters are intentionally out of scope (the daemon has no registry push). - Remaining follow-up: a content-addressable build cache (would also give `du`/`prune` real data). 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-10-15 12:51:32 +00:00
Sign in to join this conversation.
No labels
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
games-on-whales/LXC2Docker#42
No description provided.