From de6ddc25e8b404f142e551a8d942de39a275b77b Mon Sep 17 00:00:00 2001 From: twald Date: Tue, 25 Nov 2025 14:08:14 +0200 Subject: [PATCH] Add image registry integration --- CommunityToolkit.Aspire.slnx | 1 + ...oolkit.Aspire.Hosting.ImageRegistry.csproj | 14 ++ .../ImageRegistryResourceExtensions.cs | 86 +++++++++++ .../README.md | 143 ++++++++++++++++++ 4 files changed, 244 insertions(+) create mode 100644 src/CommunityToolkit.Aspire.Hosting.ImageRegistry/CommunityToolkit.Aspire.Hosting.ImageRegistry.csproj create mode 100644 src/CommunityToolkit.Aspire.Hosting.ImageRegistry/ImageRegistryResourceExtensions.cs create mode 100644 src/CommunityToolkit.Aspire.Hosting.ImageRegistry/README.md diff --git a/CommunityToolkit.Aspire.slnx b/CommunityToolkit.Aspire.slnx index a66c47b9..c9a9e18a 100644 --- a/CommunityToolkit.Aspire.slnx +++ b/CommunityToolkit.Aspire.slnx @@ -208,6 +208,7 @@ + diff --git a/src/CommunityToolkit.Aspire.Hosting.ImageRegistry/CommunityToolkit.Aspire.Hosting.ImageRegistry.csproj b/src/CommunityToolkit.Aspire.Hosting.ImageRegistry/CommunityToolkit.Aspire.Hosting.ImageRegistry.csproj new file mode 100644 index 00000000..cab78d10 --- /dev/null +++ b/src/CommunityToolkit.Aspire.Hosting.ImageRegistry/CommunityToolkit.Aspire.Hosting.ImageRegistry.csproj @@ -0,0 +1,14 @@ + + + + net10.0 + enable + enable + CommunityToolkit.Aspire.Hosting.ImageRegistry + + + + + + + diff --git a/src/CommunityToolkit.Aspire.Hosting.ImageRegistry/ImageRegistryResourceExtensions.cs b/src/CommunityToolkit.Aspire.Hosting.ImageRegistry/ImageRegistryResourceExtensions.cs new file mode 100644 index 00000000..c51c26be --- /dev/null +++ b/src/CommunityToolkit.Aspire.Hosting.ImageRegistry/ImageRegistryResourceExtensions.cs @@ -0,0 +1,86 @@ +using Aspire.Hosting; +using Aspire.Hosting.ApplicationModel; + +namespace CommunityToolkit.Aspire.Hosting.ImageRegistry; + + +/// +/// Extension methods for adding an Image registry to the Aspire application +/// +public static class ImageRegistryResourceExtensions +{ + private const string RegistryNameEnvName = "REGISTRY_NAME"; + + /// + /// Adds a Docker registry container to the application + /// + public static IResourceBuilder AddDockerRegistry( + this IDistributedApplicationBuilder builder, + [ResourceName] string name, string volumeName) + { + return builder.AddContainer(name, "registry", "3") + // isProxied must be false for host machine docker client to be able to push images to it + .WithHttpEndpoint(targetPort: 5000, name: "http", isProxied: false) + .WithHttpHealthCheck("/v2/", endpointName: "http") + .WithVolume(volumeName, "/var/lib/registry") + .WithOtlpExporter(); + } + + /// + /// Adds a Dockerfile-based image to be built and pushed to the registry + /// + public static IResourceBuilder WithRegistryDockerfile( + this IResourceBuilder registry, + string imageName, + string imageNamePrefix, + string dockerfileContext, + string tag = "latest") + { + var builder = registry.ApplicationBuilder; + + if (!Directory.Exists(dockerfileContext)) + { + throw new DirectoryNotFoundException($"Dockerfile context not found: {dockerfileContext}"); + } + + var fullImageName = $"${RegistryNameEnvName}/{imageNamePrefix}/{imageName}:{tag}"; + + builder.AddExecutable($"{registry.Resource.Name}-{imageName}-image-builder", "/bin/sh", ".") + .WithArgs("-c", $"echo \"REGISTRY_NAME: ${RegistryNameEnvName}\" && docker build -t {fullImageName} {dockerfileContext} && docker --config {Path.Join(builder.AppHostDirectory, "../docker-registry/docker-config/")} push {fullImageName}") + .WithRegistry(registry) + .WithIconName("BoxArrowUp"); + + return registry; + } + + /// + /// Adds an existing image to be pulled and pushed to the registry + /// + public static IResourceBuilder WithRegistryImage( + this IResourceBuilder registry, + string imageName, + string imageNamePrefix, + string sourceImage, + string tag = "latest") + { + var builder = registry.ApplicationBuilder; + + var fullImageName = $"${RegistryNameEnvName}/{imageNamePrefix}/{imageName}:{tag}"; + + builder.AddExecutable($"{registry.Resource.Name}-{imageName}-image-publisher", "/bin/sh", builder.AppHostDirectory) + .WithArgs("-c", $"echo \"REGISTRY_NAME: ${RegistryNameEnvName}\" && docker pull {sourceImage} && docker tag {sourceImage} {fullImageName} && docker push {fullImageName}") + .WithRegistry(registry) + .WithIconName("BoxArrowUp"); + + return registry; + } + + private static IResourceBuilder WithRegistry( + this IResourceBuilder imageBuilder, IResourceBuilder registry) + { + return imageBuilder + .WithEnvironment(RegistryNameEnvName, registry.GetEndpoint("http").Property(EndpointProperty.HostAndPort)) + .WaitFor(registry) + .WithParentRelationship(registry); + } +} diff --git a/src/CommunityToolkit.Aspire.Hosting.ImageRegistry/README.md b/src/CommunityToolkit.Aspire.Hosting.ImageRegistry/README.md new file mode 100644 index 00000000..510969cc --- /dev/null +++ b/src/CommunityToolkit.Aspire.Hosting.ImageRegistry/README.md @@ -0,0 +1,143 @@ +# Docker Registry Integration for Aspire + +## Overview + +This document describes the Docker Registry integration for the Aspire application host. The registry provides a local container registry for storing and serving Docker images, supporting both building images from Dockerfiles and importing existing images. + +Why would you need this? + +If you are building a service that serves docker images, or for some reason you need to run k8s in Aspire you can use this registry as the registry for your local cluster. + +## Usage + +### Basic Setup + +```csharp +// In AppHost.cs +var registry = builder.AddDockerRegistry("local-registry"); +``` + +### Adding Images from Dockerfiles + +```csharp +registry.WithRegistryDockerfile( + imageName: "your-image-name", + imageNamePrefix: "your/custom/image/prefix" + dockerfileContext: "../../mcp/mcp_terminal", + tag: "latest" +); +``` + +### Adding Existing Images + +```csharp +registry.WithRegistryImage( + imageName: "your-image-name", + imageNamePrefix: "your/custom/image/prefix", + sourceImage: "redis:alpine", + tag: "custom" +); +``` + +### Integration with Docker API Service + +```csharp +var dockerApi = builder.AddDockerApiResource(registry); +``` + +## Implementation Details + +### Annotations + +The `DockerRegistryImageAnnotation` class tracks image metadata: + +- `ImageName`: Name of the image in the local registry +- `Tag`: Image tag (default: "latest") +- `DockerfileContext`: Path to Dockerfile context (for builds) +- `SourceImage`: Source image to pull (for existing images) +- `IsPushed`: Track if image has been pushed to registry + +### Lifecycle Hooks (Aspire 9.4) + +The new Aspire 9.4 lifecycle hooks provide better control: + +1. **OnInitializeResource**: Set up initial configuration +2. **OnBeforeResourceStarted**: Prepare registry settings +3. **OnResourceEndpointsAllocated**: Capture allocated ports +4. **OnResourceReady**: Execute image builds and pushes + +### Registry Configuration + +- **Port**: Dynamic allocation using Aspire's endpoint system +- **Storage**: Scoped persistent volume at `/var/lib/registry` +- **Volume Name**: to support layer caching +- **Authentication**: None (local development only) +- **Health Check**: HTTP endpoint at `/v2/` + + +### Custom Build Strategies + +Implement custom build strategies by extending the image processing: + +```csharp +public interface IImageBuildStrategy +{ + Task BuildAsync(DockerRegistryImageAnnotation annotation, string registryUrl); +} +``` + +## Error Handling + +The registry integration handles several error scenarios: + +1. **Registry Startup Failure**: Retry with exponential backoff +2. **Build Failures**: Log error and mark image as failed +3. **Push Failures**: Retry with configurable attempts +4. **Network Issues**: Configurable timeout and retry policies + +## Security Considerations + +For production deployments: + +1. Enable TLS for registry communication +2. Implement authentication (basic auth or token-based) +3. Use image signing and verification +4. Implement access control policies +5. Regular security scanning of stored images + +## Performance Optimization + +1. **Parallel Builds**: Build multiple images concurrently (limited to 3) +2. **Layer Caching**: Leverage Docker's build cache +3. **Compression**: Enable registry compression +4. **Garbage Collection**: Configure periodic cleanup + +## Troubleshooting + +### Common Issues + +1. **Registry not accessible**: Check port binding and firewall rules +2. **Build failures**: Verify Dockerfile paths and build context +3. **Push failures**: Check registry health and disk space +4. **Image not found**: Verify image name and tag format + +### Debug Commands + +```bash +# Check registry catalog +curl http://localhost:/v2/_catalog + +# Check image tags +curl http://localhost:/v2//tags/list + +# Test registry health +curl http://localhost:/v2/ +``` + +## Future Enhancements + +1. **Multi-architecture builds**: Support for ARM and other platforms +2. **Registry mirroring**: Cache upstream images locally +3. **Web UI**: Add registry browser interface +4. **Metrics**: Prometheus metrics for monitoring +5. **Replication**: Support for registry clustering