Networks & Subnets
Every workspace's containers talk to each other over Docker networks. Miabi manages these for you — you rarely need to think about them — and allocates their IP subnets from its own pool so a busy host never runs out of address space.
Workspace networks
Each workspace gets a default network the moment it is created. It is fully platform-managed:
- Every application, database, and job in the workspace is automatically attached to it,
so they can reach each other by name. Each container answers to its resource's own name, which is
what
{{ .applications.<name>.host }}resolves to in a manifest. - You cannot detach a resource from the default network, and you cannot delete it — it is the shared backbone the workspace relies on.
You can also create additional networks to further segment traffic (for example, isolating a group of apps). Attach apps to them alongside the always-present default. Custom networks count against your plan's network quota; the default network does not.
Bridge on one node, overlay across a cluster
The default network's driver depends on whether cluster mode is on, and this is what decides whether a workspace can span hosts at all:
| Driver | Reach | |
|---|---|---|
| Single node / no cluster | bridge | Node-local. Recreated on each node with the same name and subnet, but as disconnected islands — an app on one node cannot reach a database on another, and Miabi refuses to attach them. |
| Cluster mode | overlay | Spans every node, attachable and encrypted. An app on one node reaches a database on another by name, with no published ports. |
Because every app and database already joins the workspace default, that one change is the whole mechanism for cross-node connectivity — nothing else has to be reconfigured, and no connection string changes (they address a database by its alias, not by its network).
Enabling cluster mode converts existing workspaces for you. Containers are not restarted: each is attached to the overlay, carrying its DNS aliases across, and then detached from the bridge. See Applying it to existing workspaces.
The reverse-proxy (gateway) network is separate: only apps exposed by a route join it, keeping unexposed apps off the public path. See Reverse proxy.
The shared gateway network
The gateway and every routed app/database container share one bridge network —
miabi-proxy by default (MIABI_PROXY_NETWORK). Because all exposed containers on the host pile onto
this single network, it needs plenty of address space, so it is treated differently from the
per-workspace networks above:
- A roomy, explicit CIDR on a managed install.
install.shandmiabi setupcreate it with10.63.0.0/16(~65k addresses) instead of a slice of Docker's small default pool. Choose another range withMIABI_SUBNETor--subnet— see Changing the subnets. - Distinct from the workspace pool. Its CIDR must not overlap
MIABI_NETWORK_POOL_CIDR(the10.64.0.0/12pool below) or your LAN. The default10.63.0.0/16sits just outside the pool.
The Compose files declare miabi-proxy as
an ordinary Compose network, so Compose creates it (and docker compose down removes it) with a subnet
from Docker's default address pools. Add an ipam block to the network in your Compose file if you
need a specific range.
The platform's private network
Miabi's own components do not sit on the shared gateway network. They get a second, private
bridge — miabi-internal — and only the gateway is on both. This is true of every install path: a
managed one (install.sh, miabi setup) creates it with an explicit 10.62.0.0/16, and the
Compose files declare it alongside
miabi-proxy.
| Container | miabi-proxy (shared) | miabi-internal (private) |
|---|---|---|
miabi-gateway | ✅ reaches your apps, and the internet for ACME | ✅ reaches Miabi and Redis |
miabi (control plane) | — | ✅ |
miabi-postgres | — | ✅ |
miabi-redis | — | ✅ |
mb-registry (built-in registry) | — | ✅ |
| your routed apps | ✅ | — |
(On the Traefik Compose variant, Traefik takes the gateway's row.)
The reason is that everything on the shared network can dial everything else on it by name. The control-plane database has a single superuser password covering every workspace and every stored secret, and Redis holds the background job queue — so one exposed application being compromised should not put them within reach. After the split, the only way into the platform is through the gateway, where your routes and middlewares — rate limits, IP allowlists, security policies — actually apply.
The built-in registry sits on the private network only for the same reason: it relies on the gateway
to authenticate every request, so app containers must not be able to reach it directly. A Compose
stack needs MIABI_INTERNAL_NETWORK set for this — see the upgrade note below.
Nothing about deploying, routing, or connecting apps changes: your containers still join miabi-proxy
when they have a route, exactly as before.
Changing the subnets
On a managed install both CIDRs must miss each other, the workspace pool (10.64.0.0/12, below),
and your LAN or VPN:
curl -fsSL https://get.miabi.io | sudo \
MIABI_DOMAIN=miabi.example.com \
MIABI_SUBNET=10.63.0.0/16 \
MIABI_INTERNAL_SUBNET=10.62.0.0/16 bash
They are also flags on miabi setup (--subnet, --internal-subnet) and are recorded in
/etc/miabi/miabi.yaml:
spec:
networking:
proxy: { name: miabi-proxy, subnet: 10.63.0.0/16 }
internal: { name: miabi-internal, subnet: 10.62.0.0/16 }
The same block also carries the managed subnet pool (pool), the host
port range (hostPorts), the default cluster's wildcard domain for one-click app URLs (external
— other clusters set theirs in the cluster's Edit dialog, see
Cluster mode) and the managed-DNS
interval (dns) — see the Install Manifest.
The split happens on your next miabi upgrade. The private network is created, every component is
attached to it while still running, and only then are the containers recreated on their final
networks — so there is no window where Miabi cannot reach its database. Expect the same brief
restart any upgrade involves.
Pull the new compose.yaml, add MIABI_INTERNAL_NETWORK=miabi-internal to your .env, and
docker compose up -d — Compose creates the network and moves the containers onto it. The variable
matters: Miabi reads it to place the helper containers it runs out of process (platform backups, the
built-in registry), and an empty value leaves them on the proxy network alone, where the database no longer is.
On the Traefik variant the control plane also carries traefik.docker.network=miabi-internal, since
Traefik's Docker provider otherwise looks for the panel's address on the proxy network and finds none.
miabi-proxyThe shared network used to be called miabi. An existing install keeps that name: miabi setup
recorded it in the manifest, and a control plane with MIABI_PROXY_NETWORK unset finds the network
on the engine instead of creating a new one. On a Compose stack, keep MIABI_PROXY_NETWORK=miabi in
your .env — the compose file names the network from it, and an unset value creates miabi-proxy
and moves the gateway away from your apps. Compose also refuses the old network, because it was
created under the old key, so adopt it with a compose.override.yaml next to compose.yaml:
networks:
miabi-proxy:
name: miabi
external: true
Managed subnet allocation
Docker's built-in address pools are small and shared across every network on a host. A platform that creates many networks — one default per workspace, plus stack, custom, and cluster overlay networks — can exhaust them, and Docker then fails network creation with:
all predefined address pools have been fully subnetted
To avoid this, Miabi manages the IP space itself: it carves a unique subnet out of a configurable network pool for every network it creates and hands Docker an explicit subnet, instead of relying on Docker's defaults.
| Setting | Default | Controls |
|---|---|---|
MIABI_NETWORK_POOL_CIDR | 10.64.0.0/12 | The private supernet subnets are carved from. |
MIABI_NETWORK_SUBNET_PREFIX | 24 | The size of each per-network subnet. |
The default /12 pool split into /24s yields 4096 networks, each with 254 usable addresses.
The base 10.64.0.0/12 is chosen to steer clear of ranges you're likely to already be using —
common Kubernetes CNI ranges, Docker's own 172.16.0.0/12 and 192.168.0.0/16 pools, and typical
LAN/VPN/Tailscale ranges. Shrink the pool (e.g. a /16, 256 networks) or move the base if it
doesn't suit your environment.
How it behaves:
- Unique & durable — allocations are recorded so they survive restarts and never overlap.
- Overlap-safe — at start-up Miabi reserves the subnets of any pre-existing Docker networks so it never hands out a colliding one; if a chosen subnet still collides it is skipped automatically.
- Reclaimed — deleting a network returns its subnet to the pool.
- Consistent across nodes — a workspace network keeps the same subnet on every node it is recreated on.
Pick a pool CIDR that doesn't overlap your LAN, VPN, or other private ranges on the host. The
default 10.64.0.0/12 is chosen to avoid the usual suspects (common Kubernetes CNI ranges,
Docker's own pools, and Tailscale's 100.64.0.0/10), but if it collides with something in your
environment,
set MIABI_NETWORK_POOL_CIDR to a free range — for example a spare /16 like 10.99.0.0/16.
IPv6
Every Miabi-created network is IPv4-only by default, and says so explicitly — a network that inherits the daemon's own default follows a setting nobody chose here, which is how a host with IPv6 defaults enabled can end up giving containers routable addresses nobody reasoned about.
Turn it on with MIABI_NETWORK_IPV6=true, or in the install manifest:
spec:
networking:
proxy: { name: miabi, subnet: 10.63.0.0/16, ipv6: true }
internal: { name: miabi-internal, subnet: 10.62.0.0/16 }
pool:
cidr: 10.64.0.0/12
# ipv6UlaPrefix: fd42:6d69:6162::/48
The manifest's ipv6 drives both halves: the platform's miabi-proxy network and the workspace
networks the server creates, so you cannot end up with a dual-stack proxy network in front of
single-stack app networks.
Seeing what a network actually got
The stored record says what Miabi asked for; the engine decides what exists. Two places show the engine's answer:
- A workspace's Networks page — the info action on a row opens the network's details: driver and scope, the IPv4 subnet and gateway, whether IPv6 is on, and the IPv6 subnet and gateway. The same action sits on an application's Networks table.
- Admin → Platform Settings → Networking — whether IPv6 is on for the install, how each
/64is chosen, the workspace subnet pool and the shared proxy network. It distinguishes requested from active: withMIABI_NETWORK_IPV6=trueon an engine that cannot honour it, the page says so rather than showing a switch that looks stuck.
A cluster overlay only exists on a node once one of its containers attaches there, so a network whose details read "not present on the control-plane node" is ordinary rather than broken.
Once a container is running, its Network tab lists the address it was actually given. The IPv6 column appears only when a container has one — which is also how you spot a container started before IPv6 was switched on: its network is dual-stack, and it is not.
A container's address changes on every redeploy, in both families. Address an app by its hostname or alias, never by IP.
Who picks the /64
| Address chosen by | Needs | Addresses are | |
|---|---|---|---|
| Default (no ULA prefix) | Docker | Engine 26+ | assigned by the daemon |
ipv6UlaPrefix set | Miabi, derived from the IPv4 subnet | Engine 25+ | stable and predictable |
Deriving maps the IPv4 subnet onto a /64 under your prefix, so the two stacks line up:
10.64.5.0/24 under fd42:6d69:6162::/48 becomes fd42:6d69:6162:4005::/64. The same network gets
the same range on every node and after every recreate, which is what makes a firewall rule possible
to write. The mapping is unique across the whole default pool, so no two networks collide.
Docker only assigns an IPv6 range itself from Engine 26. On Engine 25, enabling IPv6 without a ULA
prefix would make every network create fail — so Miabi refuses to enable it, logs the engine version
and the two ways forward, and carries on in IPv4. Set ipv6UlaPrefix to use IPv6 on Engine 25.
This covers bridge networks — a single-node install, and each node's local networks. Swarm overlay networks are not dual-stack yet, so a cluster's cross-node traffic stays IPv4 whatever this setting says.
A ULA (fd00::/8) range is private, like 10.0.0.0/8. But enabling IPv6 changes what a container can
reach and be reached on, and Miabi has no IPv6 firewall policy of its own yet
(the node network policy is still on the roadmap). Treat it as you would opening a new
address family on any host.
Monitoring the pool
Pool utilization is visible on the admin dashboard (a Subnet pool panel showing used vs. total subnets, with a warning as it nears capacity) and via Prometheus:
| Metric | Meaning |
|---|---|
miabi_network_subnet_pool_used | Subnets allocated or reserved. |
miabi_network_subnet_pool_total | Pool capacity. |
If the pool approaches exhaustion, enlarge MIABI_NETWORK_POOL_CIDR. See
Monitoring and Configuration.