Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CommunityToolkit.Aspire.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@
<Project Path="src/CommunityToolkit.Aspire.OllamaSharp/CommunityToolkit.Aspire.OllamaSharp.csproj" />
<Project Path="src/CommunityToolkit.Aspire.RavenDB.Client/CommunityToolkit.Aspire.RavenDB.Client.csproj" />
<Project Path="src/CommunityToolkit.Aspire.SurrealDb/CommunityToolkit.Aspire.SurrealDb.csproj" />
<Project Path="src\CommunityToolkit.Aspire.Hosting.ImageRegistry\CommunityToolkit.Aspire.Hosting.ImageRegistry.csproj" />
<Project Path="src\CommunityToolkit.Aspire.Hosting.Keycloak.Extensions\CommunityToolkit.Aspire.Hosting.Keycloak.Extensions.csproj" />
</Folder>
<Folder Name="/src/Dapr/">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>CommunityToolkit.Aspire.Hosting.ImageRegistry</RootNamespace>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Aspire.Hosting" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
using Aspire.Hosting;
using Aspire.Hosting.ApplicationModel;

namespace CommunityToolkit.Aspire.Hosting.ImageRegistry;


/// <summary>
/// Extension methods for adding an Image registry to the Aspire application
/// </summary>
public static class ImageRegistryResourceExtensions
{
private const string RegistryNameEnvName = "REGISTRY_NAME";

/// <summary>
/// Adds a Docker registry container to the application
/// </summary>
public static IResourceBuilder<ContainerResource> 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();
}

/// <summary>
/// Adds a Dockerfile-based image to be built and pushed to the registry
/// </summary>
public static IResourceBuilder<ContainerResource> WithRegistryDockerfile(
this IResourceBuilder<ContainerResource> 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;
}

/// <summary>
/// Adds an existing image to be pulled and pushed to the registry
/// </summary>
public static IResourceBuilder<ContainerResource> WithRegistryImage(
this IResourceBuilder<ContainerResource> 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<ExecutableResource> WithRegistry(
this IResourceBuilder<ExecutableResource> imageBuilder, IResourceBuilder<ContainerResource> registry)
{
return imageBuilder
.WithEnvironment(RegistryNameEnvName, registry.GetEndpoint("http").Property(EndpointProperty.HostAndPort))
.WaitFor(registry)
.WithParentRelationship(registry);
}
}
143 changes: 143 additions & 0 deletions src/CommunityToolkit.Aspire.Hosting.ImageRegistry/README.md
Original file line number Diff line number Diff line change
@@ -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:<port>/v2/_catalog

# Check image tags
curl http://localhost:<port>/v2/<image>/tags/list

# Test registry health
curl http://localhost:<port>/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