---
title: "Fleet Secrets"
description: "Centralized, encrypted, versioned env-var bundles you can push to labeled nodes' stacks."
---

Fleet Secrets gives you one place to author the environment variables a stack needs, and one action to push them to every node that runs that stack. Bundles are encrypted at rest, every save bumps a version, and every push is recorded in the audit log with a per-node diff.

Fleet Secrets is available on **Skipper** and **Admiral** licenses. The tab is hidden on Community.

## What's in scope

| Today | Not yet |
|-------|---------|
| Bundles of `KEY=value` env pairs | Certificates, key files, credential JSON blobs |
| Push to nodes by label selector | Pin a bundle to specific node IDs |
| Versioning on every save with a change note | Scheduled or auto-rotation |
| Diff preview before each push | Multi-region or per-environment overlays |

If a feature is not in the table above, it's deferred to a later release. Fleet Secrets in 1.0 is intentionally focused on env-var drift across copies of the same stack.

## Create a bundle

1. Open **Fleet → Secrets**.
2. Click **New bundle**.
3. Give it a name (letters, digits, dot, dash, underscore; 2–64 chars) and an optional description.
4. Add `KEY=value` rows. Keys must match the env-var convention (`A-Z`, digits, underscore; cannot start with a digit). The eye icon toggles value visibility; the copy icon copies a single value to the clipboard.
5. Add a **Change note** (optional but recommended) explaining why this version exists.
6. Click **Save**. The bundle is now version `v1`.

Saving the bundle encrypts the payload with the instance's data key (AES-256-GCM via the same key that protects MFA secrets and registry credentials). The plaintext only ever exists in memory while you're editing.

## Edit and version

Editing a bundle creates a new version. Each version is immutable once saved. Open a bundle and switch to the **Versions** tab to see who saved each version and any change notes attached.

Saves are last-write-wins. There's no row-level lock; if two operators edit the same bundle concurrently, the later save creates the higher version number and the earlier save remains visible in history.

## Push to nodes

A push reads the bundle's current version, computes the diff against each target's existing env file, and writes the merged result.

1. From the bundle row, click the **Send** action to open the push wizard.
2. **Target tab:**
   - Pick one or more node labels.
   - Choose **any** (a node matches if it has any of the selected labels) or **all** (a node must have all selected labels).
   - Enter the **Stack name** that the bundle should be applied to. The stack must exist on each target node by that name.
   - Pick the **Env file** to write. The dropdown lists every file declared by the stack's compose on a representative target node — the canonical `.env` plus any files referenced via `env_file:` directives. Per-node compose files can differ; nodes that do not declare the chosen file are reported as failed for that push.
3. Click **Preview**.
4. **Preview tab:** the table shows one row per matched node. Each row reports a quick header (`+added · ~changed · ·unchanged`) and an expander with the per-key diff. Drift — keys present on the target that are absent from the bundle — is shown as informational `removed` rows. The push will not delete those keys.
5. Click **Push to N nodes**.
6. **Results tab:** per-node status pills (`ok`, `failed`, `skipped`). Hover a failure to see the error.

Pushes are sequential per bundle to make audit ordering predictable. Only one push for a given bundle can run at a time; a second concurrent push attempt returns `409 Conflict` until the first finishes.

## Merge semantics: overlay

Fleet Secrets uses **overlay** merge:

- A key in the bundle that is missing from the target is **added**.
- A key in both with a different value is **changed** to the bundle's value.
- A key in both with the same value is **unchanged**.
- A key on the target that is **not in the bundle** is preserved on the target. The diff shows it under `removed (drift)` so you can see the divergence, but the push leaves it alone.

This is deliberately conservative for a v1 push action. If a key truly needs to be removed from a target's env file, do it in the stack editor on that node, then push the bundle to bring the rest of the keys in line.

## Audit trail

Every mutating action shows up in **Audit Log**:

| Action | Audit summary |
|--------|---------------|
| Create bundle | `Created secret` |
| Update bundle (new version) | `Updated secret: <id>` |
| Delete bundle | `Deleted secret: <id>` |
| Import env from a stack | `Imported env into secret: <id>` |
| Preview a push | `Previewed secret push: <id>` |
| Execute a push | `Pushed secret: <id>` |

The execute-push action also writes one row per target node in `secret_pushes`, capturing the bundle id, version, push id (a UUID grouping all rows from the same push), node id, stack name, env file, status, error message, and per-node `added` / `changed` / `unchanged` counts.

## Troubleshooting

### "env file '<name>' not found on node <X>"

The node's compose file for that stack does not declare the env file you asked Fleet Secrets to write to. Either pick a different env file in the wizard's **Env file** dropdown, or update that node's compose to reference the file via `env_file:`.

### "stack not found"

The named stack does not exist on that node. Fleet Secrets pushes to existing stacks only — it does not create stack directories.

### A push partially succeeded

Per-node failures are independent. The bundle and other targets are unaffected; only the failing node missed the push. Fix the underlying cause (file declared in compose, stack created, node reachable) and re-run the push. Idempotent overlay means re-pushing is safe.

### "A push for this secret is already running"

Only one push per bundle runs at a time. Wait for the previous push to finish, then retry.

### Values look wrong on the target

Open the bundle and switch to **Versions** — confirm the version you intended to push is the current one. Open the wizard, run **Preview** without pushing, and read the diff. The preview is a faithful representation of what the next push will write.
