Networking and conduits
Task-oriented recipes for reaching workloads: per-port forwards, a SOCKS5 split-tunnel, and the session conduit that selects between them. For exposing a workload publicly through a hosted tunnel, see the Tunnels guide; for wiring workloads to each other, see The workload hub.
Session conduits: port-forward vs SOCKS5
The way a session exposes workloads to the caller is its conduit mode. The default is per-port forwarding (one local listener per published port, Compose-compatible). The opt-in alternative is a single client-side SOCKS5 split-tunnel proxy: hostnames under a service-host suffix (default .cornus.internal) are tunneled to the matching workload by name, and every other destination is dialed directly from your machine. One proxy reaches every service by name, with no per-port listeners.
# Make SOCKS5 the conduit for a profile, so compose up / deploy --server use it:
cornus config set-context demo --conduit-mode socks5
# Pin the shared proxy's bind address and suffix in one value:
cornus config set-context demo --conduit-mode 'socks5://.shared:1085?suffix=.demo.internal'
# Per-run override (flag > CORNUS_CONDUIT > profile > default port-forward):
cornus compose up --conduit socks5 # join the shared proxy
cornus compose up --conduit 'socks5://' # own proxy, ephemeral port
cornus deploy --server http://cornus.example:5000 --conduit socks5 -f deploy.yamlA bare word (or socks5://.shared) joins the profile's shared proxy; a socks5:// URL with an authority spins up a private, session-local proxy that coexists with it. In SOCKS5 mode the shared per-server proxy also covers cornus daemon docker containers, so one proxy reaches Docker containers and Compose services by name. SOCKS5 CONNECT is TCP-only. The standalone ad-hoc proxy is cornus socks5.
The bind address in a socks5:// selector is loopback-only by default: the conduit proxy has no authentication and dials arbitrary destinations from your machine, so off-host it is an open proxy for anyone who can reach it. Add --allow-non-loopback to accept that exposure deliberately:
# Refused: an off-host bind with no opt-in.
cornus compose up --conduit 'socks5://0.0.0.0:10080'
# Accepted: the same proxy, exposure acknowledged.
cornus compose up --conduit 'socks5://0.0.0.0:10080' --allow-non-loopbackThe flag exists on compose up and deploy alongside --conduit, and on the standalone cornus socks5 alongside --listen. Without it the bind is refused up front, before anything is deployed.
See also: connection config, Working with remote clusters
Forward local ports to a workload
Bind a local listener per mapping and forward each connection to the first instance of a deployment, reaching ports that were never published.
cornus port-forward web 8080:80 5432:5432- Each mapping is
LOCAL:REMOTE(or a barePORT), optionally with a/tcpor/udpsuffix, e.g.cornus port-forward dns 5353:53/udp. --address 0.0.0.0binds all interfaces; UDP works on the dockerhost/containerd/bare backends but Kubernetes port-forward is TCP-only.
On the dockerhost backend the server dials the workload's container IP, so it needs a route to it. Two cases where that is not automatic, both of which report the cause rather than timing out:
macvlan/ipvlannetworks. A macvlan container cannot be reached from its own host — that is the driver's design, not a misconfiguration. If the workload is also on a bridge network, cornus dials it there; if it is on macvlan only,port-forwardsays so and your options are to publish a port, add a bridge network, or connect from another machine.- A remote
DOCKER_HOST. Container IPs on another machine's daemon mean nothing locally. SetCORNUS_DOCKER_REMOTE=1(withCORNUS_AGENT_IMAGEandCORNUS_ADVERTISE_URL) so the server reaches workloads through a per-instance companion.
A cornus server that is itself a container has a third case, handled automatically — see Server in a container.
The other host backends answer the same question differently:
containerdandbareneed nothing. Cornus builds their networks itself, with CNI, in whatever network namespace cornus is in — so the workload is always somewhere cornus can reach. What a containerized server costs there is a published port, not a port-forward: without host networking the port-publishing NAT rules land inside the server's own container. On containerd cornus can detect that and refuses to start; see Server in a container.incusinstances sit on incusd's own bridge, which the daemon host can always route to. A server containerized beside incusd cannot, and cannot join that bridge either — a cornus container is not an incus instance, so there is no equivalent of the docker self-attach. Give that container the host's network namespace, or run the server as an incus instance itself (then it is on the bridge alongside its workloads, and cornus recognizes it), or setCORNUS_INCUS_REMOTE=1(withCORNUS_AGENT_IMAGEandCORNUS_ADVERTISE_URL) to reach instances through a per-instance companion.port-forwardnames whichever of these is missing rather than timing out. See Server in a container.
See also: cornus port-forward
Run a SOCKS5 split-tunnel proxy to reach services by name
Bind a local SOCKS5 proxy that tunnels service-suffixed hosts into the cluster and dials everything else directly.
cornus socks5
curl --socks5-hostname 127.0.0.1:1080 http://web.cornus.internal/- Any host ending in
--service-host-suffix(default.cornus.internal) is tunneled to the matching service; the suffix is stripped to derive the service name. --resolve 'PATTERN=REPLACE'is the advanced form (ordered, first match wins, sed-style\1backreferences) and replaces the suffix default.
See also: cornus socks5
Choose a conduit for a deploy or compose session
Pick how a --server session exposes workload ports to you: per-port listeners or one SOCKS5 proxy.
cornus deploy -f app.yaml --server https://cornus.example.com --conduit socks5
cornus compose up --conduit port-forward- Precedence is
--conduit, thenCORNUS_CONDUIT, then the profile mode;--no-forward-portsdisables the conduit entirely. - A bare word sets only the mode; a
socks5://host:port[?suffix=SUFFIX]URL also sets the bind address and service-host suffix.
See also: cornus deploy
Reach a whole Compose stack and its web UI through one browser proxy
Run the Compose stack in SOCKS5 mode and publish the cornus web UI into the same shared conduit, so a single browser proxy setting reaches every service and the UI by name.
# 1. Make socks5 the conduit for this connection (once per profile).
cornus config set-context --conduit-mode socks5
# 2. Bring the stack up detached. In socks5 mode the background agent hosts one
# shared proxy and registers each service's short name in it.
cornus compose up -d
# 3. Publish the web UI into that same shared conduit (binds no local port).
cornus web --publish-in-conduitPoint your browser's SOCKS5 proxy at the agent's proxy — the cornus socks5 / profile listen address, 127.0.0.1:1080 by default — with remote DNS (SOCKS5h). One setting then reaches all of:
http://web.cornus.internal/— the Compose service namedweb(its short name, registered by the socks5-modecompose up).http://db.cornus.internal:5432/— any other service, likewise by short name.http://cornus.internal/— thecornus webUI.
How it fits together:
- All three share one background agent, one connection, and one SOCKS5 proxy.
compose up -d,cornus daemon docker, andcornus web --publish-in-conduitall join the same shared conduit keyed on the connection and its socks5 settings. - The Compose short names (
web, not the deployment namedemo-web) resolve only because the workload sessions run in socks5 mode — step 1 is what registers them. If your stack runs in the default port-forward mode, the UI still publishes and services still resolve by their full deployment name (demo-web.cornus.internal), but the short names do not. - The web UI binds no port of its own; it is reachable exactly where the proxy is, so it inherits the proxy's loopback boundary rather than adding a new surface.
cornus web --publish-in-conduitneeds no conduit settings of its own: it joins whichever shared conduit the agent is already running for the connection. Naming an address or a suffix in its--conduitpins those settings instead, which is how you would deliberately run a second proxy.compose up -dandcornus daemon dockerstill key on their own settings, so keep those two consistent with each other (the same--conduitURL, or both relying on the profile). Divergentlisten/suffixvalues make the second command's proxy collide with the first on its bind address — the agent now refuses that by naming the session already holding the address, rather than passing the raw bind error through.
See also: cornus web, cornus compose, cornus socks5
Reach a workload's ingress host through the conduit
Reach a workload at its declared x-cornus-ingress hostname (e.g. web.example.com) from your machine, with no real DNS — opt in with --ingress-conduit on a socks5 session.
# native: tunnel to the real cluster ingress controller (kubernetes + kube access)
cornus compose up --conduit socks5 --ingress-conduit native
# emulate: a client-side reverse proxy with a generated cert (any backend)
cornus deploy -f app.yaml --server https://cornus.example.com \
--conduit socks5 --ingress-conduit emulate
curl --socks5-hostname 127.0.0.1:1080 \
--cacert ~/.local/share/cornus/ingress-ca.pem https://web.example.com/- native hands the browser's SNI/
Hoststraight to the real controller, which routes and terminates TLS with the cluster cert; emulate proxies byHost/path to the workload and terminates TLS locally — signed by mkcert's CA when it is installed (mkcert -install, then browsers trust it automatically), else a self-signed CA (~/.local/share/cornus/ingress-ca.pem) you trust once. - Precedence is
--ingress-conduit>CORNUS_INGRESS_CONDUIT> the profile (cornus config set-context --ingress-conduit);offdisables it.cornus setupprobes the cluster and picks a default. Use remote DNS (socks5h) in your browser.
See also: Ingress, cornus config