Skip to content

Commit 2eee69f

Browse files
committed
feat: add container stop/restart for docker volume backups
- Add ContainerStopConfig with stopContainers, stopTimeout, restartRetries, and restartRetryDelay options - Support global config (docker.containerStop) and per-volume override - Add CLI flags: --stop-containers, --stop-timeout, --restart-retries - Detect auto-restart policies and warn about potential conflicts - Retry failed restarts with configurable attempts and delay - Add comprehensive tests for all new functionality
1 parent 2e041ac commit 2eee69f

20 files changed

Lines changed: 1782 additions & 54 deletions

CLAUDE.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,11 +90,13 @@ Run tests: `bun test`
9090
## CI/CD
9191

9292
CI runs on push/PR to main (`.github/workflows/ci.yml`):
93+
9394
1. Type check: `bunx tsc --noEmit`
9495
2. Lint: `bun run lint`
9596
3. Test: `bun test`
9697

9798
Release runs on version tags (`.github/workflows/release.yml`):
99+
98100
1. Same checks as CI
99101
2. Build binaries for linux-x64, linux-arm64, darwin-x64, darwin-arm64, windows-x64
100102
3. Generate SHA256 checksums
@@ -120,6 +122,7 @@ bun run build:windows-x64 # Windows x64
120122
- `js-yaml` - YAML config parsing
121123

122124
Dev:
125+
123126
- `oxlint` - Linting
124127
- `oxfmt` - Formatting
125128

@@ -132,6 +135,8 @@ Main config interface is `BackitupConfig` in `src/types/config.ts`:
132135
- `s3: S3StorageConfig` - S3/R2/MinIO settings
133136
- `schedules: Record<string, ScheduleConfig>` - Cron schedules with retention
134137
- `docker?: DockerConfig` - Docker volume backup settings
138+
- `containerStop?: ContainerStopConfig` - Global container stop/restart settings
139+
- `volumes[].containerStop?: ContainerStopConfig` - Per-volume override
135140

136141
## Common Tasks
137142

README.md

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,10 @@ Override config file settings directly from the command line. Useful for quick b
231231
| **Docker** | `--docker` | Enable Docker volume backups |
232232
| | `--no-docker` | Disable Docker volume backups |
233233
| | `--docker-volume <name>` | Docker volume to backup (can be repeated) |
234+
| | `--stop-containers` | Stop containers before volume backup |
235+
| | `--no-stop-containers` | Don't stop containers (default) |
236+
| | `--stop-timeout <seconds>` | Timeout for graceful stop (default: 30) |
237+
| | `--restart-retries <n>` | Retry attempts for restart (default: 3) |
234238

235239
### Examples
236240

@@ -306,6 +310,14 @@ version: "1.0"
306310

307311
docker:
308312
enabled: true
313+
314+
# Global container stop settings (optional)
315+
containerStop:
316+
stopContainers: true # Stop containers before backup
317+
stopTimeout: 30 # Seconds to wait for graceful stop
318+
restartRetries: 3 # Retry attempts if restart fails
319+
restartRetryDelay: 1000 # Milliseconds between retries
320+
309321
volumes:
310322
# Direct volume name
311323
- name: postgres_data
@@ -315,14 +327,42 @@ docker:
315327
type: compose
316328
composePath: ./docker-compose.yml
317329
projectName: myapp # Optional, inferred from directory
330+
331+
# Per-volume container stop override
332+
- name: redis_data
333+
containerStop:
334+
stopContainers: false # Override: don't stop for this volume
318335
```
319336
320337
### How It Works
321338
322339
1. BackItUp uses a temporary Alpine container to create the backup
323340
2. The volume is mounted read-only to ensure data safety
324-
3. If a volume is in use by running containers, backup proceeds with a warning
325-
4. Each volume produces a separate archive: `backitup-volume-{name}-{schedule}-{timestamp}.tar.gz`
341+
3. If `stopContainers` is enabled:
342+
- Containers using the volume are gracefully stopped before backup
343+
- After backup completes, containers are automatically restarted
344+
- Failed restarts are retried according to `restartRetries` setting
345+
4. If a volume is in use by running containers without stopping, backup proceeds with a warning
346+
5. Each volume produces a separate archive: `backitup-volume-{name}-{schedule}-{timestamp}.tar.gz`
347+
348+
### Container Stop/Restart
349+
350+
For data consistency (especially with databases), you can stop containers before backing up their volumes:
351+
352+
```bash
353+
# Stop containers before backup (CLI)
354+
backitup backup -s daily --docker-volume postgres_data --stop-containers --local-path /backups
355+
356+
# With custom timeout and retries
357+
backitup backup -s daily --docker-volume postgres_data \
358+
--stop-containers --stop-timeout 60 --restart-retries 5 \
359+
--local-path /backups
360+
```
361+
362+
**Important notes:**
363+
- Containers with `restart: always` or `restart: unless-stopped` policies may auto-restart after being stopped. BackItUp detects this and logs a warning.
364+
- If a container fails to restart after all retries, the backup still succeeds but a warning is logged.
365+
- Per-volume settings override global settings, allowing fine-grained control.
326366

327367
### Docker Compose Integration
328368

docs/commands/backup.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ Override or provide configuration without a config file:
5050
| `--docker` | Enable Docker volume backups |
5151
| `--no-docker` | Disable Docker volume backups |
5252
| `--docker-volume <name>` | Docker volume to backup (repeatable) |
53+
| `--stop-containers` | Stop containers before volume backup |
54+
| `--no-stop-containers` | Don't stop containers (default) |
55+
| `--stop-timeout <seconds>` | Timeout for graceful stop (default: 30) |
56+
| `--restart-retries <n>` | Retry attempts for restart (default: 3) |
5357

5458
## Schedules
5559

@@ -109,6 +113,34 @@ backitup backup -s manual --skip-volumes
109113
backitup backup -s manual --volume postgres_data --volume redis_data
110114
```
111115

116+
### Docker Volume Backups with Container Stop
117+
118+
For data consistency (especially with databases), stop containers before backing up their volumes:
119+
120+
```bash
121+
# Stop containers, backup, then restart
122+
backitup backup -s manual --volumes-only --stop-containers
123+
124+
# Inline volume backup with container stop
125+
backitup backup -s manual \
126+
--docker-volume postgres_data \
127+
--stop-containers \
128+
--local-path /backups
129+
130+
# With custom timeout and retries
131+
backitup backup -s manual \
132+
--docker-volume postgres_data \
133+
--stop-containers \
134+
--stop-timeout 60 \
135+
--restart-retries 5 \
136+
--local-path /backups
137+
```
138+
139+
**Notes:**
140+
- Containers are automatically restarted after backup completes
141+
- Containers with `restart: always` policy may auto-restart (a warning is logged)
142+
- If restart fails after all retries, backup succeeds but a warning is logged
143+
112144
### Config-Free Mode
113145

114146
Run without a config file by providing required inline options:

docs/configuration.md

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -105,12 +105,23 @@ safety:
105105
# Docker volume backup configuration (optional)
106106
docker:
107107
enabled: false
108+
109+
# Global container stop settings (optional)
110+
# Stop containers before backup to ensure data consistency
111+
containerStop:
112+
stopContainers: false # Whether to stop containers using the volume
113+
stopTimeout: 30 # Seconds to wait for graceful stop
114+
restartRetries: 3 # Number of restart attempts if restart fails
115+
restartRetryDelay: 1000 # Milliseconds between retry attempts
116+
108117
volumes:
109118
# Direct volume name
110119
- name: postgres_data
111120

112-
# Another volume
121+
# Another volume with per-volume container stop override
113122
- name: redis_data
123+
containerStop:
124+
stopContainers: true # Override: stop containers for this volume
114125

115126
# Resolve volume from docker-compose.yml service name
116127
# - name: db
@@ -213,24 +224,42 @@ Safety features for cleanup operations.
213224

214225
Docker volume backup configuration.
215226

216-
| Field | Type | Required | Description |
217-
| --------- | ------- | -------- | ---------------------------- |
218-
| `enabled` | boolean | Yes | Enable Docker volume backups |
219-
| `volumes` | array | Yes\* | List of volumes to backup |
227+
| Field | Type | Required | Description |
228+
| --------------- | ------- | -------- | -------------------------------------- |
229+
| `enabled` | boolean | Yes | Enable Docker volume backups |
230+
| `containerStop` | object | No | Global container stop/restart settings |
231+
| `volumes` | array | Yes\* | List of volumes to backup |
220232

221233
\*Required when enabled.
222234

235+
**Container Stop Settings (`containerStop`):**
236+
237+
| Field | Type | Default | Description |
238+
| ------------------- | ------- | ------- | ----------------------------------------- |
239+
| `stopContainers` | boolean | `false` | Stop containers before backup |
240+
| `stopTimeout` | number | `30` | Seconds to wait for graceful stop |
241+
| `restartRetries` | number | `3` | Number of restart attempts if fails |
242+
| `restartRetryDelay` | number | `1000` | Milliseconds between restart retry |
243+
223244
**Volume Entry:**
224245

225-
| Field | Type | Required | Description |
226-
| ------------- | ------ | -------- | ---------------------------------------------- |
227-
| `name` | string | Yes | Volume name or Compose service name |
228-
| `type` | string | No | `"volume"` (default) or `"compose"` |
229-
| `composePath` | string | No\* | Path to docker-compose.yml |
230-
| `projectName` | string | No | Compose project name (inferred from directory) |
246+
| Field | Type | Required | Description |
247+
| --------------- | ------ | -------- | ---------------------------------------------- |
248+
| `name` | string | Yes | Volume name or Compose service name |
249+
| `type` | string | No | `"volume"` (default) or `"compose"` |
250+
| `composePath` | string | No\* | Path to docker-compose.yml |
251+
| `projectName` | string | No | Compose project name (inferred from directory) |
252+
| `containerStop` | object | No | Per-volume override of container stop settings |
231253

232254
\*Required when `type: compose`.
233255

256+
**Notes on Container Stop:**
257+
258+
- When `stopContainers` is enabled, BackItUp stops all containers using the volume before backup and restarts them after.
259+
- Containers with `restart: always` or `restart: unless-stopped` policies may auto-restart after being stopped. BackItUp detects this and logs a warning.
260+
- Per-volume `containerStop` settings override global settings.
261+
- If a container fails to restart after all retries, the backup still succeeds but a warning is logged.
262+
234263
## Minimal Configuration
235264

236265
### YAML

docs/inline-config.md

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,15 @@ BackItUp supports passing configuration options directly via command-line flags.
6464

6565
### Docker
6666

67-
| Option | Description |
68-
| ------------------------ | ------------------------------------ |
69-
| `--docker` | Enable Docker volume backups |
70-
| `--no-docker` | Disable Docker volume backups |
71-
| `--docker-volume <name>` | Docker volume to backup (repeatable) |
67+
| Option | Description |
68+
| ---------------------------- | ----------------------------------------- |
69+
| `--docker` | Enable Docker volume backups |
70+
| `--no-docker` | Disable Docker volume backups |
71+
| `--docker-volume <name>` | Docker volume to backup (repeatable) |
72+
| `--stop-containers` | Stop containers before volume backup |
73+
| `--no-stop-containers` | Don't stop containers (default behavior) |
74+
| `--stop-timeout <seconds>` | Timeout for graceful stop (default: 30) |
75+
| `--restart-retries <n>` | Retry attempts for restart (default: 3) |
7276

7377
## Usage Modes
7478

@@ -169,6 +173,26 @@ backitup backup -s manual \
169173
--local-path /backups
170174
```
171175

176+
### Docker Volumes with Container Stop
177+
178+
For data consistency (especially with databases), stop containers before backup:
179+
180+
```bash
181+
# Stop containers before backup, restart after
182+
backitup backup -s manual \
183+
--docker-volume postgres_data \
184+
--stop-containers \
185+
--local-path /backups
186+
187+
# With custom timeout and retries
188+
backitup backup -s manual \
189+
--docker-volume postgres_data \
190+
--stop-containers \
191+
--stop-timeout 60 \
192+
--restart-retries 5 \
193+
--local-path /backups
194+
```
195+
172196
### S3-Compatible Storage (MinIO, R2)
173197

174198
```bash

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"build:all": "bun run build:linux-x64 && bun run build:linux-arm64",
2626
"test": "bun test",
2727
"lint": "bunx oxlint --fix",
28-
"format": "bunx oxfmt@latest --fix"
28+
"format": "bunx oxfmt@latest"
2929
},
3030
"dependencies": {
3131
"@clack/prompts": "^0.11.0",

src/cli/commands/backup.ts

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,11 +185,48 @@ export async function backupCommand(args: string[]): Promise<number> {
185185
label: "Volume names",
186186
value: volumeNames,
187187
});
188+
189+
// Count containers that were stopped
190+
const stoppedContainers = result.volumeBackups
191+
.flatMap((v) => v.stoppedContainers || [])
192+
.filter((name, i, arr) => arr.indexOf(name) === i); // dedupe
193+
if (stoppedContainers.length > 0) {
194+
summaryItems.push({
195+
label: "Containers stopped",
196+
value: stoppedContainers.join(", "),
197+
});
198+
}
199+
200+
// Check for failed restarts
201+
const failedRestarts = result.volumeBackups
202+
.flatMap((v) => v.failedToRestart || [])
203+
.filter((name, i, arr) => arr.indexOf(name) === i); // dedupe
204+
if (failedRestarts.length > 0) {
205+
summaryItems.push({
206+
label: "Failed to restart",
207+
value: `${failedRestarts.join(", ")} (manual restart required)`,
208+
});
209+
}
210+
211+
// Check for auto-restart warnings
212+
const hadAutoRestartWarning = result.volumeBackups.some((v) => v.hadAutoRestartWarning);
213+
if (hadAutoRestartWarning) {
214+
summaryItems.push({
215+
label: "Warning",
216+
value: "Some containers had auto-restart policy",
217+
});
218+
}
219+
188220
const inUseCount = result.volumeBackups.filter((v) => v.wasInUse).length;
189-
if (inUseCount > 0) {
221+
const stoppedCount = result.volumeBackups.filter(
222+
(v) => v.stoppedContainers && v.stoppedContainers.length > 0,
223+
).length;
224+
// Only show "in use" warning if containers weren't stopped
225+
if (inUseCount > 0 && stoppedCount < inUseCount) {
226+
const notStoppedCount = inUseCount - stoppedCount;
190227
summaryItems.push({
191228
label: "Volumes in use",
192-
value: `${inUseCount} (data may be inconsistent)`,
229+
value: `${notStoppedCount} (data may be inconsistent)`,
193230
});
194231
}
195232
}
@@ -254,6 +291,16 @@ ${color.dim("INLINE CONFIG OPTIONS:")}
254291
--no-docker Disable Docker volume backups
255292
--docker-volume <name> Docker volume to backup (can be repeated)
256293
294+
${color.dim("DOCKER CONTAINER OPTIONS:")}
295+
--stop-containers Stop containers using volumes before backup
296+
--no-stop-containers Keep containers running during backup (default)
297+
--stop-timeout <seconds> Graceful stop timeout (default: 30)
298+
--restart-retries <n> Restart retry attempts (default: 3)
299+
300+
${color.dim("IMPORTANT:")} When using --stop-containers, be aware that containers with
301+
restart policy "always" or "unless-stopped" may auto-restart during backup.
302+
Consider using "restart: on-failure" for cleaner backups.
303+
257304
${color.dim("EXAMPLES:")}
258305
backitup backup # Interactive schedule selection
259306
backitup backup -s manual # Manual backup (non-interactive)
@@ -268,5 +315,10 @@ ${color.dim("INLINE CONFIG EXAMPLES:")}
268315
backitup backup -s manual --source /data --local-path /backups
269316
backitup backup -s manual --source /app --s3-bucket my-backups --no-local
270317
backitup backup -s manual --source /db --retention-count 5 --retention-days 7
318+
319+
${color.dim("DOCKER EXAMPLES:")}
320+
backitup backup -s manual --docker-volume mydb --stop-containers
321+
backitup backup -s manual --stop-containers --stop-timeout 60
322+
backitup backup -s manual --docker-volume postgres_data --stop-containers --restart-retries 5
271323
`);
272324
}

0 commit comments

Comments
 (0)