Container ports are published bound only to 127.0.0.1 during local run, leaving no fallback when IPv4 loopback is broken
- 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
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