From 67335ee6b85b9ce4250ed679a9009a13439d2da4 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 25 Jul 2026 14:14:54 +0000 Subject: [PATCH 1/2] fix(generate): drop `nix-store --optimise` from dev Dockerfile The dev Dockerfile generated by `devbox generate dockerfile` (and `devbox generate devcontainer`) ran: RUN devbox run -- echo "Installed Packages." && nix-store --gc && nix-store --optimise `nix-store --optimise` deduplicates the Nix store by hard-linking identical files. On Docker's default overlay2 storage driver the rename it performs on the temporary link fails with: error: filesystem error: cannot rename: Stale file handle [/nix/store/.tmp-link-...] [/nix/store/...-aws-c-common-.../nix-support/setup-hook] which aborts the image build. In a freshly built, single-purpose image there is little to no duplication to reclaim (Nix itself reports "hard linking saves -0.00 MiB"), so the optimise pass provides negligible benefit while reliably breaking `docker build`. Remove the optimise step and keep `nix-store --gc`, which is what actually shrinks the image by dropping build-time dependencies. Add a regression test that renders the dev Dockerfile and asserts it no longer contains `nix-store --optimise` while still running `nix-store --gc`. Fixes #2616 Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01W6BX9jAviSLnWk93YUeD6W --- .../devbox/generate/devcontainer_util_test.go | 57 +++++++++++++++++++ .../devbox/generate/tmpl/dev.Dockerfile.tmpl | 11 +++- 2 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 internal/devbox/generate/devcontainer_util_test.go diff --git a/internal/devbox/generate/devcontainer_util_test.go b/internal/devbox/generate/devcontainer_util_test.go new file mode 100644 index 00000000000..c7719d0d389 --- /dev/null +++ b/internal/devbox/generate/devcontainer_util_test.go @@ -0,0 +1,57 @@ +// Copyright 2024 Jetify Inc. and contributors. All rights reserved. +// Use of this source code is governed by the license in the LICENSE file. + +package generate + +import ( + "context" + "os" + "path/filepath" + "strings" + "testing" +) + +// TestCreateDockerfileDevOmitsNixStoreOptimise ensures the generated dev +// Dockerfile does not run `nix-store --optimise`. That command fails on Docker's +// default overlay2 storage driver ("cannot rename: Stale file handle") and +// breaks the image build, while providing negligible size savings in a freshly +// built image. Regression test for +// https://github.com/jetify-com/devbox/issues/2616. +func TestCreateDockerfileDevOmitsNixStoreOptimise(t *testing.T) { + for _, rootUser := range []bool{false, true} { + dockerfile := generateDevDockerfile(t, rootUser) + + if strings.Contains(dockerfile, "nix-store --optimise") { + t.Errorf("generated dev Dockerfile (rootUser=%t) should not run "+ + "`nix-store --optimise`; it breaks Docker builds on overlay2.\n%s", + rootUser, dockerfile) + } + // The garbage-collection pass is still wanted: it removes build-time + // dependencies and is what actually shrinks the image. + if !strings.Contains(dockerfile, "nix-store --gc") { + t.Errorf("generated dev Dockerfile (rootUser=%t) should still run "+ + "`nix-store --gc`.\n%s", rootUser, dockerfile) + } + } +} + +func generateDevDockerfile(t *testing.T, rootUser bool) string { + t.Helper() + + dir := t.TempDir() + g := &Options{ + Path: dir, + RootUser: rootUser, + } + if err := g.CreateDockerfile(context.Background(), CreateDockerfileOptions{ + ForType: "dev", + }); err != nil { + t.Fatalf("CreateDockerfile failed: %v", err) + } + + contents, err := os.ReadFile(filepath.Join(dir, "Dockerfile")) + if err != nil { + t.Fatalf("reading generated Dockerfile: %v", err) + } + return string(contents) +} diff --git a/internal/devbox/generate/tmpl/dev.Dockerfile.tmpl b/internal/devbox/generate/tmpl/dev.Dockerfile.tmpl index 036af2ff1bd..325b238e7f8 100644 --- a/internal/devbox/generate/tmpl/dev.Dockerfile.tmpl +++ b/internal/devbox/generate/tmpl/dev.Dockerfile.tmpl @@ -21,7 +21,16 @@ COPY devbox.lock devbox.lock {{range $i, $element := .LocalFlakeDirs -}} COPY {{$element}} {{$element}} {{end}} -RUN devbox run -- echo "Installed Packages." && nix-store --gc && nix-store --optimise +{{- /* +We run `nix-store --gc` to drop build-time dependencies and shrink the image, +but intentionally do NOT run `nix-store --optimise`. Optimise hard-links +duplicate files in the Nix store, and on Docker's default overlay2 storage +driver the rename it performs fails with "cannot rename: Stale file handle", +which breaks the image build. In a freshly built, single-purpose image there is +little to no duplication to reclaim anyway, so the optimise pass provides +negligible benefit. See https://github.com/jetify-com/devbox/issues/2616. +*/ -}} +RUN devbox run -- echo "Installed Packages." && nix-store --gc {{if .IsDevcontainer}} RUN devbox shellenv --init-hook >> ~/.profile {{- else}} From 009a94c8e0dcab2f6cae7be5f3aafea68a52eba8 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 25 Jul 2026 14:17:49 +0000 Subject: [PATCH 2/2] test(generate): use t.Context() to satisfy usetesting linter golangci-lint's `usetesting` linter flagged context.Background() in the new test; switch to t.Context(). Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01W6BX9jAviSLnWk93YUeD6W --- internal/devbox/generate/devcontainer_util_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/devbox/generate/devcontainer_util_test.go b/internal/devbox/generate/devcontainer_util_test.go index c7719d0d389..f74edf88673 100644 --- a/internal/devbox/generate/devcontainer_util_test.go +++ b/internal/devbox/generate/devcontainer_util_test.go @@ -4,7 +4,6 @@ package generate import ( - "context" "os" "path/filepath" "strings" @@ -43,7 +42,7 @@ func generateDevDockerfile(t *testing.T, rootUser bool) string { Path: dir, RootUser: rootUser, } - if err := g.CreateDockerfile(context.Background(), CreateDockerfileOptions{ + if err := g.CreateDockerfile(t.Context(), CreateDockerfileOptions{ ForType: "dev", }); err != nil { t.Fatalf("CreateDockerfile failed: %v", err)