Appearance
Networking & DNS
Every managed service has more than one address, and managed now shows all of them — labeled by where each one is reachable from — instead of only a public IP that often can't connect.
Why the public IP wasn't enough
A database like MySQL declares its port from: private, so the firewall never opens 3306 to the internet. The old connection string — mysql://root:***@<public-ip>:3306 — pointed at an address that would simply time out, and the overlay address you actually needed was impossible to see.
Connection strings
managed apply and managed status now print, per node, every address that service is reachable at:
production-database (primary)
public mysql://root:***@203.0.113.10:3306 not reachable from the public internet — firewalled to your private network
private mysql://root:***@10.100.0.2:3306 WireGuard overlay — reachable from any server in this project
private-dns mysql://root:***@production-database.myproject.managed.internal:3306 stable overlay name — resolves on any server in this projectEach line is tagged by kind:
| Kind | Host | Reachable from |
|---|---|---|
public | public IPv4 | the internet (only if the port is from: public) |
public-dns | <service>.<project>.<account>.by-managed.works | the internet (hosted projects) |
private | overlay/VPC IP | servers on your overlay or VPC |
private-dns | <service>.<project>.managed.internal | servers on your overlay |
The same set is shown on the dashboard and is what managed repl connects over — for a firewalled service it dials the overlay address rather than the dead public one.
Internal DNS (*.managed.internal)
On every server in the overlay, managed publishes a block into /etc/hosts mapping stable names to overlay IPs, regenerated on every apply:
production-database.myproject.managed.internal → 10.100.0.2 (the primary/writer)
production-database-primary.myproject.managed.internal → 10.100.0.2
production-database-replica-1.myproject.managed.internal → 10.100.0.3Names resolve only to overlay addresses, so they work from any server in the project but not from your laptop unless it's on the overlay. The bare <service>.<project>.managed.internal always tracks the writer (primary), so after a failover it re-points to the new primary on the next apply — an app can hardcode it and never chase an IP again.
<service>is your managed.yaml key (e.g.production-database), not the package name.<project>comes fromname:in managed.yaml. The bare<service>.managed.internalform is also published, so names work even without a project name.
Removals converge too: when a server is destroyed or a service is removed, the remaining servers' overlay peers and *.managed.internal records are updated as part of that same run — including when the fleet shrinks to a single box, whose stale peers and names are cleaned up rather than left behind.
Public DNS (*.by-managed.works)
For services actually exposed to the internet, managed builds a public name under the hosted scheme:
<service>.<project>.<account>.by-managed.works<account> and <project> are the hosted slugs your project is connected to; they're supplied by the control plane when you run managed ui connect. A public name is only shown for a service whose port is from: public (a public name for a firewalled database would be useless) and only on a connected project. Override the parent zone with network.public_dns_zone in managed.yaml if you self-host.
How the records get published
The CLI is the single source of truth for which names exist and where they point: on every apply it writes the desired records (name → public/reserved IP) into state.public_dns_records and syncs them. The control plane (ui/) then publishes that set to the DNS provider — tagging each record per-project so it only touches records it created, and removing records for deleted services.
The only thing that lives outside the code is the DNS zone itself: a domain in Cloudflare and an API token. That's a one-time setup for whoever runs the control plane — see Public DNS with Cloudflare. Until it's configured the CLI still computes and shows the names; the control plane just skips publishing.
The internal *.managed.internal zone needs none of this — it's entirely on-box.