-
Notifications
You must be signed in to change notification settings - Fork 46
Add a new documentation page for WithContainerFiles and PublishWithContainerFiles #400
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,335 @@ | ||
| --- | ||
| 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. | ||
|
|
||
| <Aside type="caution"> | ||
| `WithContainerFiles` is primarily intended for development-time configuration and is not supported at publish time. To inject files into containers during publish, use [`PublishWithContainerFiles`](#inject-files-at-publish-time) instead. | ||
| </Aside> | ||
|
|
||
| ### 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 | ||
| } | ||
| ``` | ||
|
Comment on lines
+140
to
+147
|
||
|
|
||
| ### 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 | ||
| } | ||
| ] | ||
| } | ||
| ] | ||
| } | ||
| ``` | ||
|
Comment on lines
+153
to
+177
|
||
|
|
||
| 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) | ||
| } | ||
| ``` | ||
|
Comment on lines
+181
to
+189
|
||
|
|
||
| ### 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" | ||
| builder.AddContainer("myapp", "myapp:latest") | ||
| .WithContainerFiles("/certs", [ | ||
| 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<Projects.Api>("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 YARP | ||
|
|
||
| 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.AddYarp("gateway") | ||
| .PublishWithStaticFiles(frontend); | ||
|
|
||
| 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`. | ||
danegsta marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| ### 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<Projects.Api>("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/) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This C# snippet shows standalone
new ContainerFile { ... }expressions without terminating semicolons or assigning them to variables/collections, so it won’t compile as written. Consider adding semicolons or wrapping the examples in a containing statement (e.g., an array/collection initializer) to make the snippet copy/paste runnable.