Skip to content

feat(node): add build output annotation#13999

Open
Waleed-KH wants to merge 2 commits intodotnet:mainfrom
Waleed-KH:node-build-output
Open

feat(node): add build output annotation#13999
Waleed-KH wants to merge 2 commits intodotnet:mainfrom
Waleed-KH:node-build-output

Conversation

@Waleed-KH
Copy link

Description

Add build output annotation that can be used with Node apps to inform the default docker publisher which files need to be copied to the final container image.

Fixes #13907

Checklist

  • Is this feature complete?
    • Yes. Ready to ship.
    • No. Follow-up changes expected.
  • Are you including unit tests for the changes and scenario tests if relevant?
    • Yes
    • No
  • Did you add public API?
    • Yes
      • If yes, did you have an API Review for it?
        • Yes
        • No
      • Did you add <remarks /> and <code /> elements on your triple slash comments?
        • Yes
        • No
    • No
  • Does the change make any security assumptions or guarantees?
    • Yes
      • If yes, have you done a threat model and had a security review?
        • Yes
        • No
    • No
  • Does the change require an update in our Aspire docs?

Copilot AI review requested due to automatic review settings January 19, 2026 09:57
@github-actions
Copy link
Contributor

github-actions bot commented Jan 19, 2026

🚀 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 -- 13999

Or

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

@dotnet-policy-service dotnet-policy-service bot added the community-contribution Indicates that the PR has been added by a community member label Jan 19, 2026
@Waleed-KH
Copy link
Author

@dotnet-policy-service agree

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 PR adds a build output annotation for Node.js apps to inform the default Docker publisher which files need to be copied to the final container image. The feature allows developers to specify which paths from the build process should be included when containerizing Node.js applications.

Changes:

  • Added JavaScriptBuildOutputAnnotation class to specify build output paths
  • Added WithBuildOutput extension method for configuring build output paths
  • Modified Docker generation logic to use annotation-specified paths instead of copying all /app content
  • Updated playground example to demonstrate the new feature with dependency updates

Reviewed changes

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

Show a summary per file
File Description
JavaScriptBuildOutputAnnotation.cs New annotation class to specify build output paths with validation
JavaScriptHostingExtensions.cs Added WithBuildOutput method and updated Docker generation to use annotation paths
AppHost.cs Updated to use the new WithBuildOutput API
package.json Updated OpenTelemetry dependencies and added tsdown build tool
pnpm-lock.yaml Dependency lockfile updates
tsdown.config.ts New build configuration file
run.mjs New entry point file
Dockerfile Removed (now using auto-generated Docker support)
.dockerignore Added to exclude dist and node_modules

@radical
Copy link
Member

radical commented Jan 23, 2026

This needs tests too.

/// <summary>
/// Represents an annotation that specifies the output file/directory paths to be included in the final image.
/// </summary>
public sealed class JavaScriptBuildOutputAnnotation : IResourceAnnotation
Copy link
Member

Choose a reason for hiding this comment

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

One thing that jumped out at me is that this annotation isn't respected for "non-Node" javascript apps (i.e. client apps). I wonder if we should give this annotation a different name to differentiate it. But I'm not sure what that name should be.

The client apps already have an annotation for this - ContainerFilesSourceAnnotation - which is a general annotation that indicates which files from one container should be copied to another.

Maybe that fits here too? Would it make sense to reuse ContainerFilesSourceAnnotation?

Copy link
Author

@Waleed-KH Waleed-KH Jan 23, 2026

Choose a reason for hiding this comment

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

I already thought of making it also work with "non-Node" javascript apps, but the default Dockerfile builder for client apps has no build stage. Should we add build stage in case JavaScriptBuildScriptAnnotation/JavaScriptBuildOutputAnnotation exist in the resource? I think it would be better to have a common Dockerfile builder for both Node & JavaScript as they already very similar.

As of ContainerFilesSourceAnnotation in the client apps:

.WithAnnotation(new ContainerFilesSourceAnnotation() { SourcePath = "/app/dist" })

I found that the above has no effect on the output Dockerfile. maybe I'm missing something

Copy link
Member

Choose a reason for hiding this comment

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

Node and JavaScript dockerfiles are similar, but have different purposes.

The Node app is "runnable" - it has a web server in it and can host the application.

The client-side JavaScript apps aren't runnable by themselves, they need a host (asp.net, yarp, nginx, node, etc.). That's why they only have 1 stage: a build stage. Someone else comes along and picks up the files that were built and puts them in the host.

Copy link
Member

Choose a reason for hiding this comment

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

I think a new annotation here is fine. If you have thoughts on a more specific name, we should come up with the best name. But this one will work if there isn't anything better.

Copy link
Author

Choose a reason for hiding this comment

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

It would be better to keep the name as is, I'm sure client apps can benefit from it, just need to figure out how.

Copy link
Author

Choose a reason for hiding this comment

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

I got an idea in how can client apps benefit from it, should I implement it here or in another pr ?

Copy link
Member

Choose a reason for hiding this comment

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

Let's keep this PR focused. It still needs tests before this can be merged.

What is the idea?

Copy link
Author

Choose a reason for hiding this comment

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

Right now the default JavaScript builder have one stage that contains all the app files (source, dependencies, build output ...). When used with a host (by PublishWithContainerFiles) it would copy /app/dist (defined by ContainerFilesSourceAnnotation) to the host destination.

The problem is it always assumes that all JavaScript apps output to /app/dist even if it has no build script.

To resolve this issue, we could have a build stage for client apps, and final stage that would take the needed files (configurable with WithBuildOutput) from the build stage, e.g.:

FROM node:22-slim AS build
WORKDIR /app
COPY package*.json ./
RUN --mount=type=cache,target=/root/.npm npm ci
COPY . .
RUN npm run build

FROM node:22-slim
WORKDIR /app
COPY --from=build /app/dist /app       # Defined by JavaScriptBuildOutputAnnotation

Now we could update the ContainerFilesSourceAnnotation to point to /app and when it used with a host it would only contains the build output.

Note that currently the JavaScriptBuildOutputAnnotation generate COPY command that match source with destination, e.g.:

COPY --from=build /app/dist /app/dist

There is a few reasons I did it this way:

If we would use JavaScriptBuildOutputAnnotation with client apps (as mentioned above) while avoiding breaking behavior, when should make it only accepts one output directory path, and document that when WithBuildOutput is used with Node app scriptPath should be relative to the output directory not the app directory.

Copy link
Author

Choose a reason for hiding this comment

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

I've implemented the client apps use of JavaScriptBuildOutputAnnotation on a new branch 937f144 .
Instead of adding extra build stage I was thinking of mapping JavaScriptBuildOutputAnnotation to ContainerFilesSourceAnnotation , but I'm not sure what is the right place to add this code:

var buildOutput = "/app";
if (resource.TryGetLastAnnotation<JavaScriptBuildOutputAnnotation>(out var buildOutputAnnotation))
{
var p = buildOutputAnnotation.Path;
buildOutput = string.Join('/', "/app", p.StartsWith("./") ? p[2..] : p);
}
c.WithAnnotation(new ContainerFilesSourceAnnotation() { SourcePath = buildOutput });

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

community-contribution Indicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Node app build output files

4 participants