Output from azd version
azd version 1.27.1 (commit 257908961d465e98c1a6574eae9624a7954b21cf)
Describe the bug
PackRemoteBuildSource (cli/azd/pkg/containerregistry/pack_source.go) — which tars the Docker build context for the ACR remote build path — does not handle symlinks. A symlink that points to a directory (e.g. src/content/docs -> ../../docs/user) inside the build context causes the whole pack to fail with:
... io.Copy ... read <context>/src/content/docs: is a directory
so azd deploy / azd up fails for any container app whose context contains a symlinked directory, even though a local docker build of the same context succeeds.
Root cause
In the filepath.WalkDir callback:
filepath.WalkDir does not follow symlinks, and the DirEntry reflects Lstat. For a symlink-to-dir, d.IsDir() is false, so it skips the directory branch (line ~72) and falls through to the file branch. There is no info.Mode()&fs.ModeSymlink (or IsRegular) guard — every non-directory entry is assumed to be a regular file.
tar.FileInfoHeader(info, info.Name()) (line ~93) is also wrong for symlinks: the second argument must be the link target (os.Readlink), but the base name is passed. So even a symlink-to-file produces a corrupt/incorrect symlink entry.
os.Open(path) (line ~104) follows the symlink to the directory, then io.Copy(tw, f) (line ~110) does read(2) on a directory fd → EISDIR ("is a directory") → the walk returns the error and the archive is aborted.
The .dockerignore check runs before the open, which is why excluding the symlink path is a viable workaround.
Expected behavior
Match docker build, which preserves symlinks as symlink entries in the context tar (it does not read the target directory's bytes). azd's remote-build packer should:
- Detect symlinks:
info.Mode()&fs.ModeSymlink != 0.
- Resolve the target:
target, err := os.Readlink(path).
- Emit a proper header:
tar.FileInfoHeader(info, target) + WriteHeader.
- Skip the
os.Open/io.Copy body — symlink tar entries carry no payload.
With this, a symlink whose target is inside the context (the common case) resolves on the remote builder exactly as it does locally, and no .dockerignore workaround is needed.
While here, consider handling other irregular file types (sockets, fifos, devices) rather than assuming every non-dir entry is a regular file.
To reproduce
- In a container app's Docker build context, create a symlink to a directory that lives elsewhere in the context, e.g.
ln -s ../../docs/user src/content/docs.
- Deploy via the ACR remote build path (
azd deploy / azd up where remote build is used).
- Observe the failure:
... read .../src/content/docs: is a directory.
Note: local docker build of the same context succeeds — the failure is specific to azd's remote-build tar packer.
Workaround
Exclude the symlink in .dockerignore (the ignore check runs before the read) and recreate it inside the Dockerfile so the build still resolves it (the real target files remain in the context). The proper fix above removes the need for this.
Notes
- No symlink test coverage currently exists in
containerregistry_test.go; a regression test seeding a symlink-to-dir (and symlink-to-file) should be added with the fix.
Output from
azd versionazd version 1.27.1 (commit 257908961d465e98c1a6574eae9624a7954b21cf)Describe the bug
PackRemoteBuildSource(cli/azd/pkg/containerregistry/pack_source.go) — which tars the Docker build context for the ACR remote build path — does not handle symlinks. A symlink that points to a directory (e.g.src/content/docs -> ../../docs/user) inside the build context causes the whole pack to fail with:so
azd deploy/azd upfails for any container app whose context contains a symlinked directory, even though a localdocker buildof the same context succeeds.Root cause
In the
filepath.WalkDircallback:filepath.WalkDirdoes not follow symlinks, and theDirEntryreflectsLstat. For a symlink-to-dir,d.IsDir()isfalse, so it skips the directory branch (line ~72) and falls through to the file branch. There is noinfo.Mode()&fs.ModeSymlink(orIsRegular) guard — every non-directory entry is assumed to be a regular file.tar.FileInfoHeader(info, info.Name())(line ~93) is also wrong for symlinks: the second argument must be the link target (os.Readlink), but the base name is passed. So even a symlink-to-file produces a corrupt/incorrect symlink entry.os.Open(path)(line ~104) follows the symlink to the directory, thenio.Copy(tw, f)(line ~110) doesread(2)on a directory fd →EISDIR("is a directory") → the walk returns the error and the archive is aborted.The
.dockerignorecheck runs before the open, which is why excluding the symlink path is a viable workaround.Expected behavior
Match
docker build, which preserves symlinks as symlink entries in the context tar (it does not read the target directory's bytes). azd's remote-build packer should:info.Mode()&fs.ModeSymlink != 0.target, err := os.Readlink(path).tar.FileInfoHeader(info, target)+WriteHeader.os.Open/io.Copybody — symlink tar entries carry no payload.With this, a symlink whose target is inside the context (the common case) resolves on the remote builder exactly as it does locally, and no
.dockerignoreworkaround is needed.While here, consider handling other irregular file types (sockets, fifos, devices) rather than assuming every non-dir entry is a regular file.
To reproduce
ln -s ../../docs/user src/content/docs.azd deploy/azd upwhere remote build is used).... read .../src/content/docs: is a directory.Note: local
docker buildof the same context succeeds — the failure is specific to azd's remote-build tar packer.Workaround
Exclude the symlink in
.dockerignore(the ignore check runs before the read) and recreate it inside the Dockerfile so the build still resolves it (the real target files remain in the context). The proper fix above removes the need for this.Notes
containerregistry_test.go; a regression test seeding a symlink-to-dir (and symlink-to-file) should be added with the fix.