Tutorials
Tutorial: Deploy to a Fleet
Roll out .dotfiles across multiple workstations and verify they're in sync.
Scenario
You have three hosts:
- macbook-t2 — your MacBook (current host, already installed)
- surface-pro — your Linux laptop (new install)
- geekom-a9 — your desktop NUC (new install)
Goal: all three running identical .dotfiles with per-host customization, with auditable cross-host attestation.
Step 1: Per-Host Install
On each new host:
# Download and verify the release installer per the installation guide first.
During chezmoi init, answer with the host's preset:
- Surface Pro →
machine = "surface-pro" - Geekom A9 →
machine = "geekom-a9"
Step 2: Exchange Signing Keys
Each host has its own SSH ED25519 key. To verify commits and attestations across the fleet, publish each host's public key to every other host.
On each host, copy the public key:
# ssh-ed25519 AAAAC3N...jkl5 user@macbook-t2
Collect all three keys. Append them to ~/.ssh/allowed_signers on every host:
user@macbook-t2 ssh-ed25519 AAAAC3N...jkl5
user@surface-pro ssh-ed25519 AAAAC3N...abc7
user@geekom-a9 ssh-ed25519 AAAAC3N...def9
Optionally commit allowed_signers as a template so all hosts receive updates via dot update:
# On macbook-t2
&&
Now every other host gets the updated list on dot update.
Step 3: Share the Age Key (for Shared Secrets)
If you want shared secrets decrypted on all three hosts, each host must have an Age key recipient in .sops.yaml.
Option A: Share one key across all hosts (simpler, less granular). Option B: Each host has its own key; all three are listed as recipients (better operational security).
For Option B:
# On macbook-t2 (already set up)
# On surface-pro
# On geekom-a9
Add all three public keys to .sops.yaml:
creation_rules:
- path_regex: \.sops\.yaml$
age: >-
age1aaa...111, # macbook-t2
age1bbb...222, # surface-pro
age1ccc...333 # geekom-a9
Re-encrypt existing secrets for the new recipients:
# On macbook-t2
Other hosts on dot update decrypt with their own private key automatically.
Step 4: Verify Fleet Alignment
From any host:
Expected output:
Fleet Attestation — v0.2.501
Host Git SHA Policy Tools OK Drift Verified
-------------- ---------- --------- ---------- ------- --------
macbook-t2 abc123d 0x7f2a… ✓ 0 ✓
surface-pro abc123d 0x7f2a… ✓ 0 ✓
geekom-a9 abc123d 0x7f2a… ✓ 0 ✓
-------------- ---------- --------- ---------- ------- --------
FLEET STATUS: 3/3 aligned
If any row shows drift or missing verification:
# Check which host is out of sync
# SSH into the affected host and fix
&&
Step 5: Per-Host Customization
Each host has its own ~/.config/chezmoi/chezmoi.toml — use it for things that shouldn't propagate:
# surface-pro's chezmoi.toml
[data]
machine = "surface-pro"
theme = "monterey-dark" # per-host default
default_shell = "fish"
terminal_font_size = 11 # smaller for HiDPI
# geekom-a9's chezmoi.toml
[data]
machine = "geekom-a9"
theme = "valley-dark" # different default
default_shell = "zsh"
terminal_font_size = 14 # larger for external 1440p
Both hosts render from the same templates, producing correct platform-appropriate output.
Step 6: Fleet-Wide Updates
When you push a commit to main, each host pulls independently:
# On each host
dot update does:
git pull --rebase --autostashin~/.dotfileschezmoi applywith progress outputdot healif apply detected drift- Reports changes applied
For simultaneous update across the fleet (requires SSH):
# Connects to each host in parallel
# Runs `dot update` via SSH
# Collects outcomes
Step 7: Secrets Rotation
When a secret is compromised (or as part of routine rotation):
# 1. Edit the secret on any host
# Change the compromised value, save
# 2. Commit and push
# 3. All fleet hosts pick up the new value on next update
# On each host:
Since hosts all have the Age private key for themselves, decryption is automatic.
Step 8: Decommission a Host
When retiring a host:
# 1. Remove the host's public key from allowed_signers
# Edit dot_ssh/allowed_signers.tmpl (on any host)
# 2. Remove the host's Age public key from .sops.yaml
# 3. Re-encrypt all SOPS files (remove the decommissioned recipient)
# 4. Commit
# 5. On the decommissioned host: wipe the dotfiles state
Troubleshooting
"Fleet host unreachable"
dot fleet couldn't SSH to a host. Check:
# If this fails, fix SSH connectivity first
Drift on One Host
Run dot heal on that host:
If drift persists:
# Inspect what's different
Policy Hash Mismatch
One host has an older policy than the others. Usually means that host missed an update:
Attestation Signature Failed
The host's SSH key isn't in ~/.ssh/allowed_signers on the verifying host. Fix by redistributing allowed_signers.
Summary
A three-host fleet:
- Installs from one repository
- Has independent signing keys (each host verifies commits from others)
- Can share secrets across specific hosts via SOPS recipients
- Is auditable via
dot fleet attest - Updates independently with
dot update(or in parallel withdot fleet sync)