Add AddBunApp for Bun-native apps#17416
Conversation
- Add AddBunApp(name, appDirectory, scriptPath) and BunAppResource to
Aspire.Hosting.JavaScript, parallel to AddNodeApp.
- Default Dockerfile uses oven/bun:1 for build and runtime stages with
multi-stage build, bun install cache mount, USER bun, and ENTRYPOINT
["bun", scriptPath].
- WithBunDefaults wires OtlpExporter, RequiredCommand check, and
NODE_ENV. Intentionally omits the Node cert-trust env hooks because
Bun's CA story differs.
- AddBunApp auto-applies WithBun() when package.json is present.
- 11 unit tests in AddBunAppTests.cs and 2 real-bun functional tests
in BunFunctionalTests.cs (gated by [RequiresTools(["bun"])]).
- TypeScript AppHost playground at playground/AspireWithBun exercises
the API end-to-end with both direct file and WithRunScript('start')
resources and a Docker Compose publish target.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 17416Or
iex "& { $(irm https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 17416" |
There was a problem hiding this comment.
Pull request overview
Adds first-class Bun support to Aspire.Hosting.JavaScript by introducing a new AddBunApp builder extension (parallel to AddNodeApp) and a corresponding BunAppResource, including Dockerfile generation defaults and coverage via unit + functional tests plus a TypeScript playground.
Changes:
- Add
AddBunAppandWithBunDefaultsto model Bun-native “direct entry script” apps and generate publish-time Dockerfiles targetingoven/bun:1. - Introduce
BunAppResourceand validate behavior via new unit tests and Bun-gated functional tests. - Add a new TypeScript AppHost playground (
playground/AspireWithBun) demonstrating run + publish flows.
Show a summary per file
| File | Description |
|---|---|
| tests/Aspire.Hosting.JavaScript.Tests/BunFunctionalTests.cs | Adds Bun-gated functional tests validating direct execution vs package-script execution. |
| tests/Aspire.Hosting.JavaScript.Tests/BunAppFixture.cs | Test fixture that boots two Bun resources and waits for readiness via HTTP probes. |
| tests/Aspire.Hosting.JavaScript.Tests/AddBunAppTests.cs | Adds unit tests covering manifest shape, Dockerfile generation, package manager auto-detection, and argument validation. |
| src/Aspire.Hosting.JavaScript/JavaScriptHostingExtensions.cs | Implements AddBunApp, Bun defaults, run-script behavior, and Bun Dockerfile generation. |
| src/Aspire.Hosting.JavaScript/BunAppResource.cs | Introduces the new Bun resource type for the application model. |
| playground/AspireWithBun/tsconfig.json | TypeScript config for the Bun playground AppHost. |
| playground/AspireWithBun/README.md | Documents how to run the Bun playground and what to expect. |
| playground/AspireWithBun/package.json | Declares deps/scripts for the TypeScript AppHost playground. |
| playground/AspireWithBun/package-lock.json | Locks the playground npm dependencies. |
| playground/AspireWithBun/BunFrontend/server.ts | Minimal Bun HTTP server used by the playground scenario. |
| playground/AspireWithBun/BunFrontend/package.json | Defines the start script used by the package-script path. |
| playground/AspireWithBun/aspire.config.json | Declares the TypeScript AppHost entrypoint and required packages. |
| playground/AspireWithBun/apphost.mts | TypeScript AppHost exercising addBunApp for both direct and script-based execution. |
Copilot's findings
Files not reviewed (1)
- playground/AspireWithBun/package-lock.json: Language not supported
- Files reviewed: 12/13 changed files
- Comments generated: 1
|
Re-running the failed jobs in the CI workflow for this pull request because 1 job was identified as retry-safe transient failures in the CI run attempt.
Matched test failure patterns (1 test)
|
Switch the AddBunApp runtime stage to Bun's recommended Dockerfile pattern so that devDependencies are excluded from the final image and add NODE_EXTRA_CA_CERTS / NODE_OPTIONS=--use-openssl-ca wiring so Bun apps honor Aspire certificate trust configuration. Dockerfile changes: - Runtime stage now copies node_modules from the prod-deps stage and the application source from the build context (`COPY . .`), rather than copying /app wholesale from the build stage. Docker COPY --from has merge semantics that left dev dependencies in place after overlay. - Emit a default .dockerignore next to the resource working directory (skipped when the user already has one) so `COPY . .` does not pull in node_modules, .aspire, aspire-output, dotenv files, etc. Certificate trust: - WithBunDefaults now calls WithCertificateTrustConfiguration. Append scope maps to NODE_EXTRA_CA_CERTS (supported in Bun 1.3+). Override and System scopes set NODE_OPTIONS=--use-openssl-ca so Bun uses the bundled / OS trust store. Playground: - BunFrontend now declares a prod dependency (`ms`) and a dev dependency (`chalk`) so the published image can be inspected to confirm dev deps are pruned. - Commit the generated .dockerignore and bun.lock so `aspire publish` produces a build-context-safe image out of the box. Tests: - Update VerifyDockerfile snapshot to the new runtime layout. - Add unit tests for the Append and Override cert-trust scopes. - All 13 AddBunAppTests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Re-running the failed jobs in the CI workflow for this pull request because 1 job was identified as retry-safe transient failures in the CI run attempt.
|
…r-Dockerfile convention
Generated .dockerignore now lives next to the published Dockerfile in aspire-output/
as <resource>.Dockerfile.dockerignore (BuildKit per-Dockerfile ignore convention),
instead of being written into the user's source tree. This keeps generated artifacts
contained to aspire-output/ and leaves the user's package.json directory clean.
When the build context root already contains a user-authored .dockerignore, Aspire
skips emitting the per-Dockerfile sibling so the user's file is honored — BuildKit
gives per-Dockerfile ignore files precedence, so emitting both would silently
shadow a user override.
Changes:
- Add public BuildContextIgnoreContent property and EmitBuildContextIgnoreAsync
method on DockerfileBuildAnnotation. The annotation already gates this API
under [Experimental("ASPIREDOCKERFILEBUILDER001")].
- Call EmitBuildContextIgnoreAsync from ManifestPublishingContext and
DockerComposePublishingContext after copying the Dockerfile to the output path.
- AddBunApp sets the default content on the annotation instead of writing into
the source tree.
- Remove the previously committed playground/AspireWithBun/BunFrontend/.dockerignore
so the playground exercises the default (no user file) path.
- Add unit tests covering default emission and user-override skip behavior.
Verified end-to-end with the playground:
- aspire publish emits bunapp.Dockerfile.dockerignore next to bunapp.Dockerfile.
- docker build honors the per-Dockerfile ignore (devDependencies excluded;
user-polluted node_modules/ in BunFrontend not copied into the image).
- Re-publishing with a user-authored BunFrontend/.dockerignore does NOT write
the per-Dockerfile sibling.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Ensure Bun's generated Docker ignore content is honored anywhere Aspire builds or publishes generated Dockerfiles: - Emit the per-Dockerfile ignore next to materialized factory Dockerfiles for direct image builds and DCP build paths. - Emit the copied-output sibling from Azure and Kubernetes publishers as well as manifest and Docker Compose. - Remove stale copied-output siblings when a user-authored context-root .dockerignore is added later, so BuildKit does not keep shadowing the user's file. - Avoid accumulating duplicate Docker Compose deployment target annotations when pipeline steps are resolved repeatedly. - Use separate startup and readiness timeout budgets in the Bun functional test fixture. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add DockerfileBuildAnnotation.EmitDockerfileArtifactsAsync so callers provide a DockerfileFactoryContext and an optional destination Dockerfile path, and the annotation owns materializing the generated Dockerfile plus companion files such as the BuildKit per-Dockerfile .dockerignore. Publishers now use the same method when copying generated Dockerfiles to output paths, while direct build paths use it without a destination path. This keeps the artifact emission flow future-proof as more generated Dockerfile companion files are added. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
mitchdenny
left a comment
There was a problem hiding this comment.
Reviewed the PR. The Bun integration works end-to-end (aspire run + aspire publish + docker build all succeed, see the testing report comment below). A few concrete issues to consider:
- Flaky test pattern in
BunAppFixture.WaitReadyStateAsync(single-shot HTTP probe with no retry). EmitDockerfileArtifactsAsyncusesStringComparison.Ordinalto compare filesystem paths — case-insensitive FSes (Windows / default macOS) can triggerFile.Copyfrom a file to itself.EmitBuildContextIgnoreAsyncsilently deletes any pre-existing<dockerfile>.dockerignoresibling when a context-root.dockerignoreappears — including one the user authored themselves.- In run-mode container image builds (
ResourceContainerImageManager), the generated sibling.dockerignoreis written next to the materialized Dockerfile in the user's source tree.
Inline comments below.
🧪 PR Testing ReportSmoke-tested the PR build end-to-end on macOS arm64 (Bun 1.3.14 + Docker Desktop). CLI version verified: Scenarios
Notable observations
Suggested follow-up: ACA deployment via live e2eLocal Before merging, it would be worth piggy-backing on the existing live ACA e2e tests (the same suite that deploys other
Adding an ACA scenario for Test artifactsAll evidence captured in transcript. Temp workspace cleaned up; built image and container removed. |
- Use HTTP health checks and resource health waits in Bun functional tests instead of single-shot HTTP probes. - Compare Dockerfile artifact paths with a platform-aware filesystem comparison. - Preserve user-authored per-Dockerfile .dockerignore siblings when a context-root .dockerignore exists, only deleting siblings that match Aspire-generated content. - Ignore generated *.Dockerfile.dockerignore siblings in Bun build contexts. - Add focused Dockerfile artifact tests for these behaviors. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Replying to #17416 (review): Thanks, this is the generated PR overview and does not require a code change. I left it as-is. |
|
Replying to #17416 (review): Accepted items 1-3 and partially accepted item 4. I added health-based readiness, platform-aware path comparison, preservation of user-authored per-Dockerfile ignore siblings, and focused coverage; for item 4, the AddBunApp generated Dockerfile is materialized outside the source tree, and I also added |
Remove the Docker Compose-only deployment target annotation replacement helper. Other deployment target preparers append annotations directly today, so this avoids making Docker Compose special in this PR. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Re-reviewed the two follow-up commits (
The Compose One small extra: adding Finding #4 (run-mode source-tree pollution) — note, not blockingMy fourth finding was about CIThe failing LGTM to merge after CI is green. The ACA live-deployment suggestion from the previous testing report still stands as good follow-up validation but isn't a blocker. |
Replace inline string and contains assertions for generated manifest, Dockerfile, and dockerignore content with Verify snapshots so changes are reviewed as full artifacts. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Record that Bun 1.3.10 and 1.3.14 currently reject Aspire's injected self-signed localhost certificate for outgoing HTTPS requests even though curl and Node accept the same certificate. Link the tracking issue from the certificate trust comments. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
❓ CLI E2E Tests unknown — 96 passed, 0 failed, 5 unknown (commit View all recordings
📹 Recordings uploaded automatically from CI run #26377256994 |
|
✅ No documentation update needed. Documentation changes are needed ( Prepared changes (commit
A manual docs PR targeting |
Description
Adds an
AddBunAppextension onIDistributedApplicationBuildertoAspire.Hosting.JavaScript, parallel to the existingAddNodeApp. Bun-native apps typically run a TypeScript or JavaScript file directly (bun server.ts) rather than going through apackage.jsonscript, so this API takes a resource name, an app directory, and the entry script path.This complements #17382, which renamed the package-script publish API to reflect that package-script publishing now covers npm, pnpm, Yarn, and Bun.
AddBunAppis the matching direct-entrypoint shape for Bun, the same wayAddNodeAppis for Node.User-facing usage
C# AppHost:
TypeScript AppHost:
Behavior
oven/bun:1(configurable viaWithDockerfileBaseImage). Multi-stage build,bun installwith a build-cache mount,USER bun(uid 1000 on the official image),NODE_ENV=production, andENTRYPOINT ["bun", scriptPath].prod-depsstage runsbun install --frozen-lockfile --production, the runtime stage copiesnode_modulesfromprod-depsand the application source from the build context (COPY . .). This mirrors the pattern in Bun's official Docker guide (https://bun.com/guides/ecosystem/docker) and ensuresdevDependenciesdo not leak into the final image..dockerignoreis emitted alongside generated Dockerfiles using BuildKit's per-Dockerfile convention (<dockerfile-name>.dockerignore), keeping generated artifacts out of the user's source tree. To override, drop a.dockerignoreat the build context root (next topackage.json); Aspire skips/removes the generated sibling so BuildKit honors the user's file. See https://docs.docker.com/build/concepts/context/#filename-and-location.WithBunDefaultswires upWithOtlpExporter,WithRequiredCommand("bun", "https://bun.sh/docs/installation"),NODE_ENV(Bun honorsNODE_ENV— see https://bun.com/docs/runtime/env), and certificate trust. Certificate trust uses Bun's Node compatibility surface:Appendscope maps toNODE_EXTRA_CA_CERTSandOverride/Systemscopes setNODE_OPTIONS=--use-openssl-ca. Known limitation: Bun 1.3.10 and 1.3.14 still reject Aspire's injected self-signed localhost certificate for outgoing HTTPS requests withUNABLE_TO_VERIFY_LEAF_SIGNATURE, whilecurl --cacertand Node.js withNODE_EXTRA_CA_CERTSaccept the same certificate. This dependency is tracked in Track Bun custom CA support for HTTPS OTLP export to Aspire Dashboard #17455.WithBun()when apackage.jsonexists in the app directory (parallel toAddNodeAppauto-applyingWithNpm()).OnBeforeStartswaps the run-mode command to the configured package manager whenWithRunScriptis set, so a user-selected manager (WithYarn,WithPnpm, …) is respected in run mode.Supporting changes in core publishing
To keep generated artifacts contained to generated Dockerfile locations, this PR introduces a small additive API on
DockerfileBuildAnnotation:string? BuildContextIgnoreContent { get; set; }property — when set, Aspire emits a per-Dockerfile<dockerfile-name>.dockerignoresibling next to generated Dockerfiles.EmitDockerfileArtifactsAsync(DockerfileFactoryContext context, string? dockerfilePath = null)method — materializes the generated Dockerfile, optionally copies it to the caller-provided output path, and writes/removes generated sibling files as needed. If the build context root already contains a user-authored.dockerignore, Aspire removes stale siblings only when their content matches the configured generated content, preserving user-authored per-Dockerfile overrides.The emission is wired into direct image builds/DCP builds via
DockerfileHelper, and copied publish outputs for manifest, Docker Compose, Kubernetes, and Azure publishers all call the same artifact-emission method with their destination Dockerfile path.Validation
Unit + functional tests in
tests/Aspire.Hosting.JavaScript.Tests:AddBunAppTests(15 tests): manifest shape, Dockerfile generation with and withoutpackage.json, custom base image override, per-Dockerfile.dockerignoreemission, stale generated sibling cleanup when a user-authored.dockerignoreappears, package-manager auto-config, run script, command, argument validation, and certificate-trust callback wiring forAppendandOverridescopes.BunFunctionalTests(2 tests, gated by[RequiresTools(["bun"])]): in-process Aspire app spins up twoBunAppResourceinstances - direct file execution andWithRunScript("start")- against the realbunruntime, uses HTTP health checks for readiness, and verifies the served payloads.Aspire.Hosting.JavaScript.Tests(157 tests),Aspire.Hosting.Docker.Tests(96 passed / 1 Windows-only skipped), DockerfileBuildAnnotation-focused tests (11 tests), and affected publisher builds pass.Live end-to-end with the locally-built CLI against
playground/AspireWithBun(TypeScript AppHost):aspire run: bothbunappandbunscriptresources reportRunning+Healthy;curlreturns the expected payloads (Hello from bun!/Hello from bun script!).aspire publishwithaddDockerComposeEnvironment("compose"): emitsdocker-compose.yaml, per-resource Dockerfiles usingoven/bun:1, andaspire-output/bunapp.Dockerfile.dockerignore/aspire-output/bunscript.Dockerfile.dockerignorenext to them. The user'sBunFrontend/directory is left clean — no.dockerignoreis created in source.aspire do --list-steps: pipeline step discovery succeeds for the playground.aspire do build: builds both Bun images successfully. With a deliberately polluted localBunFrontend/node_modules/ms@99.0.0, the resulting image still containsms@2.1.3fromprod-depsand nochalkdevDependency, confirming the generated ignore is honored by the direct build path.BunFrontend/.dockerignore, re-running publish removes/skips Aspire-generated siblings so the user's file wins. User-authored per-Dockerfile siblings are preserved.NODE_EXTRA_CA_CERTS/NODE_OPTIONS, and Dashboard OTLP over HTTPS accepts the injected certificate withcurl --cacertand Node.js. Bun 1.3.10 and 1.3.14 still fail the same request withUNABLE_TO_VERIFY_LEAF_SIGNATURE; follow-up issue Track Bun custom CA support for HTTPS OTLP export to Aspire Dashboard #17455 tracks the Bun dependency before adding a Bun-to-Dashboard HTTPS OTLP E2E test.Checklist
<remarks />and<code />elements on your triple slash comments?