Skip to content

fix: keep project build context as container path so local builder can stat it (#2314)#2346

Merged
kmendell merged 2 commits intogetarcaneapp:mainfrom
GiulioSavini:fix/project-build-context-resolution
Apr 11, 2026
Merged

fix: keep project build context as container path so local builder can stat it (#2314)#2346
kmendell merged 2 commits intogetarcaneapp:mainfrom
GiulioSavini:fix/project-build-context-resolution

Conversation

@GiulioSavini
Copy link
Copy Markdown
Contributor

@GiulioSavini GiulioSavini commented Apr 11, 2026

Summary

Fixes #2314 — clicking Build on a project whose compose file contains a build: section fails with build context not found: stat /<host-projects-dir>/<service>: no such file or directory whenever the projects bind mount uses a different host path than the container path (e.g. /storage/volumes/arcane:/app/data). The reporter's case is the canonical setup:

services:
  caddy:
    image: caddy
    container_name: caddy
    build: .

with Dockerfile next to compose.yaml under PROJECTS_DIRECTORY=/app/data/projects.

Root cause

prepareServiceBuildRequest in backend/internal/services/project_service.go was passing the build context (and any absolute Dockerfile path) through translateBuildPathInternalpathMapper.ContainerToHost, converting /app/data/projects/caddy into /storage/volumes/arcane/projects/caddy before stuffing it into the BuildRequest.

This translation is correct for bind mount sources — those are sent to the daemon and have to make sense from the host's perspective. It is wrong for the build context, which is read by Arcane's own process:

  • The docker provider tarballs the context locally via archive.TarWithOptions(contextDir, …) and then streams it through the daemon's /build API (backend/pkg/libarcane/libbuild/builder_docker.go:208).
  • The buildkit provider hands the context to buildkit through SolveOpt.LocalDirs{"context": contextDir, "dockerfile": dockerfileDir} (backend/pkg/libarcane/libbuild/builder_buildkit.go:149), which is a client-side local directory.
  • Both paths then hit validateBuildRequestInternal which calls os.Stat(contextDir) on the Arcane process's filesystem (backend/pkg/libarcane/libbuild/builder_utils.go:31).

Once the contextDir has been rewritten to the host path, none of those steps can see the directory and the build aborts. Inline Dockerfile builds were just as broken (they go through the same code path), which is why the reporter found that the workaround from #1959 also failed.

The translation was added in cea8140 (PR #1875, "feat(autoupdate/compose): implement docker compose aware update logic"). The accompanying test, TestProjectService_PrepareServiceBuildRequest_UsesExecutorVisiblePaths, locked in the regression by asserting host paths.

Fix

  • Remove both translateBuildPathInternal calls in prepareServiceBuildRequest — the build context and Dockerfile path now flow through unchanged.
  • Drop the now-unused translateBuildPathInternal helper.
  • Add a comment at the call site explaining why the build context must stay as a container path so this regression doesn't get reintroduced.
  • Rename and rewrite the existing test to assert the container path, and add a second regression test (TestProjectService_PrepareServiceBuildRequest_BuildDotKeepsContainerPath) that mirrors 🐞 Bug: Project builds not working as expected #2314 exactly: build: ., projects mounted at /app/data/projects from /storage/volumes/arcane/projects.

pathMapper is still received by prepareServiceBuildRequest (callers up the chain pass it through), but is no longer consumed inside this function. I left the parameter in place to keep the diff scoped to the bug — it can be cleaned up alongside any future refactor of the build orchestration entry points.

Tests

  • go test ./backend/internal/services/... -run PrepareServiceBuildRequest -v ✅ (5/5)
  • go test ./backend/internal/services/...
  • gofmt -l clean, go vet ./backend/internal/services/... clean

Test plan

  • On an installation with a non-matching projects mount (e.g. /storage/volumes/arcane:/app/data), create a project with build: . and Dockerfile next to compose.yaml, hit Build — image builds successfully
  • Same setup, hit Build & Deploy — image builds and the project comes up
  • Inline Dockerfile (dockerfile_inline:) variant of the same project also builds (covers the regression from 🐞 Bug: Project builds not working as expected #1959 that 🐞 Bug: Project builds not working as expected #2314 also re-tripped)
  • Existing matching-mount installations (Linux default /app/data/projects:/app/data/projects) still build

Disclaimer Greptiles Reviews use AI, make sure to check over its work.

To better help train Greptile on our codebase, if the comment is useful and valid Like the comment, if its not helpful or invalid Dislike

To have Greptile Re-Review the changes, mention greptileai.

Greptile Summary

This PR fixes a bug (#2314) where clicking Build on a project with a build: section in its compose file would fail with stat /<host-path>/...: no such file or directory on installations where the projects bind mount uses a different host path than the container path. The root cause was prepareServiceBuildRequest translating the build context and Dockerfile paths from container paths to host paths via pathMapper.ContainerToHost, even though both the docker provider (archive.TarWithOptions) and the buildkit provider (SolveOpt.LocalDirs) read those paths from the Arcane process's own (container) filesystem — not the host's.

The fix removes both translateBuildPathInternal calls and deletes the now-unused helper, adds a clear explanatory comment at the call site, and includes two regression tests mirroring the exact failure scenario.

Confidence Score: 5/5

Safe to merge — the fix correctly removes erroneous container-to-host path translation for build contexts, is well-tested, and introduces no new risk.

The logic change is correct and well-understood: os.Stat, archive.TarWithOptions, and SolveOpt.LocalDirs all operate on the Arcane container's filesystem, so keeping the container path is the only valid option. The fix removes the helper entirely rather than just disabling it, and two targeted regression tests guard against re-introduction. Only a P2 style note remains about the _ = pathMapper blank assignment.

No files require special attention.

Fix All in Codex

Prompt To Fix All With AI
This is a comment left during a code review.
Path: backend/internal/services/project_service.go
Line: 1897

Comment:
**Consider renaming the unused parameter instead of blank-assigning it**

In Go, function parameters don't cause a compile error when unused, so `_ = pathMapper` isn't required by the toolchain. The comment above it already documents the intent clearly. When the follow-up refactor happens, prefer renaming the parameter to `_` in the function signature rather than keeping the blank assignment — that keeps the signal at the declaration site and avoids a no-op statement in the body.

(Remove the `_ = pathMapper` line; the comment above already explains the intent.)

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix: keep project build context as conta..." | Re-trigger Greptile

Greptile also left 1 inline comment on this PR.

@kmendell
Copy link
Copy Markdown
Member

kmendell commented Apr 11, 2026

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

Comment thread backend/internal/services/project_service.go Outdated
@kmendell kmendell merged commit 8131e8a into getarcaneapp:main Apr 11, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

🐞 Bug: Project builds not working as expected

2 participants