DefangLabs / DefangLabs/defang

serviceNameReplacer buckets on port mode, not networks: internal services get a public FQDN that doesn't resolve

Open
#2,263 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Go
Stars
166
Forks
24
Avg merge
10h 8m
Merged PRs (30d)
33

Description

Summary

NewServiceNameReplacer decides whether a service name in an env var / build arg is substituted with a private or public DNS name purely from the port mode:, ignoring compose networks:. The code says so:

src/pkg/cli/compose/serviceNameReplacer.go:39-44

// HACK: we only check the ports for "host" mode and don't care about the networks; TODO: consider dependsOn / networks
if hasHostPort(svccfg) {
    privateServiceNames = append(privateServiceNames, regexp.QuoteMeta(svccfg.Name))
} else if len(svccfg.Ports) > 0 {
    publicServiceNames = append(publicServiceNames, regexp.QuoteMeta(svccfg.Name))
}

So a service with an ingress port that sits in a private / internal: true network lands in the publicServiceNames bucket. replaceServiceNameWithDNS then rewrites a reference like marketing:8080 to ServicePublicDNS(...) — for BYOC, <svc>.<project>.<tenant>.defang.app (src/pkg/cli/client/byoc/baseclient.go:297-301) — a public FQDN that does not resolve for a service which was deliberately not published publicly.

Why this matters now

The provider side has moved to networks deciding public vs private (DefangLabs/pulumi-defang#380: the default network is public unless internal: true; any other network is private; port mode only selects the exposure type). Once the provider honors that, the two halves disagree:

  • the provider gives an ingress-port service in a private network an internal-only endpoint (e.g. Azure External: false),
  • the CLI rewrites references to it to a public defang.app hostname,

so the env var points at a name with no record. This is the shape behind the DefangLabs/station 502s discussed in pulumi-defang#380, and it's why DefangLabs/pulumi-defang#555 was deliberately scoped to the host-mode path only — that path needs no CLI change, because host-mode already buckets private and ServicePrivateDNS is the bare service name on Azure.

Backwards compatibility — the reason this needs care

This cannot be a straight behavior change. Today any service with ports and no host-mode port gets a public FQDN substituted. Making the bucketing networks-aware would silently change substituted values for existing projects — including projects that declare an internal network today and are (perhaps unknowingly) relying on the public name being injected, e.g. because traffic currently egresses and comes back in through the public LB.

Options worth weighing:

  1. Opt-in gate — only apply networks-aware bucketing when the project declares a top-level networks: block with internal: true, so projects that never mention networks are untouched.
  2. Warn-then-switch — emit a warning for one release when port mode and networks disagree (a divergence warning is already tracked as a follow-up in pulumi-defang#380), then flip the default.
  3. Compose-version / feature gate — tie it to an explicit project-level opt-in.

My inclination is (1), since it's precisely scoped to projects that have expressed an intent about networks, and it leaves the no-networks: common case bit-identical.

Related

  • DefangLabs/pulumi-defang#380 — networks decide public vs private (provider side)
  • DefangLabs/pulumi-defang#555 — Azure host-mode → internal ingress (the half that needs no CLI change)
  • DefangLabs/pulumi-defang#253 — public + host exposure / DNS

Also worth noting

ServicePublicDNS hardcodes the defang.app name rather than using the delegate domain Fabric returned — there's already a TODO for it at src/pkg/cli/client/byoc/baseclient.go:299:

// TODO: this should use the delegate domain we got from Fabric
return fmt.Sprintf("%s.%s.%s.defang.app", getServiceLabel(serviceName), b.GetProjectLabel(projectName), tenantLabel)

Probably worth fixing in the same pass, since both are about this function returning a name that may not be the one actually provisioned.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Rxct3jubVBBRu4ZPzuBTJt

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with src/pkg/cli/compose/serviceNameReplacer.go:39-44 and trace replaceServiceNameWithDNS, then read src/pkg/cli/client/byoc/baseclient.go:297-301. Review the linked provider issues and decide how the compatibility gate should work. Done means private-network services no longer receive an unusable public name while projects without networks retain current behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
cli, cloud, networking
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.