---
title: Push a Shared Environment File to Every Node Running a Stack
sidebarTitle: Push shared secrets to your fleet
description: Author one encrypted env-var bundle on the control instance and push it to a stack running on every labeled node, with a diff you can read before anything is written.
---

Say the same `inventory-api` stack runs on two nodes in your fleet, each with its own `.env` file, and you need to rotate `DB_PASSWORD` on both without SSHing into either host or risking one node drifting out of sync with the other. This walks through building an encrypted secret bundle on your control instance, targeting both nodes by a shared label, and pushing it with a preview step that shows exactly what will change before you write anything.

By the end, one bundle holds the source of truth for the stack's environment, and a single push keeps every labeled node's `.env` in sync with it.

This tutorial covers creating a bundle, targeting nodes by label, and reading the push preview and results. It doesn't cover importing an existing `.env` from a running stack, editing a bundle to a new version, or the full audit and encryption model; see the [Fleet Secrets](/features/fleet-secrets) feature page for those.

## Prerequisites

- An **admin** account on the instance you're signed into. Fleet Secrets decrypts and writes credentials fleet-wide, so every route requires an administrator; there is no lesser role that can push a bundle.
- You're working from the **control instance** (the hub), not a remote node. Fleet Secrets is a hub concern: its API routes never proxy to a remote node, so the **Secrets** tab and its data always reflect the instance you're currently signed into.
- At least two nodes in your fleet (Local plus one enrolled node), each running a stack with the **same name** and declaring an env file via `env_file:` in its compose. This tutorial uses `inventory-api`:

  ```yaml
  services:
    api:
      image: nginx:alpine
      restart: unless-stopped
      ports:
        - "8099:80"
      env_file:
        - .env
  ```

  Deploy this stack on both nodes before you start (an empty `.env` is fine; the push will populate it). A stack whose compose only has an inline `environment:` block won't show up as a push target, since Fleet Secrets writes to a file, not inline compose values.
- Both nodes carry a **shared label**. This tutorial uses `inventory` on both. Add labels from **Settings → Nodes** if they aren't set yet.

<Note>
  Fleet Secrets is available on every Sencho installation; no Admiral requirement.
</Note>

<Steps>
  <Step title="Open Fleet and create a bundle">
    Open **Fleet**, select the **Secrets** tab, and click **New bundle** (or **Create your first bundle** if this is the first one on the instance).

    <Frame>
      <img src="/images/tutorials/set-up-fleet-secrets/secrets-tab-empty.png" alt="Fleet view with the Secrets tab selected, showing the empty state: heading 'One source of truth for env' and a Create your first bundle button." />
    </Frame>

    Give the bundle a **Name** (`inventory-api-env` here) and an optional **Description**. Add a `KEY=value` row for each variable the stack needs; click **Add key** for more rows. This tutorial uses `LOG_LEVEL=info`, `REGION=us-east`, and `DB_PASSWORD` set to a placeholder value you'll rotate later. Add a **Change note** describing the initial save.

    <Frame>
      <img src="/images/tutorials/set-up-fleet-secrets/create-bundle-filled.png" alt="New secret bundle sheet with Name inventory-api-env, Description 'Shared environment for the inventory-api stack', three key rows LOG_LEVEL=info, REGION=us-east, DB_PASSWORD=change-me-2026, and a change note reading 'Initial inventory-api bundle'." />
    </Frame>

    Click **Save**. The values are encrypted before the ciphertext is written to disk; the plaintext only lives in the editor while you're filling it in.
  </Step>
  <Step title="Confirm the bundle was saved">
    The bundle now appears as a row in the **Secret bundles** table, at version `v1` with a key count matching what you entered.

    <Frame>
      <img src="/images/tutorials/set-up-fleet-secrets/bundle-created.png" alt="Secret bundles table showing one row: inventory-api-env, description 'Shared environment for the inventory-api stack', version v1, 3 keys, with edit, send, and delete icons on the right." />
    </Frame>
  </Step>
  <Step title="Target the push by label and stack name">
    Click the **Send** icon on the bundle's row to open the push wizard. On the **Target** tab, toggle **any** or **all** (any is fine when you have one label) and pick the label both nodes carry, `inventory` here. Enter the **Stack name** the bundle should write to, exactly as it appears on each node. The **Env file** dropdown populates from `env_file:` entries a representative target's compose declares; leave it at `.env` unless your stack uses a different filename.

    <Frame>
      <img src="/images/tutorials/set-up-fleet-secrets/push-target.png" alt="Push wizard Target tab. Target nodes section with the any toggle selected and the inventory label chosen. Target stack section with Stack name inventory-api and Env file dropdown showing .env." />
    </Frame>

    Click **Preview**. This step only reads; it doesn't write anything yet.
  </Step>
  <Step title="Read the preview before writing anything">
    Each row is a node the label selector matched, with a summary of how many keys will be added, changed, or left unchanged. Expand a row to see the per-key breakdown. Since both nodes started with an empty `.env`, every key shows as added.

    <Frame>
      <img src="/images/tutorials/set-up-fleet-secrets/push-preview.png" alt="Push wizard Preview tab with two expanded node rows, Local and warehouse-02, each showing +3 added, ~0 changed, ·0 unchanged, and per-key ADDED rows for DB_PASSWORD, LOG_LEVEL, and REGION." />
    </Frame>

    This is the safety net: the diff shown here is exactly what the next click writes. If a row looks wrong, close the wizard, fix the bundle, and reopen Send to re-run Preview.
  </Step>
  <Step title="Push and confirm the result">
    Click **Push to N nodes**. Sencho writes to each matched node in sequence and reports a per-node outcome on the **Results** tab: a green check with the same added/changed/unchanged counts on success, or a red error with the exact failure string.

    <Frame>
      <img src="/images/tutorials/set-up-fleet-secrets/push-results.png" alt="Push wizard Results tab with two green success rows: Local and warehouse-02, each showing +3 ~0 ·0." />
    </Frame>
  </Step>
</Steps>

## Verify it worked

Check from two independent surfaces so you're not trusting a single UI element.

**The Results tab itself**, shown above: both nodes report success with the counts matching the bundle's key count.

**The Audit Log.** Open **More → Audit**. Reading newest first, the top entry reads `pushed secret: 1` and the one just below it `previewed secret push: 1`, both attributed to the account that ran the push.

<Frame>
  <img src="/images/tutorials/set-up-fleet-secrets/audit-log-push.png" alt="Audit log showing two recent entries: 'admin pushed secret: 1' and 'admin previewed secret push: 1', each with a timestamp, node, and 200 status." />
</Frame>

If you have shell access to either node, `cat`-ing the stack's `.env` file is a third way to confirm the write landed, but the two UI surfaces above are enough for day-to-day verification.

## If something goes wrong

**A node lands in the failed column.** The most common first-time cause is the **Stack name** not matching exactly what's deployed on every target. Retarget the same bundle at a name with a typo, `inventory-apy` instead of `inventory-api`, and Preview still succeeds (it reads an empty, nonexistent file as "nothing set yet" and shows every key as added), but the actual push fails on both nodes: the local node returns an `ENOENT` filesystem error, and the proxied node returns `No env file exists for this stack` over HTTP.

<Frame>
  <img src="/images/tutorials/set-up-fleet-secrets/push-failed-typo.png" alt="Push wizard Results tab with two red failure rows: Local showing an ENOENT no such file or directory error, and warehouse-02 showing 'failed to write env (HTTP 404: No env file exists for this stack)'." />
</Frame>

Preview cannot catch this class of mistake because it treats an unreadable target as an empty file rather than a missing stack. Fix the stack name on the Target tab and re-run Preview; a failed push is safe to retry immediately, since the overlay write is idempotent. See [Fleet Secrets · Troubleshooting](/features/fleet-secrets#troubleshooting) for the other common causes, including a stack whose compose doesn't declare the chosen env file at all.

## Related

<CardGroup cols={2}>
  <Card title="Fleet Secrets" icon="key-round" href="/features/fleet-secrets">
    Versioning, the import-from-stack flow, concurrency and lifecycle behavior, and the full audit trail.
  </Card>
  <Card title="Enroll a Remote Node" icon="server" href="/tutorials/enroll-a-remote-node">
    Bring a second node into your fleet if you don't have one to label yet.
  </Card>
</CardGroup>
