Skip to content

fix: do not add service name as alias on external networks#13784

Open
stasadev wants to merge 1 commit intodocker:mainfrom
stasadev:20260508_stasadev_fix_external_network_aliases
Open

fix: do not add service name as alias on external networks#13784
stasadev wants to merge 1 commit intodocker:mainfrom
stasadev:20260508_stasadev_fix_external_network_aliases

Conversation

@stasadev
Copy link
Copy Markdown
Contributor

@stasadev stasadev commented May 8, 2026

Summary

What changed

Before this fix, getAliases() always added the service name as an alias when useNetworkAliases was true. This put internal service names onto external networks (networks Compose does not manage). On a shared external network, two projects with a service named db would resolve to each other - which is the bug.

The fix skips the service name on external networks. Aliases that the user sets under services.<name>.networks.<net>.aliases still go through.

Discoverability

Users who relied on cross-project resolution over a shared external network would lose it silently. To make the change visible, up prints one warning per external network used by the project:

WARN[0000] service names [db, web] not registered as aliases on external network "shared-net"; add them under each service's networks.shared-net.aliases if needed

One warning per network keeps the output short, even on stacks with many services on the same external network. A service that already lists its own name under networks.<net>.aliases is left out of the warning, so the warning disappears once the user adopts the workaround.

Only up prints the warning. create and run --use-aliases do not:

  • create is rarely used alone - it almost always runs as part of up
  • run --use-aliases creates one-off containers, where the warning would just be noise

Workaround

Add the alias per service, under that service's network entry. The alias namespace lives on the service, not on the network itself:

networks:
  shared-net:
    external: true

services:
  web:
    networks:
      shared-net:
        aliases: [web]
  db:
    networks:
      shared-net:
        aliases: [db]

Related

ddev/ddev#8390 - DDEV projects share one external network (ddev_default). A db in one project resolved to another project's db over that shared network, because the service name was registered as an alias on it.

Before / After

Setup

Two independent Compose projects and a standalone router share one external network. Project1 has web and db. Project2 has only web - no db of its own, so any db resolution from it would cross into project1. The router is a separate project on shared-net only, simulating a reverse proxy like ddev-router.

docker network create shared-net

cat <<'EOF' > project1.yaml
networks:
  default: {}
  shared-net:
    external: true

services:
  web:
    image: busybox
    command: sleep infinity
    networks:
      - default
      - shared-net
  db:
    image: busybox
    command: sleep infinity
    networks:
      - default
      - shared-net
EOF

cat <<'EOF' > project2.yaml
networks:
  default: {}
  shared-net:
    external: true

services:
  web:
    image: busybox
    command: sleep infinity
    networks:
      - default
      - shared-net
EOF

cat <<'EOF' > router.yaml
networks:
  shared-net:
    external: true

services:
  router:
    image: busybox
    command: sleep infinity
    networks:
      - shared-net
EOF

docker-compose -p project1 -f project1.yaml up -d
docker-compose -p project2 -f project2.yaml up -d
docker-compose -p router -f router.yaml up -d

Cleanup:

docker-compose -p project1 -f project1.yaml down
docker-compose -p project2 -f project2.yaml down
docker-compose -p router -f router.yaml down
docker network rm shared-net
Before (buggy behavior)

Service names appear as aliases on the external network:

$ docker inspect $(docker-compose -p project1 -f project1.yaml ps -q web) \
    --format '{{json .NetworkSettings.Networks}}' | jq 'to_entries[] | {network: .key, aliases: .value.Aliases}'
{"network": "project1_default", "aliases": ["project1-web-1", "web"]}
{"network": "shared-net",       "aliases": ["project1-web-1", "web"]}
#                                                               ^^^
#                                          service name leaks onto external network

Within project1, web can ping its own db by service name (expected):

$ docker-compose -p project1 -f project1.yaml exec web ping -c1 db
PING db (172.18.0.3): 56 data bytes
64 bytes from 172.18.0.3: seq=0 ttl=64 time=0.121 ms

Project2's web has no db of its own, but can reach project1's db via shared-net (bug):

$ docker-compose -p project2 -f project2.yaml exec web ping -c1 db
PING db (172.20.0.3): 56 data bytes
64 bytes from 172.20.0.3: seq=0 ttl=64 time=0.112 ms

router in project2 can also resolve project1's services via shared-net (bug):

$ docker-compose -p router -f router.yaml exec router ping -c1 web
PING web (172.20.0.2): 56 data bytes
64 bytes from 172.20.0.2: seq=0 ttl=64 time=0.098 ms

$ docker-compose -p router -f router.yaml exec router ping -c1 db
PING db (172.20.0.3): 56 data bytes
64 bytes from 172.20.0.3: seq=0 ttl=64 time=0.104 ms
After (fixed behavior)

Service names are no longer registered as aliases on the external network:

$ docker inspect $(docker-compose -p project1 -f project1.yaml ps -q web) \
    --format '{{json .NetworkSettings.Networks}}' | jq 'to_entries[] | {network: .key, aliases: .value.Aliases}'
{"network": "project1_default", "aliases": ["project1-web-1", "web"]}
{"network": "shared-net",       "aliases": ["project1-web-1"]}
#                                                        no service name

Within project1, web can still ping its own db by service name via the internal default network (unchanged):

$ docker-compose -p project1 -f project1.yaml exec web ping -c1 db
PING db (172.18.0.3): 56 data bytes
64 bytes from 172.18.0.3: seq=0 ttl=64 time=0.121 ms

Project2's web can no longer resolve project1's db (fixed):

$ docker-compose -p project2 -f project2.yaml exec web ping -c1 db
ping: bad address 'db'

router can no longer resolve services from project1 either (fixed):

$ docker-compose -p router -f router.yaml exec router ping -c1 web
ping: bad address 'web'

$ docker-compose -p router -f router.yaml exec router ping -c1 db
ping: bad address 'db'

Containers are still reachable on shared-net by their fully qualified container name, which remains as an alias on the external network:

$ docker-compose -p router -f router.yaml exec router ping -c1 project1-web-1
PING project1-web-1 (172.20.0.2): 56 data bytes
64 bytes from 172.20.0.2: seq=0 ttl=64 time=0.094 ms

$ docker-compose -p router -f router.yaml exec router ping -c1 project2-web-1
PING project2-web-1 (172.20.0.4): 56 data bytes
64 bytes from 172.20.0.4: seq=0 ttl=64 time=0.091 ms

Test plan

  • TestCreateEndpointSettings - non-external network case still produces containerName, serviceName, and configured aliases
  • TestCreateEndpointSettings_ExternalNetwork - external network case produces only containerName and configured aliases (no service name)
  • TestWarnExternalNetworkAliases - covers the warning helper:
    • internal-only networks emit no warning
    • external network emits one warning per network, with services listed in sorted order
    • services that already list their own name under networks.<net>.aliases are excluded
    • if all services are explicitly self-aliased, no warning is emitted
    • multiple external networks each get their own warning
  • TestExternalNetworkAliases (e2e) - mixed internal + external network fixture, where web has no explicit alias on the external network and db declares networks.external-net.aliases: [db]:
    • the warning is logged on up
    • the warning lists web and excludes db (since db is self-aliased)
    • web's service name is in Aliases on the internal network
    • web's service name is not in Aliases on the external network
    • db's explicit alias [db] is registered on the external network (workaround works end-to-end)
    • service-name DNS resolution still works between services over the internal network (web -> db)
  • golangci-lint run --build-tags "e2e" ./pkg/compose/... ./pkg/e2e/... - 0 issues

Copilot AI review requested due to automatic review settings May 8, 2026 09:12
@stasadev stasadev requested a review from a team as a code owner May 8, 2026 09:12
@stasadev stasadev requested review from glours and ndeloof May 8, 2026 09:12
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR prevents Docker Compose from automatically adding the service name as a network alias when attaching containers to external networks, avoiding service-name leakage and potential DNS collisions across workloads sharing the same external network.

Changes:

  • Update alias generation to append the service name only for non-external networks.
  • Thread networkKey into getAliases() so it can consult the project network definition.
  • Add a unit test validating alias behavior on external networks.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
pkg/compose/create.go Adjusts alias computation to skip adding service.Name on external networks.
pkg/compose/create_test.go Adds a test ensuring external networks do not receive the service-name alias (but keep explicit aliases).

@glours
Copy link
Copy Markdown
Contributor

glours commented May 8, 2026

/review

Copy link
Copy Markdown

@docker-agent docker-agent Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assessment: 🟢 APPROVE

The fix is correct and well-scoped. getAliases() now guards the automatic service-name alias behind a !n.External check, preventing service discovery names from leaking onto shared external networks. The Go map zero-value lookup (project.Networks[networkKey]NetworkConfig{}) is safe — External defaults to false, and Compose validation guarantees all service network keys exist in project.Networks before this point. Explicit user-configured aliases under service.networks.<name>.aliases are intentionally still passed through (confirmed by the PR description and test). The new test TestCreateEndpointSettings_ExternalNetwork correctly covers the fixed scenario. No issues found.

@glours
Copy link
Copy Markdown
Contributor

glours commented May 8, 2026

Thanks for the fix and for the detailed write-up. The collision scenario is a real pain point, and the proposed approach makes sense.

Before merging, though, I think this should be called out as a silent breaking change. Users who intentionally rely on service-name resolution across an external network will suddenly lose DNS resolution, with no indication from Compose as to why. While a workaround exists through explicit aliases, it is not discoverable.

Could you add a log notice when the service-name alias is skipped on an external network? Something along the lines of:
service "web": not registering service name as an alias on external network "shared-net"; add it explicitly under networks.shared-net.aliases if needed

A logrus.Warnf level message would feel appropriate here.

An end-to-end test covering a mixed internal + external network scenario would also be very helpful. The current unit test covers the single-network case, but a test asserting that the service name still resolves on the internal network while not being registered on the external one would provide a stronger regression guard.

@ndeloof what do you think?

@codecov
Copy link
Copy Markdown

codecov Bot commented May 8, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Previously, getAliases() unconditionally appended the service name as
a network alias when useNetworkAliases was true. This caused containers
to register their service name as an alias on external networks, leaking
internal service discovery names into networks managed outside of
Compose.

Guard the service name alias behind an external-network check: only
append it when the network is not marked as external. Explicitly
configured aliases in the service network config are still passed
through regardless.

To make the behavior change discoverable, `up` now emits one warning
per external network the project is connected to, listing the services
whose service-name alias was skipped. Services that already declare
their own name under networks.<net>.aliases are excluded, so the
warning disappears once the user adopts the workaround. `create` and
`run --use-aliases` are intentionally not warned about: `create` is
rarely used standalone, and `run --use-aliases` produces ephemeral
one-off containers where the warning would be noise.

Fixes docker#8223

Signed-off-by: Stanislav Zhuk <stasadev@gmail.com>
@stasadev stasadev force-pushed the 20260508_stasadev_fix_external_network_aliases branch from 5056e7f to 49a4cb1 Compare May 8, 2026 11:54
@stasadev
Copy link
Copy Markdown
Contributor Author

stasadev commented May 8, 2026

this should be called out as a silent breaking change.

Makes sense, I added a logrus.Warnf and the e2e test. PR description updated with the full details.

One note: the warning lives in a separate helper warnExternalNetworkAliases(project) called once at the top of Up(), not inside getAliases() - putting it there would print the message many times and mix it with the TUI progress output.

@thaJeztah
Copy link
Copy Markdown
Member

I wonder if we need to have a closer look at different scenarios.

This is a known problem for some use-cases, but disabling the alias may also be covering over other issues;

Networks in docker also form a security boundary; services connected to the same network are granted access to other services connected to the same network. Disabling DNS resolution won't prevent other services from connecting; in the example given (multiple projects defining a db service); that may mean that unrelated projects might be able to connect to a database service from another project.

If the shared network is used for some reverse proxy (traefik, caddy, nginx-proxy), that could even mean the database may become exposed publicly.

The reverse can also be true; perhaps a less common use-case, but I've seen users work with "composable" compose projects; a network that's set up, then multiple compose projects that define part of the stack, but attached to the common network; for those, DNS resolution between those may be intentional.

Wondering of some options should be configurable on the network, to more clearly define the purpose of the network, which could be an ingress network, disallowing communication between services attached to the network.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add option to remove service name as default alias on networks

4 participants