OCPBUGS-83863: Remove rhel8 build stage, strip debug info#228
Conversation
|
@sdodson: This pull request references Jira Issue OCPBUGS-83863, which is valid. 3 validation(s) were run on this bug
The bug has been updated to refer to the pull request using the external bug tracker. DetailsIn response to this:
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository. |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughThe Dockerfile removes the rhel8 builder stage, updates the windows build stage to use rhel-9, and changes the runtime to copy Linux binaries into ChangesBuild and packaging updates
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Suggested reviewers
🚥 Pre-merge checks | ✅ 15✅ Passed checks (15 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: sdodson The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@Dockerfile`:
- Around line 24-26: The two separate RUN layers creating hardlinks should be
combined into a single RUN so the hardlinks share the same inode; update the
Dockerfile so the ln operations that link /usr/src/plugins/bin/* into
/usr/src/plugins/rhel9/bin/ and /usr/src/plugins/rhel10/bin/ are executed in one
RUN instruction (i.e., run both ln commands together in the same shell
invocation) to avoid duplicate file data across layers and preserve inode
identity.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository: openshift/coderabbit/.coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: 5cdb716f-6ea1-43d1-9996-e288ff5a7acc
📒 Files selected for processing (1)
Dockerfile
2bde2a8 to
93bb9d6
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@build_linux.sh`:
- Line 20: The build command in build_linux.sh calls ${GO:-go} build with a
hardcoded -ldflags "-s -w" before "$@", which allows caller-supplied -ldflags in
"$@" to override and drop the strip flags; update the script to scan and remove
any -ldflags and its argument from the "$@" array, capture the caller's ldflags
value (handling both -ldflags <val> and -ldflags=<val> forms), merge it with "-s
-w" (ensuring no duplication) into one combined ldflags string, and then invoke
${GO:-go} build -o "${PWD}/bin/$plugin" -ldflags "<combined>" "$@" so only a
single -ldflags is passed to go build.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository: openshift/coderabbit/.coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: c7c78c96-b587-40e7-9c03-c3c553fb4899
📒 Files selected for processing (2)
Dockerfilebuild_linux.sh
💤 Files with no reviewable changes (1)
- Dockerfile
| if [ "${plugin}" != "windows" ]; then | ||
| echo " $plugin" | ||
| ${GO:-go} build -o "${PWD}/bin/$plugin" "$@" ./"$d" | ||
| ${GO:-go} build -o "${PWD}/bin/$plugin" -ldflags "-s -w" "$@" ./"$d" |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
For go build, when -ldflags is provided multiple times in one command, does the last value override earlier ones or are they merged?
💡 Result:
When the -ldflags flag is provided multiple times in a single go build command, the values are not merged; instead, the last value provided on the command line overrides any earlier instances of the flag [1]. The go command processes flags in the order they appear. If you specify -ldflags multiple times, the go tool effectively discards the previous -ldflags settings in favor of the final one [1]. This behavior is consistent with how the go command handles many other flags, where later command-line arguments take precedence over earlier ones or over defaults set via environment variables like GOFLAGS [1]. If you need to pass multiple linker flags, you must include them within a single -ldflags argument, separated by spaces [2][3]. For example: go build -ldflags "-s -w -X main.Version=1.0.0" In this example, all three flags (-s, -w, and -X) are passed to the linker because they are contained within a single -ldflags string [2][3]. If you were to write go build -ldflags "-s" -ldflags "-w", the linker would only receive the -w flag [1].
Citations:
- 1: cmd/go: override semantics break e.g. GOFLAGS=-ldflags golang/go#38522
- 2: https://pkg.go.dev/cmd/go
- 3: https://pkg.go.dev/cmd/go@go1.26.3
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== build_linux.sh (around line 15-40) ==="
nl -ba build_linux.sh | sed -n '1,120p'
echo "=== release workflow(s) referencing build_linux.sh or -ldflags ==="
rg -n "build_linux\.sh|ldflags" .github/workflows -S || true
for f in $(rg -l "build_linux\.sh|ldflags" .github/workflows -S || true); do
echo "--- $f (around matches) ---"
nl -ba "$f" | rg -n "build_linux\.sh|ldflags" -n || true
done
echo "=== Any other callers of build_linux.sh ==="
rg -n "./build_linux\.sh|build_linux\.sh" -S . || trueRepository: openshift/containernetworking-plugins
Length of output: 185
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== build_linux.sh (around lines 1-120) ==="
cat -n build_linux.sh | sed -n '1,120p'
echo "=== release workflow(s) referencing build_linux.sh or -ldflags ==="
rg -n "build_linux\.sh|ldflags" .github/workflows -S || true
echo "=== Any other callers of build_linux.sh ==="
rg -n "build_linux\.sh" . -S || trueRepository: openshift/containernetworking-plugins
Length of output: 1599
Merge caller and script linker flags in build_linux.sh (avoid duplicate -ldflags)
build_linux.sh unconditionally adds -ldflags "-s -w" before "$@", but release.yaml passes its own -ldflags (including -X ...BuildVersion=...). For go build, the last -ldflags wins (not merged), so the release-provided -ldflags overrides "-s -w", meaning the strip flags are dropped.
Fix: remove/consume any caller -ldflags from "$@" and re-emit a single combined -ldflags containing both the caller linker flags and -s -w.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@build_linux.sh` at line 20, The build command in build_linux.sh calls
${GO:-go} build with a hardcoded -ldflags "-s -w" before "$@", which allows
caller-supplied -ldflags in "$@" to override and drop the strip flags; update
the script to scan and remove any -ldflags and its argument from the "$@" array,
capture the caller's ldflags value (handling both -ldflags <val> and
-ldflags=<val> forms), merge it with "-s -w" (ensuring no duplication) into one
combined ldflags string, and then invoke ${GO:-go} build -o "${PWD}/bin/$plugin"
-ldflags "<combined>" "$@" so only a single -ldflags is passed to go build.
93bb9d6 to
f07fb4b
Compare
There was a problem hiding this comment.
♻️ Duplicate comments (1)
build_linux.sh (1)
20-20:⚠️ Potential issue | 🔴 CriticalCaller-supplied
-ldflagsin"$@"will override the strip flags.When
"$@"contains another-ldflagsargument (e.g., fromrelease.yamlpassing-X ...BuildVersion=...), Go's build command uses only the last-ldflagsvalue, discarding the earlier-s -w. This means the strip flags are lost when the script is invoked with additional linker flags.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@build_linux.sh` at line 20, The build invocation currently hardcodes -ldflags "-s -w" before passing "$@", which allows a caller-supplied -ldflags in "$@" to override and drop the strip flags; change the script to detect if "$@" contains an -ldflags argument and, if so, append " -s -w" to that -ldflags value, otherwise keep passing -ldflags "-s -w" as before; update the command that constructs the build invocation (the line using ${GO:-go} build ... -ldflags "-s -w" "$@" ./"$d") to use the merged/modified arguments so -s -w are preserved while still allowing caller -X or other linker flags, handling both the forms -ldflagsVALUE and -ldflags VALUE and leaving other "$@" items untouched.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Duplicate comments:
In `@build_linux.sh`:
- Line 20: The build invocation currently hardcodes -ldflags "-s -w" before
passing "$@", which allows a caller-supplied -ldflags in "$@" to override and
drop the strip flags; change the script to detect if "$@" contains an -ldflags
argument and, if so, append " -s -w" to that -ldflags value, otherwise keep
passing -ldflags "-s -w" as before; update the command that constructs the build
invocation (the line using ${GO:-go} build ... -ldflags "-s -w" "$@" ./"$d") to
use the merged/modified arguments so -s -w are preserved while still allowing
caller -X or other linker flags, handling both the forms -ldflagsVALUE and
-ldflags VALUE and leaving other "$@" items untouched.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository: openshift/coderabbit/.coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: ee2d22d1-364a-4218-b10d-dbb23186f0b7
📒 Files selected for processing (2)
Dockerfilebuild_linux.sh
|
/retest-required |
RHEL 8 is end-of-life. Remove the rhel8 build stage, switch the windows builder to the rhel-9 image, and use rhel9-built binaries as the default in /usr/src/plugins/bin/. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> rh-pre-commit.version: 2.3.2 rh-pre-commit.check-secrets: ENABLED
The rhel9/bin/ directory contains the same binaries as bin/. Use hardlinks to avoid duplicating them in the image layer. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> rh-pre-commit.version: 2.3.2 rh-pre-commit.check-secrets: ENABLED
b573d98 to
d14eba2
Compare
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@Dockerfile`:
- Line 2: Replace all FROM lines that reference registry.ci.openshift.org with
UBI minimal or distroless images hosted on catalog.redhat.com; specifically
update the stage declared as "FROM
registry.ci.openshift.org/ocp/builder:rhel-9-golang-1.25-openshift-4.22 AS
rhel9" and the other two FROM statements (the other build/runtime stages) to use
the appropriate catalog.redhat.com UBI or distroless equivalents, ensuring tags
match required Go and OpenShift tooling versions and preserving the same AS
aliases (e.g., rhel9) so the rest of the Dockerfile continues to reference the
same stage names.
- Line 3: The Dockerfile currently uses the broad COPY . /usr/src/plugins
instruction (appearing as "COPY . /usr/src/plugins") in the builder stages;
change these to copy only the required artifacts (for example copy package.json
and package-lock.json, then the source directory or specific files needed for
build, and any Docker-related config) and remove the blanket context copy to
avoid leaking secrets—replace each "COPY . /usr/src/plugins" occurrence with
precise COPY lines for the minimal set of files needed for the build and runtime
(e.g., COPY package*.json, COPY src/, COPY Dockerfile or other explicit files)
and ensure any .dockerignore excludes secrets.
- Around line 18-28: The final image currently runs as root; update the
Dockerfile to create a non-root user and switch to it at runtime: add steps to
create a dedicated user/group (e.g., "plugins" or "nonroot"), chown the plugin
directories (/usr/src/plugins and subpaths referenced by the COPY/ln steps) to
that user, and add a USER instruction near the end of the Dockerfile to set the
non-root user as the runtime user so containers do not run as root; ensure
ownership changes occur after the COPY/ln operations that populate
/usr/src/plugins.
- Around line 18-28: Add a Docker HEALTHCHECK instruction to the Dockerfile
(after the plugin binaries are copied/linked) to provide a runtime liveness
probe; the HEALTHCHECK should run a lightweight command that validates the
container’s main service (for example an HTTP GET to the service health endpoint
or invoking a packaged health-check binary under /usr/src/plugins/bin/) and
include sensible parameters (interval, timeout and start-period). Place the
HEALTHCHECK after the COPY/ln steps that populate /usr/src/plugins/bin (so the
health binary/endpoint is available) and ensure it returns non-zero on failure
so the container runtime marks unhealthy.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository: openshift/coderabbit/.coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: a01254eb-6bf7-401b-a3fe-e5e89605f7ef
📒 Files selected for processing (2)
Dockerfilebuild_linux.sh
There was a problem hiding this comment.
Caution
Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@Dockerfile`:
- Line 2: Replace all FROM lines that reference registry.ci.openshift.org with
UBI minimal or distroless images hosted on catalog.redhat.com; specifically
update the stage declared as "FROM
registry.ci.openshift.org/ocp/builder:rhel-9-golang-1.25-openshift-4.22 AS
rhel9" and the other two FROM statements (the other build/runtime stages) to use
the appropriate catalog.redhat.com UBI or distroless equivalents, ensuring tags
match required Go and OpenShift tooling versions and preserving the same AS
aliases (e.g., rhel9) so the rest of the Dockerfile continues to reference the
same stage names.
- Line 3: The Dockerfile currently uses the broad COPY . /usr/src/plugins
instruction (appearing as "COPY . /usr/src/plugins") in the builder stages;
change these to copy only the required artifacts (for example copy package.json
and package-lock.json, then the source directory or specific files needed for
build, and any Docker-related config) and remove the blanket context copy to
avoid leaking secrets—replace each "COPY . /usr/src/plugins" occurrence with
precise COPY lines for the minimal set of files needed for the build and runtime
(e.g., COPY package*.json, COPY src/, COPY Dockerfile or other explicit files)
and ensure any .dockerignore excludes secrets.
- Around line 18-28: The final image currently runs as root; update the
Dockerfile to create a non-root user and switch to it at runtime: add steps to
create a dedicated user/group (e.g., "plugins" or "nonroot"), chown the plugin
directories (/usr/src/plugins and subpaths referenced by the COPY/ln steps) to
that user, and add a USER instruction near the end of the Dockerfile to set the
non-root user as the runtime user so containers do not run as root; ensure
ownership changes occur after the COPY/ln operations that populate
/usr/src/plugins.
- Around line 18-28: Add a Docker HEALTHCHECK instruction to the Dockerfile
(after the plugin binaries are copied/linked) to provide a runtime liveness
probe; the HEALTHCHECK should run a lightweight command that validates the
container’s main service (for example an HTTP GET to the service health endpoint
or invoking a packaged health-check binary under /usr/src/plugins/bin/) and
include sensible parameters (interval, timeout and start-period). Place the
HEALTHCHECK after the COPY/ln steps that populate /usr/src/plugins/bin (so the
health binary/endpoint is available) and ensure it returns non-zero on failure
so the container runtime marks unhealthy.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository: openshift/coderabbit/.coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: a01254eb-6bf7-401b-a3fe-e5e89605f7ef
📒 Files selected for processing (2)
Dockerfilebuild_linux.sh
🛑 Comments failed to post (3)
Dockerfile (3)
2-2:
⚠️ Potential issue | 🟠 Major | 🏗️ Heavy liftUse catalog.redhat.com UBI/distroless base images for all stages.
Line 2, Line 10, and Line 18 use
registry.ci.openshift.org/...images, which does not satisfy the repository container-base policy.As per coding guidelines, "Base image: UBI minimal or distroless from catalog.redhat.com".
Also applies to: 10-10, 18-18
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@Dockerfile` at line 2, Replace all FROM lines that reference registry.ci.openshift.org with UBI minimal or distroless images hosted on catalog.redhat.com; specifically update the stage declared as "FROM registry.ci.openshift.org/ocp/builder:rhel-9-golang-1.25-openshift-4.22 AS rhel9" and the other two FROM statements (the other build/runtime stages) to use the appropriate catalog.redhat.com UBI or distroless equivalents, ensuring tags match required Go and OpenShift tooling versions and preserving the same AS aliases (e.g., rhel9) so the rest of the Dockerfile continues to reference the same stage names.Source: Coding guidelines
3-3:
⚠️ Potential issue | 🟠 Major | 🏗️ Heavy liftAvoid copying the entire build context into builder stages.
Line 3 and Line 11 use
COPY . /usr/src/plugins; this violates the rule to copy only explicit required files and increases secret-exposure risk.As per coding guidelines, "COPY specific files, not entire context".
Also applies to: 11-11
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@Dockerfile` at line 3, The Dockerfile currently uses the broad COPY . /usr/src/plugins instruction (appearing as "COPY . /usr/src/plugins") in the builder stages; change these to copy only the required artifacts (for example copy package.json and package-lock.json, then the source directory or specific files needed for build, and any Docker-related config) and remove the blanket context copy to avoid leaking secrets—replace each "COPY . /usr/src/plugins" occurrence with precise COPY lines for the minimal set of files needed for the build and runtime (e.g., COPY package*.json, COPY src/, COPY Dockerfile or other explicit files) and ensure any .dockerignore excludes secrets.Source: Coding guidelines
18-28:
⚠️ Potential issue | 🟠 Major | ⚡ Quick winRun the final image as a non-root user.
The runtime stage never sets
USER, so containers run as root by default.Suggested patch
FROM registry.ci.openshift.org/ocp/4.22:base-rhel9 RUN mkdir -p /usr/src/plugins/bin && \ mkdir -p /usr/src/plugins/rhel9/bin && \ mkdir -p /usr/src/plugins/rhel10/bin && \ mkdir -p /usr/src/plugins/windows/bin COPY --from=rhel9 /usr/src/plugins/bin/* /usr/src/plugins/bin/ RUN ln /usr/src/plugins/bin/* /usr/src/plugins/rhel9/bin/ # For now assume rhel9 binaries are compatible with rhel10 RUN ln /usr/src/plugins/bin/* /usr/src/plugins/rhel10/bin/ COPY --from=windows /usr/src/plugins/bin/* /usr/src/plugins/windows/bin/ +USER 65532:65532As per coding guidelines, "USER non-root; never run as root".
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.FROM registry.ci.openshift.org/ocp/4.22:base-rhel9 RUN mkdir -p /usr/src/plugins/bin && \ mkdir -p /usr/src/plugins/rhel9/bin && \ mkdir -p /usr/src/plugins/rhel10/bin && \ mkdir -p /usr/src/plugins/windows/bin COPY --from=rhel9 /usr/src/plugins/bin/* /usr/src/plugins/bin/ RUN ln /usr/src/plugins/bin/* /usr/src/plugins/rhel9/bin/ # For now assume rhel9 binaries are compatible with rhel10 RUN ln /usr/src/plugins/bin/* /usr/src/plugins/rhel10/bin/ COPY --from=windows /usr/src/plugins/bin/* /usr/src/plugins/windows/bin/ USER 65532:65532🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@Dockerfile` around lines 18 - 28, The final image currently runs as root; update the Dockerfile to create a non-root user and switch to it at runtime: add steps to create a dedicated user/group (e.g., "plugins" or "nonroot"), chown the plugin directories (/usr/src/plugins and subpaths referenced by the COPY/ln steps) to that user, and add a USER instruction near the end of the Dockerfile to set the non-root user as the runtime user so containers do not run as root; ensure ownership changes occur after the COPY/ln operations that populate /usr/src/plugins.Source: Coding guidelines
⚠️ Potential issue | 🟠 Major | ⚡ Quick winAdd a HEALTHCHECK for runtime liveness.
The final image has no health probe, which violates the container policy and weakens runtime observability.
Suggested patch
COPY --from=windows /usr/src/plugins/bin/* /usr/src/plugins/windows/bin/ +HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \ + CMD ["/bin/sh", "-c", "test -x /usr/src/plugins/bin/bridge"]As per coding guidelines, "HEALTHCHECK defined".
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@Dockerfile` around lines 18 - 28, Add a Docker HEALTHCHECK instruction to the Dockerfile (after the plugin binaries are copied/linked) to provide a runtime liveness probe; the HEALTHCHECK should run a lightweight command that validates the container’s main service (for example an HTTP GET to the service health endpoint or invoking a packaged health-check binary under /usr/src/plugins/bin/) and include sensible parameters (interval, timeout and start-period). Place the HEALTHCHECK after the COPY/ln steps that populate /usr/src/plugins/bin (so the health binary/endpoint is available) and ensure it returns non-zero on failure so the container runtime marks unhealthy.Source: Coding guidelines
d14eba2 to
5c5552a
Compare
Pass -ldflags "-s -w" to go build to reduce binary size by stripping the symbol table and DWARF debug information. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> rh-pre-commit.version: 2.4.0 rh-pre-commit.check-secrets: ENABLED
5c5552a to
25477af
Compare
| if [ "${plugin}" != "windows" ]; then | ||
| echo " $plugin" | ||
| ${GO:-go} build -o "${PWD}/bin/$plugin" "$@" ./"$d" | ||
| ${GO:-go} build -o "${PWD}/bin/$plugin" -ldflags "$LDFLAGS" "$@" ./"$d" |
There was a problem hiding this comment.
@bpickard22 Should I be pushing these changes upstream?
|
@sdodson: The following test failed, say
Full PR test history. Your PR dashboard. DetailsInstructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here. |
Remove the rhel8 build stage and strip debug symbols from binaries.
The rhel9 version-specific subdirectory is retained as a hardlink to avoid duplicating the layer.