Skip to content

Production fleet configuration reference

Diagram source
flowchart LR
  subgraph Authority["Protected authority"]
    JSON["Fleet JSON"] --> STATE["Secrets + PKI + topology"]
  end
  subgraph Bundles["Per-node bundles"]
    STATE --> C["Control"]
    STATE --> D["DNS"]
    STATE --> E["Edge"]
    STATE --> M["Telemetry"]
  end

Copy deploy/production/examples/starter-fleet.json or multi-region-fleet.json to a protected local fleet.json, then change deployment data there. Checked-in examples are templates; repository scripts and generated Compose manifests are not configuration surfaces.

Common command options

These options work before or after a subcommand:

OptionDefaultPurpose
--state-dir/var/lib/cdnfoundry-fleetProtected authoritative fleet state
--output-dir/var/lib/cdnfoundry-fleet/bundlesGenerated per-node bundles
--repo-rootRepository containing the scriptBase production Compose file and deployment assets
--confignoneJSON input for setup or node commands
--non-interactivefalseNever prompt; fail when required input is absent
--dry-runfalseValidate intent without writing state or bundles
--yesfalseConfirm destructive or rotation operations

The convenience wrapper uses repository-local defaults unless environment variables override them:

text
CDNFOUNDRY_FLEET_STATE_DIR
CDNFOUNDRY_FLEET_OUTPUT_DIR

Setup config schema

json
{
  "preset": "control-monitoring",
  "global": {
    "operator_domain": "ops.example.com",
    "platform_domain": "example.net",
    "release": "v1.0.0",
    "acme_email": "operations@example.com",
    "ipv6": false
  },
  "nodes": [],
  "features": {
    "monitoring": {"mode": "disabled", "host": null},
    "logs": {"mode": "disabled", "host": null, "endpoint": null},
    "backups": {"mode": "disabled", "repository": null, "region": "us-east-1"}
  }
}

Presets

PresetResult
control-onlyControl node, monitoring disabled
control-monitoringControl node with colocated telemetry services
dedicated-monitoringControl node plus a monitoring-role node
customFeature configuration comes from features or later commands

Node object

FieldRequiredDescription
nameyesLowercase stable identifier, letters/digits/hyphens
roleyescontrol, dns, edge, dns-edge, or monitoring
regionyesRouting/operations region label
locationyesHuman-readable site label
hostnamenoDefaults to NAME.OPERATOR_DOMAIN
public_ipv4yesPublic or routed IPv4 used for inventory and policy
public_ipv6noIPv6 service address
bind_ipv4noLocal listener bind, defaults to 0.0.0.0
bind_ipv6noIPv6 bind; defaults to :: in dual-stack fleets
monitor_ipv4 / monitor_ipv6noPrivate monitoring addresses; IPv4 otherwise uses public_ipv4
log_ipv4 / log_ipv6noPrivate log-source address metadata
releasenoPer-node immutable override of the global release
extra_envnoExplicit per-node Compose overrides; always preserved in the generated .env.prod, including variables that have Compose defaults
enablednoExclude disabled nodes from rendering and targets
drainingnoKeep node configured but remove it from preferred routing

Example:

json
{
  "name": "pop-singapore",
  "role": "dns-edge",
  "region": "asia",
  "location": "singapore",
  "hostname": "pop-singapore.ops.example.com",
  "public_ipv4": "192.0.2.40",
  "public_ipv6": "2001:db8::40",
  "bind_ipv4": "0.0.0.0",
  "bind_ipv6": "::",
  "monitor_ipv4": "10.30.0.40",
  "release": "v1.0.0",
  "extra_env": {},
  "enabled": true,
  "draining": false
}

Edge registration fields

Do not put bootstrap tokens in a version-controlled setup JSON. The control plane's one-time modal is deployment-neutral and returns exactly:

dotenv
EDGE_ID=11111111-2222-3333-4444-555555555555
EDGE_BOOTSTRAP_TOKEN=the-one-time-token

Paste them into the prepared host's mode-0600 .env.prod, then start the edge profile explicitly:

bash
sudo docker compose --env-file .env.prod --profile edge up -d

Alternatively, add --profile edge to that host's generated start.sh Compose up command. Edge and Fleet node names do not have to match.

For Fleet-managed secrets, configure-edge-registration and clear-edge-bootstrap-token remain available as automation primitives. They are not a control-plane requirement. Optional host overrides such as EDGE_GATEWAY_ADDRESS_MAP, EDGE_GATEWAY_REQUIRE_ADDRESS_MAP, runtime versions, MMDB settings, and gateway capacity belong in extra_env. The address map is needed only for NAT/load-balancer translation; directly assigned service addresses bind without it.

Control database selection

Embedded PostgreSQL is used when neither DB_URL nor a non-default DB_HOST is present.

Remote PostgreSQL is selected when the control node has either:

json
"extra_env": {
  "DB_HOST": "postgres.internal.example",
  "DB_PORT": "5432",
  "DB_SSLMODE": "verify-full"
}

or a non-empty DB_URL.

Use set-secret --secret control-db-password --from-file FILE to replace the generated password with the remote database credential without exposing it in command arguments. In remote mode the control bundle omits control-db and its volume.

Feature objects

Monitoring

json
{"mode": "disabled", "host": null}
  • disabled: no telemetry stack or node exporters.
  • colocated: telemetry stack runs on the control node.
  • dedicated: telemetry stack runs on the named monitoring-role node.

Logs

json
{"mode": "centralized", "host": "monitoring-1", "endpoint": null}
  • disabled: no generated log collector.
  • centralized: every enabled node receives a generated Vector config and node-specific authentication token.
  • endpoint: optional explicit Loki-compatible URL; otherwise derived from the configured host.

Backups

json
{
  "mode": "all-stateful",
  "repository": "s3:s3.example.com/cdnfoundry-production",
  "region": "us-east-1"
}

Modes are disabled, control, and all-stateful.

Exit codes

CodeMeaning
0Success
2Command-line usage error
3Invalid topology, config, or failed doctor check
4Missing, locked, or inconsistent fleet state
5Compose, PKI, file-copy, or bundle rendering failure
130Operator interrupted the command

Generated bundle contract

Every rendered node directory includes:

text
.env.prod
generated Compose manifest
README.md
validate.sh
start.sh
pki/
secrets/
generated/              # when required
referenced docker/...   # only runtime files used by selected services

DNS nodes may additionally receive reconcile-pdns-password.sh and a pending password file during a staged rotation.

Generated start.sh contains a fixed list of Compose profiles. A combined dns-edge bundle rendered before enrollment starts only DNS. After an operator pastes EDGE_ID and EDGE_BOOTSTRAP_TOKEN into .env.prod, start the edge profile explicitly or add --profile edge to the script's Compose up command. The script does not inspect enrollment state or modify .env.prod.

Security properties

  • State directories use mode 0700.
  • State, secrets, environment files, manifests, and private keys use mode 0600 at render and transfer time. During control activation, generated start.sh changes only pki/edge-identity-ca.key to owner root, numeric group 82, mode 0640, so the core image's PHP-FPM worker can read the signing key.
  • Node bundles are assembled in temporary directories and activated atomically.
  • Normal rendering does not rotate secrets.
  • DNS database credentials are node-scoped.
  • CA private keys remain in authoritative fleet state, except the edge identity CA key required by the control service in the control bundle.
  • Every operator-controlled Compose interpolation value is present in the node's generated .env.prod; production Compose provides no fallback deployment values.
  • Compose environment mappings remain explicit per-service allowlists. Replacing them with a shared env_file entry would expose unrelated database, PKI, and API credentials to every container, so containers receive only the variables they own while Compose reads values through --env-file .env.prod.

CDNFoundry documentation