Skip to content

.dockerignore patterns with a leading slash (e.g. /foo/a.py) are not applied, so matching files are still added to the build context #2700

Open

Description

@hidingmyname

Location (docker-java/docker-java @ default branch 002b6eb):

  • docker-java/src/main/java/com/github/dockerjava/core/dockerfile/Dockerfile.java:98
pattern = FilenameUtils.normalize(pattern)

FilenameUtils.normalize(pattern) keeps the leading /, and getIgnores adds the pattern unchanged.

Problem
When a .dockerignore entry starts with / (e.g. /foo/a.py), the leading slash is preserved after FilenameUtils.normalize at Dockerfile.java:98 and passed on to GoLangFileMatch.match. The resulting regex is anchored on a literal / at the start, which never matches the relative file paths of the build context. As a result the pattern has no effect and the file remains in filesToAdd, even though the Docker documentation states that /foo/bar and foo/bar should exclude the same file.

Reproduction

Test (from the applied patch):

package com.github.dockerjava.core.dockerfile;

import com.google.common.base.Function;
import org.junit.Test;

import java.io.File;
import java.util.Collection;

import static com.google.common.collect.Collections2.transform;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsInAnyOrder;

public class DockerfileLeadingSlashIgnoreTest {

    private static final Function<File, String> TO_FILE_NAMES = File::getName;

    @Test
    public void leadingSlashPatternShouldExcludeFile() throws Exception {
        File baseDir = fileFromBuildTestResource("dockerignore/LeadingSlashPattern");
        Dockerfile dockerfile = new Dockerfile(new File(baseDir, "Dockerfile"), baseDir);
        Dockerfile.ScannedResult result = dockerfile.parse();
        Collection<String> filesToAdd = transform(result.filesToAdd, TO_FILE_NAMES);

        assertThat(filesToAdd,
                containsInAnyOrder("Dockerfile", ".dockerignore", "b.py"));
    }

    private File fileFromBuildTestResource(String resource) {
        return new File(Thread.currentThread().getContextClassLoader()
                .getResource("buildTests/" + resource).getFile());
    }
}

Build context resources:

  • .dockerignore → /foo/a.py
  • Dockerfile → FROM alpine:3.1
  • foo/a.py, foo/b.py

Failing assertion on HEAD:

[ERROR] com.github.dockerjava.core.dockerfile.DockerfileLeadingSlashIgnoreTest.leadingSlashPatternShouldExcludeFile  Time elapsed: 0.003 s  <<< FAILURE!
java.lang.AssertionError: 
Expected: iterable with items ["Dockerfile", ".dockerignore", "b.py"] in any order

The assertion fails because a.py is not excluded and remains in filesToAdd — the exact symptom described above.

Environment:

  • Commit: 002b6eb7aa2b892be729c7c2a5e95f782924db38 (default branch, 2026-06-22)
  • Toolchain: openjdk version "21.0.2" 2024-01-16, Apache Maven 3.9.6 (bc0240f3c744dd6b6ec2920b3cd08dcc295161ae)

The control run without the test patch exits 0 on the same suite.

Already fixed in docker/docker-py
PR: docker/docker-py#1709 — "Leading slash in .dockerignore should be ignored" (merged 2017-08-15T22:40:17Z, merge commit 92a2e48e174). It strips any leading / from each .dockerignore pattern before the patterns are used for exclusion, so /foo/a.py and foo/a.py behave identically.

How docker-py fixed it:

--- a/docker/utils/build.py
+++ b/docker/utils/build.py
@@ -26,6 +26,7 @@ def exclude_paths(root, patterns, dockerfile=None):
     if dockerfile is None:
         dockerfile = 'Dockerfile'
 
+    patterns = [p.lstrip('/') for p in patterns]
     exceptions = [p for p in patterns if p.startswith('!')]
 
     include_patterns = [p[1:] for p in exceptions]
--- a/tests/unit/utils_test.py
+++ b/tests/unit/utils_test.py
@@ -768,6 +768,11 @@ def test_single_subdir_single_filename(self):
             self.all_paths - set(['foo/a.py'])
         )
 
+    def test_single_subdir_single_filename_leading_slash(self):
+        assert self.exclude(['/foo/a.py']) == convert_paths(
+            self.all_paths - set(['foo/a.py'])
+        )
+
     def test_single_subdir_with_path_traversal(self):
         assert self.exclude(['foo/whoops/../a.py']) == convert_paths(
             self.all_paths - set(['foo/a.py'])

Suggested fix

Strip leading slashes from each .dockerignore pattern before matching, as docker-py does:

diff --git a/docker-java-core/src/main/java/com/github/dockerjava/core/dockerfile/Dockerfile.java b/docker-java-core/src/main/java/com/github/dockerjava/core/dockerfile/Dockerfile.java
--- a/docker-java-core/src/main/java/com/github/dockerjava/core/dockerfile/Dockerfile.java
+++ b/docker-java-core/src/main/java/com/github/dockerjava/core/dockerfile/Dockerfile.java
@@ -91,7 +91,8 @@
             List<String> dockerIgnoreFileContent = FileUtils.readLines(dockerIgnoreFile);
             for (String pattern : dockerIgnoreFileContent) {
                 lineNumber++;
-                pattern = pattern.trim();
+                // a leading slash refers to the build context root, same as no slash
+                pattern = pattern.trim().replaceFirst("^/+", "");
                 if (pattern.isEmpty()) {
                     continue; // skip empty lines
                 }

Verified on 002b6eb (OpenJDK 21.0.2, Maven 3.9.6): the test above fails without this patch and passes with it; no other test results change.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions