Skip to content

.dockerignore wildcard exceptions lose files inside excluded directories聽#3448

Description

@jyn514

馃 this issue description was generated by an LLM. i reviewed it before submitting. 馃

I used Codex for the investigation, reproduction scripts, and this draft.

When I generate a build context with docker.utils.tar, the rules ** and !**/a.txt omit every nested a.txt. Docker's BuildKit keeps those files with the same input. The SDK therefore drops files that the exception explicitly includes.

Reproduction

Save this script as reproduce-wildcard.py:

import tarfile
from pathlib import Path
from tempfile import TemporaryDirectory

import docker
from docker.utils import tar

patterns = ['**', '!**/a.txt']
files = ['foo/a.txt', 'foo/bar/a.txt', 'target/a.txt']
with TemporaryDirectory() as directory:
    root = Path(directory)
    (root / 'Dockerfile').write_text('FROM scratch\nCOPY . /\n')
    (root / '.dockerignore').write_text('\n'.join(patterns) + '\n')
    for relative in files:
        path = root / relative
        path.parent.mkdir(parents=True, exist_ok=True)
        path.write_text(relative)
    with tar(str(root), exclude=patterns.copy()) as context:
        with tarfile.open(fileobj=context) as archive:
            actual = sorted(name for name in archive.getnames() if name in files)
    print('docker:', docker.__version__)
    print('patterns:', patterns)
    print('SDK context files:', actual)

I ran it in an isolated uv environment, using commit 56343ddf8f0c44281e151c2dad016c16cdb8393d:

uv run --isolated --no-project --no-config --no-cache \
  --python /home/jyn/.local/share/mise/installs/python/3.14.7/bin/python3 \
  --with 'docker @ git+https://github.com/docker/docker-py@56343ddf8f0c44281e151c2dad016c16cdb8393d' \
  python reproduce-wildcard.py

The --python path selects my tested Python installation; use the path to your Python executable on another machine. The pinned requirement selects the upstream SDK rather than an installed copy. I did not modify the SDK or replace its modules with mocks. This reproducer creates disposable files and examines the SDK's context tar without contacting a Docker daemon.

Expected behavior

The SDK should retain foo/a.txt, foo/bar/a.txt, and target/a.txt. I built the same files with FROM scratch and COPY . / using BuildKit, and its local output retained all three.

Actual output

docker: 7.2.1.dev26+g56343ddf8
patterns: ['**', '!**/a.txt']
SDK context files: []

BuildKit control

I ran the following standalone control with Docker's active lima context and lima builder. It creates the same input files, runs docker buildx build with a local filesystem export, and enumerates every exported regular file before removing the temporary directories. The command uses the active Docker context and builder; another machine can use its own working configuration.

Buildx command and exported-file inspection

Save this script as buildkit-control-wildcard.py:

import subprocess
from pathlib import Path
from tempfile import TemporaryDirectory

patterns = ['**', '!**/a.txt']
files = ['foo/a.txt', 'foo/bar/a.txt', 'target/a.txt']
with TemporaryDirectory() as directory:
    context = Path(directory) / 'input'
    context.mkdir()
    output = Path(directory) / 'output'
    (context / 'Dockerfile').write_text('FROM scratch\nCOPY . /\n')
    (context / '.dockerignore').write_text('\n'.join(patterns) + '\n')
    for relative in files:
        path = context / relative
        path.parent.mkdir(parents=True, exist_ok=True)
        path.write_text(relative)
    subprocess.run([
        'docker', 'buildx', 'build',
        '--progress=plain', '--provenance=false',
        '--output', f'type=local,dest={output}', str(context),
    ], check=True)
    print('BuildKit output files:', sorted(
        path.relative_to(output).as_posix()
        for path in output.rglob('*') if path.is_file()
    ))

Run it with:

python3 buildkit-control-wildcard.py

I got this stdout; Buildx wrote its progress log to stderr and exited successfully:

BuildKit output files: ['foo/a.txt', 'foo/bar/a.txt', 'target/a.txt']

Related report

A commenter described a similar wildcard-exception failure in the closed issue #1514. I can reproduce the minimized case below using the current upstream commit.

#1514 (comment)

Environment

  • Docker SDK for Python: 7.2.1.dev26+g56343ddf8, using upstream commit 56343ddf8f0c44281e151c2dad016c16cdb8393d.
  • Python: 3.14.7 (main, Sep 29 2026, 15:01:40) [Clang 22.1.3 ].
  • OS/distribution: CachyOS Linux, rolling release (BUILD_ID=rolling; /etc/os-release does not define VERSION_ID).
  • Kernel/platform: Linux-7.2.9-1-cachyos-x86_64-with-glibc2.44.
  • Docker CLI: 29.8.2; Docker Engine: 29.8.0 (linux/amd64).
  • Buildx: github.com/docker/buildx 0.37.2 2d379c0c3f22da0d2759d132a0ec81ca949098f0.
  • The SDK tar reproducer requires no engine connection; the separate BuildKit comparison used the versions above.

Activity

  1. SparkDrago05 commented on Oct 9, 2026

    @SparkDrago05

    I checked this against Docker 29.8.1 with the same files and .dockerignore (**, !**/a.txt, !Dockerfile):

    Builder Files in context
    BuildKit foo/a.txt, foo/bar/a.txt, target/a.txt
    Classic builder (DOCKER_BUILDKIT=0) none
    docker-py none

    So docker-py currently matches the classic builder. The existing test test_include_wildcard pins that behaviour (its comment says it was checked against the 18.05 CLI). BuildKit differs because fsutil never skips an excluded directory when an exception pattern contains wildcards (onlyPrefixExcludeExceptions in tonistiigi/fsutil/filter.go). The classic TarWithOptions only keeps walking when an exception is a literal path prefix of the directory.

    Should docker-py follow BuildKit here? BuildKit is the default builder, and the classic builder is deprecated. If so, I can open a PR that mirrors the fsutil rule and updates test_include_wildcard. I didn't want to change the existing tested behaviour without a maintainer's opinion.

    I opened a separate fix for #3449 in #3454, which is wrong under both builders.

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