Skip to content

ACR remote build: PackRemoteBuildSource fails on symlinks ("is a directory"); mishandles symlink tar entries #9070

Description

@bterlson

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:

  1. 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.
  2. 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.
  3. 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

  1. 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.
  2. Deploy via the ACR remote build path (azd deploy / azd up where remote build is used).
  3. 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.

Metadata

Metadata

Labels

area/service-targetsContainer Apps, App Service, Functions, AKS, SWAarea/toolsExternal tools (Docker, npm, Python)bugSomething isn't working

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions