From 3460d8a3eabe20dec3edb8071cbcd4c05c03b5c1 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 5 Jan 2026 14:29:03 +0100 Subject: [PATCH] restart: Match Docker Compose semantics (#145) * restart: Match Docker Compose semantics Change `cf restart` from doing `down + up` to using `docker compose restart`, matching the Docker Compose command behavior. This provides command naming parity with Docker Compose. Users who want the old behavior can use `cf down mystack && cf up mystack`. - Update restart implementation to use `docker compose restart` - Remove traefik regeneration from restart (no longer recreates containers) - Update all documentation and help text - Remove restart from self-update SSH handling (no longer involves down) * web: Clarify Update tooltip uses 'recreate' not 'restart' Avoid confusion now that 'restart' means something different. * web: Fix Update All tooltip to use 'recreates' --- CLAUDE.md | 2 +- README.md | 13 ++++++------- compose-farm.example.yaml | 2 +- docs/commands.md | 4 ++-- docs/configuration.md | 2 +- docs/traefik.md | 1 - examples/README.md | 2 +- examples/compose-farm.yaml | 2 +- src/compose_farm/cli/lifecycle.py | 14 ++++++-------- src/compose_farm/example-config.yaml | 2 +- src/compose_farm/web/static/app.js | 2 +- src/compose_farm/web/streaming.py | 4 ++-- src/compose_farm/web/templates/index.html | 2 +- src/compose_farm/web/templates/stack.html | 4 ++-- 14 files changed, 26 insertions(+), 30 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index cf99292..45e9961 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -137,7 +137,7 @@ CLI available as `cf` or `compose-farm`. | `down` | Stop stacks (`docker compose down`). Use `--orphaned` to stop stacks removed from config | | `stop` | Stop services without removing containers (`docker compose stop`) | | `pull` | Pull latest images | -| `restart` | `down` + `up -d` | +| `restart` | Restart running containers (`docker compose restart`) | | `update` | Pull, build, recreate only if changed (`up -d --pull always --build`) | | `apply` | Make reality match config: migrate stacks + stop orphans. Use `--dry-run` to preview | | `compose` | Run any docker compose command on a stack (passthrough) | diff --git a/README.md b/README.md index e8596f9..5060b52 100644 --- a/README.md +++ b/README.md @@ -369,7 +369,7 @@ The CLI is available as both `compose-farm` and the shorter `cf` alias. | `cf up ` | Start stack (auto-migrates if host changed) | | `cf down ` | Stop and remove stack containers | | `cf stop ` | Stop stack without removing containers | -| `cf restart ` | down + up | +| `cf restart ` | Restart running containers | | `cf update ` | Pull, build, recreate only if changed | | `cf pull ` | Pull latest images | | `cf logs -f ` | Follow logs | @@ -400,7 +400,7 @@ cf down --orphaned # stop stacks removed from config # Pull latest images cf pull --all -# Restart (down + up) +# Restart running containers cf restart plex # Update (pull + build, only recreates containers if images changed) @@ -473,8 +473,7 @@ Full `--help` output for each command. See the [Usage](#usage) table above for a │ stop Stop services without removing containers (docker compose │ │ stop). │ │ pull Pull latest images (docker compose pull). │ -│ restart Restart stacks (down + up). With --service, restarts just │ -│ that service. │ +│ restart Restart running containers (docker compose restart). │ │ update Update stacks. Only recreates containers if images changed. │ │ apply Make reality match config (start, migrate, stop │ │ strays/orphans as needed). │ @@ -658,7 +657,7 @@ Full `--help` output for each command. See the [Usage](#usage) table above for a Usage: cf restart [OPTIONS] [STACKS]... - Restart stacks (down + up). With --service, restarts just that service. + Restart running containers (docker compose restart). ╭─ Arguments ──────────────────────────────────────────────────────────────────╮ │ stacks [STACKS]... Stacks to operate on │ @@ -1281,12 +1280,12 @@ published ports. **Auto-regeneration** -To automatically regenerate the Traefik config after `up`, `down`, `restart`, or `update`, +To automatically regenerate the Traefik config after `up`, `down`, or `update`, add `traefik_file` to your config: ```yaml compose_dir: /opt/compose -traefik_file: /opt/traefik/dynamic.d/compose-farm.yml # auto-regenerate on up/down/restart/update +traefik_file: /opt/traefik/dynamic.d/compose-farm.yml # auto-regenerate on up/down/update traefik_stack: traefik # skip stacks on same host (docker provider handles them) hosts: diff --git a/compose-farm.example.yaml b/compose-farm.example.yaml index ae186f0..e313468 100644 --- a/compose-farm.example.yaml +++ b/compose-farm.example.yaml @@ -3,7 +3,7 @@ compose_dir: /opt/compose -# Optional: Auto-regenerate Traefik file-provider config after up/down/restart/update +# Optional: Auto-regenerate Traefik file-provider config after up/down/update traefik_file: /opt/traefik/dynamic.d/compose-farm.yml traefik_stack: traefik # Skip stacks on same host (docker provider handles them) diff --git a/docs/commands.md b/docs/commands.md index 9e907ee..2b39d32 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -14,7 +14,7 @@ The Compose Farm CLI is available as both `compose-farm` and the shorter alias ` | | `up` | Start stacks | | | `down` | Stop stacks | | | `stop` | Stop services without removing containers | -| | `restart` | Restart stacks (down + up) | +| | `restart` | Restart running containers | | | `update` | Update stacks (only recreates if images changed) | | | `pull` | Pull latest images | | | `compose` | Run any docker compose command | @@ -197,7 +197,7 @@ cf stop immich --service database ### cf restart -Restart stacks (down + up). With `--service`, restarts just that service. +Restart running containers (`docker compose restart`). With `--service`, restarts just that service. ```bash cf restart [OPTIONS] [STACKS]... diff --git a/docs/configuration.md b/docs/configuration.md index 15dd680..e4c279f 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -107,7 +107,7 @@ Supported compose file names (checked in order): ### traefik_file -Path to auto-generated Traefik file-provider config. When set, Compose Farm regenerates this file after `up`, `down`, `restart`, and `update` commands. +Path to auto-generated Traefik file-provider config. When set, Compose Farm regenerates this file after `up`, `down`, and `update` commands. ```yaml traefik_file: /opt/traefik/dynamic.d/compose-farm.yml diff --git a/docs/traefik.md b/docs/traefik.md index 7a7355b..5b5be63 100644 --- a/docs/traefik.md +++ b/docs/traefik.md @@ -139,7 +139,6 @@ stacks: With `traefik_file` set, these commands auto-regenerate the config: - `cf up` - `cf down` -- `cf restart` - `cf update` - `cf apply` diff --git a/examples/README.md b/examples/README.md index 8c70e74..cea8844 100644 --- a/examples/README.md +++ b/examples/README.md @@ -168,4 +168,4 @@ traefik_file: /opt/stacks/traefik/dynamic.d/compose-farm.yml traefik_stack: traefik ``` -With `traefik_file` configured, compose-farm automatically regenerates the config after `up`, `down`, `restart`, and `update` commands. +With `traefik_file` configured, compose-farm automatically regenerates the config after `up`, `down`, and `update` commands. diff --git a/examples/compose-farm.yaml b/examples/compose-farm.yaml index 13cedff..5183053 100644 --- a/examples/compose-farm.yaml +++ b/examples/compose-farm.yaml @@ -5,7 +5,7 @@ compose_dir: /opt/stacks/compose-farm/examples -# Auto-regenerate Traefik file-provider config after up/down/restart/update +# Auto-regenerate Traefik file-provider config after up/down/update traefik_file: /opt/stacks/compose-farm/examples/traefik/dynamic.d/compose-farm.yml traefik_stack: traefik # Skip Traefik's host in file-provider (docker provider handles it) diff --git a/src/compose_farm/cli/lifecycle.py b/src/compose_farm/cli/lifecycle.py index 193bd24..19c7da4 100644 --- a/src/compose_farm/cli/lifecycle.py +++ b/src/compose_farm/cli/lifecycle.py @@ -28,7 +28,7 @@ from compose_farm.cli.common import ( ) from compose_farm.cli.management import _discover_stacks_full from compose_farm.console import MSG_DRY_RUN, console, print_error, print_success -from compose_farm.executor import run_compose_on_host, run_on_stacks, run_sequential_on_stacks +from compose_farm.executor import run_compose_on_host, run_on_stacks from compose_farm.operations import ( stop_orphaned_stacks, stop_stray_stacks, @@ -161,19 +161,17 @@ def restart( service: ServiceOption = None, config: ConfigOption = None, ) -> None: - """Restart stacks (down + up). With --service, restarts just that service.""" + """Restart running containers (docker compose restart).""" stack_list, cfg = get_stacks(stacks or [], all_stacks, config) if service: if len(stack_list) != 1: print_error("--service requires exactly one stack") raise typer.Exit(1) - # For service-level restart, use docker compose restart (more efficient) - raw = True - results = run_async(run_on_stacks(cfg, stack_list, f"restart {service}", raw=raw)) + cmd = f"restart {service}" else: - raw = len(stack_list) == 1 - results = run_async(run_sequential_on_stacks(cfg, stack_list, ["down", "up -d"], raw=raw)) - maybe_regenerate_traefik(cfg, results) + cmd = "restart" + raw = len(stack_list) == 1 + results = run_async(run_on_stacks(cfg, stack_list, cmd, raw=raw)) report_results(results) diff --git a/src/compose_farm/example-config.yaml b/src/compose_farm/example-config.yaml index 0d2e6c7..c074c54 100644 --- a/src/compose_farm/example-config.yaml +++ b/src/compose_farm/example-config.yaml @@ -76,7 +76,7 @@ stacks: # traefik_file: (optional) Auto-generate Traefik file-provider config # ------------------------------------------------------------------------------ # When set, compose-farm automatically regenerates this file after -# up/down/restart/update commands. Traefik watches this file for changes. +# up/down/update commands. Traefik watches this file for changes. # # traefik_file: /opt/compose/traefik/dynamic.d/compose-farm.yml diff --git a/src/compose_farm/web/static/app.js b/src/compose_farm/web/static/app.js index a0086cb..a216b22 100644 --- a/src/compose_farm/web/static/app.js +++ b/src/compose_farm/web/static/app.js @@ -628,7 +628,7 @@ function playFabIntro() { stackCmd('Down', 'Stop', 'down', icons.square), stackCmd('Restart', 'Restart', 'restart', icons.rotate_cw), stackCmd('Pull', 'Pull', 'pull', icons.cloud_download), - stackCmd('Update', 'Pull + restart', 'update', icons.refresh_cw), + stackCmd('Update', 'Pull + recreate', 'update', icons.refresh_cw), stackCmd('Logs', 'View logs for', 'logs', icons.file_text), ); diff --git a/src/compose_farm/web/streaming.py b/src/compose_farm/web/streaming.py index 1e2160c..9b25ebd 100644 --- a/src/compose_farm/web/streaming.py +++ b/src/compose_farm/web/streaming.py @@ -103,8 +103,8 @@ def _is_self_update(stack: str, command: str) -> bool: """ if not CF_WEB_STACK or stack != CF_WEB_STACK: return False - # Commands that involve 'down' need SSH: update, restart, down - return command in ("update", "restart", "down") + # Commands that involve 'down' need SSH: update, down + return command in ("update", "down") async def _run_cli_via_ssh( diff --git a/src/compose_farm/web/templates/index.html b/src/compose_farm/web/templates/index.html index ce8fee6..aaf61bb 100644 --- a/src/compose_farm/web/templates/index.html +++ b/src/compose_farm/web/templates/index.html @@ -18,7 +18,7 @@ {{ action_btn("Apply", "/api/apply", "primary", "Make reality match config", check()) }} {{ action_btn("Refresh", "/api/refresh", "outline", "Update state from reality", refresh_cw()) }} {{ action_btn("Pull All", "/api/pull-all", "outline", "Pull latest images for all stacks", cloud_download()) }} - {{ action_btn("Update All", "/api/update-all", "outline", "Update all stacks except web (only restarts if changed)", rotate_cw()) }} + {{ action_btn("Update All", "/api/update-all", "outline", "Update all stacks except web (only recreates if changed)", rotate_cw()) }}
diff --git a/src/compose_farm/web/templates/stack.html b/src/compose_farm/web/templates/stack.html index 304a163..dbb0827 100644 --- a/src/compose_farm/web/templates/stack.html +++ b/src/compose_farm/web/templates/stack.html @@ -22,8 +22,8 @@ {{ action_btn("Up", "/api/stack/" ~ name ~ "/up", "primary", "Start stack (docker compose up -d)", play()) }} {{ action_btn("Down", "/api/stack/" ~ name ~ "/down", "outline", "Stop stack (docker compose down)", square()) }} - {{ action_btn("Restart", "/api/stack/" ~ name ~ "/restart", "secondary", "Restart stack (down + up)", rotate_cw()) }} - {{ action_btn("Update", "/api/stack/" ~ name ~ "/update", "accent", "Update to latest (only restarts if changed)", download()) }} + {{ action_btn("Restart", "/api/stack/" ~ name ~ "/restart", "secondary", "Restart running containers", rotate_cw()) }} + {{ action_btn("Update", "/api/stack/" ~ name ~ "/update", "accent", "Update to latest (only recreates if changed)", download()) }}