:/data busybox tar -czf - /data > snapshot.tar.gz`, or app-aware tooling such as `pg_basebackup` / `mysqldump` / `mongodump`) to capture the volume on node A.
+3. Transfer the artifact to node B and restore it into the named volume there.
4. Update the Blueprint's selector to include node B; click **Apply now**.
A future Volume Migration feature will automate this with app-aware backup tooling.
@@ -138,6 +142,14 @@ Confirm the drift policy is `enforce` and the blueprint is enabled. Open the det
Blueprints with active or drifted deployments refuse to disable; you would orphan them silently. Withdraw the deployments first, then disable.
+### Where is my data after "Snapshot, then evict"?
+
+The named volumes managed by the stack on the target node are removed when the eviction runs `docker compose down`. The snapshot in Fleet → Snapshots holds the compose definition only; volume bytes are not included. To preserve data, capture the volume by hand before withdrawing (see *Migrating stateful data between nodes*). Bind mounts on the host filesystem are left in place by both eviction modes.
+
+### "Failed to capture compose snapshot before eviction"
+
+Sencho aborted the eviction because the pre-eviction compose snapshot could not be written. The deployment is still in place. Check the database is reachable (the snapshot lives in `fleet_snapshots`), then retry. If you accept data loss and want to evict regardless, use **Evict and destroy data** instead.
+
## What's not in scope
By design, Blueprints do not include:
diff --git a/frontend/src/components/blueprints/BlueprintDetail.tsx b/frontend/src/components/blueprints/BlueprintDetail.tsx
index f2a2a2a4..9d37081b 100644
--- a/frontend/src/components/blueprints/BlueprintDetail.tsx
+++ b/frontend/src/components/blueprints/BlueprintDetail.tsx
@@ -146,7 +146,10 @@ export function BlueprintDetail({ blueprintId, open, onOpenChange, onChanged, ca
try {
const result = await withdrawDeployment(blueprint.id, nodeId, confirm);
if (result.error) toast.error(result.error);
- else toast.success(confirm === 'evict_and_destroy' ? 'Evicted and data removed' : 'Deployment withdrawn');
+ else if (confirm === 'evict_and_destroy') toast.success('Evicted and data removed');
+ else if (confirm === 'snapshot_then_evict' && result.snapshotId !== null) {
+ toast.success(`Compose snapshot #${result.snapshotId} captured. Deployment withdrawn.`);
+ } else toast.success('Deployment withdrawn');
await refresh();
onChanged();
} catch (err) {
diff --git a/frontend/src/components/blueprints/EvictionDialog.tsx b/frontend/src/components/blueprints/EvictionDialog.tsx
index b5d672a0..bd253d33 100644
--- a/frontend/src/components/blueprints/EvictionDialog.tsx
+++ b/frontend/src/components/blueprints/EvictionDialog.tsx
@@ -65,7 +65,7 @@ export function EvictionDialog({
Snapshot, then evict (recommended)
- Captures the compose definition into the existing fleet-snapshot store, then runs the eviction. Note: volume bytes are not shipped; that ships in a future Volume Migration feature.
+ Captures this stack's compose definition to Fleet → Snapshots, then runs the eviction. Volume bytes stay on this node and are removed by docker compose down. Relocate them by hand if you need them on another node.
diff --git a/frontend/src/lib/blueprintsApi.ts b/frontend/src/lib/blueprintsApi.ts
index 028310fc..7d0f6ed9 100644
--- a/frontend/src/lib/blueprintsApi.ts
+++ b/frontend/src/lib/blueprintsApi.ts
@@ -176,7 +176,7 @@ export async function withdrawDeployment(
blueprintId: number,
nodeId: number,
confirm: WithdrawConfirm,
-): Promise<{ status: BlueprintDeploymentStatus; error: string | null; snapshotPolicy: WithdrawConfirm }> {
+): Promise<{ status: BlueprintDeploymentStatus; error: string | null; snapshotPolicy: WithdrawConfirm; snapshotId: number | null }> {
const res = await apiFetch(`/blueprints/${blueprintId}/withdraw/${nodeId}`, {
method: 'POST',
body: JSON.stringify({ confirm }),