From 13fbffd7101995622fb09a10728dcb6692214880 Mon Sep 17 00:00:00 2001 From: David Negstad Date: Wed, 11 Feb 2026 14:24:46 -0800 Subject: [PATCH 1/2] Add a new documentation page for WithContainerFiles and PublishContainerFiles APIs --- src/frontend/config/sidebar/docs.topics.ts | 4 + .../content/docs/app-host/container-files.mdx | 332 ++++++++++++++++++ 2 files changed, 336 insertions(+) create mode 100644 src/frontend/src/content/docs/app-host/container-files.mdx diff --git a/src/frontend/config/sidebar/docs.topics.ts b/src/frontend/config/sidebar/docs.topics.ts index f748183c..b6508bb5 100644 --- a/src/frontend/config/sidebar/docs.topics.ts +++ b/src/frontend/config/sidebar/docs.topics.ts @@ -780,6 +780,10 @@ export const docsTopics: StarlightSidebarTopicsUserConfig = { label: 'Dockerfiles', slug: 'app-host/withdockerfile', }, + { + label: 'Container files', + slug: 'app-host/container-files', + }, { label: 'Executable resources', slug: 'app-host/executable-resources', diff --git a/src/frontend/src/content/docs/app-host/container-files.mdx b/src/frontend/src/content/docs/app-host/container-files.mdx new file mode 100644 index 00000000..deef4a22 --- /dev/null +++ b/src/frontend/src/content/docs/app-host/container-files.mdx @@ -0,0 +1,332 @@ +--- +title: Container files +description: Learn how to inject files and directories into containers at development time and publish time using the container file APIs in Aspire. +--- + +import { Aside } from '@astrojs/starlight/components'; + +Aspire provides APIs to inject files and directories into containers, enabling you to configure containerized resources with custom configuration files, scripts, certificates, and other assets. There are two complementary APIs: + +- **`WithContainerFiles`**: Injects files into containers at development time when they start during `aspire run`. +- **`PublishWithContainerFiles`**: Copies files from one resource's container into another resource's container as build artifacts at publish time during `aspire publish`. + +## Inject files at development time + +The `WithContainerFiles` extension method creates or updates files and directories inside a container at a specified destination path. It supports three approaches depending on your needs: inline entries for declarative file definitions, a source path for copying from the host file system, and an async callback for dynamic file generation. + + + +### Inline entries + +Use the inline entries overload to declaratively define files and directories using `ContainerFileSystemItem` objects. This is useful when file contents are known at build time or can be expressed as string literals. + +```csharp title="AppHost.cs" +var builder = DistributedApplication.CreateBuilder(args); + +builder.AddContainer("myapp", "myapp:latest") + .WithContainerFiles("/app/config", [ + new ContainerFile + { + Name = "appsettings.json", + Contents = """ + { + "Logging": { + "LogLevel": { + "Default": "Information" + } + } + } + """ + }, + new ContainerDirectory + { + Name = "scripts", + Entries = [ + new ContainerFile + { + Name = "init.sh", + Contents = "#!/bin/bash\necho 'Initializing...'", + Mode = UnixFileMode.UserRead + | UnixFileMode.UserWrite + | UnixFileMode.UserExecute + } + ] + } + ]); + +builder.Build().Run(); +``` + +In the preceding example: + +- A JSON configuration file is created at `/app/config/appsettings.json` with the specified contents. +- A nested `scripts` directory is created at `/app/config/scripts/` containing an executable shell script. + +### Source path + +Use the source path overload to copy files from a directory on the host machine into the container. This is useful when you have existing configuration files or assets on disk. + +```csharp title="AppHost.cs" +var builder = DistributedApplication.CreateBuilder(args); + +builder.AddContainer("myapp", "myapp:latest") + .WithContainerFiles("/app/config", "./config-files"); + +builder.Build().Run(); +``` + +Unless the source path is a rooted (absolute) path, it's interpreted as relative to the AppHost project directory. All files in the source directory are copied to the destination path in the container at startup. + +### Async callback + +Use the callback overload to generate files dynamically when the container starts. The callback receives a `ContainerFileSystemCallbackContext` that provides access to the `IServiceProvider` and the resource's `IResource` model, enabling you to resolve services or inspect the app model. + +```csharp title="AppHost.cs" +var builder = DistributedApplication.CreateBuilder(args); + +builder.AddContainer("worker", "worker:latest") + .WithContainerFiles("/app/config", + async (context, cancellationToken) => + { + var config = new + { + MachineName = Environment.MachineName, + Timestamp = DateTime.UtcNow + }; + + return + [ + new ContainerFile + { + Name = "runtime-config.json", + Contents = JsonSerializer.Serialize(config) + } + ]; + }); + +builder.Build().Run(); +``` + +The callback is invoked each time the container starts, so the generated files always reflect the current state. + +## File types + +The `WithContainerFiles` API uses a type hierarchy rooted at the abstract `ContainerFileSystemItem` class. Each type represents a different kind of file system entry. + +### ContainerFile + +`ContainerFile` represents a standard file. Set either `Contents` (a string) or `SourcePath` (an absolute path on the host) to provide the file data — the two are mutually exclusive. + +```csharp title="AppHost.cs" +// File with inline contents +new ContainerFile +{ + Name = "config.yaml", + Contents = "key: value" +} + +// File sourced from the host file system +new ContainerFile +{ + Name = "data.csv", + SourcePath = "/path/to/data.csv" +} +``` + +Set `ContinueOnError` to `true` to allow the container to start even if creating this particular file fails: + +```csharp title="AppHost.cs" +new ContainerFile +{ + Name = "optional-config.json", + Contents = "{}", + ContinueOnError = true +} +``` + +### ContainerDirectory + +`ContainerDirectory` represents a directory that can contain nested `ContainerFileSystemItem` entries, allowing you to build arbitrary directory trees. + +```csharp title="AppHost.cs" +new ContainerDirectory +{ + Name = "certs", + Entries = [ + new ContainerFile + { + Name = "ca.pem", + SourcePath = "/path/to/ca.pem" + }, + new ContainerDirectory + { + Name = "private", + Entries = [ + new ContainerFile + { + Name = "server.key", + SourcePath = "/path/to/server.key", + Mode = UnixFileMode.UserRead + } + ] + } + ] +} +``` + +You can also populate a `ContainerDirectory` from files on disk using the static `GetFileSystemItemsFromPath` method: + +```csharp title="AppHost.cs" +new ContainerDirectory +{ + Name = "assets", + Entries = ContainerDirectory.GetFileSystemItemsFromPath( + "/path/to/assets", + searchOptions: SearchOption.AllDirectories) +} +``` + +### ContainerOpenSSLCertificateFile + +`ContainerOpenSSLCertificateFile` represents a PEM-encoded public certificate. In addition to placing the certificate file in the container, Aspire automatically creates an OpenSSL-compatible symlink (`[subject hash].[n]`) in the same directory — equivalent to running `openssl rehash`. This enables containers that use OpenSSL for certificate validation to discover the certificate automatically. + +```csharp title="AppHost.cs" +new ContainerOpenSSLCertificateFile +{ + Name = "ca-cert.pem", + Contents = pemCertificateString +} +``` + +## File permissions and ownership + +All `WithContainerFiles` overloads accept optional parameters to control file ownership and permissions. + +### Owner and group + +The `defaultOwner` and `defaultGroup` parameters set the default UID and GID applied to all created files and directories. Both default to `0` (root) when not specified. You can override ownership on individual items using the `Owner` and `Group` properties on any `ContainerFileSystemItem`. + +```csharp title="AppHost.cs" +builder.AddContainer("myapp", "myapp:latest") + .WithContainerFiles("/app/data", + [ + new ContainerFile + { + Name = "shared.txt", + Contents = "shared data" + }, + new ContainerFile + { + Name = "user-only.txt", + Contents = "private data", + Owner = 1000, + Group = 1000 + } + ], + defaultOwner: 33, // www-data + defaultGroup: 33); +``` + +In this example, `shared.txt` inherits the default owner/group of `33`, while `user-only.txt` overrides with UID/GID `1000`. + +### Umask + +The `umask` parameter controls default permissions by subtracting (masking) permission bits from the base defaults. Without an explicit `Mode` set on an item: + +- **Directories** start with `0777` (read/write/execute for all) and have the umask subtracted +- **Files** start with `0666` (read/write for all) and have the umask subtracted + +The default umask is `0022`, which results in: + +- Directories: `0755` (owner: rwx, group: r-x, others: r-x) +- Files: `0644` (owner: rw-, group: r--, others: r--) + +You can set `Mode` directly on individual items to override the umask-based default: + +```csharp title="AppHost.cs" +builder.AddContainer("myapp", "myapp:latest") + .WithContainerFiles("/app/scripts", + [ + new ContainerFile + { + Name = "run.sh", + Contents = "#!/bin/bash\necho 'Running'", + Mode = UnixFileMode.UserRead + | UnixFileMode.UserWrite + | UnixFileMode.UserExecute + } + ], + umask: UnixFileMode.OtherRead + | UnixFileMode.OtherWrite + | UnixFileMode.OtherExecute); +``` + +### Persistent containers + +For containers with `ContainerLifetime.Persistent`, changing the contents of container file entries causes the container to be recreated. Ensure any data written through `WithContainerFiles` is idempotent for a given app model configuration to avoid unintended container restarts. + +## Inject files at publish time + +The `PublishWithContainerFiles` method copies files from one resource's container into another resource's container during `aspire publish`. This is the preferred approach for injecting files into containers at publish time. + +A key use case is embedding single-page application (SPA) or static JavaScript frontends into a reverse proxy or web server container for production deployment. During development, frontend apps like Vite or React typically run as standalone dev servers. In production, however, the compiled static assets are often served by a backend API or a dedicated web server like Nginx. `PublishWithContainerFiles` bridges this gap by copying the built frontend output into the serving container as part of the publish process — no manual file copying or multi-stage Dockerfile required. + +### Embed a frontend in a backend + +```csharp title="AppHost.cs" +var builder = DistributedApplication.CreateBuilder(args); + +var frontend = builder.AddViteApp("frontend", "../frontend"); + +var api = builder.AddProject("api") + .PublishWithContainerFiles(frontend, "./wwwroot"); + +builder.Build().Run(); +``` + +In this example: + +1. The `frontend` resource builds inside its container, producing compiled JavaScript, CSS, and HTML. +2. During publish, Aspire copies those files from the frontend container into the `api` container at `./wwwroot`. +3. The resulting `api` container includes both the API code and the frontend static assets, ready to serve the full application. + +### Serve a frontend from Nginx + +You can also embed frontend assets into a dedicated web server container: + +```csharp title="AppHost.cs" +var builder = DistributedApplication.CreateBuilder(args); + +var frontend = builder.AddViteApp("frontend", "../frontend"); + +var nginx = builder.AddContainer("webserver", "nginx:alpine") + .PublishWithContainerFiles(frontend, "/usr/share/nginx/html"); + +builder.Build().Run(); +``` + +This produces a self-contained Nginx container that serves the frontend application, with no external volume mounts or runtime file copying needed. + +`PublishWithContainerFiles` only applies in publish mode — it has no effect during `aspire run`. The destination resource must implement `IContainerFilesDestinationResource` (such as `ProjectResource`), and the source resource must implement `IResourceWithContainerFiles`. + +### Customize the source path + +By default, the source resource exports files from its container based on its configured output paths. Use `WithContainerFilesSource` to specify which path inside the source container to copy from: + +```csharp title="AppHost.cs" +var frontend = builder.AddViteApp("frontend", "../frontend") + .WithContainerFilesSource("/app/dist"); + +var api = builder.AddProject("api") + .PublishWithContainerFiles(frontend, "./wwwroot"); +``` + +Use `ClearContainerFilesSources` to remove any previously configured source paths before adding new ones. + +## See also + +- [Certificate configuration](/app-host/certificate-configuration/) +- [Add Dockerfiles to your app model](/app-host/withdockerfile/) +- [Persistent container lifetimes](/app-host/persistent-containers/) From 80d334d6d1045537fdf3681138c0a2dc6718a6c8 Mon Sep 17 00:00:00 2001 From: David Negstad Date: Wed, 11 Feb 2026 14:57:05 -0800 Subject: [PATCH 2/2] Update based on PR feedback --- .../content/docs/app-host/container-files.mdx | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/frontend/src/content/docs/app-host/container-files.mdx b/src/frontend/src/content/docs/app-host/container-files.mdx index deef4a22..eebf9707 100644 --- a/src/frontend/src/content/docs/app-host/container-files.mdx +++ b/src/frontend/src/content/docs/app-host/container-files.mdx @@ -15,7 +15,7 @@ Aspire provides APIs to inject files and directories into containers, enabling y The `WithContainerFiles` extension method creates or updates files and directories inside a container at a specified destination path. It supports three approaches depending on your needs: inline entries for declarative file definitions, a source path for copying from the host file system, and an async callback for dynamic file generation. ### Inline entries @@ -193,11 +193,14 @@ new ContainerDirectory `ContainerOpenSSLCertificateFile` represents a PEM-encoded public certificate. In addition to placing the certificate file in the container, Aspire automatically creates an OpenSSL-compatible symlink (`[subject hash].[n]`) in the same directory — equivalent to running `openssl rehash`. This enables containers that use OpenSSL for certificate validation to discover the certificate automatically. ```csharp title="AppHost.cs" -new ContainerOpenSSLCertificateFile -{ - Name = "ca-cert.pem", - Contents = pemCertificateString -} +builder.AddContainer("myapp", "myapp:latest") + .WithContainerFiles("/certs", [ + new ContainerOpenSSLCertificateFile + { + Name = "ca-cert.pem", + Contents = pemCertificateString + } + ]); ``` ## File permissions and ownership @@ -292,17 +295,17 @@ In this example: 2. During publish, Aspire copies those files from the frontend container into the `api` container at `./wwwroot`. 3. The resulting `api` container includes both the API code and the frontend static assets, ready to serve the full application. -### Serve a frontend from Nginx +### Serve a frontend from YARP -You can also embed frontend assets into a dedicated web server container: +You can also embed frontend assets into a dedicated reverse proxy container: ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); var frontend = builder.AddViteApp("frontend", "../frontend"); -var nginx = builder.AddContainer("webserver", "nginx:alpine") - .PublishWithContainerFiles(frontend, "/usr/share/nginx/html"); +var nginx = builder.AddYarp("gateway") + .PublishWithStaticFiles(frontend); builder.Build().Run(); ```