Skip to main content

Networking

Clouduler networking answers three questions: how do your containers talk to each other across machines, how do they reach the outside world, and how does the outside world reach them. You answer them by declaring networks in the cluster config and attaching services to them in your stack files.

This page assumes you know what a container is, but not what VXLAN is. New terms are in the glossary.

How it works​

A cluster has a handful of named networks. Each one has a kind that says what it is for.

KindIn one sentenceSubnetDomainPer-node setting
internalThe one trusted network nodes use for raft, drive sync and the container overlay. Exactly one is required.requiredoptionalan IP, on every node
privateAn extra trusted LAN-style network with a subnet.requiredoptionalan IP, on nodes that use it
publicReachable from the internet. No subnet. Hostnames resolve to each node's public address.noneneeded for web routes and DNSan ipv4 / ipv6 / domain block and optional open_ports
egressForces a service's outbound traffic out of one named host interface (for example a VPN), with a kill-switch.nonenot allowedthe host interface name
wireguardLike egress, but Clouduler creates the WireGuard tunnel from a config you import.nonenot allowedset by importing a config
natLike egress, but uses the node's normal routing (ordinary masquerade). No setup.nonenot allowednothing

The container overlay​

Containers on different machines reach each other through an overlay: a virtual network built on VXLAN (a tunnel that carries ethernet frames over UDP port 4789). You do not set it up. For each stack and each internal, private or public network it uses, every node builds a bridge plus a VXLAN device, with a static list of peers (no multicast). Containers get addresses from a cluster-wide pool, 10.244.0.0/16, carved into one /24 per overlay. The gateway is .1 and is the same on every node.

Containers find each other by name. The cluster DNS answers on the overlay gateway:

NameAnswer
SERVICE.STACKevery running instance, round robin
tasks.SERVICE.STACKthe same set (Swarm style)
NODEthat node's internal IP
a proxy hostname on a public networkthis node's internal IP, so containers see the same service the outside sees
anything elseforwarded to the host's upstream resolvers

Inside a stack, the bare service name also works (db), because each container gets a search list of STACK.DOMAIN and DOMAIN. Answers have a 5 second TTL.

The overlay MTU is the underlay MTU minus 50 bytes, so 1500 becomes 1450. This is derived automatically from the link that holds the node's internal IP. Egress bridges always use 1500.

Declare a network​

Networks live in the cluster config, not in a stack. Each node says how it is attached to each network.

Open Settings, tab Network, page Networks, and click Add network. Rename the card with the pencil button, then choose Network type: Internal, Private, Public, Egress, WireGuard or NAT. Depending on the type you also fill in Address range (CIDR), DNS domain, and Publish to DNS provider (Cloudflare). Save the page; risky changes ask for confirmation.

Then, in the node settings, a Network addresses section has a field for each network the node is on: Address on NETWORK for internal and private, Public IPv4, Public IPv6, Alias hostname and Open ports for public, and Interface, Use a backup interface, Fallback interface, Health check address and Open ports for egress. A nat network shows no field at all.

/settings/network
Settings, Network tab.

A read-only overview of each network is at /networks/NAME, listing the nodes on it and whether they are reachable.

/networks/lan
Network detail page.

Network options​

Option
Type / default
Reload
Description
enumno default
live reload
One of internal, private, public, egress, wireguard, nat.
Watch out: Exactly one internal network is required.
CIDRno default
live reload
Address range for node IPs. Required for internal and private, forbidden for the other kinds.
Watch out: Every node IP on the network must be inside it. The container overlay skips this range.
DNS nameno default
live reload
Names under it resolve to node addresses on this network. Required for proxy routes and DNS on a public network. Forbidden for egress, wireguard and nat.
Watch out: No leading or trailing dot, unique across networks. When Cloudflare is configured the domain must be inside a configured zone: DOMAIN is not covered by any configured cloudflare zone.
booldefault true
live reload
false keeps the network's hostnames out of Cloudflare; only the cluster DNS server answers them.
Watch out: Only internal and private networks may set false. A public network always publishes.

Node address options​

Option
Type / default
Reload
Description
IPno default
live reload
The node's address on the network: raft, drive sync and the overlay underlay.
Watch out: Must be inside the subnet. Internal addresses are unique per node. Every node needs one on the internal network.
IPv4no default
live reload
A record for the public name. Set at least one of ipv4, ipv6, domain.
Watch out: A bare IP on a public network is an error.
IPv6no default
live reload
AAAA record.
DNS nameno default
live reload
CNAME record instead of an IP.
list of {port, protocol}default []
live reload
Ports your router forwards to this node. Gates placement of raw expose ports. protocol defaults to tcp.
interface nameno default
live reload
Host interface a service on the network sends its traffic out of. Letters, digits, _ . - and at most 15 bytes.
Watch out: A bare YAML IP parses as an IP, not an interface. Clouduler never creates this interface.
strno default
live reload
Same as the bare form, when you also need open_ports or fallback.
strno default
live reload
Interface to route out of when the primary stops answering. Must differ from the primary.
IPno default
live reload
Address pinged out of the primary interface every tick (ping -I, 2 s wait).
Watch out: Required with fallback. A missing ping binary counts as unreachable, so the fallback is used. Ignored on wireguard.
set by importno default
live reload
The tunnel interface wg-NETWORK (the network name is shortened if needed to fit Linux's 15-byte interface name limit), stored when you import a wg-quick config. open_ports works as for egress.
Watch out: Set by the import flow. A hand-written bare host interface name is also accepted, the same shape as egress, but use the import.
forbiddenno default
live reload
A nat network takes no node address.
Watch out: Any entry is rejected.
u16default 7373
needs restart
Raft port. The drive transport uses +1 and the container relay +2.

Use a network from a stack​

Declare the networks at the top of the stack file, then list them on each service.

stack.yaml
networks:
lan: {} # empty is fine: no allowed list means unrestricted
home: {} # a nat network, so the service can reach the internet

services:
web:
image: example/web:1
networks: [lan, home] # one overlay plus at most one egress, wireguard or nat

Clouduler picks the stack's overlay network (the one that provides DNS and an overlay address) as the first internal or private network in alphabetical order of network name (not declaration order), otherwise the first public network, otherwise none. A stack that only uses egress, wireguard and nat networks has no overlay: no internal DNS, no proxy gating, and Docker's default resolver.

Option
Type / default
Reload
Description
map or emptyno default
n/a
Declares a cluster network the stack's services may use.
Watch out: Must exist in the cluster.
list of strdefault unrestricted
n/a
Restricts what the stack's containers can reach.
Watch out: `host:port` entries are rejected on networks that are not overlay networks.
list of namesdefault overlay only
n/a
Attaches the service to the networks.
Watch out: At most one egress, wireguard or nat network per service. Each must be implemented by some node (nat excepted).
nameno default
n/a
Network a route is published on. Decides gating.
Watch out: Must have a domain.
nameno default
n/a
Network whose reachability gates placement.
Watch out: For public or wireguard, some node must list the port in open_ports.
u16no default
n/a
Port bound on every node.
Watch out: Unique within a stack. Across stacks the first by name wins and the other is ignored with a warning.
booldefault false
n/a
Send a PROXY v2 header with the real client address. TCP only.

Stack networks must exist in the cluster, and some node must implement each one (except nat). Add the node's address first, then deploy. The CLI says no network named NET declared in the cluster or no node implements network NET, this service could never be placed. The web UI does not check this at deploy time; the same problem shows up on the stack's Placement tab (see Placement).

Publishing a service​

There are four ways to let the outside reach a service. Pick by protocol and by whether the outside needs a name.

MethodConfigWhat the outside seesNeeds
Proxy route (HTTP/HTTPS)x-clouduler.proxy on a network with a domainhost.domain with TLSthe proxy on ports 80/443, and for a public network a public address per node
Raw TCP/UDP portx-clouduler.exposeNODE_IP:publishedon a public network, a node whose router forwards the port (open_ports)
Port through a WireGuard tunnela WireGuard ingress rulethe tunnel's far end forwards to the nodethe container publishes target_port with ports:
Docker ports:plain publishthe node's host address and portnothing managed by Clouduler

A proxy route on a stack looks like this. A bare host gets the network's domain appended:

stack.yaml
networks:
wan: {}
services:
app:
image: example/app:1
networks: [wan]
x-clouduler:
proxy:
- host: app # becomes app.example.com
network: wan
port: 8080

For HTTP details (middlewares, TLS, authentication) see Proxy and TLS and for public DNS records see DNS publishing.

Who can use a route. A route on a subnetted network (internal, private) only accepts a client whose connection comes from inside that network's subnet (or a trusted overlay). Anyone else gets 403 this route is only reachable from its own network. A route on a public network has no such gate. To publish externally, use a public network.

Raw ports with expose are for things like SSH:

stack.yaml
services:
git:
image: example/git:1
networks: [wan]
x-clouduler:
expose:
- port: 22
published: 2222
network: wan
proxy_protocol: false

expose forwards in user space, so the backend sees the node's bridge address as the client. Use proxy_protocol: true (TCP only) and enable PROXY protocol v2 on the backend first, or the connection breaks. A published port with no running instance stays bound and refuses connections.

Where DNS and proxy settings live​

Option
Type / default
Reload
Description
web.networkrequired
nameno default
live reload
Network the dashboard hostname resolves through. Must have a domain.
all-nodes or owning-nodedefault all-nodes
live reload
all-nodes publishes every node's address for each proxied hostname. owning-node publishes only nodes running the containers and pins all containers sharing a hostname to one node.
Watch out: No UI control: set it in cluster.yaml.
booldefault false
live reload
Run a cluster DNS server on each node's address, answering by the client's network.
list of namesdefault every subnetted network the node is on
live reload
Networks the DNS server answers for. A public network is only served if listed.
u16default 53
needs restart
Port the DNS server listens on.
u16default 80, 443
live reload
Proxy listener ports. Also the ports containers are allowed to reach on a node.

Allowed hosts and ports​

By default a stack's containers can reach whatever their networks let them. An allowed list on a network narrows that to a short list.

stack.yaml
networks:
lan:
allowed:
- auth.example.com # hostname only: enforced by the proxy
- "*.example.net" # wildcard host, no port
- db.example.com:5432 # host:port, enforced by the firewall
- 192.0.2.10:9000-9010/udp # IPv4 literal with a port range and protocol
services:
app:
image: example/app:1
networks: [lan]
  • Entries are host, *.suffix, host:port, host:lo-hi, host:port/udp, or an IPv4 literal. allowed: [] denies everything. A bare allowed: with no value is an error.
  • On an overlay network (internal, private or public), hostname-only entries are enforced by the proxy: a blocked request gets 403 stack STACK may not reach HOST through network NET: it is not in the network's allowed list. Entries with ports are enforced by the host firewall (CLDR-ALLOW).
  • On an egress, wireguard or nat network, every entry is enforced by the firewall, with hostnames resolved through the stack's DNS. The stack must also declare an internal, private or public network, otherwise you get network NET has an allowed list ... the stack needs an internal, private or public network as well.
  • A wildcard host cannot take a port on an overlay network: a wildcard host is checked by the proxy here and cannot take a port.
  • An allowed entry can only narrow what routing already carries. It cannot open a host port the firewall keeps closed.

WireGuard networks​

A wireguard network lets Clouduler bring up a tunnel for you on each node, and send a service's traffic through it. It is the easy way to route a service through a VPN provider.

  1. Declare the network with type: wireguard in the cluster config.
  2. For each node that should have a tunnel, import a wg-quick config. The config is stored as a sealed secret and the node's interface is named wg-NETWORK.
  3. Attach services to the network in the stack.

In the node settings the network appears as a card titled NETWORK (wireguard) with Config source (Paste or File), a text area and an import button. After import it reads "Imported. Tunnel interface: wg-NETWORK", with an Open ports editor and a Remove button. The import applies immediately, like a secret.

To let a port in through the tunnel, open Proxy, tab Ingress rules (/proxy/ingress), click New ingress rule, and fill in Network, Port (on the tunnel), Protocol and Target port (on this node). The target is a host port that a container publishes with ports:.

/proxy/ingress
Proxy, Ingress tab: one rule for the vpn network, 51820/udp.
Option
Type / default
Reload
Description
body {content}no default
live reload
Import a wg-quick config for one node. content is base64 text. Returns the interface name.
Watch out: The node must hold the cluster key (otherwise: this node holds no cluster key). Limit 1 MiB.
no default
live reload
Remove the config. The tunnel is torn down on the next tick.
strno default
live reload
Required. Address uses the first entry only.
strno default
live reload
Required, and at least one [Peer].
no default
live reload
Optional peer settings. AllowedIPs must be CIDRs.
u16no default
live reload
Optional local UDP port.
no default
live reload
Silently ignored: Clouduler does its own routing.
nameno default
live reload
Which wireguard network the port comes in on.
u16no default
live reload
Port on the tunnel.
Watch out: Not checked against open_ports, so a rule can open a port nothing forwards.
tcp or udpdefault tcp
live reload
Protocol.
u16no default
live reload
The host port a container publishes with ports:. A DNAT to this node's internal address is added when it differs from port.

Import errors you may see (wrapped as parsing wireguard config: ...): missing PrivateKey in [Interface], missing Address in [Interface], no [Peer] section found, a [Peer] section is missing PublicKey, invalid AllowedIPs entry ..., invalid Address ..., malformed line: .... Other errors: NETWORK is not a wireguard network, wireguard config must be utf8.

The firewall chains​

Clouduler manages host firewall rules with iptables. It only touches chains with its own names, rebuilds them every 10 seconds (so a hand edit to them is overwritten), and re-checks the jumps so they survive a Docker restart.

ChainWhat it does
CLDR-IN (filter, INPUT)Stops containers reaching the node's own listeners. From container addresses it accepts only: replies, DNS, the proxy's HTTP and HTTPS ports, every expose port, and ping. Everything else, including the API, raft, drive and relay ports, is dropped. Docker -p publishes bypass it.
CLDR-OVL-SNAT (nat, POSTROUTING)Masquerades overlay traffic to another node's proxy ports (and ping), so the remote proxy sees a node address and can apply its network gate.
CLDR-NAT-BRIDGE (nat)Masquerades an egress bridge out of its interface.
CLDR-FWD-BRIDGE (filter)The egress kill-switch: allow bridge to interface, drop everything else from the bridge. If the interface has no route, the bridge is locked down.
CLDR-WGIN-IFACE (filter, nat)Default deny from a WireGuard tunnel, then each ingress rule. Adds a DNAT to the node's internal IP when the target port differs.
CLDR-ALLOW (filter, DOCKER-USER)Enforces allowed entries with ports. Exists only when some stack has an allowed list.
CLDR-ALLOW-NAT (nat, PREROUTING)Sends a restricted stack's proxy traffic to this node's own proxy so the proxy can tell stacks apart.

In plain words: a container can reach the proxy on any node, the DNS on its gateway, and any expose port on any node. It cannot reach anything else on the host, and it cannot reach the internet unless it is on an egress, wireguard or nat network.

For a host process to reach a container on another node, Clouduler uses a relay on the node's rpc_port + 2 (7375 by default), authenticated with the cluster key. This port must be open between nodes on the internal network.

Rename a network​

Renaming rewrites the network everywhere: the cluster config, node address maps, web.network, every deployed stack, redirects, static routes, WireGuard ingress rules and sealed WireGuard secrets. Use the pencil button on the network card in Settings (it calls POST /api/config/networks/NAME/rename with {"new_name": "..."}). There is no CLI command.

Quirks and gotchas​