Skip to content

Add image labels for base image, base image digest, and source#2163

Draft
lbussell wants to merge 5 commits into
mainfrom
lbussell/use-labels-2
Draft

Add image labels for base image, base image digest, and source#2163
lbussell wants to merge 5 commits into
mainfrom
lbussell/use-labels-2

Conversation

@lbussell

@lbussell lbussell commented Jul 1, 2026

Copy link
Copy Markdown
Member

Part of #1602.

This PR adds the following labels to images automatically at build time.

Label Example value
org.opencontainers.image.source https://github.com/dotnet/docker-tools
org.opencontainers.image.revision c0ffeec0ffeec0ffee...
org.opencontainers.image.base.name mcr.microsoft.com/dotnet/runtime:9.0-azurelinux3.0
org.opencontainers.image.base.digest sha256:abcdef123456...
com.microsoft.imagebuilder.dockerfile src/Dockerfile.linux

I needed to add one custom label, com.microsoft.imagebuilder.dockerfile, in order to maintain parity with what's tracked in ImageArtifactDetails/image-info.json. It contains the path to the Dockerfile that the image was built from relative to the repo root.

Change to the build command's execution flow

While the git diff doesn't show it nicely, this PR rearranges the steps in the build command. Previously, there were multiple loops over all images. Now, everything is computed in one loop. This is necessary in order for downstream/intermediate images to have accurate base image digest labels set at build time. Here is a high-level before/after comparison:

Before:

  • Build:
    • Pull base images
    • For each image:
      • Build image
    • For each image:
      • Push image
    • For each image:
      • Compute image metadata (base image, layers, URL, etc.)
      • Populate image info
    • Write image-info.json

After:

  • Build
    • Pull base images
    • For each image:
      • Compute labels (base image, commit, etc.)
      • Build image
      • Populate image info
      • Push image
    • Write image-info.json

Note: labels are inherited by downstream images by default.

@lbussell lbussell requested review from baronfel and mthalman July 1, 2026 01:04
manifestServiceFactory: CreateManifestServiceFactoryMock(
localImageDigestResults: [new(baseImageTag, baseImageDigest)]).Object,
imageCacheService: new ImageCacheService(Mock.Of<ILogger<ImageCacheService>>(), Mock.Of<IGitService>()));
command.Options.Manifest = Path.Combine(tempFolderContext.Path, "manifest.json");
command.Options.SourceRepoUrl = sourceRepoUrl;

const string runtimeRelativeDir = "1.0/runtime/os";
Directory.CreateDirectory(Path.Combine(tempFolderContext.Path, runtimeRelativeDir));

const string runtimeRelativeDir = "1.0/runtime/os";
Directory.CreateDirectory(Path.Combine(tempFolderContext.Path, runtimeRelativeDir));
string dockerfileRelativePath = Path.Combine(runtimeRelativeDir, "Dockerfile");
const string runtimeRelativeDir = "1.0/runtime/os";
Directory.CreateDirectory(Path.Combine(tempFolderContext.Path, runtimeRelativeDir));
string dockerfileRelativePath = Path.Combine(runtimeRelativeDir, "Dockerfile");
string dockerfileAbsolutePath = PathHelper.NormalizePath(Path.Combine(tempFolderContext.Path, dockerfileRelativePath));
CreateImage(
new Platform[] { platform })));

File.WriteAllText(Path.Combine(tempFolderContext.Path, command.Options.Manifest), JsonConvert.SerializeObject(manifest));
copyImageService: Mock.Of<ICopyImageService>(),
manifestServiceFactory: CreateManifestServiceFactoryMock().Object,
imageCacheService: new ImageCacheService(Mock.Of<ILogger<ImageCacheService>>(), Mock.Of<IGitService>()));
command.Options.Manifest = Path.Combine(tempFolderContext.Path, "manifest.json");
command.Options.Manifest = Path.Combine(tempFolderContext.Path, "manifest.json");

const string runtimeRelativeDir = "1.0/runtime/os";
Directory.CreateDirectory(Path.Combine(tempFolderContext.Path, runtimeRelativeDir));

const string runtimeRelativeDir = "1.0/runtime/os";
Directory.CreateDirectory(Path.Combine(tempFolderContext.Path, runtimeRelativeDir));
string dockerfileRelativePath = Path.Combine(runtimeRelativeDir, "Dockerfile");
const string runtimeRelativeDir = "1.0/runtime/os";
Directory.CreateDirectory(Path.Combine(tempFolderContext.Path, runtimeRelativeDir));
string dockerfileRelativePath = Path.Combine(runtimeRelativeDir, "Dockerfile");
string dockerfileAbsolutePath = PathHelper.NormalizePath(Path.Combine(tempFolderContext.Path, dockerfileRelativePath));
CreateImage(
new Platform[] { platform })));

File.WriteAllText(Path.Combine(tempFolderContext.Path, command.Options.Manifest), JsonConvert.SerializeObject(manifest));
IEnumerable<TagInfo> tagsForDigest = imageArtifactDetails is null ? [] : concreteTags;

// Log in again to refresh token as it may have expired from a long build
await ExecuteWithDockerCredentialsAsync(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are the perf consequences of running this for every single image that gets built?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm testing it internally and I'll get back to you.

string? digest = null;
for (int attempt = 0; attempt <= RetryHelper.MaxRetries && digest is null; attempt++)
{
digest = await imageDigestCache.GetLocalImageDigestAsync(tag.FullyQualifiedName, Options.IsDryRun);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Include some sort of timing backoff for each attempt.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm going to take a second pass at this method. This is not how we should do retries. Thanks for calling it out.

Comment on lines +221 to +223
string repoRoot = _gitService.GetRepoRoot(platform.DockerfilePath);
labels[ImageBuilderLabels.Dockerfile] =
PathHelper.NormalizePath(Path.GetRelativePath(repoRoot, platform.DockerfilePath));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use GitServiceExtensions.GetDockerfileCommitUrl instead?

@lbussell lbussell Jul 1, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a workaround for #2165. Additionally, since the source URL is already present in the org.opencontainers.image.source label, including the repo base URL a second time in another label is not necessary. That's why I chose only the Dockerfile path here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we already have org.opencontainers.image.source then why do we need this label?

@lbussell lbussell mentioned this pull request Jul 1, 2026
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants