microsoft / microsoft/aspire

Container ports are published bound only to 127.0.0.1 during local run, leaving no fallback when IPv4 loopback is broken

Open
#19,803 3 comments 0 reactions 0 assignees View on GitHub
area-app-model area-orchestrator needs-area-label triage:bot-seen
Dominant language
C#
Stars
6.3k
Forks
991
Avg merge
2d 15h
Merged PRs (30d)
196

Description

### Is there an existing issue for this?

Yes, I searched. The closest is #13460 (host IP binding for `Aspire.Hosting.Docker` compose publishing), but that is about publish output, not the local run experience.

### Describe the bug

When running locally on Windows, Aspire publishes container ports bound **only** to `127.0.0.1`:

```text
# Aspire (AddSqlServer, local run)
sql-njawbqtk mcr.microsoft.com/mssql/server:2022-latest 127.0.0.1:56971->1433/tcp
```

For comparison, Testcontainers publishes the same image on the same machine as:

```text
zen_darwin mcr.microsoft.com/mssql/server:2022-latest 0.0.0.0:61930->1433/tcp, [::]:61930->1433/tcp
```

This single-address binding leaves no fallback path. On a host where IPv4 loopback port forwarding is broken, the resource becomes completely unreachable — including to Aspire's own health checks — and there is no setting to change the binding.

I hit this with a real Docker Desktop regression on Windows (4.88.0 / 4.88.1). On that host, `127.0.0.1` stalls during the SQL Server TDS pre-login handshake while `localhost` and `::1` work fine. Because Testcontainers also binds `[::]`, switching the host name is a viable workaround there. With Aspire there is nothing to switch to — `127.0.0.1` is the only address the port is published on.

I want to be clear that the underlying defect is Docker's, not Aspire's (reported at docker/desktop-feedback#622). The Aspire-side issue is the lack of any fallback or knob when the single bound address is unusable.

### Expected Behavior

Either of:

1. Publish container ports on all loopback addresses (or dual-stack) during local run, matching what Testcontainers does, so a broken IPv4 loopback still leaves `::1` / `localhost` reachable; or
2. Expose a supported way to control the host binding address for container endpoints during local run.

I understand binding to `127.0.0.1` only is likely a deliberate choice to avoid exposing containers on the LAN, and I am not asking to weaken that by default. An opt-in knob would be enough.

### Steps To Reproduce

On a Windows host where IPv4 loopback forwarding to Docker published ports is broken (Docker Desktop 4.88.0 / 4.88.1 reproduces this for SQL Server):

1. AppHost:

```csharp
var builder = DistributedApplication.CreateBuilder(args);
var db = builder.AddSqlServer("sql")
.WithLifetime(ContainerLifetime.Session)
.AddDatabase("bookstore-db");
builder.Build().Run();
```

2. Run any `Aspire.Hosting.Testing` integration test against it.
3. All tests fail. The `sql_check` health check never becomes healthy.
4. `docker ps` shows the container published as `127.0.0.1:->1433/tcp`.

### Exceptions (if any)

```text
Microsoft.Data.SqlClient.SqlException : Connection Timeout Expired.
The timeout period elapsed while attempting to consume the pre-login handshake acknowledgement.
... [Pre-Login] initialization=11; handshake=14999;
```

TCP connect completes in ~11 ms; the pre-login handshake then times out at 15 s. A raw TDS PRELOGIN probe against the same container at the same moment returns 9 bytes via `127.0.0.1` but the full 26 bytes via `::1` and `localhost` — the response is being truncated on the IPv4 path.

### .NET Version

10.0.400

### Anything else?

Things I tried that did **not** help:

- **Upgrading Aspire 13.4.6 → 13.5.3.** Same result.
- **`WithEndpoint("tcp", e => e.TargetHost = "localhost")`.** `TargetHost` exists in both 13.4.6 and 13.5.3, and `"tcp"` is the correct endpoint name (`SqlServerServerResource.PrimaryEndpointName`, the same one `WithHostPort` uses), but the tests still fail. As far as I can tell `TargetHost` controls the advertised/proxy host name rather than the Docker publish binding, so it does not reach this.
- **Testcontainers' `~/.testcontainers.properties` with `host.override=localhost`.** Aspire does not read it, and more fundamentally there would be no second address to point at.

Source reading (main branch) that led me here:

- `src/Aspire.Hosting/Dcp/Model/Service.cs` — `AddressAllocationModes` documents `IPv4ZeroOne` as "Bind only to 127.0.0.1"; `ServiceSpec.AddressAllocationMode` defaults to `Localhost`.
- `src/Aspire.Hosting/Dcp/DcpExecutor.cs` — the mode is only switched to `Proxyless` based on `endpoint.IsProxied`; I could not find any other entry point.
- The actual publish binding appears to be performed by DCP, which is not in this repository, so I could not verify further from source.

Environment: Windows 11 Pro 26200, Docker Desktop 4.88.1 (237512), Engine 29.7.2, WSL 2.7.12.0, .NET SDK 10.0.400, Aspire 13.4.6 and 13.5.3.

Cross-platform check: the exact same sample and the same Docker Desktop version (4.88.1, build 237512) on macOS passes all 44 tests, and the PRELOGIN probe returns the full response on all three addresses. The Docker defect is Windows-only; the missing fallback in Aspire is what turns it into a hard failure.

Contributor guide

Open the contributing guide

Research direction

Read src/Aspire.Hosting/Dcp/Model/Service.cs and src/Aspire.Hosting/Dcp/DcpExecutor.cs to trace AddressAllocationMode and endpoint proxy handling. Reproduce the failure with an Aspire.Hosting.Testing integration test on the described Windows environment; the work is done when local container endpoints retain a reachable fallback or expose a supported host-binding control without weakening the default.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp, docker
Domain
devtools, infrastructure
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.