Repository navigation
Kubernetes Dockerless build drops .devsy-internal during StreamMount and fails before build starts #1108
Description
Summary
On Devsy 1.15.0 with the Kubernetes provider, a Dockerfile-backed devcontainer can fail before the Dockerless build starts because the generated .devsy-internal build artifacts are excluded from the streamed workspace mount.
Observed error:
dockerless build: rename dir: open /workspaces/.dockerless/.devsy-internal: no such file or directory
I reproduced this with https://github.com/joshyorko/actions against a Kubernetes context where the equivalent Skevetter DevPod 0.26.1 flow gets through the Dockerless build successfully.
Reproducer
Workspace:
https://github.com/joshyorko/actions
The repo contains a Dockerfile-backed .devcontainer/devcontainer.json, so the Kubernetes provider correctly falls back to Dockerless.
Run:
devsy workspace up https://github.com/joshyorko/actionsEnvironment from the failing run:
- Devsy Desktop / CLI:
1.15.0 - provider: Kubernetes
- Kubernetes context:
ror - namespace:
devsy - Dockerless image:
ghcr.io/devsy-org/dockerless:0.2.0 - architecture:
amd64
Actual behavior
Devsy successfully:
- clones the repository;
- resolves
.devcontainer/Dockerfile; - resolves the Python base image;
- calculates the prebuild hash;
- creates the PVC and Kubernetes pod;
- injects the Devsy agent;
- streams the workspace into
/workspaces/actions.
The pod is configured with:
DOCKERLESS_CONTEXT=/workspaces/actions
DOCKERLESS_DOCKERFILE=/workspaces/actions/.devsy-internal/Dockerfile-without-features
The workspace upload reaches ~102 MB, then setup fails immediately:
Copy .../actions into DevContainer /workspaces/actions
Uploaded 102.69 MB
dockerless build: rename dir: open /workspaces/.dockerless/.devsy-internal: no such file or directory
Dockerless never reaches its actual build command.
Expected behavior
The generated Dockerless build information should survive the host -> Kubernetes workspace handoff so that prepareBuildDirectory can find either:
/workspaces/actions/.devsy-internal
or the fallback:
/workspaces/.dockerless/.devsy-internal
and then start the Dockerless build.
Likely regression / code-path evidence
The Dockerless fallback creates generated build artifacts under the context path:
// pkg/devcontainer/build.go
devsyInternalFolder := filepath.Join(contextPath, config.DevsyContextFeatureFolder)
err := os.MkdirAll(devsyInternalFolder, 0o755)
...
devsyDockerfile = filepath.Join(devsyInternalFolder, "Dockerfile-without-features")DevsyContextFeatureFolder is the generated .devsy-internal directory.
However, pkg/agent/tunnelserver/tunnelserver.go currently applies build-artifact exclusions when streaming a mount:
excludes := append(t.workspaceIgnoreExcludes(), config.BuildArtifactExcludes()...)
...
extract.WriteTarExclude(buf, mount.Source, false, excludes)and pkg/devcontainer/config/artifacts.go defines:
var buildArtifactNames = []string{
DevsyContextFeatureFolder,
}So .devsy-internal is generated locally and then explicitly excluded from the mount stream that carries the workspace into the Kubernetes pod.
Inside the pod, prepareBuildDirectory then cannot find the build directory at the workspace path and falls back to /workspaces/.dockerless/.devsy-internal, which is also absent, producing the observed rename dir failure.
Working control: Skevetter DevPod
I ran the same joshyorko/actions workspace through Skevetter's DevPod 0.26.1 using the Kubernetes provider and the same Kubernetes context.
DevPod's analogous StreamMount only applies its workspace / .devpodignore exclusions; it does not add an equivalent build-artifact exclusion for .devpod-internal.
That run successfully reaches:
Copy .../actions/content into DevContainer /workspaces/actions
Uploaded 98.90 MB
starting dockerless build: /.dockerless/dockerless build ...
...
dockerless build completed
Suggested fix direction
StreamMount should not blanket-exclude .devsy-internal when that stream is also responsible for transporting the generated Dockerless build artifacts required by the Kubernetes fallback.
Possible approaches:
- do not apply
BuildArtifactExcludes()to this Dockerless workspace stream; or - explicitly seed/persist
.devsy-internalinto/workspaces/.dockerlessbefore excluding it from the normal workspace payload.
The key invariant is that the generated Dockerless build directory must exist in one of the locations expected by prepareBuildDirectory before DockerlessBuild runs.
Acceptance criteria
- A fresh Kubernetes-provider
workspace upfor a Dockerfile-backed devcontainer reaches the Dockerless build instead of failing inprepareBuildDirectory. .devsy-internalis available at the workspace build context or the documented Dockerless fallback location inside the pod.- Existing cleanup behavior still removes generated build artifacts after setup.
- Add a regression test covering streamed mounts + Dockerless generated build artifacts so
BuildArtifactExcludes()cannot remove the only copy needed by the in-pod builder.
Activity
coderabbitai commentedon Aug 19, 2026
🔗 Related PRs
#398 - fix: use actual container mount path after workspace rename [merged]
#486 - fix(workspace): delete container on rename to fix stale bind mounts [merged]
#519 - fix(docker): stale bind-mount inode to Docker Desktop on up/delete/up [merged]
#603 - feat(ci): add devsy ci command for devcontainer CI [merged]
#615 - fix(delivery): cleanup build files for mounted volume [merged]
📝 Issue Planner
Check the box below or use the
@coderabbitai plancommand to generate an implementation plan and prompts that you can use with your favorite coding assistant.✅ Successfully generated implementation plan.
💡 Use
@coderabbitai <feedback>to replan🧪 Issue enrichment is currently in open beta.
You can configure auto-planning by selecting labels in the issue_enrichment configuration.
To disable automatic issue enrichment, add the following to your
.coderabbit.yaml:💬 Have feedback or questions? Drop into our discord!
joshyorko commentedon Aug 19, 2026
Additional independent reproducer:
joshyorko/room-of-requirement.This is a useful control because the same repo is known to build successfully on Kubernetes with Skevetter's DevPod provider path, while Devsy 1.15.0 fails at the same
.devsy-internalhandoff before Dockerless starts.Devsy run details:
https://github.com/joshyorko/room-of-requirementrordevsyghcr.io/devsy-org/dockerless:0.2.0amd64Devsy successfully resolves the Dockerfile and the
docker-in-dockerdevcontainer feature, calculates the prebuild hash, creates the PVC/pod, injects the agent, and begins streaming the workspace.The pod is configured with:
Then immediately after the workspace copy begins, setup fails with:
This reproduces the same failure with a second Dockerfile-backed devcontainer, including a generated
Dockerfile-with-featurespath rather than theDockerfile-without-featurespath in the originalactionsreproducer.That makes the failure appear independent of the specific Dockerfile contents and independent of whether Devcontainer Features are involved; the common failure point is the generated
.devsy-internaldirectory not surviving the streamed Kubernetes workspace handoff.coderabbitai commentedon Aug 19, 2026
Coding Plan
Summary
config.BuildArtifactExcludes()exclusion fromStreamMountso the streamed workspace mount carries the generated.devsy-internalDockerless build directory into the Kubernetes pod..devsyignorehandling and all otherBuildArtifactExcludes()callers unchanged, because.devsy-internalis generated only in the Dockerless flow that needs it.StreamMountregression test that asserts the.devsy-internalbuild directory is present in the streamed tar.Design Choices
Design Choice 1: How should the generated `.devsy-internal` build directory reach the pod?
Options Considered:
config.BuildArtifactExcludes()inStreamMount, so the streamed mount carries.devsy-internalinto the pod. This matches the working DevPod control behavior, where the mount stream does not exclude the equivalent internal folder.StreamMount, but add a separate step that seeds/persists.devsy-internalinto the/workspaces/.dockerlessvolume before excluding it from the normal payload. This requires a new transport path, because/workspaces/.dockerlessis aType: "volume"mount that is never part ofconfig.GetMounts()and is never streamed.Chosen Option: 1
Rationale: This option matches the proven DevPod control flow. This option is the minimal change.
.devsy-internalis generated only for the Dockerless (Kubernetes/non-Docker) flow, which is exactly the flow that needs the folder in the pod.💡 User Tips
Regenerate the plan with different choices with
@coderabbitai <feedback>.Implementation Steps
Phase 1: Deliver the Dockerless build directory to the pod through the mount stream
This phase makes the streamed workspace mount carry the generated
.devsy-internalbuild directory to the Kubernetes pod. The goal is thatprepareBuildDirectoryfinds the build directory at the workspace context path beforeDockerlessBuildruns.Task 1: Stop excluding build artifacts in StreamMount
Remove the build-artifact exclusion from the mount stream so the generated Dockerless build directory reaches the pod.
StreamMountinpkg/agent/tunnelserver/tunnelserver.go. Change its exclude list so it no longer appendsconfig.BuildArtifactExcludes().workspaceIgnoreExcludes()in place. The.devsyignorebehavior must not change..devsy-internalis generated only in the Dockerless (non-Docker driver) build path. The Docker driver never creates this folder. So removing the exclude affects only the Dockerless flow, where the pod requires the folder.StreamMountmust not exclude.devsy-internal. State that the Dockerless build directory travels to the pod through this mount stream.StreamWorkspaceorStreamSnapshotVolumesin this ticket. The reported failure uses theStreamMountpath for the primary workspace mount. Leave the other RPCs andpkg/agent/delivery/workspace_seed.goandpkg/devcontainer/config/prebuild.gounchanged.Task 2: Confirm host cleanup and pod fallback remain intact
Verify that existing cleanup behavior still runs and that no additional change is required for the fallback path.
cleanupBuildInformation(deferred inpkg/devcontainer/run.goUp()) still removes the host.devsy-internalfolder after setup. Do not change this behavior.cleanupBuildDirectoryinpkg/agent/dockerless.gostill moves.devsy-internalinto/workspaces/.dockerlessafter the build. Do not change this behavior.config.BuildArtifactExcludes()andconfig.RemoveBuildArtifacts()inpkg/devcontainer/config/artifacts.goremain unchanged, because other callers (prebuild hash, volume seeding) still need them.🤖 Prompt for AI agents
Phase 2: Add regression coverage
This phase adds a test that fails if a future change re-excludes the Dockerless build directory from the mount stream.
Add a StreamMount regression test
Add a test that proves
StreamMountincludes.devsy-internalin the streamed tar.pkg/agent/tunnelserver/tunnelserver_test.go. Follow the existingTestStreamSnapshotVolumes_TarsMountTargetspattern.fakeStreamMountServerthat embedstunnel.Tunnel_StreamMountServerand collects sent chunks, withSendandContextmethods.t.TempDir(). Add a normal file. Add aconfig.DevsyContextFeatureFoldersubdirectory (.devsy-internal) that contains a file such asDockerfile-without-features.tunnelServerstruct literal withmountsset to a single*config.MountwhoseSourceis the temp dir.srv.StreamMount(&tunnel.StreamMountRequest{Mount: mount.String()}, fake). Reassemble the tar from the collected chunks. Walk the tar witharchive/tar..devsy-internal/Dockerfile-without-featuresentry is present in the tar. This is the core regression assertion.github.com/stretchr/testify/require, consistent with the existing test file.🤖 Prompt for AI agents
Research
The host CLI process resolves the devcontainer config and builds the image. For non-Docker drivers (Kubernetes),
dockerlessFallbackinpkg/devcontainer/build.gowrites.devsy-internal/Dockerfile-without-featuresunder the build context path. This path sits inside the local workspace folder.The pod agent runs
cmd/internal/agentcontainer/setup.go. This process first callssyncMounts, which streams each mount from the host through theStreamMountRPC inpkg/agent/tunnelserver/tunnelserver.go. The process then runsagent.DockerlessBuild(pkg/agent/dockerless.go).prepareBuildDirectoryexpects.devsy-internalat<DOCKERLESS_CONTEXT>/.devsy-internalor at the fallback/workspaces/.dockerless/.devsy-internal.StreamMountcurrently appendsconfig.BuildArtifactExcludes()(which returns.devsy-internal) to its exclude list. As a result, the only host copy of the build directory is stripped from the tar before it reaches the pod.Host-side cleanup runs later, at the end of
Up(), through a deferredRemoveBuildArtifactscall.🚀 Next Steps
🤖 All AI agent prompts combined
💡 Iterate on the plan with:
`@coderabbitai` <feedback>💬 Have feedback or questions? Drop into our discord!