PublishWithContainerFiles builds :latest while Docker Compose references a deployment-tagged image
- Dominant language
- C#
- Stars
- 6.3k
- Forks
- 991
- Avg merge
- 2d 15h
- Merged PRs (30d)
- 196
Description
### Aspire version
Aspire CLI, AppHost SDK, `Aspire.Hosting.Docker`, and related hosting packages: `13.5.3`
### Describe the bug
Combining a Docker Compose deployment environment with `PublishWithContainerFiles` produces a Docker Compose image reference that does not match the final image built locally.
The final layered backend image is built as:
```text
backend:latest
```
However, `.env.E2E` contains a generated deployment tag similar to:
```text
BACKEND_IMAGE=backend:aspire-deploy-YYYYMMDDHHMMSS
```
`docker compose up` therefore tries to pull an image that does not exist, despite Aspire reporting that the backend image was built successfully.
This reproduces on a clean Linux CI agent, where no previously cached image can hide the mismatch.
### Minimal AppHost configuration
```csharp
var builder = DistributedApplication.CreateBuilder(args);
var backend = builder
.AddProject("backend", launchProfileName: null)
.WithHttpEndpoint();
var frontend = builder
.AddViteApp("frontend", "../frontend")
.WithAnnotation(
new JavaScriptBuildScriptAnnotation("build", null),
ResourceAnnotationMutationBehavior.Replace);
var compose = builder.AddDockerComposeEnvironment("e2e");
backend
.WithComputeEnvironment(compose)
.WithExternalHttpEndpoints()
.PublishWithContainerFiles(frontend, "/app/wwwroot");
builder.Build().Run();
```
### Steps to reproduce
1. Use Aspire 13.5.3 and Docker on a clean Linux machine.
2. Configure an AppHost as above.
3. Run the repository's Compose preparation step:
```bash
aspire do prepare-e2e \
--environment E2E \
--apphost src/AppHost/AppHost.csproj \
--non-interactive \
--output-path artifacts/e2e
```
This generates the Docker Compose deployment artifacts, including
`artifacts/e2e/docker-compose.yaml` and `.env.E2E`.
4. After the Aspire publish/build step reports success, inspect the generated image reference and local images:
```bash
grep BACKEND_IMAGE artifacts/e2e/.env.E2E
docker image inspect backend:latest
docker image inspect "backend:aspire-deploy-"
```
The first image exists locally, while the deployment-tagged image referenced by `.env.E2E` does not.
5. Start the generated deployment:
```bash
docker compose \
--file artifacts/e2e/docker-compose.yaml \
--env-file artifacts/e2e/.env.E2E \
up --detach
```
### Expected behavior
The image reference written to `.env.E2E` should identify the final layered image produced by `PublishWithContainerFiles`, allowing Compose to start it without attempting a registry pull.
Either the final layered build should preserve the generated `aspire-deploy-` tag, or Compose should reference `backend:latest` when that is the image Aspire built.
### Actual behavior
The final layered build uses `backend:latest`, while Compose resolves `BACKEND_IMAGE` to `backend:aspire-deploy-`.
Compose then fails with output similar to:
```text
pull access denied for backend, repository does not exist or may require 'docker login': denied: requested access to the resource is denied
```
Docker itself is available and healthy, and the preceding Aspire build step reports success.
### Additional investigation
The behavior appears to involve two independently tested paths:
- `ProjectResource` defaults the local image tag to `latest`.
- Docker Compose resolves `ContainerImageReference` to an `aspire-deploy-` tag.
- The final Dockerfile/Buildx operation created for `PublishWithContainerFiles` builds the destination image using the image name without preserving that resolved deployment tag.
The existing Docker Compose test `PrepareStep_ResolvesContainerImageReferenceViaIValueProvider` verifies the generated deployment tag. Container-file tests verify Dockerfile layering. I could not find a test combining those paths and confirming that the image written to the Compose environment file exists after the final layered build.
A regression test covering that complete pipeline would catch the mismatch.
### Workaround
For the Docker Compose environment, explicitly select the tag produced by the layered build:
```csharp
#pragma warning disable ASPIREPIPELINES003
backend.WithRemoteImageTag("latest");
#pragma warning restore ASPIREPIPELINES003
```
This workaround is applied only when configuring the Docker Compose deployment environment. It causes the generated Compose configuration to reference `backend:latest`, which is the image produced by the final layered build.
### Related issues
I found #9711 while checking for duplicates. It appears related to explicit container image naming or tag customization, whereas this reproduction does not explicitly override the image name or tag. The mismatch occurs from combining Docker Compose with `PublishWithContainerFiles`.
### Environment
- Clean hosted Linux CI agent
- .NET 10
- Aspire 13.5.3
- Docker with Buildx and Compose v2
### AI use acknowledgment
GitHub Copilot assisted with searching the Aspire source, organizing the reproduction details, and drafting this report. I reviewed the cited source paths, commands, logs, and technical conclusions before submission.
Contributor guide
Research direction
Start with ProjectResource's local image tag handling, Docker Compose's ContainerImageReference resolution, and the final Dockerfile/Buildx operation used by PublishWithContainerFiles. Compare the existing PrepareStep_ResolvesContainerImageReferenceViaIValueProvider and container-file tests, then add coverage for the complete pipeline. Done means the generated .env.E2E image exists locally and docker compose up starts successfully on a clean agent.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp, docker, docker-compose
- Domain
- build-system, devops
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100