The fleet file — clanker.yml

The host-level file: one per host, owned by the host owner. It names the host and the repositories every engineer’s pod on that host holds, each at a pinned ref, and who may hold them. A repository’s own container settings are not here — each repository carries its standard dev-container file. Where it all runs is the host spec the fleet points at. From nothing to a first pod: Quickstart.

# yaml-language-server: $schema=https://clanker.net/schema/clanker-fleet.v1.json
version: 1
host: ./hosts/dev-host.yml # or a repo: reference, or the host spec inline
pod: # present → one POD per engineer; absent → one container per repo
  default: acme/api # what a fresh pod shows first
repos:
  - url: https://github.com/acme/shop
    ref: develop # a branch (resolved at every read) or a 40-character sha
    devcontainer: .devcontainer/devcontainer.json # the default
    access: { github: { org: acme } } # REQUIRED — who may hold a pod with it
  - url: acme/api # the bare slug is the same repository
    ref: main
    access: { github: { org: acme } }
  - url: acme/handbook
    ref: main
    devcontainer: false # no container: a clone every member of the pod can see
    access: { github: { org: acme } }
sync:
  schedule: "0 6 * * *" # when the daily fleet-sync runs, optional
  • Schema: https://clanker.net/schema/clanker-fleet.v1.json.
  • Validate: replicator validate clanker.yml. The report names the host, every enabled repository with the sha its ref resolved to, the file that was read and its ssh port, and every enabled: false entry as skipped. --offline makes no network call (entries are listed unresolved with a warning); --json gives the same as data.

The fleet file lists the repositories a host runs. It does not describe a repository’s own staging, production or sites: that is the repository’s environments file, run by the same engine and unrelated to which hosts list the repository.

What a pod is

With pod: present, every engineer who provisions on this host holds ONE pod named after them: a member for each repository that has a dev-container file, a workspace clone for each one that does not — all behind ONE public name and ONE address on your private network.

flowchart TB
  subgraph pod["Pod dev-alice — one address on your private network"]
    direction TB
    door["Port 22 → the <b>selected</b> member"]
    m1["Member <b>shop</b><br/>its database, its ssh port 2201<br/>runs /workspace/shop"]
    m2["Member <b>api</b><br/>its ssh port 2202<br/>runs /workspace/api"]
    ws["<b>/workspace</b> — the shared root, seen by every member<br/>shop · api · handbook (a workspace clone)<br/>CLAUDE.md · AGENTS.md · the <code>clanker</code> agent"]
    door -. currently .-> m2
    m1 --- ws
    m2 --- ws
  end
  public["<b>dev-alice.your.zone</b><br/>and app-dev-alice.your.zone …"] -- "the selected member" --> door

Members share one network: they reach each other on localhost, and every port a member declares is a port of the whole pod (which is why two members cannot declare the same number). Each member keeps its own storage — its clone, its database, its keys — and every checkout is a sibling in every member: a member’s clone is also mounted, read-write, at /workspace/<name> in every other member, beside the workspace clones. So from any member ls /workspace lists every repository of the fleet; the files are the ones each member runs. What stays a member’s own is its runtime — its processes, ports, hooks and tmux session. (pod.siblings: false keeps each clone inside its own member; repos[].pod.sibling: false keeps one clone out.) Nothing is lost on a teardown; only purge removes storage.

The shared root is also a project for the coding agents: it carries a CLAUDE.md you may edit, an AGENTS.md generated at every boot (every repository, its path, its ssh port, which one the public name shows), a clanker agent that works inside one repository, and the coordination server. A session started at /workspace fans out one clanker per repository and closes with the checks each repository names; a session inside one repository follows that repository’s own instructions. The members’ secrets are denied to that root session on purpose.

Without pod:, the fleet keeps the single-container shape: one container per engineer per repository, each with its own name and address.

Fields

Strict throughout: an unknown key is an error, not a silent no-op.

version (required)

2.

host (required)

Where the fleet runs — a host reference as a path, a repository reference, or the host spec inline:

FormResolved from
./hosts/<name>.ymla file relative to the fleet file
repo:<owner>/<repo>@<ref>:<path>.ymlthe host owner’s repository at that ref (a private one needs the workflow’s checkout_app_from)
an objectthe host spec itself, in place

--host on the command line overrides it: a host id, a path, or a reference.

repos[] (required, unless discover lists them)

KeyTypeDefaultMeaning
urlrepository URL or owner/name—The repository; the URL and the bare slug are one identity, and a repository listed twice is refused
refbranch/tag or 40-hex sha—Where the dev-container file is read. A branch means “whatever it is at read time” — the run summary records the sha it resolved to; pin a sha for reproducibility
devcontainerrepo-relative path, or false.devcontainer/devcontainer.jsonThe file to read at ref. false makes the entry a workspace: no container runs — the repository is cloned at /workspace/<name>, where every member sees it (needs pod:). A branch ref is fast-forwarded on every start and sync; a sha holds it at one commit
accessobject— (required)Who may hold a pod with this repository on this host, and how they reach it. Below
enabledbooleantruefalse keeps the entry in the file but out of every read: not fetched, not converged. In a pod fleet its member leaves every pod like a removed one (storage kept), and it still counts for the ssh port table, so its port waits for it
ownerteam name (lowercase letters, digits, dashes)—The team that owns the repository. replicator plan shows it next to the entry, and a host’s gateway.admit.owners (hosts) admits by it. It grants nothing on its own
pod.sshPort1–65535, not 22the port the pod already gave it, else the next free oneThe member’s ssh port on the pod’s address (needs pod:). A port is given to a repository the first time the pod sees it and kept: inserting or reordering repos moves nobody, and a port a repository gave up is never handed to another. Pin it to choose the port yourself; two entries on one port are refused. A pod with no history starts from 2200 + the entry’s place among container entries, and keeps those ports from then on
pod.startauto | on-demand | cloneautoHow the repository joins a pod (needs pod:). auto: its container is always part of the pod. on-demand: a clone at /workspace/<name> until someone selects it, which starts that one container and touches no other. clone: only a clone — its dev-container file is never read. Below, “Many repositories”
pod.siblingbooleantrueWith pod.siblings on, this clone is mounted in every member at /workspace/<name>. false keeps this one clone inside its own member (which still sees the others)

No entry is flagged as the coordination repository. Every enabled entry is tracked by the host’s coordination server, and push access to any of them is what lets a person sign in. In a pod fleet the pod coordinates in one workspace, named for the pod.default entry (else the first enabled one): every member works in it, whichever member boots. A host spec with a gateway: block needs at least one enabled entry (host up and host converge refuse otherwise); a host without that block needs none.

The file is read at the sha the ref resolved to, so a push between two reads cannot straddle a run. A repository with no dev-container file at its ref fails naming the path, the ref and the sha:

fleet repo `acme/api`: no `.devcontainer/devcontainer.json` at ref `develop` (b2c…) — every fleet repo carries its dev-container file at the pinned ref

access

Who may hold a container of this repository on this host, and how they reach it:

KeyTypeDefaultMeaning
github.orglogin—Only members of this organisation may provision. Needs an org-read token by name in the host’s secrets store
github.userslogin[]—An explicit allow-list; needs no token
ssh.keySourcegithub-keys | sshid | listgithub-keysWhere authorised keys come from. github-keys = the login’s published keys
ssh.users.<login>.sshidstringthe loginsshid.io username override
ssh.users.<login>.keysstring[]—Static keys, for list
tailscale.hostnametemplate (single label)first label of the default hostnameThe container’s name on your private network — single-container fleets only. In a pod fleet the pod has ONE address (pod.tailscale.hostname) and this key is a warning asking you to remove it

Required, with github.org or github.users inside it. “Who may hold a pod on this host” is a grant of the host owner’s compute, so the host owner states it, in the host owner’s file — a repository cannot grant itself a host, which is why access is refused inside a dev-container file and why an entry without it is an error rather than an open door.

sync

KeyTypeDefaultMeaning
schedulefive-field cron expression—When the daily fleet-sync re-reads every repository at its ref and converges every pod on the host

policy — the organisation’s rules for environments

Optional. Rules the host owner sets once for every repository’s environments (replicator infra diff|deploy --policy clanker.yml, see Environments). They sit beside the built-in checks that never delete a data resource, a DNS record or anything on a protected stage.

KeyTypeMeaningA refusal names
allowedRegionslist of locationsThe only locations a server, database or bucket may be created in, as the component spells them (hel1, WEUR, aws-eu-central-1); compared without regard to caseregion
requiredLabelsmap of label to valueLabels every labelled resource (servers) must carry. A value of "" only requires the labellabel
protectedKindslist of kindsKinds of resource never deleted or replaced on any stage, whatever the caller allows: bucket, database, dns, site, secrets, serverprotected-kind
maxStagesinteger, 1 to 50The most stages one repository’s environments file may declarestage-count

pod — one pod per engineer

KeyTypeDefaultMeaning
hostnames.defaulttemplate ending in .{zone}dev-{user}.{zone}The pod’s one public name, answered by the selected member. Must contain {user}; {repo} is refused (one name for the whole pod). Its first label names the pod
hostnames.porttemplate ending in .{zone}{port}-dev-{user}.{zone}Every other public port of the selected member, by the port’s name. Must contain {user} and {port}; may contain {repo} (the member’s name) only with hostnames.portScope: member
hostnames.portScopeshared | membersharedWho owns a public port name. shared: one member per name, and the same name in two members is refused. member: names are per member — put {repo} in hostnames.port and two repositories can both publish app
maxMembersinteger ≥ 1—The most containers one engineer’s pod may run at once (auto entries plus the on-demand ones they started). Over it, validate and provision refuse and list the entries
enforceBudgetbooleanfalsetrue turns the check of the members’ summed hostRequirements against the host from a warning into a refusal
tailscale.hostnametemplate (single label)dev-{user}The pod’s ONE name on your private network. Must contain {user}. Every member’s ssh is a port on it
defaultowner/namethe first container entryWhat a fresh pod shows before anyone selects. Must be a container entry (a workspace has nothing to show)
siblingsbooleantrueEvery member’s clone mounted read-write in every member at /workspace/<name> — one engineer, one user, one trust domain (every member must declare the same remoteUser). false keeps each clone inside its own member

All keys are optional — pod: {} takes every default. The pod’s templates name every member; a member’s own file may repeat them or leave them out, but a file declaring different templates is refused, because it would be routed under names it never gets.

Adding a repository

A repository joins the fleet by describing itself; nothing about it is written into CLANKER.NET. Three steps:

  1. List it here. Add an entry to repos: with its URL, the ref to track and who may open it:

    repos:
      - url: https://github.com/acme/billing
        ref: main
        access:
          github: { org: acme }
  2. Describe it in the repository. A standard dev-container file at .devcontainer/devcontainer.json (its settings under customizations.replicator), and, for anything it deploys, replicator.config.ts for its own environments. A repository with no dev-container file is a workspace clone, not a container.

  3. Give it a thin workflow. One short file that calls the shared control plane by pin and passes the one secret. Everything else stays in the shared workflow, so a fix there reaches every repository:

    name: Environments
    on: { workflow_dispatch: {} }
    permissions:
      contents: read
    jobs:
      staging:
        uses: clankerlabs/CLANKERNET/.github/workflows/replicator-deploy.yml@<40-character SHA> # v1
        permissions:
          contents: read
          packages: read
        with:
          action: infra-diff
          stage: staging
          replicator_ref: <40-character SHA> # v1
        secrets:
          REPLICATOR_STATE_TOKEN: ${{ secrets.REPLICATOR_STATE_TOKEN }}

The next fleet-sync adds it to every pod. The entry field of the pod workflow takes any enabled container entry in this file as owner/name, and names the valid ones when it is given another.

Reaching your repos

A pod has ONE address on your private network and ONE public name. The onboarding card (the job summary of your provision) prints all of this for your own pod; the shape, for an engineer alice on the host above:

PortReaches
22the selected member, whichever it is
2201shop
2202api
ssh developer@dev-alice                # :22 → the selected member → its tmux session
ssh -p 2201 developer@dev-alice        # shop, whatever is selected
ssh -p 2202 developer@dev-alice        # api
mosh developer@dev-alice               # the selected member
scp -P 2202 file dev-alice:~/api/

Every member runs its own ssh service on its port, authenticates you with its own entry’s access.ssh.keySource, and drops you into its workspaces tmux session at the shared root — where a session works across every repository. Every repository of the pod is a tmux session of its own, named by the repository and opened in /workspace/<name> (so cd .. is the root): this member’s own session runs the Procfile’s processes, each other one holds a login shell in its directory. prefix s lists them, Alt+Left/Right steps between them, prefix Tab returns to the last one, and the right of the status bar reads <you>@<member>; services.tmuxSession names the session of a standalone container only. The pod shares one host key, so a switch never changes what your client sees. The card also prints a ~/.ssh/config block so each member has a name of its own:

Host dev-alice
  HostName dev-alice
  User developer
Host dev-alice-shop
  HostName dev-alice
  User developer
  Port 2201

Switching

select moves the public name (the apex and the port names) and the port-22 front door to one member. Nothing restarts; established ssh sessions keep their connection; only the next ssh dev-alice lands on the new member.

sequenceDiagram
  actor A as alice
  participant API as member api
  participant SHOP as member shop
  participant P as public name
  Note over P: shows api
  A->>API: replicator select shop
  API-->>P: route + port 22 now point at shop
  Note over P: shows shop — a few seconds, nothing restarted
  A->>SHOP: ssh dev-alice (port 22)
  Note over API: still running, no public route<br/>ssh -p 2202 reaches it

Three ways, one result:

  • From the app: the Pod screen’s DEV URL section (one tap per dev container), or /select <name> in a DM with your pod’s agent. The agent runs the pod’s own factory-select, so nothing new holds a credential, and only the pod’s owner can switch it.
  • From inside any member: replicator select <name> (replicator select alone lists the members and the current selection). No credential leaves the host; a member can only pick from its own pod.
  • From the workflow: action: select, picking the dev container from the entry list (the fleet’s container entries; current keeps what is shown, which is what a routine provision wants). The run then checks that the public name answers 200 on the member’s health path and that port 22 reaches it.

The member that is not selected keeps running, and its public PORT names keep answering (a public port label belongs to one member of the pod, so nothing contends for it); only the apex — and the path-routed ports on it — moves with the selection. DNS never changes on a switch: every member’s public names are published once, at provision.

Who may open a pod’s names

Every public name of a pod — the apex and each port name — is behind the same GitHub sign-in as the app. The host’s ingress asks the coordination server before every request; a browser without a sign-in for that name is sent to the app (https://app.<zone>/auth/pod), which hands it a single-use ticket back to the pod’s name, where it becomes a cookie for that name alone, good for twelve hours and ended early when the person loses push access to the fleet’s repositories. Nothing of the app’s own session reaches a pod: a pod serves whatever its developer’s checkout serves, and a cookie shared across the zone would be a token every pod could read (client/AUTH.md, “Opening a pod”).

A member opts one port out of the gate with auth: none in its own file (docs/devcontainer.md, “Who may open a public port”) — for a webhook or callback target no browser sign-in can precede. The port a member’s services.s3.public names is open by default, a presigned URL being its own authorisation. The gate is the host’s: the fleet declares nothing, and the coordination server learns the zone (the host’s first DNS zone) and the app’s origin (https://app.<zone>) from the host spec at converge.

Keeping a host converged: fleet-sync

fleet-sync (the daily job from sync.schedule, or on demand) converges every pod on the host to the fleet file. For each pod, each repository in the fleet is compared with what the pod holds:

flowchart LR
  r["A repository in the fleet…"]
  r --> u["unchanged, or only its sha moved"] --> ku["member keeps running<br/>same container, same session"]
  r --> c["its dev-container file changed,<br/>or a new image was published"] --> rc["member keeps running, reported <b>stale</b><br/>the engineer's own provision applies it"]
  r --> a["added"] --> j["joins every existing pod<br/>nobody re-dispatches provision"]
  r --> d["removed or disabled"] --> l["member leaves every pod<br/>storage kept, its public names pruned"]
  r --> n["no longer allowed for this person"] --> o["left out of their pod<br/>never torn down"]

Three rules protect people. fleet-sync never restarts a running container — nobody consented to that: a member whose dev-container file changed, or whose image the run pulled past (a new release), keeps running and its pod is reported stale, naming the container and why; the engineer applies it with their own provision, when it suits them. A moved commit alone is never a change — the clone lives on the member’s storage and sync fast-forwards it. A pod whose owner cannot be checked (the lookup failed rather than answered “no”) is reported as failed and nothing runs on it. And someone allowed for nothing is skipped and reported, never torn down — offboarding is an admin’s explicit teardown with user: (Quickstart). The run summary lists every pod with its members, their shas and ports, the selection and the outcome.

An engineer’s own sync is the same idea for one pod: every member fast-forwards its clone and re-runs its updateContentCommand, every workspace pulls — fast-forward only, never a reset, so a commit you made in a workspace clone is never lost.

Discovery: a rule instead of a list

A fleet of twenty repositories does not have to name each one. A discover rule lists an organisation’s repositories and expands to entries when the file is read:

discover:
  - org: acme
    topic: replicator # only repositories carrying this topic
    exclude: [acme-handbook, "legacy-*"] # names or globs
    autoJoin: join # `plan-only` (the default) shows, never starts
    access:
      github: { org: acme }
repos:
  - url: acme/billing # an explicit entry always wins: its own ref, owner, pod settings, `enabled: false`
    ref: release
    access:
      github: { org: acme }
KeyDefaultMeaning
org— (required)The organisation (or user) whose repositories are listed. Archived repositories are never taken
topicevery repository of orgOnly repositories carrying this topic. Without it, exclude is how you narrow the rule
excludenoneRepository names or globs (*, ?), matched against the name and against owner/name
refthe repository’s default branchThe ref every discovered entry is read at
access— (required)The same block an entry has: who may hold a pod with these repositories
devcontainer, enabled, owner, podas on an entryApplied to every discovered entry (pod.sshPort cannot be pinned for a whole rule)
autoJoinplan-onlyplan-only: the run lists what the rule found and adds no entry — nothing is read or started, so a topic drifting cannot start a container. join: they are entries like any other

An expanded entry equals the entry you would have written by hand: same ref rules, same file read at the sha, same ssh port. Entries keep a stable order — explicit ones in file order, then each rule’s repositories by name — and ports follow the pod’s record (see “Many repositories”), so a repository that gains the topic takes the next free port and moves nobody. If two rules list one repository, the first keeps it; an explicit entry beats every rule.

replicator validate and replicator plan print each rule’s expansion (discover acme topic replicator: 5 found, 2 joined, 3 pending (autoJoin: plan-only)), list the entries a rule added (discovered), and carry "source": "explicit"|"discovered" on every row of --json. --offline cannot list an organisation: the rule is reported as not expanded.

A listing that fails removes nothing. A rule whose listing cannot be read (not found, a server error, a redirect, an empty answer, something that is not a repository list) is reported unreachable and treated as unknown, not as “no repositories”. fleet-sync keeps every member the pods already hold from that organisation, pinned at the commit they were last converged at, and says so; a developer’s own provision, sync and select, and a host converge (which admits people by the fleet’s repositories), stop with the same message instead of shrinking a pod or the sign-in. When the listing answers again, a repository that lost the topic leaves every pod like any removed member (its storage is kept).

Fleet status

replicator fleet status [--host <id>] [--json] shows every entry once, and changes nothing:

fleet clanker.yml (host dev-host): 5 entries, 3 pod(s)

owner platform
  acme/web@main → a1a1a1a · container · explicit · ssh :2201 · behind · 1/2 pod(s) current · last sync 2026-11-11T05:20:00.000Z
  acme/api@b2b2… · container · explicit · ssh :2202 · in sync · 3/3 pod(s) current
no owner
  acme/notes@main → c3c3c3c · workspace · explicit · not provisioned
  acme/found@main · container · discovered · pending
discover acme topic replicator: 2 found, 1 pending (autoJoin: plan-only)

Each row has its owner (rows are grouped by it), kind, the commit its ref resolves to, whether it was written or discovered, and its ssh port. With the host’s pod records it adds how many pods hold the entry, how many of those are at the current commit (in sync, behind, not provisioned), and when the host last wrote a pod’s record, which is the last provision, fleet-sync or select. A repository a pod still holds that the fleet no longer lists is departed; one held while its organisation’s listing cannot be read is also marked UNREACHABLE listing and is not reported as removed. An entry whose dev-container file does not resolve is unreadable with the reason, a plan-only find is pending, a disabled entry disabled.

--offline reads the fleet file alone (no repository, no host); --no-records reads the repositories but does not ask the host. Neither writes anything.

Many repositories

A pod scales to twenty repositories without any of them naming another.

Ports stay put. A repository’s ssh port is recorded in the pod the first time the pod sees it. Adding a repository anywhere in the file, reordering the file, or disabling an entry changes no other member’s port; the new one takes the lowest port nobody has held. Removing a repository retires its port for good — adding it back later is a new entry with a new port — so a saved ssh command never reaches someone else’s repository. A pod created before this rule keeps the ports its members already run on.

Port names per repository. By default a public port name belongs to one member (hostnames.portScope: shared), because the name alone makes the public hostname. With twenty independent repositories, “app” and “api” will repeat. Set hostnames.portScope: member and write the hostname template with {repo}:

pod:
  hostnames:
    port: "{port}-{repo}-dev-{user}.{zone}"
    portScope: member

Two repositories that publish app now answer at app-one-dev-alice… and app-two-dev-alice…. The single public name still belongs to the selected repository, a hostname that two members would still render alike is refused, and the names are published once for the whole pod. Existing fleets keep shared and their current URLs.

Only what you work on runs. Most repositories in a large fleet are read, searched and edited, not run. Mark them:

repos:
  - url: acme/web # always running (the default, `start: auto`)
  - url: acme/billing
    pod: { start: on-demand } # a clone until you select it
  - url: acme/handbook
    pod: { start: clone } # a clone, never a container
pod:
  maxMembers: 4
  enforceBudget: true

An on-demand repository is validated like any member (its ports and names are checked and reserved), but no container exists until an engineer selects it: select starts that one container, moves the public name to it, and recreates nothing else. It stays running until the pod is rebuilt without it (provision, which keeps started members). Until then it is a clone in the shared root, and its checkout is mounted in every member as usual. A clone entry never starts and its dev-container file is never read. maxMembers counts what runs; with enforceBudget the summed hostRequirements of what runs must fit the host or the pod is refused.

Refusals, in one place

ConditionResult
version other than 1error
host neither a reference form nor a valid inline specerror
neither repos nor discover listing anything, or the same repository twice (either spelling)error
url not a repository URL or owner/nameerror
ref with whitespace, .., a leading -//, or a .lock suffixerror
devcontainer absolute, climbing (..), or not .jsonerror
access missing, or access.github missing / with neither org nor userserror
access.tailscale.hostname with an unknown placeholder or a doterror
sync.schedule not five cron fieldserror
any unknown key at any levelerror
a repository with no dev-container file at its ref (online)error
a ref that cannot be resolved (online)error
--offlinewarning — nothing fetched; a repo: host also skipped
devcontainer: false or pod.sshPort without a top-level pod:error — a workspace or a port means nothing without a pod
pod.hostnames.* without {user}, with {repo}, or not ending in .{zone}; port without {port}error
pod.tailscale.hostname without {user}, or with a doterror
pod.default not listed, or naming a workspace (devcontainer: false, pod.start: clone) or an on-demand entryerror
pod.start: on-demand/clone on a devcontainer: false entry, or pod.start without a top-level pod:error
pod.maxMembers smaller than the number of auto entries (or, at provision, of what would run)error — lists the entries
{repo} in pod.hostnames.port while portScope is shared; {repo} in pod.hostnames.default alwayserror
(online) portScope: member and two members’ ports rendering to one hostnameerror
(online) pod.enforceBudget on and the members’ summed hostRequirements over the hosterror
two container entries on one ssh port (a pin colliding with a positional default counts)error
pod.sshPort = 22 (the front door)error
(online) pod.sshPort = 5432, 6379, 8079 or 9000 (the built-in services)error — one database, one cache, one object store per pod
in a pod fleet, per-entry access.tailscalewarning — ignored; the pod has one address
(online) services.postgres, services.redis or services.s3 in two memberserror — one database, one cache, one object store per pod
(online) a port number declared by two members, or equal to a member’s ssh porterror — every declared port is a port of the whole pod
(online) a public port name declared by two memberserror — its hostname routes for every member at once
(online) a routed port left on the default auth (the sign-in) on a host with no gateway: blockerror — it would answer anyone; add the block, or auth: none on the port
(online) a member’s name reserved (net, redis, redis-rest, s3, s3-init, routes, egress), all digits, or shared with another member or workspaceerror — customizations.replicator.repo.name renames it
(online) a workspace named like a file of the shared root (agents.md, .claude, …)error — it would be mounted over that file
(online) pod.siblings on and two members declaring different remoteUserserror — one user is what makes a shared tree safe
(online) a member’s name equal to any member’s public port nameerror — one label space in the pod’s routes
(online) a member’s file declaring hostnames different from the pod’serror — the pod’s templates name every member
(online) a member declaring the standard docker-in-docker featurewarning — unavailable in a pod; the member runs without it
(online) the members’ summed hostRequirements over the hostwarning