Skip to content

feat: app-to-app network linking via networkWith description token#1736

Open
Cabecinha84 wants to merge 3 commits into
developmentfrom
dependson
Open

feat: app-to-app network linking via networkWith description token#1736
Cabecinha84 wants to merge 3 commits into
developmentfrom
dependson

Conversation

@Cabecinha84
Copy link
Copy Markdown
Member

@Cabecinha84 Cabecinha84 commented May 22, 2026

Summary

Lets an app share a private network with other apps and reach their
containers — as if both apps were one. The owner opts in by embedding a
token in the app description text:

networkWith:[appA,appB]

Brackets are required, quotes are optional, the networkWith key is matched
case-insensitively, and names are comma-separated. An app whose description
has no (or a malformed) token behaves exactly as before.

This is purely node-local behaviour — no app specification field, no
validation change, no network/consensus impact. The description field
already exists and is signed; the node simply parses it.

A second, related capability rides on top of the same link: a LOG=SEND
component in app B can now ship to a LOG=COLLECT component in a linked
app A (previously only same-app sender→collector worked).

Behaviour

When the token is present:

  • Before install / soft redeploy / hard redeploy, the node verifies every
    named app is installed locally and owned by the same owner — otherwise
    the operation fails.
  • Each of the app's component containers is attached to the private docker
    network of every linked app (fluxDockerNetwork_<linked>), so it can reach
    that app's components by their docker DNS name flux<component>_<linkedApp>.
  • When a linked-to app is itself (re)deployed, every locally installed app
    networked with it is reconnected to its network (auto-heals the gap a hard
    redeploy would otherwise leave).

If a linked app is uninstalled, the dependent keeps running and is
automatically reconnected when the linked app returns.

Cross-app log shipping

If a LOG=SEND component is being installed in an app whose own compose has
no LOG=COLLECT component, the node walks every networkWith-linked app in
declaration order and ships to the first linked app exposing a collector. The
container's Docker GELF log driver is wired to the linked collector's IP at
create time; reachability is provided by the network linking above.

Fallbacks (warning logged, container drops to json-file logging):

  • the linked collector container exists but isn't reachable at install time;
  • the linked app is enterprise with compose blanked in the local DB and
    cannot be decrypted on this node.

Implementation

  • New module appLifecycle/appNetworkLinker.js — token parser, install
    gate, forward/reverse network wiring, and cross-app log-collector
    resolution (findLinkedAppLogCollector).
  • dockerService additionsappDockerNetworkConnect (idempotent
    attach via inspect-first), getAppContainerNames /
    getAppContainerObjects (anchored regex match for v4+ component
    containers plus literal match for legacy v≤3 single-component containers;
    also consumed by getNextAvailableIPForApp to eliminate the duplicated
    inline filter).
  • appDockerCreate falls back to findLinkedAppLogCollector for LOG=SEND
    components when no in-compose collector exists.
  • The gate and forward wiring run inside installApplicationHard /
    installApplicationSoft (the only callers of appDockerCreate), so every
    container-creation path is covered
    , including direct callers that bypass
    registerAppLocally (container health recovery, legacy v≤3 redeploys). The
    gate also runs early in registerAppLocally / softRegisterAppLocally to
    fail fast before any side effects.
  • Reverse wiring runs in registerAppLocally / softRegisterAppLocally; a
    boot-time reconcile sweep in appStartupManager re-applies all links.
  • Validation-side regex constants (APP_NAME_REGEX, APP_NAME_REGEX_LEGACY)
    centralised in utils/appConstants.js and consumed by both appValidator
    and appNetworkLinker.

appUninstaller, appSpawner, appValidator (behaviour) and routes are
unchanged.

Testing

  • tests/unit/appNetworkLinker.test.js — 30 cases (parser edge cases,
    install gate, forward/reverse wiring, boot reconcile, cross-app log
    collector resolution including the legacy enviromentParameters typo and
    blanked-compose enterprise fallback).
  • tests/unit/dockerService.test.js — 6 cases for appDockerNetworkConnect
    (inspect-first / already-attached / inspect-fails / race-window /
    generic-404 / overloaded-403) plus 2 cases for getAppContainerNames
    (multi+legacy match anchored to flux/zel; regex-metachar escape).
  • All affected suites baseline-compared: zero regressions across 380 unit
    tests in the touched modules.

Notes for reviewers

  • Apps must be co-located for network linking to work — placement is
    passive: an app simply fails to install on nodes lacking its linked apps
    and the spawn loop retries elsewhere.
  • A network link may only target an app with the same owner.
  • The declaration is one-directional (app B writes networkWith:[A]; A
    declares nothing), but the resulting reachability is mutual.
  • Cross-app traffic stays within the existing 172.23.0.0/16 range already
    permitted by the DOCKER-USER firewall rules.
  • Cross-app log shipping inherits the same co-location and same-owner
    guarantees from the network link it rides on. The linked collector must be
    running when the sender installs; if it isn't, the sender silently drops
    to json-file (a warning is logged) and there is no auto-rewire when the
    collector later starts. Acceptable for v1; a reactive rewire can follow.

An app owner can link an app to other apps by embedding a token in the
app description text: networkWith:[appA,appB] (brackets required, quotes
optional, key case-insensitive, comma separated). This is purely
node-local behaviour — no app specification field, no validation change,
no network consensus impact.

When the token is present:
- Before install/redeploy, the node verifies every named app is
  installed locally and owned by the same owner; otherwise the operation
  fails.
- Each of the app's component containers is attached to the private
  docker network of every linked app (fluxDockerNetwork_<linked>), so it
  can reach that app's components by docker DNS name
  flux<component>_<linkedApp>, as if both apps were a single app.
- When a linked-to app is (re)deployed, any locally installed app that
  is networked with it is reconnected to its network.

New module appNetworkLinker.js holds the parser, the install gate, and
the forward/reverse network wiring. The gate and forward wiring run in
installApplicationHard/installApplicationSoft (the only callers of
appDockerCreate), so every container-creation path is covered, including
direct callers that bypass registerAppLocally (container health recovery
and legacy v<=3 redeploys). Reverse wiring runs in registerAppLocally and
softRegisterAppLocally; a boot-time reconcile sweep re-applies all links.

dockerService gains an idempotent appDockerNetworkConnect helper.

Adds tests/unit/appNetworkLinker.test.js (parser, gate, wiring,
reconcile) and appDockerNetworkConnect coverage in dockerService.test.js.
@Cabecinha84 Cabecinha84 changed the title feat: app-to-app dependency networking via dependsOn description token feat: app-to-app network linking via networkWith description token May 22, 2026
Copy link
Copy Markdown
Contributor

@MorningLightMountain713 MorningLightMountain713 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. Small changes

Comment thread ZelBack/src/services/appLifecycle/appNetworkLinker.js Outdated
Comment thread ZelBack/src/services/appLifecycle/appNetworkLinker.js Outdated
Comment thread ZelBack/src/services/dockerService.js Outdated
- extract APP_NAME_REGEX (v8+) and APP_NAME_REGEX_LEGACY (v<=7 / components)
  into appConstants; consume from appValidator and appNetworkLinker
- move getAppContainerNames / getAppContainerObjects into dockerService;
  anchor the multi-component match to ^(?:flux|zel)[a-zA-Z0-9]+_<app>$ and
  escape regex metacharacters in the app name; refactor
  getNextAvailableIPForApp to use the same helper
- rewrite appDockerNetworkConnect to inspect the container's
  NetworkSettings.Networks first and skip the connect when already
  attached; drop the blanket 403 catch (overloaded by docker) in favour of
  a narrow already-exists message match as a TOCTOU race fallback
- update affected unit tests
When a SEND component is being installed in an app whose own compose has no
LOG=COLLECT component, walk every app it is networkWith-linked to and ship
to the first linked app that exposes a collector. Reachability is provided
by the existing networkWith wiring (sender's container is already attached
to the linked app's private docker network).

Enterprise linked apps whose compose is blanked in the local DB and cannot
be decrypted on this node are skipped — the SEND container falls back to
json-file logging with a warning. Same fallback applies if the collector
container is not reachable at install time.

- new appNetworkLinker.findLinkedAppLogCollector(fullAppSpecs) that resolves
  the linked app + component name (handles the legacy enviromentParameters
  typo too)
- appDockerCreate calls it as a fallback after the existing in-compose
  collector lookup, only for SEND components
Copy link
Copy Markdown
Contributor

@MorningLightMountain713 MorningLightMountain713 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ack

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants