public async Task InitializeAsync()
        {
            ImageName = Environment.GetEnvironmentVariable("QBT_IMAGE") ?? DefaultImageName;
            var os = Environment.GetEnvironmentVariable("QBT_OS") ?? DefaulOS;

            var sourceDir = Path.Combine(Utils.StartupFolder, "docker", ImageName.Replace(':', '-'), os);
            var env       = File.ReadAllText(Path.Combine(sourceDir, "env.json"));

            Console.WriteLine("Test Environment:");
            Console.WriteLine(env);
            Env = JsonConvert.DeserializeObject <Env>(env);
            await DownloadBinaries();

            var config = new DockerClientConfiguration(new Uri("http://localhost:2375"));

            Client = config.CreateClient();

            Console.WriteLine($"\tSearching docker image {ImageName}...");
            var images = await Client.Images.ListImagesAsync(
                new ImagesListParameters { MatchName = ImageName });

            if (!images.Any())
            {
                Console.WriteLine("\tImage not found.");
                Console.WriteLine($"\tCreating image {ImageName}");

                var fileName = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid():N}.tgz");

                Utils.CreateTarGz(fileName, sourceDir);

                Stream inputStream = null;
                try
                {
                    inputStream = File.OpenRead(fileName);
                    var progressStream = await Client.Images.BuildImageFromDockerfileAsync(
                        inputStream,
                        new ImageBuildParameters
                    {
                        Tags = new List <string> {
                            ImageName
                        },
                    });

                    using (var reader = new StreamReader(progressStream))
                    {
                        while (true)
                        {
                            var text = await reader.ReadLineAsync();

                            if (text == null)
                            {
                                break;
                            }

                            Console.WriteLine($"\t\t{text}");
                        }
                    }
                }
                finally
                {
                    Console.WriteLine($"\tFinished creating image {ImageName}.");
                    inputStream?.Dispose();
                    File.Delete(fileName);
                }
            }

            async Task DownloadBinaries()
            {
                if (Env.Binaries?.Any() != true)
                {
                    return;
                }

                Console.WriteLine("Downloading binaries...");
                using (var httpClient = new HttpClient())
                {
                    foreach (var pair in Env.Binaries)
                    {
                        var filename = Path.Combine(sourceDir, pair.Key);
                        if (File.Exists(filename))
                        {
                            Console.WriteLine($"\t\tFile {pair.Key} already exists. Skipped downloading.");
                            continue;
                        }

                        var uri = string.IsNullOrEmpty(pair.Value)
                            ? new Uri($"https://fedarovich.blob.core.windows.net/qbittorrent-test/{ImageName.Replace(':', '-')}/{os}/{pair.Key}")
                            : new Uri(pair.Value);

                        Console.WriteLine($"\t\tDownloading {pair.Key} from {uri}...");
                        using (var inStream = await httpClient.GetStreamAsync(uri))
                            using (var outStream = File.OpenWrite(filename))
                            {
                                await inStream.CopyToAsync(outStream);
                            }
                        Console.WriteLine($"\t\tDownloaded {pair.Key} to {filename}.");
                    }
                }
            }
        }
        /// <summary>
        /// Runs the docker image build command to build this image
        /// </summary>
        /// <inheritdoc />
        public override async Task <string> Resolve(CancellationToken ct = default)
        {
            if (ct.IsCancellationRequested)
            {
                return(null);
            }

            if (DeleteOnExit)
            {
                ResourceReaper.RegisterImageForCleanup(ImageName, DockerClient);
            }

            _logger.LogDebug("Begin building image: {}", ImageName);

            var tempTarPath = Path.Combine(Path.GetTempPath(), ImageName.Replace('/', '_') + ".tar");

            try
            {
                using (var tempFile = new FileStream(tempTarPath, FileMode.Create))
                    using (var tarArchive = TarArchive.CreateOutputTarArchive(tempFile))
                    {
                        if (!string.IsNullOrWhiteSpace(BasePath))
                        {
                            // the algorithm here is carefully crafted to minimise the use of
                            // Path.GetFullPath. Path.GetFullPath is used very sparingly and
                            // completely avoided in loops. The reason is because Path.GetFullPath
                            // is a very expensive call and can reduce CPU time by at least 1 order
                            // of magnitude if avoided
                            var fullBasePath = Path.GetFullPath(OS.NormalizePath(BasePath));

                            var ignoreFullPaths = GetIgnores(fullBasePath);

                            // sending a full path will result in entries with full path
                            var allFullPaths = GetAllFilesInDirectory(fullBasePath);

                            // a thread pool that is starved can decrease the performance of
                            // this method dramatically. Using `AsParallel()` will circumvent such issues.
                            // as a result, methods and classes used by this needs to be thread safe.
                            var validFullPaths = allFullPaths
                                                 .AsParallel()
                                                 .Where(f => !IsFileIgnored(ignoreFullPaths, f));

                            foreach (var fullPath in validFullPaths)
                            {
                                // we can safely perform a substring without expanding the paths
                                // using Path.GetFullPath because we know fullBasePath has already been
                                // expanded and the paths in validFullPaths are derived from fullBasePath
                                var relativePath = fullPath.Substring(fullBasePath.Length);

                                // if fullBasePath does not end with directory separator,
                                // relativePath will start with directory separator and that should not be the case
                                if (relativePath.StartsWith(Path.DirectorySeparatorChar.ToString()))
                                {
                                    relativePath = relativePath.Substring(1);
                                }

                                await new MountableFile(fullPath)
                                .TransferTo(tarArchive, relativePath, ct)
                                .ConfigureAwait(false);
                            }

                            _logger.LogDebug("Transferred base path [{}] into tar archive", BasePath);
                        }

                        foreach (var entry in Transferables)
                        {
                            var destinationPath = entry.Key;
                            var transferable    = entry.Value;
                            await transferable
                            .TransferTo(tarArchive, destinationPath, ct)
                            .ConfigureAwait(false);

                            _logger.LogDebug("Transferred [{}] into tar archive", destinationPath);
                        }

                        tarArchive.Close();
                    }

                if (ct.IsCancellationRequested)
                {
                    return(null);
                }

                var buildImageParameters = new ImageBuildParameters
                {
                    Dockerfile = DockerfilePath,
                    Labels     = DeleteOnExit ? ResourceReaper.Labels : null,
                    Tags       = new List <string> {
                        ImageName
                    }
                };

                using (var tempFile = new FileStream(tempTarPath, FileMode.Open))
                {
                    var output =
                        await DockerClient.Images.BuildImageFromDockerfileAsync(tempFile, buildImageParameters, ct);

                    using (var reader = new StreamReader(output))
                    {
                        while (!reader.EndOfStream)
                        {
                            _logger.LogTrace(reader.ReadLine());
                        }
                    }
                }
            }
            finally
            {
                File.Delete(tempTarPath);
            }

            _logger.LogInformation("Dockerfile image built: {}", ImageName);

            // we should not catch exceptions thrown by inspect because the image is
            // expected to be available since we've just built it
            var image = await DockerClient.Images.InspectImageAsync(ImageName, ct);

            ImageId = image.ID;

            return(ImageId);
        }