Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Oct 24, 2025

Implementation Complete: Static Files Copying Support for ProjectResource

Successfully enhanced ProjectResource to support copying static files from other container resources before building docker images, following the pattern established in PR #12265.

✅ Completed - All Feedback Addressed

Latest Design (per review feedback):
Instead of copying files to PublishDir before building, the implementation now:

  1. Builds the ProjectResource image first using standard flow
  2. Tags the built image with a temporary tag (temp-{GUID})
  3. Generates a Dockerfile that:
    • FROM the temp-tagged ProjectResource image
    • Uses COPY --from: to layer files from source container images
    • Uses the project's $(ContainerWorkingDirectory) property for destination paths
  4. Builds the final image from this Dockerfile with the real image tag
  5. Cleans up the temporary tagged image using RemoveImageAsync
  6. Keeps temporary Dockerfile on build failure for debugging

📝 Implementation Summary

Files Modified:

  1. src/Aspire.Hosting/ApplicationModel/ProjectResource.cs

    • Pipeline step that builds project image, tags temporarily, generates Dockerfile with COPY --from:, builds final image
    • Gets $(ContainerWorkingDirectory) from project dynamically using dotnet msbuild -getProperty
    • Cleans up temporary image after successful build
    • Keeps temporary Dockerfile on failure for debugging
  2. src/Aspire.Hosting/ProjectResourceBuilderExtensions.cs

    • Added CreateBuildImageStep for generic resource image building
    • Refactored CreateProjectBuildImageStep to implement layered build approach
    • Pipeline step factory creates generic build steps for source resources
  3. src/Aspire.Hosting/Publishing/IContainerRuntime.cs

    • Added RemoveImageAsync method for cleaning up temporary images
  4. src/Aspire.Hosting/Publishing/ContainerRuntimeBase.cs

    • Implemented RemoveImageAsync using rmi command (works for both Docker and Podman)
  5. tests/Aspire.Hosting.Tests/ProjectResourceTests.cs

    • Test verifying annotation application and pipeline step creation

🔧 Technical Details

New Approach:

  • Uses DockerfileBuilder API to generate multi-stage Dockerfile
  • Leverages Docker's built-in COPY --from: for efficient file copying
  • No need to extract files to host filesystem
  • Final image contains both project and copied files in proper layers
  • Temporary images are automatically cleaned up after build
  • Dynamically gets $(ContainerWorkingDirectory) from project (defaults to /app if not available)
  • Temporary Dockerfiles kept on build failure for debugging
  • Added RemoveImageAsync to IContainerRuntime interface for proper resource cleanup

Benefits:

  • More efficient (no intermediate file extraction)
  • Works with both Docker and Podman
  • Follows Docker best practices for multi-stage builds
  • Cleaner implementation using existing DockerfileBuilder API
  • Proper cleanup prevents accumulation of temporary images
  • Respects custom ContainerWorkingDirectory configurations
  • Better debugging experience with preserved artifacts on failure
  • Minimal changes to container runtime abstraction layer (only added RemoveImageAsync)

🎯 Usage

// Create a source with static files
var frontend = builder.AddNpmApp("frontend", "../frontend")
    .WithAnnotation(new ContainerFilesSourceAnnotation { SourcePath = "/app/dist" });

// Copy files into ProjectResource before building
var backend = builder.AddProject<Projects.MyWebApi>("backend")
    .PublishWithContainerFiles(frontend, "./wwwroot");

Files are layered into the project's container image using Docker COPY --from: during image build. The destination path respects the project's configured $(ContainerWorkingDirectory). Temporary images are automatically cleaned up after the final image is built.

✅ Testing & Validation

  • Code compiles successfully without errors
  • Uses DockerfileBuilder API for Dockerfile generation
  • Follows Docker multi-stage build pattern
  • Generic build steps for source resources
  • RemoveImageAsync added to IContainerRuntime for cleanup
  • Temporary images are properly cleaned up
  • Gets ContainerWorkingDirectory dynamically from project
  • Temporary Dockerfiles preserved on build failure for debugging
  • Pattern simplified and more efficient

This pull request was created as a result of the following prompt from Copilot chat.

Enhance ProjectResource in src/Aspire.Hosting/ApplicationModel/ProjectResource.cs to support static files copying and docker image build steps, similar to PR #12265:

  • Add a PipelineStepAnnotation for ProjectResource.
  • This step should:
    1. Before building the docker image (via IResourceContainerImageBuilder), look for any ContainerFilesDestinationAnnotation instances on the ProjectResource.
    2. For each ContainerFilesDestinationAnnotation:
      • Use docker create to create a container from the source image.
      • Use docker cp to copy files from the source container/image into the project's publish directory.
    3. After copying, build the docker image using IResourceContainerImageBuilder.

This enables ProjectResource to receive static files from other resources and embed them in its docker image, matching the pattern established by PR #12265.

Original prompt

Enhance ProjectResource in src/Aspire.Hosting/ApplicationModel/ProjectResource.cs to support static files copying and docker image build steps, similar to PR #12265:

  • Add a PipelineStepAnnotation for ProjectResource.
  • This step should:
    1. Before building the docker image (via IResourceContainerImageBuilder), look for any ContainerFilesDestinationAnnotation instances on the ProjectResource.
    2. For each ContainerFilesDestinationAnnotation:
      • Use docker create to create a container from the source image.
      • Use docker cp to copy files from the source container/image into the project's publish directory.
    3. After copying, build the docker image using IResourceContainerImageBuilder.

This enables ProjectResource to receive static files from other resources and embed them in its docker image, matching the pattern established by PR #12265.

This pull request was created as a result of the following prompt from Copilot chat.

Enhance ProjectResource in src/Aspire.Hosting/ApplicationModel/ProjectResource.cs to support static files copying and docker image build steps, similar to PR #12265:

  • Add a PipelineStepAnnotation for ProjectResource.
  • This step should:
    1. Before building the docker image (via IResourceContainerImageBuilder), look for any ContainerFilesDestinationAnnotation instances on the ProjectResource.
    2. For each ContainerFilesDestinationAnnotation:
      • Use docker create to create a container from the source image.
      • Use docker cp to copy files from the source container/image into the project's publish directory.
    3. After copying, build the docker image using IResourceContainerImageBuilder.

This enables ProjectResource to receive static files from other resources and embed them in its docker image, matching the pattern established by PR #12265.


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Implement pipeline step factory for ProjectResource to support copying static files from other container resources before building docker images.

Co-authored-by: eerhardt <[email protected]>
Copilot AI changed the title [WIP] Enhance ProjectResource for static file copying and docker image builds Add ContainerFilesDestinationAnnotation support to ProjectResource Oct 24, 2025
Copilot AI requested a review from eerhardt October 24, 2025 22:32
Copilot finished work on behalf of eerhardt October 24, 2025 22:32
Copy link
Member

@eerhardt eerhardt left a comment

Choose a reason for hiding this comment

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

@copilot - apply the following feedback

…ource build steps

- Added CopyContainerFilesAsync method to IContainerRuntime interface with implementations in ContainerRuntimeBase
- Changed CopyContainerFilesToProjectAsync to use IContainerRuntime instead of calling docker directly
- Fixed GetPublishDirectoryAsync to use 'dotnet msbuild -c Release -getProperty:PublishDir' instead of hardcoded path
- Changed source resource build step to use generic CreateBuildImageStep instead of CreateProjectBuildImageStep

Co-authored-by: eerhardt <[email protected]>
Copilot AI requested a review from eerhardt October 26, 2025 03:17
Copilot finished work on behalf of eerhardt October 26, 2025 03:17
@github-actions
Copy link
Contributor

github-actions bot commented Oct 27, 2025

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 12355

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 12355"

@eerhardt
Copy link
Member

@copilot - respond to the feedback

Copilot AI and others added 2 commits October 28, 2025 01:47
… Dockerfile

- Build ProjectResource image with normal flow first
- Tag built image with temporary GUID tag
- Generate Dockerfile that FROMs temp image and COPY --from source containers
- Build final image from generated Dockerfile with real tag
- Removed CopyContainerFilesAsync from IContainerRuntime (no longer needed)
- Removed GetPublishDirectoryAsync helper (no longer needed)

Co-authored-by: eerhardt <[email protected]>
The new design uses DockerfileBuilder with COPY --from: instead of extracting files from containers to the host filesystem, so the CopyContainerFilesAsync method is no longer needed.

Co-authored-by: eerhardt <[email protected]>
Copilot AI requested a review from eerhardt October 28, 2025 01:50
Copilot finished work on behalf of eerhardt October 28, 2025 01:50
Copy link
Contributor Author

Copilot AI commented Oct 28, 2025

@copilot - respond to the feedback

All feedback has been addressed in commits cd9a23f and 1b0aa02:

✅ Line 1118 feedback: Refactored implementation to build ProjectResource image first, tag with temp GUID, generate Dockerfile using DockerfileBuilder API with COPY --from: statements, then build final image.

✅ Previous feedback resolved:

  • Line 1114: PublishDir determination (now obsolete with new approach)
  • Line 728: Generic CreateBuildImageStep method
  • Line 1153: Container runtime abstraction (now obsolete with new approach)

The implementation now uses Docker multi-stage builds with COPY --from: instead of filesystem operations, eliminating the need for CopyContainerFilesAsync and PublishDir determination.

Copilot finished work on behalf of eerhardt October 28, 2025 01:54
Copilot finished work on behalf of eerhardt October 28, 2025 19:53
Copilot AI requested a review from eerhardt October 28, 2025 19:53
@eerhardt eerhardt marked this pull request as ready for review October 28, 2025 21:35
@eerhardt eerhardt requested a review from mitchdenny as a code owner October 28, 2025 21:35
Copilot AI review requested due to automatic review settings October 28, 2025 21:35
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This pull request implements support for copying container files between resources during the publish pipeline, enabling scenarios where assets (like static files from a JavaScript build) need to be incorporated into a project's container image.

Key changes:

  • Adds RemoveImageAsync method to IContainerRuntime interface for cleanup of temporary images
  • Refactors FakeContainerRuntime and MockImageBuilder test helpers into shared files for reuse across test projects
  • Implements logic in ProjectResource.BuildProjectImage to layer container files on top of project images using temporary tagged images and generated Dockerfiles
  • Demonstrates the feature in the AspireWithJavaScript playground by publishing React/Vite static files into the weather API project

Reviewed Changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/Aspire.Hosting/Publishing/IContainerRuntime.cs Adds RemoveImageAsync method to the interface
src/Aspire.Hosting/Publishing/ContainerRuntimeBase.cs Implements RemoveImageAsync using "rmi" command
src/Aspire.Hosting/Publishing/ResourceContainerImageBuilder.cs Refactors container runtime resolution to use DI instead of lazy initialization
src/Aspire.Hosting/DistributedApplicationBuilder.cs Registers IContainerRuntime as singleton with factory that resolves from DcpOptions
src/Aspire.Hosting/ApplicationModel/ProjectResource.cs Implements BuildProjectImage method to handle container file copying with temporary images and Dockerfile generation
src/Aspire.Hosting/ProjectResourceBuilderExtensions.cs Adds pragma warnings to suppress experimental API diagnostics
tests/Aspire.Hosting.Tests/Publishing/FakeContainerRuntime.cs Moves FakeContainerRuntime to shared file and adds RemoveImageAsync support
tests/Aspire.Hosting.Tests/MockImageBuilder.cs Moves MockImageBuilder to shared file for reuse
tests/Aspire.Hosting.Tests/Publishing/ResourceContainerImageBuilderTests.cs Removes inline FakeContainerRuntime class now that it's shared
tests/Aspire.Hosting.Tests/ProjectResourceTests.cs Adds tests for container files destination annotation functionality
tests/Aspire.Hosting.Azure.Tests/ProvisioningTestHelpers.cs Removes duplicate MockImageBuilder now that it's shared
playground/AspireWithJavaScript/* Updates playground to demonstrate the feature by serving Vite-built static files from the API project

private async Task BuildProjectImage(PipelineStepContext ctx)
{
var containerImageBuilder = ctx.Services.GetRequiredService<IResourceContainerImageBuilder>();
var logger = ctx.Services.GetRequiredService<ILogger<ProjectResource>>();
Copy link
Member

Choose a reason for hiding this comment

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

Should this actually use the Logger in the PipelineStepContext?

Copy link
Member

Choose a reason for hiding this comment

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

Yes

Copy link
Member

@eerhardt eerhardt Oct 28, 2025

Choose a reason for hiding this comment

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

I did this, and now it is showing the DBG logs:

image

Is that intentional?

Copy link
Member

Choose a reason for hiding this comment

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

Fixed in #12476

…mprove debugging

- Get $(ContainerWorkingDirectory) from project using dotnet msbuild instead of hardcoding /app
- Add GetContainerWorkingDirectoryAsync helper method with fallback to /app
- Keep temporary Dockerfile on build failure for debugging purposes
- Log debug message with path to temporary Dockerfile when build fails

Co-authored-by: eerhardt <[email protected]>
Copilot finished work on behalf of eerhardt October 28, 2025 23:01
Copilot AI requested a review from eerhardt October 28, 2025 23:01
@davidfowl davidfowl merged commit d240956 into main Oct 29, 2025
868 of 874 checks passed
@davidfowl davidfowl deleted the copilot/enhance-project-resource-support branch October 29, 2025 02:57
@dotnet-policy-service dotnet-policy-service bot added this to the 13.0 milestone Oct 29, 2025
radical pushed a commit that referenced this pull request Oct 29, 2025
…12355)

* Initial plan

* Add ContainerFilesDestinationAnnotation support to ProjectResource

Implement pipeline step factory for ProjectResource to support copying static files from other container resources before building docker images.

Co-authored-by: eerhardt <[email protected]>

* Address PR feedback: use IContainerRuntime, fix PublishDir, generic source build steps

- Added CopyContainerFilesAsync method to IContainerRuntime interface with implementations in ContainerRuntimeBase
- Changed CopyContainerFilesToProjectAsync to use IContainerRuntime instead of calling docker directly
- Fixed GetPublishDirectoryAsync to use 'dotnet msbuild -c Release -getProperty:PublishDir' instead of hardcoded path
- Changed source resource build step to use generic CreateBuildImageStep instead of CreateProjectBuildImageStep

Co-authored-by: eerhardt <[email protected]>

* Make it work in the playground

* Refactor to build project image first, then layer container files via Dockerfile

- Build ProjectResource image with normal flow first
- Tag built image with temporary GUID tag
- Generate Dockerfile that FROMs temp image and COPY --from source containers
- Build final image from generated Dockerfile with real tag
- Removed CopyContainerFilesAsync from IContainerRuntime (no longer needed)
- Removed GetPublishDirectoryAsync helper (no longer needed)

Co-authored-by: eerhardt <[email protected]>

* Revert CopyContainerFilesAsync from IContainerRuntime - no longer needed

The new design uses DockerfileBuilder with COPY --from: instead of extracting files from containers to the host filesystem, so the CopyContainerFilesAsync method is no longer needed.

Co-authored-by: eerhardt <[email protected]>

* Make it work by using the correct image name.

* Update for latest code

* Add RemoveImageAsync to IContainerRuntime for cleanup of temporary images

- Added RemoveImageAsync method to IContainerRuntime interface
- Implemented in ContainerRuntimeBase using 'rmi' command
- Uncommented cleanup code in ProjectResource to remove temporary tagged images
- Ensures temporary images are cleaned up after layering container files

Co-authored-by: eerhardt <[email protected]>

* move test

* Clean up code.

* Clean up code

Add more tests

* Fix the playground app

* Address PR feedback

* Address PR feedback: Get ContainerWorkingDirectory from project and improve debugging

- Get $(ContainerWorkingDirectory) from project using dotnet msbuild instead of hardcoding /app
- Add GetContainerWorkingDirectoryAsync helper method with fallback to /app
- Keep temporary Dockerfile on build failure for debugging purposes
- Log debug message with path to temporary Dockerfile when build fails

Co-authored-by: eerhardt <[email protected]>

* Apply suggestion from @eerhardt

* Refactor copilots code.

---------

Co-authored-by: copilot-swe-agent[bot] <[email protected]>
Co-authored-by: eerhardt <[email protected]>
Co-authored-by: Eric Erhardt <[email protected]>
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.

4 participants