From 76833c19c8d3d3a4464ca047d1ce2c1e5289dc0f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 15 Jan 2026 01:56:14 +0000 Subject: [PATCH 1/3] Initial plan From 70a95623128754dfeca9b88c11a2f9b5c7488b7b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 15 Jan 2026 01:59:09 +0000 Subject: [PATCH 2/3] Update README with comprehensive documentation Co-authored-by: BethMassi <5115571+BethMassi@users.noreply.github.com> --- README.md | 258 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 256 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a7686fd..90b4074 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,259 @@ # Persistent Volume & GHCR Publish Sample -This sample is based on the aspire-samples/volumemount sample. It demonstrates how to configure a SQL Server container to use a persistent volume in Aspire, so that the data is persisted across app launches. It shows how to write files (image uploads) to a persistent volume from a Blazor Web app. +This sample demonstrates how to use .NET Aspire to orchestrate a multi-container application with persistent data storage and automated deployment to production. It showcases: -In addition to this, it also shows how to publish the docker-compose artifacts and push the BlazorWeb image to GitHub Container Registry from a GitHub Actions workflow. +- **Persistent SQL Server volumes** - Database data persists across container restarts +- **Persistent file storage volumes** - User-uploaded images persist across container restarts +- **Aspire Docker Integration** - Seamless configuration and orchestration of containerized services +- **GitHub Actions CI/CD** - Automated building, publishing, and deploying using the Aspire CLI +- **Identity management** - ASP.NET Core Identity with SQL Server backend +- **Blazor Server UI** - Interactive web application for file uploads + +## What This Sample Demonstrates + +### Aspire AppHost Configuration + +The `AppHost.cs` file in the `VolumeMount.AppHost` project is the heart of the Aspire orchestration. It demonstrates: + +#### 1. Docker Compose Environment Setup +```csharp +var compose = builder.AddDockerComposeEnvironment("volumemount-env") + .WithProperties(env => { env.DashboardEnabled = true; }) + .ConfigureComposeFile(composeFile => + { + // Define top-level volumes for sharing across services + composeFile.AddVolume(new Volume + { + Name = "volumemount-blazor-uploads", + Driver = "local" + }); + }); +``` + +#### 2. Container Registry Configuration +```csharp +var endpoint = builder.AddParameter("registry-endpoint"); +var repository = builder.AddParameter("registry-repository"); +builder.AddContainerRegistry("container-registry", endpoint, repository); +``` + +This configures the container registry (GitHub Container Registry in this case) where the BlazorWeb container image will be pushed during deployment. + +#### 3. SQL Server with Persistent Volume +```csharp +var sqlPassword = builder.AddParameter("sqlserver-password", secret: true); +var sqlserver = builder.AddSqlServer("sqlserver", password: sqlPassword) + .WithDataVolume("volumemount-sqlserver-data") + .WithLifetime(ContainerLifetime.Persistent) + .WithHostPort(1433) + .WithEndpoint("tcp", e => + { + e.Port = 1433; + e.TargetPort = 1433; + e.Protocol = ProtocolType.Tcp; + e.IsProxied = false; + e.IsExternal = true; + }); +``` + +**Key Features:** +- `WithDataVolume()` - Mounts a named volume to `/var/opt/mssql` to persist database files +- `WithLifetime(ContainerLifetime.Persistent)` - Container stays running across app restarts +- `WithEndpoint()` with `IsProxied = false` and `IsExternal = true` - Allows direct connection from SQL Server Management Studio (SSMS) even when Aspire isn't running + +#### 4. Blazor Web Application with File Upload Volume +```csharp +var blazorweb = builder.AddProject("blazorweb") + .WithExternalHttpEndpoints() + .WithReference(sqlDatabase) + .WaitFor(sqlDatabase) + .WithRemoteImageTag("latest") + .PublishAsDockerComposeService((resource, service) => + { + // Mount volume for persistent file uploads + service.AddVolume(new Volume + { + Name = "volumemount-blazor-uploads", + Source = "volumemount-blazor-uploads", + Target = "/app/wwwroot/uploads", + Type = "volume", + ReadOnly = false + }); + + // Configure permissions for file uploads + service.User = "root"; + service.Command = new List + { + "/bin/sh", "-c", + "chown -R app:app /app/wwwroot/uploads && chmod -R 755 /app/wwwroot/uploads && exec su app -c 'dotnet /app/VolumeMount.BlazorWeb.dll'" + }; + }); +``` + +**Key Features:** +- `WithRemoteImageTag("latest")` - Tags the container image for pushing to registry +- `PublishAsDockerComposeService()` - Customizes the Docker Compose service definition +- Volume mounting to `/app/wwwroot/uploads` - Persists user-uploaded images +- Permission management - Ensures the app user can write to the uploads directory + +### Persistent Volumes Explained + +#### SQL Server Volume (`volumemount-sqlserver-data`) +- **Purpose:** Stores SQL Server database files (`.mdf`, `.ldf`) +- **Mount Point:** `/var/opt/mssql` inside the container +- **Benefits:** + - Database persists across container restarts + - No data loss when redeploying applications + - Allows database upgrades without data migration + +#### Blazor Uploads Volume (`volumemount-blazor-uploads`) +- **Purpose:** Stores user-uploaded image files +- **Mount Point:** `/app/wwwroot/uploads` inside the container +- **Benefits:** + - Uploaded images persist across container restarts + - Multiple container instances can share the same upload storage + - Files remain available after application updates + +### File Upload Implementation + +The Blazor application uses a `PhotoUploadService` that writes files directly to the persistent volume: + +```csharp +public async Task UploadPhotoAsync(IBrowserFile photo) +{ + // Upload path is configured to use the mounted volume + var fileName = $"{Guid.NewGuid()}{Path.GetExtension(photo.Name)}"; + var filePath = Path.Combine(_uploadPath, fileName); + + await using var fileStream = new FileStream(filePath, FileMode.Create); + await using var uploadStream = photo.OpenReadStream(maxAllowedSize: 10485760); + await uploadStream.CopyToAsync(fileStream); +} +``` + +## GitHub Actions Workflow - Aspire CLI Integration + +The `.github/workflows/aspire-build-push.yml` workflow demonstrates automated deployment using the Aspire CLI: + +### Workflow Steps + +#### 1. Build and Restore +```yaml +- name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '10.0.x' + +- name: Restore dependencies + run: dotnet restore + +- name: Build solution + run: dotnet build --configuration Release --no-restore +``` + +#### 2. Install Aspire CLI +```yaml +- name: Install Aspire CLI + run: dotnet tool install --global aspire.cli --prerelease +``` + +The Aspire CLI is a global .NET tool that provides commands for publishing and deploying Aspire applications. + +#### 3. Publish Docker Compose Artifacts +```yaml +- name: Prepare Docker Compose with Aspire + run: | + aspire publish \ + --project VolumeMount.AppHost/VolumeMount.AppHost.csproj \ + --output-path ./aspire-output +``` + +**What `aspire publish` does:** +- Generates a `docker-compose.yaml` file based on the AppHost configuration +- Creates an `.env` template file for environment variables +- Packages all configuration needed to deploy the application +- Outputs artifacts to `./aspire-output/` directory + +**Generated Files:** +- `docker-compose.yaml` - Complete service definitions for all containers +- `.env` - Template for required environment variables (passwords, ports, etc.) + +#### 4. Push Container Image to GitHub Container Registry +```yaml +- name: Login to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + +- name: Push images with Aspire + env: + Parameters__registry_endpoint: ghcr.io + Parameters__registry_repository: bethmassi/volumemount + run: aspire do push +``` + +**What `aspire do push` does:** +- Builds the Docker container image for the BlazorWeb project +- Tags the image with the configured registry endpoint and repository +- Pushes the image to GitHub Container Registry (ghcr.io) +- Uses parameters defined in the AppHost configuration + +**Environment Variables:** +- `Parameters__registry_endpoint` - Maps to the `registry-endpoint` parameter in AppHost +- `Parameters__registry_repository` - Maps to the `registry-repository` parameter in AppHost + +### Deployment Artifacts + +After the workflow runs, you get: +1. **Docker Compose files** uploaded as GitHub Actions artifacts (retained for 30 days) +2. **Container image** published to GitHub Container Registry at `ghcr.io/bethmassi/volumemount/blazorweb:latest` + +These can be used to deploy the application to any Docker-compatible environment: +```bash +# Download the docker-compose.yaml and .env files +# Set required environment variables in .env file +docker-compose up -d +``` + +## Local Development + +To run the sample locally: + +1. Install [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0) +2. Install [Docker Desktop](https://www.docker.com/products/docker-desktop) +3. Set the SQL Server password as a user secret: + ```bash + dotnet user-secrets set "Parameters:sqlserver-password" "YourSecurePassword123!" --project VolumeMount.AppHost + ``` +4. Run the AppHost: + ```bash + cd VolumeMount.AppHost + dotnet run + ``` +5. Open the Aspire Dashboard (URL shown in console output) +6. Access the BlazorWeb application through the dashboard + +## Production Deployment + +To deploy to production using the published artifacts: + +1. Download the `docker-compose.yaml` and `.env` files from the Aspire publish output +2. Configure environment variables in the `.env` file: + ``` + SQLSERVER_PASSWORD=YourSecurePassword + BLAZORWEB_IMAGE=ghcr.io/bethmassi/volumemount/blazorweb:latest + BLAZORWEB_PORT=8080 + ``` +3. Deploy using Docker Compose: + ```bash + docker-compose up -d + ``` + +## Key Takeaways + +- **Aspire simplifies container orchestration** - Configure everything in C# with strong typing +- **Persistent volumes preserve data** - Both database and uploaded files survive container restarts +- **Aspire CLI enables GitOps** - Generate deployment artifacts and publish images in CI/CD pipelines +- **Flexible deployment options** - Same configuration works for local development and production From 99262cb9c11535b425c524d4ad48e6c1a134c427 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 15 Jan 2026 18:21:17 +0000 Subject: [PATCH 3/3] Address PR feedback: Add Aspire links, aspire run/deploy commands, and deployment details Co-authored-by: BethMassi <5115571+BethMassi@users.noreply.github.com> --- README.md | 39 ++++++++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 90b4074..7917f62 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Persistent Volume & GHCR Publish Sample -This sample demonstrates how to use .NET Aspire to orchestrate a multi-container application with persistent data storage and automated deployment to production. It showcases: +This sample demonstrates how to use Aspire to orchestrate a multi-container application with persistent data storage and automated deployment to production. It showcases: - **Persistent SQL Server volumes** - Database data persists across container restarts - **Persistent file storage volumes** - User-uploaded images persist across container restarts @@ -222,24 +222,45 @@ docker-compose up -d To run the sample locally: 1. Install [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0) -2. Install [Docker Desktop](https://www.docker.com/products/docker-desktop) -3. Set the SQL Server password as a user secret: +2. Install Docker Desktop +3. Install Aspire CLI +4. Set the SQL Server password as a user secret: ```bash dotnet user-secrets set "Parameters:sqlserver-password" "YourSecurePassword123!" --project VolumeMount.AppHost ``` -4. Run the AppHost: +5. Run the AppHost: ```bash cd VolumeMount.AppHost - dotnet run + aspire run ``` -5. Open the Aspire Dashboard (URL shown in console output) -6. Access the BlazorWeb application through the dashboard +6. Open the Aspire Dashboard (URL shown in console output) +7. Access the BlazorWeb application through the dashboard + +## Local Deployment + +To containerize and deploy the application locally: + +```bash +aspire deploy +``` + +This will set up the containerized application and volume mounts on your Docker desktop. + +**Running the Aspire Dashboard:** +- The dashboard will be available at `http://localhost:18888` (default port) +- You can view logs, metrics, and traces for all services +- Navigate to the services tab to see all running containers + +**Running the BlazorWeb app:** +- Once deployed, the BlazorWeb application will be available on the port specified in your `.env` file (default: 8080) +- Access it at `http://localhost:8080` or the configured port +- The app connects to the SQL Server container automatically via the configured connection string ## Production Deployment To deploy to production using the published artifacts: -1. Download the `docker-compose.yaml` and `.env` files from the Aspire publish output +1. Download the `docker-compose.yaml` and `.env` files from the Aspire publish output. These are the artifacts you can download as an archive (.zip) from the Actions workflow run. 2. Configure environment variables in the `.env` file: ``` SQLSERVER_PASSWORD=YourSecurePassword @@ -255,5 +276,5 @@ To deploy to production using the published artifacts: - **Aspire simplifies container orchestration** - Configure everything in C# with strong typing - **Persistent volumes preserve data** - Both database and uploaded files survive container restarts -- **Aspire CLI enables GitOps** - Generate deployment artifacts and publish images in CI/CD pipelines +- **Aspire CLI enables GitOps** - Generate deployment artifacts and publish images in CI/CD pipelines using the aspire CLI commands `aspire publish` and `aspire do push`. - **Flexible deployment options** - Same configuration works for local development and production