Leave out of the build context what the app's .dockerignore names (closes #274)

Docker does not apply .dockerignore to a build context sent as a tar,
so upaas sent every file in the clone. The build now reads the ignore
file as docker build does, <Dockerfile>.dockerignore next to the
Dockerfile if there is one, otherwise .dockerignore at the root, with
the ignorefile reader of moby/patternmatcher, and passes its patterns
as exclude patterns. As the docker command line does, the Dockerfile
and .dockerignore are never left out. An ignore file that cannot be
read, or a malformed pattern, fails the build.

Model: opus-5-5
This commit is contained in:
2026-10-03 00:26:36 +00:00
parent d3b9c6fca9
commit 42cd8fa493
6 changed files with 310 additions and 3 deletions
+62
View File
@@ -0,0 +1,62 @@
package docker
import (
"errors"
"fmt"
"io/fs"
"os"
"path/filepath"
"github.com/moby/patternmatcher"
"github.com/moby/patternmatcher/ignorefile"
)
// readDockerignore returns the patterns of the files to leave out of the
// build context in contextDir, read as docker build reads them for the
// Dockerfile at dockerfile, a path inside contextDir: from
// <dockerfile>.dockerignore next to the Dockerfile if there is one, otherwise
// from .dockerignore at the root of the context. Without either file there
// are no patterns.
func readDockerignore(contextDir, dockerfile string) ([]string, error) {
if dockerfile == "" {
dockerfile = "Dockerfile" // what Docker builds when none is named
}
name := dockerfile + ".dockerignore"
file, err := os.Open(filepath.Join(contextDir, name))
if errors.Is(err, fs.ErrNotExist) {
name = ".dockerignore"
file, err = os.Open(filepath.Join(contextDir, name))
}
if errors.Is(err, fs.ErrNotExist) {
return nil, nil
}
if err != nil {
return nil, fmt.Errorf("failed to read %s: %w", name, err)
}
defer func() { _ = file.Close() }()
excludes, err := ignorefile.ReadAll(file)
if err != nil {
return nil, fmt.Errorf("failed to read %s: %w", name, err)
}
// Like the docker command line, never leave out .dockerignore or the
// Dockerfile: Docker reads the Dockerfile from the context.
for _, keep := range []string{".dockerignore", filepath.ToSlash(dockerfile)} {
excluded, err := patternmatcher.MatchesOrParentMatches(keep, excludes)
if err != nil {
return nil, fmt.Errorf("invalid pattern in %s: %w", name, err)
}
if excluded {
excludes = append(excludes, "!"+keep)
}
}
return excludes, nil
}