diff --git a/container.go b/container.go
index 6f17a7e3ff..8747335a28 100644
--- a/container.go
+++ b/container.go
@@ -1,6 +1,7 @@
 package testcontainers
 
 import (
+	"archive/tar"
 	"context"
 	"errors"
 	"fmt"
@@ -10,6 +11,7 @@ import (
 	"strings"
 	"time"
 
+	"github.com/cpuguy83/dockercfg"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/api/types/network"
@@ -85,7 +87,7 @@ type ImageBuildInfo interface {
 // rather than using a pre-built one
 type FromDockerfile struct {
 	Context        string                         // the path to the context of the docker build
-	ContextArchive io.Reader                      // the tar archive file to send to docker that contains the build context
+	ContextArchive io.ReadSeeker                  // the tar archive file to send to docker that contains the build context
 	Dockerfile     string                         // the path from the context to the Dockerfile for the image, defaults to "Dockerfile"
 	Repo           string                         // the repo label for image, defaults to UUID
 	Tag            string                         // the tag label for image, defaults to UUID
@@ -305,30 +307,90 @@ func (c *ContainerRequest) GetTag() string {
 	return strings.ToLower(t)
 }
 
-// Deprecated: Testcontainers will detect registry credentials automatically, and it will be removed in the next major release
-// GetAuthConfigs returns the auth configs to be able to pull from an authenticated docker registry
+// Deprecated: Testcontainers will detect registry credentials automatically, and it will be removed in the next major release.
+// GetAuthConfigs returns the auth configs to be able to pull from an authenticated docker registry.
+// Panics if an error occurs.
 func (c *ContainerRequest) GetAuthConfigs() map[string]registry.AuthConfig {
-	return getAuthConfigsFromDockerfile(c)
+	auth, err := getAuthConfigsFromDockerfile(c)
+	if err != nil {
+		panic(fmt.Sprintf("failed to get auth configs from Dockerfile: %v", err))
+	}
+	return auth
+}
+
+// dockerFileImages returns the images from the request Dockerfile.
+func (c *ContainerRequest) dockerFileImages() ([]string, error) {
+	if c.ContextArchive == nil {
+		// Source is a directory, we can read the Dockerfile directly.
+		images, err := core.ExtractImagesFromDockerfile(filepath.Join(c.Context, c.GetDockerfile()), c.GetBuildArgs())
+		if err != nil {
+			return nil, fmt.Errorf("extract images from Dockerfile: %w", err)
+		}
+
+		return images, nil
+	}
+
+	// Source is an archive, we need to read it to get the Dockerfile.
+	dockerFile := c.GetDockerfile()
+	tr := tar.NewReader(c.FromDockerfile.ContextArchive)
+
+	for {
+		hdr, err := tr.Next()
+		if err != nil {
+			if errors.Is(err, io.EOF) {
+				return nil, fmt.Errorf("Dockerfile %q not found in context archive", dockerFile)
+			}
+
+			return nil, fmt.Errorf("reading tar archive: %w", err)
+		}
+
+		if hdr.Name != dockerFile {
+			continue
+		}
+
+		images, err := core.ExtractImagesFromReader(tr, c.GetBuildArgs())
+		if err != nil {
+			return nil, fmt.Errorf("extract images from Dockerfile: %w", err)
+		}
+
+		// Reset the archive to the beginning.
+		if _, err := c.ContextArchive.Seek(0, io.SeekStart); err != nil {
+			return nil, fmt.Errorf("seek context archive to start: %w", err)
+		}
+
+		return images, nil
+	}
 }
 
 // getAuthConfigsFromDockerfile returns the auth configs to be able to pull from an authenticated docker registry
-func getAuthConfigsFromDockerfile(c *ContainerRequest) map[string]registry.AuthConfig {
-	images, err := core.ExtractImagesFromDockerfile(filepath.Join(c.Context, c.GetDockerfile()), c.GetBuildArgs())
+func getAuthConfigsFromDockerfile(c *ContainerRequest) (map[string]registry.AuthConfig, error) {
+	images, err := c.dockerFileImages()
 	if err != nil {
-		return map[string]registry.AuthConfig{}
+		return nil, fmt.Errorf("docker file images: %w", err)
+	}
+
+	// Get the auth configs once for all images as it can be a time-consuming operation.
+	configs, err := getDockerAuthConfigs()
+	if err != nil {
+		return nil, err
 	}
 
 	authConfigs := map[string]registry.AuthConfig{}
 	for _, image := range images {
-		registry, authConfig, err := DockerImageAuth(context.Background(), image)
+		registry, authConfig, err := dockerImageAuth(context.Background(), image, configs)
 		if err != nil {
+			if !errors.Is(err, dockercfg.ErrCredentialsNotFound) {
+				return nil, fmt.Errorf("docker image auth %q: %w", image, err)
+			}
+
+			// Credentials not found no config to add.
 			continue
 		}
 
 		authConfigs[registry] = authConfig
 	}
 
-	return authConfigs
+	return authConfigs, nil
 }
 
 func (c *ContainerRequest) ShouldBuildImage() bool {
@@ -361,7 +423,10 @@ func (c *ContainerRequest) BuildOptions() (types.ImageBuildOptions, error) {
 	buildOptions.Dockerfile = c.GetDockerfile()
 
 	// Make sure the auth configs from the Dockerfile are set right after the user-defined build options.
-	authsFromDockerfile := getAuthConfigsFromDockerfile(c)
+	authsFromDockerfile, err := getAuthConfigsFromDockerfile(c)
+	if err != nil {
+		return types.ImageBuildOptions{}, fmt.Errorf("auth configs from Dockerfile: %w", err)
+	}
 
 	if buildOptions.AuthConfigs == nil {
 		buildOptions.AuthConfigs = map[string]registry.AuthConfig{}
@@ -378,7 +443,7 @@ func (c *ContainerRequest) BuildOptions() (types.ImageBuildOptions, error) {
 	for _, is := range c.ImageSubstitutors {
 		modifiedTag, err := is.Substitute(tag)
 		if err != nil {
-			return buildOptions, fmt.Errorf("failed to substitute image %s with %s: %w", tag, is.Description(), err)
+			return types.ImageBuildOptions{}, fmt.Errorf("failed to substitute image %s with %s: %w", tag, is.Description(), err)
 		}
 
 		if modifiedTag != tag {
@@ -401,8 +466,9 @@ func (c *ContainerRequest) BuildOptions() (types.ImageBuildOptions, error) {
 	// Do this as late as possible to ensure we don't leak the context on error/panic.
 	buildContext, err := c.GetContext()
 	if err != nil {
-		return buildOptions, err
+		return types.ImageBuildOptions{}, err
 	}
+
 	buildOptions.Context = buildContext
 
 	return buildOptions, nil
diff --git a/container_test.go b/container_test.go
index d2a070f2de..3cb14ac296 100644
--- a/container_test.go
+++ b/container_test.go
@@ -147,7 +147,7 @@ func Test_BuildImageWithContexts(t *testing.T) {
 	type TestCase struct {
 		Name               string
 		ContextPath        string
-		ContextArchive     func() (io.Reader, error)
+		ContextArchive     func() (io.ReadSeeker, error)
 		ExpectedEchoOutput string
 		Dockerfile         string
 		ExpectedError      string
@@ -157,7 +157,7 @@ func Test_BuildImageWithContexts(t *testing.T) {
 		{
 			Name: "test build from context archive",
 			// fromDockerfileWithContextArchive {
-			ContextArchive: func() (io.Reader, error) {
+			ContextArchive: func() (io.ReadSeeker, error) {
 				var buf bytes.Buffer
 				tarWriter := tar.NewWriter(&buf)
 				files := []struct {
@@ -202,7 +202,7 @@ func Test_BuildImageWithContexts(t *testing.T) {
 		},
 		{
 			Name: "test build from context archive and be able to use files in it",
-			ContextArchive: func() (io.Reader, error) {
+			ContextArchive: func() (io.ReadSeeker, error) {
 				var buf bytes.Buffer
 				tarWriter := tar.NewWriter(&buf)
 				files := []struct {
@@ -255,14 +255,14 @@ func Test_BuildImageWithContexts(t *testing.T) {
 			ContextPath:        "./testdata",
 			Dockerfile:         "echo.Dockerfile",
 			ExpectedEchoOutput: "this is from the echo test Dockerfile",
-			ContextArchive: func() (io.Reader, error) {
+			ContextArchive: func() (io.ReadSeeker, error) {
 				return nil, nil
 			},
 		},
 		{
 			Name:        "it should error if neither a context nor a context archive are specified",
 			ContextPath: "",
-			ContextArchive: func() (io.Reader, error) {
+			ContextArchive: func() (io.ReadSeeker, error) {
 				return nil, nil
 			},
 			ExpectedError: "create container: you must specify either a build context or an image",
@@ -275,9 +275,8 @@ func Test_BuildImageWithContexts(t *testing.T) {
 			t.Parallel()
 			ctx := context.Background()
 			a, err := testCase.ContextArchive()
-			if err != nil {
-				t.Fatal(err)
-			}
+			require.NoError(t, err)
+
 			req := testcontainers.ContainerRequest{
 				FromDockerfile: testcontainers.FromDockerfile{
 					ContextArchive: a,
diff --git a/docker.go b/docker.go
index 5650f88612..5f6c415627 100644
--- a/docker.go
+++ b/docker.go
@@ -1114,7 +1114,7 @@ func (p *DockerProvider) CreateContainer(ctx context.Context, req ContainerReque
 		// forward the host ports to the container ports.
 		sshdForwardPortsHook, err := exposeHostPorts(ctx, &req, req.HostAccessPorts...)
 		if err != nil {
-			return nil, fmt.Errorf("failed to expose host ports: %w", err)
+			return nil, fmt.Errorf("expose host ports: %w", err)
 		}
 
 		defaultHooks = append(defaultHooks, sshdForwardPortsHook)
@@ -1292,12 +1292,12 @@ func (p *DockerProvider) ReuseOrCreateContainer(ctx context.Context, req Contain
 func (p *DockerProvider) attemptToPullImage(ctx context.Context, tag string, pullOpt image.PullOptions) error {
 	registry, imageAuth, err := DockerImageAuth(ctx, tag)
 	if err != nil {
-		p.Logger.Printf("Failed to get image auth for %s. Setting empty credentials for the image: %s. Error is:%s", registry, tag, err)
+		p.Logger.Printf("Failed to get image auth for %s. Setting empty credentials for the image: %s. Error is: %s", registry, tag, err)
 	} else {
 		// see https://github.com/docker/docs/blob/e8e1204f914767128814dca0ea008644709c117f/engine/api/sdk/examples.md?plain=1#L649-L657
 		encodedJSON, err := json.Marshal(imageAuth)
 		if err != nil {
-			p.Logger.Printf("Failed to marshal image auth. Setting empty credentials for the image: %s. Error is:%s", tag, err)
+			p.Logger.Printf("Failed to marshal image auth. Setting empty credentials for the image: %s. Error is: %s", tag, err)
 		} else {
 			pullOpt.RegistryAuth = base64.URLEncoding.EncodeToString(encodedJSON)
 		}
diff --git a/docker_auth.go b/docker_auth.go
index d45393f325..99e2d2fdba 100644
--- a/docker_auth.go
+++ b/docker_auth.go
@@ -2,8 +2,13 @@ package testcontainers
 
 import (
 	"context"
+	"crypto/md5"
 	"encoding/base64"
+	"encoding/hex"
 	"encoding/json"
+	"errors"
+	"fmt"
+	"io"
 	"net/url"
 	"os"
 	"sync"
@@ -21,15 +26,21 @@ var defaultRegistryFn = defaultRegistry
 // Finally, it will use the credential helpers to extract the information from the docker config file
 // for that registry, if it exists.
 func DockerImageAuth(ctx context.Context, image string) (string, registry.AuthConfig, error) {
-	defaultRegistry := defaultRegistryFn(ctx)
-	reg := core.ExtractRegistry(image, defaultRegistry)
-
-	cfgs, err := getDockerAuthConfigs()
+	configs, err := getDockerAuthConfigs()
 	if err != nil {
+		reg := core.ExtractRegistry(image, defaultRegistryFn(ctx))
 		return reg, registry.AuthConfig{}, err
 	}
 
-	if cfg, ok := getRegistryAuth(reg, cfgs); ok {
+	return dockerImageAuth(ctx, image, configs)
+}
+
+// dockerImageAuth returns the auth config for the given Docker image.
+func dockerImageAuth(ctx context.Context, image string, configs map[string]registry.AuthConfig) (string, registry.AuthConfig, error) {
+	defaultRegistry := defaultRegistryFn(ctx)
+	reg := core.ExtractRegistry(image, defaultRegistry)
+
+	if cfg, ok := getRegistryAuth(reg, configs); ok {
 		return reg, cfg, nil
 	}
 
@@ -80,24 +91,93 @@ func defaultRegistry(ctx context.Context) string {
 	return info.IndexServerAddress
 }
 
-// authConfig represents the details of the auth config for a registry.
-type authConfig struct {
+// authConfigResult is a result looking up auth details for key.
+type authConfigResult struct {
 	key string
 	cfg registry.AuthConfig
+	err error
+}
+
+// credentialsCache is a cache for registry credentials.
+type credentialsCache struct {
+	entries map[string]credentials
+	mtx     sync.RWMutex
+}
+
+// credentials represents the username and password for a registry.
+type credentials struct {
+	username string
+	password string
+}
+
+var creds = &credentialsCache{entries: map[string]credentials{}}
+
+// Get returns the username and password for the given hostname
+// as determined by the details in configPath.
+func (c *credentialsCache) Get(hostname, configKey string) (string, string, error) {
+	key := configKey + ":" + hostname
+	c.mtx.RLock()
+	entry, ok := c.entries[key]
+	c.mtx.RUnlock()
+
+	if ok {
+		return entry.username, entry.password, nil
+	}
+
+	// No entry found, request and cache.
+	user, password, err := dockercfg.GetRegistryCredentials(hostname)
+	if err != nil {
+		return "", "", fmt.Errorf("getting credentials for %s: %w", hostname, err)
+	}
+
+	c.mtx.Lock()
+	c.entries[key] = credentials{username: user, password: password}
+	c.mtx.Unlock()
+
+	return user, password, nil
+}
+
+// configFileKey returns a key to use for caching credentials based on
+// the contents of the currently active config.
+func configFileKey() (string, error) {
+	configPath, err := dockercfg.ConfigPath()
+	if err != nil {
+		return "", err
+	}
+
+	f, err := os.Open(configPath)
+	if err != nil {
+		return "", fmt.Errorf("open config file: %w", err)
+	}
+
+	defer f.Close()
+
+	h := md5.New()
+	if _, err := io.Copy(h, f); err != nil {
+		return "", fmt.Errorf("copying config file: %w", err)
+	}
+
+	return hex.EncodeToString(h.Sum(nil)), nil
 }
 
 // getDockerAuthConfigs returns a map with the auth configs from the docker config file
 // using the registry as the key
-var getDockerAuthConfigs = sync.OnceValues(func() (map[string]registry.AuthConfig, error) {
+func getDockerAuthConfigs() (map[string]registry.AuthConfig, error) {
 	cfg, err := getDockerConfig()
 	if err != nil {
 		return nil, err
 	}
 
-	cfgs := map[string]registry.AuthConfig{}
-	results := make(chan authConfig, len(cfg.AuthConfigs)+len(cfg.CredentialHelpers))
+	configKey, err := configFileKey()
+	if err != nil {
+		return nil, err
+	}
+
+	size := len(cfg.AuthConfigs) + len(cfg.CredentialHelpers)
+	cfgs := make(map[string]registry.AuthConfig, size)
+	results := make(chan authConfigResult, size)
 	var wg sync.WaitGroup
-	wg.Add(len(cfg.AuthConfigs) + len(cfg.CredentialHelpers))
+	wg.Add(size)
 	for k, v := range cfg.AuthConfigs {
 		go func(k string, v dockercfg.AuthConfig) {
 			defer wg.Done()
@@ -112,16 +192,23 @@ var getDockerAuthConfigs = sync.OnceValues(func() (map[string]registry.AuthConfi
 				Username:      v.Username,
 			}
 
-			if v.Username == "" && v.Password == "" {
-				u, p, _ := dockercfg.GetRegistryCredentials(k)
+			switch {
+			case ac.Username == "" && ac.Password == "":
+				// Look up credentials from the credential store.
+				u, p, err := creds.Get(k, configKey)
+				if err != nil {
+					results <- authConfigResult{err: err}
+					return
+				}
+
 				ac.Username = u
 				ac.Password = p
-			}
-
-			if v.Auth == "" {
+			case ac.Auth == "":
+				// Create auth from the username and password encoding.
 				ac.Auth = base64.StdEncoding.EncodeToString([]byte(ac.Username + ":" + ac.Password))
 			}
-			results <- authConfig{key: k, cfg: ac}
+
+			results <- authConfigResult{key: k, cfg: ac}
 		}(k, v)
 	}
 
@@ -131,11 +218,19 @@ var getDockerAuthConfigs = sync.OnceValues(func() (map[string]registry.AuthConfi
 		go func(k string) {
 			defer wg.Done()
 
-			ac := registry.AuthConfig{}
-			u, p, _ := dockercfg.GetRegistryCredentials(k)
-			ac.Username = u
-			ac.Password = p
-			results <- authConfig{key: k, cfg: ac}
+			u, p, err := creds.Get(k, configKey)
+			if err != nil {
+				results <- authConfigResult{err: err}
+				return
+			}
+
+			results <- authConfigResult{
+				key: k,
+				cfg: registry.AuthConfig{
+					Username: u,
+					Password: p,
+				},
+			}
 		}(k)
 	}
 
@@ -144,12 +239,22 @@ var getDockerAuthConfigs = sync.OnceValues(func() (map[string]registry.AuthConfi
 		close(results)
 	}()
 
-	for ac := range results {
-		cfgs[ac.key] = ac.cfg
+	var errs []error
+	for result := range results {
+		if result.err != nil {
+			errs = append(errs, result.err)
+			continue
+		}
+
+		cfgs[result.key] = result.cfg
+	}
+
+	if len(errs) > 0 {
+		return nil, errors.Join(errs...)
 	}
 
 	return cfgs, nil
-})
+}
 
 // getDockerConfig returns the docker config file. It will internally check, in this particular order:
 // 1. the DOCKER_AUTH_CONFIG environment variable, unmarshalling it into a dockercfg.Config
diff --git a/docker_auth_test.go b/docker_auth_test.go
index 1f8df17c09..5b3e1e1067 100644
--- a/docker_auth_test.go
+++ b/docker_auth_test.go
@@ -2,6 +2,7 @@ package testcontainers
 
 import (
 	"context"
+	_ "embed"
 	"fmt"
 	"os"
 	"path/filepath"
@@ -9,6 +10,7 @@ import (
 
 	"github.com/cpuguy83/dockercfg"
 	"github.com/docker/docker/api/types/image"
+	"github.com/docker/docker/api/types/registry"
 	"github.com/docker/docker/client"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
@@ -198,7 +200,7 @@ func TestBuildContainerFromDockerfile(t *testing.T) {
 		WaitingFor:      wait.ForLog("Ready to accept connections"),
 	}
 
-	redisC, err := prepareRedisImage(ctx, req, t)
+	redisC, err := prepareRedisImage(ctx, req)
 	require.NoError(t, err)
 	terminateContainerOnEnd(t, ctx, redisC)
 }
@@ -249,7 +251,7 @@ func TestBuildContainerFromDockerfileWithDockerAuthConfig(t *testing.T) {
 		WaitingFor:      wait.ForLog("Ready to accept connections"),
 	}
 
-	redisC, err := prepareRedisImage(ctx, req, t)
+	redisC, err := prepareRedisImage(ctx, req)
 	require.NoError(t, err)
 	terminateContainerOnEnd(t, ctx, redisC)
 }
@@ -281,7 +283,7 @@ func TestBuildContainerFromDockerfileShouldFailWithWrongDockerAuthConfig(t *test
 		WaitingFor:      wait.ForLog("Ready to accept connections"),
 	}
 
-	redisC, err := prepareRedisImage(ctx, req, t)
+	redisC, err := prepareRedisImage(ctx, req)
 	require.Error(t, err)
 	terminateContainerOnEnd(t, ctx, redisC)
 }
@@ -369,7 +371,7 @@ func prepareLocalRegistryWithAuth(t *testing.T) string {
 	return mp
 }
 
-func prepareRedisImage(ctx context.Context, req ContainerRequest, t *testing.T) (Container, error) {
+func prepareRedisImage(ctx context.Context, req ContainerRequest) (Container, error) {
 	genContainerReq := GenericContainerRequest{
 		ProviderType:     providerType,
 		ContainerRequest: req,
@@ -380,3 +382,32 @@ func prepareRedisImage(ctx context.Context, req ContainerRequest, t *testing.T)
 
 	return redisC, err
 }
+
+//go:embed testdata/.docker/config.json
+var dockerConfig string
+
+func Test_getDockerAuthConfigs(t *testing.T) {
+	t.Run("file", func(t *testing.T) {
+		got, err := getDockerAuthConfigs()
+		require.NoError(t, err)
+		require.NotNil(t, got)
+	})
+
+	t.Run("env", func(t *testing.T) {
+		t.Setenv("DOCKER_AUTH_CONFIG", dockerConfig)
+
+		got, err := getDockerAuthConfigs()
+		require.NoError(t, err)
+
+		// We can only check the keys as the values are not deterministic.
+		expected := map[string]registry.AuthConfig{
+			"https://index.docker.io/v1/": {},
+			"https://example.com":         {},
+			"https://my.private.registry": {},
+		}
+		for k := range got {
+			got[k] = registry.AuthConfig{}
+		}
+		require.Equal(t, expected, got)
+	})
+}
diff --git a/internal/core/images.go b/internal/core/images.go
index 6c7213f12c..2892267e9c 100644
--- a/internal/core/images.go
+++ b/internal/core/images.go
@@ -2,6 +2,7 @@ package core
 
 import (
 	"bufio"
+	"io"
 	"net/url"
 	"os"
 	"regexp"
@@ -25,17 +26,22 @@ const (
 
 var rxURL = regexp.MustCompile(URL)
 
+// ExtractImagesFromDockerfile extracts images from the Dockerfile sourced from dockerfile.
 func ExtractImagesFromDockerfile(dockerfile string, buildArgs map[string]*string) ([]string, error) {
-	var images []string
-
 	file, err := os.Open(dockerfile)
 	if err != nil {
 		return nil, err
 	}
 	defer file.Close()
 
+	return ExtractImagesFromReader(file, buildArgs)
+}
+
+// ExtractImagesFromReader extracts images from the Dockerfile sourced from r.
+func ExtractImagesFromReader(r io.Reader, buildArgs map[string]*string) ([]string, error) {
+	var images []string
 	var lines []string
-	scanner := bufio.NewScanner(file)
+	scanner := bufio.NewScanner(r)
 	for scanner.Scan() {
 		lines = append(lines, scanner.Text())
 	}