package docker import ( "archive/tar" "context" "fmt" "io" "os" "path/filepath" "strings" "github.com/moby/moby/client" ) // BuildImage builds a Docker image from a directory containing a Dockerfile. // The directory is packaged as a tar archive and sent to the Docker daemon. // The tag parameter is the image name:tag to apply (e.g., "dw-site-myapp:latest"). func (c *Client) BuildImage(ctx context.Context, contextDir, tag string) error { // Create tar archive of the build context. pr, pw := io.Pipe() go func() { tw := tar.NewWriter(pw) err := filepath.Walk(contextDir, func(path string, info os.FileInfo, err error) error { if err != nil { return err } relPath, err := filepath.Rel(contextDir, path) if err != nil { return fmt.Errorf("rel path: %w", err) } relPath = filepath.ToSlash(relPath) if relPath == "." { return nil } header, err := tar.FileInfoHeader(info, "") if err != nil { return fmt.Errorf("tar header for %s: %w", relPath, err) } header.Name = relPath if err := tw.WriteHeader(header); err != nil { return fmt.Errorf("write tar header for %s: %w", relPath, err) } if info.IsDir() { return nil } file, err := os.Open(path) if err != nil { return fmt.Errorf("open %s: %w", path, err) } defer file.Close() if _, err := io.Copy(tw, file); err != nil { return fmt.Errorf("copy %s to tar: %w", relPath, err) } return nil }) if closeErr := tw.Close(); closeErr != nil && err == nil { err = closeErr } pw.CloseWithError(err) }() resp, err := c.api.ImageBuild(ctx, pr, client.ImageBuildOptions{ Dockerfile: "Dockerfile", Tags: []string{tag}, Remove: true, ForceRemove: true, }) if err != nil { return fmt.Errorf("build image %s: %w", tag, err) } defer resp.Body.Close() // Read the build output to completion (required for the build to finish). output, err := io.ReadAll(resp.Body) if err != nil { return fmt.Errorf("read build output for %s: %w", tag, err) } // Check for error in build output. if strings.Contains(string(output), `"error"`) { return fmt.Errorf("build image %s: build errors in output", tag) } return nil }