Skip to content

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 project

Each line is tagged by kind:

KindHostReachable from
publicpublic IPv4the internet (only if the port is from: public)
public-dns<service>.<project>.<account>.by-managed.worksthe internet (hosted projects)
privateoverlay/VPC IPservers on your overlay or VPC
private-dns<service>.<project>.managed.internalservers 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.3

Names 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 from name: in managed.yaml. The bare <service>.managed.internal form 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.