feat(deploy): add --parallel flag for concurrent image builds
When --parallel is set: - DOCKER_BUILDKIT=1 is injected into the subprocess environment to ensure BuildKit is active regardless of host daemon config - docker compose build runs first (all images built concurrently) - docker compose up -d follows without --build (no redundant checks) Without --parallel the original up --build path is preserved. --parallel and --no-cache compose correctly (build --no-cache).
This commit is contained in:
@@ -248,6 +248,7 @@ def deploy(
|
||||
mutate_interval: Optional[int] = typer.Option(30, "--mutate-interval", help="Automatically rotate services every N minutes"),
|
||||
dry_run: bool = typer.Option(False, "--dry-run", help="Generate compose file without starting containers"),
|
||||
no_cache: bool = typer.Option(False, "--no-cache", help="Force rebuild all images, ignoring Docker layer cache"),
|
||||
parallel: bool = typer.Option(False, "--parallel", help="Build all images concurrently (enables BuildKit, separates build from up)"),
|
||||
ipvlan: bool = typer.Option(False, "--ipvlan", help="Use IPvlan L2 instead of MACVLAN (required on WiFi interfaces)"),
|
||||
config_file: Optional[str] = typer.Option(None, "--config", "-c", help="Path to INI config file"),
|
||||
api: bool = typer.Option(False, "--api", help="Start the FastAPI backend to ingest and serve logs"),
|
||||
@@ -379,7 +380,7 @@ def deploy(
|
||||
)
|
||||
|
||||
from decnet.deployer import deploy as _deploy
|
||||
_deploy(config, dry_run=dry_run, no_cache=no_cache)
|
||||
_deploy(config, dry_run=dry_run, no_cache=no_cache, parallel=parallel)
|
||||
|
||||
if mutate_interval is not None and not dry_run:
|
||||
import subprocess # nosec B404
|
||||
|
||||
@@ -49,9 +49,11 @@ def _sync_logging_helper(config: DecnetConfig) -> None:
|
||||
shutil.copy2(_CANONICAL_LOGGING, dest)
|
||||
|
||||
|
||||
def _compose(*args: str, compose_file: Path = COMPOSE_FILE) -> None:
|
||||
def _compose(*args: str, compose_file: Path = COMPOSE_FILE, env: dict | None = None) -> None:
|
||||
import os
|
||||
cmd = ["docker", "compose", "-f", str(compose_file), *args]
|
||||
subprocess.run(cmd, check=True) # nosec B603
|
||||
merged = {**os.environ, **(env or {})}
|
||||
subprocess.run(cmd, check=True, env=merged) # nosec B603
|
||||
|
||||
|
||||
_PERMANENT_ERRORS = (
|
||||
@@ -68,12 +70,15 @@ def _compose_with_retry(
|
||||
compose_file: Path = COMPOSE_FILE,
|
||||
retries: int = 3,
|
||||
delay: float = 5.0,
|
||||
env: dict | None = None,
|
||||
) -> None:
|
||||
"""Run a docker compose command, retrying on transient failures."""
|
||||
import os
|
||||
last_exc: subprocess.CalledProcessError | None = None
|
||||
cmd = ["docker", "compose", "-f", str(compose_file), *args]
|
||||
merged = {**os.environ, **(env or {})}
|
||||
for attempt in range(1, retries + 1):
|
||||
result = subprocess.run(cmd, capture_output=True, text=True) # nosec B603
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, env=merged) # nosec B603
|
||||
if result.returncode == 0:
|
||||
if result.stdout:
|
||||
print(result.stdout, end="")
|
||||
@@ -100,7 +105,7 @@ def _compose_with_retry(
|
||||
raise last_exc
|
||||
|
||||
|
||||
def deploy(config: DecnetConfig, dry_run: bool = False, no_cache: bool = False) -> None:
|
||||
def deploy(config: DecnetConfig, dry_run: bool = False, no_cache: bool = False, parallel: bool = False) -> None:
|
||||
client = docker.from_env()
|
||||
|
||||
# --- Network setup ---
|
||||
@@ -145,10 +150,24 @@ def deploy(config: DecnetConfig, dry_run: bool = False, no_cache: bool = False)
|
||||
save_state(config, compose_path)
|
||||
|
||||
# --- Bring up ---
|
||||
# With --parallel: force BuildKit, run build explicitly (so all images are
|
||||
# built concurrently before any container starts), then up without --build.
|
||||
# Without --parallel: keep the original up --build path.
|
||||
build_env = {"DOCKER_BUILDKIT": "1"} if parallel else {}
|
||||
|
||||
console.print("[bold cyan]Building images and starting deckies...[/]")
|
||||
build_args = ["build"]
|
||||
if no_cache:
|
||||
_compose_with_retry("build", "--no-cache", compose_file=compose_path)
|
||||
_compose_with_retry("up", "--build", "-d", compose_file=compose_path)
|
||||
build_args.append("--no-cache")
|
||||
|
||||
if parallel:
|
||||
console.print("[bold cyan]Parallel build enabled — building all images concurrently...[/]")
|
||||
_compose_with_retry(*build_args, compose_file=compose_path, env=build_env)
|
||||
_compose_with_retry("up", "-d", compose_file=compose_path, env=build_env)
|
||||
else:
|
||||
if no_cache:
|
||||
_compose_with_retry("build", "--no-cache", compose_file=compose_path)
|
||||
_compose_with_retry("up", "--build", "-d", compose_file=compose_path)
|
||||
|
||||
# --- Status summary ---
|
||||
_print_status(config)
|
||||
|
||||
Reference in New Issue
Block a user