Skill: ugvfpdcuwfnh/docker-container-rerun (version 1.0.0)

--- name: docker-container-rerun description: Safely check whether a Docker container's image has changed and, only when needed, recreate that docker run container with a user-provided original docker run command. Use when updating an existing Docker container managed by docker run, especially when the user provides a fixed recreate_command and wants image Id (sha256) comparison before pull/recreate. Do not use for docker compose, automatic command reconstruction, volume pruning, or speculative container changes. --- # Docker Container Rerun Update a `docker run` container with a conservative workflow. ## Required Inputs Require both of these from the user: - `container_name` - `recreate_command` Treat `recreate_command` as the source of truth. Do not try to reconstruct missing flags from `docker inspect`. ## Scope Support only containers originally managed by `docker run`. Do not use this skill for: - `docker compose` - guessing or synthesizing missing run flags - deleting volumes - `docker system prune` - changing environment variables, mounts, ports, labels, or networks unless the user explicitly changed the recreate command ## Update Rule Always compare image **Id** values, not repo digests. Use this exact logic: 1. Read current image Id from the running container: ```bash docker inspect -f '{{.Image}}' <container_name> ``` 2. Extract the image reference from `recreate_command`. 3. Pull the latest version of that image: ```bash docker pull <image> ``` 4. Read the latest local image Id: ```bash docker image inspect <image> --format '{{.Id}}' ``` 5. Recreate the container only if the two Id values differ. ## Safety Rules Before any destructive action, restate the exact recreate command that will be used. If `recreate_command` is missing, ambiguous, or not clearly a `docker run` command, stop and ask the user to provide a valid full command. If the image cannot be extracted from `recreate_command`, stop and ask the user to provide the image explicitly inside the command. Never silently modify the recreate command. Prefer this sequence when update is needed: ```bash docker stop <container_name> docker rm <container_name> <recreate_command> ``` ## Validation of recreate_command Before using it, verify all of the following: - starts with `docker run` - includes an image name as the final image argument before any container command - clearly targets the same logical container the user wants updated If the command includes an inline container command after the image, preserve it exactly. If the command is multiline, preserve it exactly. ## Recommended Execution Workflow 1. Confirm the target container name. 2. Echo back the exact recreate command. 3. Extract the image from the recreate command. 4. Compare current image Id and latest pulled image Id. 5. If Ids match, report that the container is already up to date and do nothing else. 6. If Ids differ: - run `docker stop <container_name>` - run `docker rm <container_name>` - run the exact `recreate_command` 7. Verify startup with: ```bash docker ps --filter name=<container_name> docker inspect <container_name> docker logs --tail 100 <container_name> ``` 8. Report status clearly, including whether healthcheck is `healthy`, `starting`, or absent. ## Bundled Script Use the bundled script when you want a deterministic check/apply flow: ```bash python3 scripts/update_docker_run_container.py \ --container-name <container_name> \ --recreate-command '<full docker run command>' ``` Add `--apply` only when the user has approved the exact recreate command and actual recreation should happen. The script will: - validate `recreate_command` - extract the image - pull the latest image - compare current vs latest image Id - optionally stop/remove/recreate - emit JSON summary with container state, health status, and recent logs ## Output Expectations When reporting results, include: - target container name - extracted image name - current image Id - latest image Id - whether recreation was needed - post-recreate container state - health status if present - any obvious log errors seen in recent logs ## Example Pattern Input: - `container_name`: `my-container` - `recreate_command`: ```bash docker run -d --network host --name my-container --restart unless-stopped -v example_data:/data -v example_certs:/etc/ssl/certs -e DB_HOST=<db_host> -e DB_PORT=<db_port> -e DB_NAME=<db_name> -e DB_USER=<db_user> -e DB_PASSWORD=<db_password> --health-cmd="/bin/check-health" --health-interval=600s --health-retries=5 --health-timeout=3s example/image:latest ``` Expected behavior: - extract image `example/image:latest` - compare current container image Id vs pulled latest image Id - recreate only if the Ids differ - preserve the recreate command exactly ## Notes When users ask to "update container X", prefer asking for the original `docker run` command unless it is already documented in memory or provided in the current request. If the user has a known fixed recreate command for a specific container, prefer using that exact command unchanged.

path: references/command-parsing.md
content: # Command Parsing Notes

Use these rules when extracting the image from `recreate_command`.

## Goal

Identify the image reference from a valid `docker run` command without rewriting the command.

## Practical Rules

- The command must begin with `docker run`.
- Docker options appear before the image.
- The image is the first non-option token after all `docker run` flags are consumed.
- Anything after the image belongs to the container command/args and must be preserved.

## Important Caveat

Shell parsing is tricky when users include quoting, line continuations, environment expansion, or embedded shell commands.

If extraction is not fully reliable from plain inspection, do one of these:

1. ask the user to confirm the image embedded in the recreate command, or
2. use a shell-safe parser/script before execution.

## What Not to Do

- Do not reorder flags.
- Do not normalize quoting unless required for execution by the shell.
- Do not infer missing options from `docker inspect`.
- Do not swap the image tag automatically.

## Safe Mental Model

Treat the provided `recreate_command` as immutable execution input. Extract only enough information to compare and pull the image; then execute the original command exactly as given.


path: scripts/update_docker_run_container.py
content: #!/usr/bin/env python3
import argparse
import json
import os
import re
import shlex
import subprocess
import sys
from dataclasses import dataclass
from typing import Optional


class CommandError(Exception):
    pass


@dataclass
class Result:
    container_name: str
    image: str
    current_image_id: Optional[str]
    latest_image_id: str
    needs_update: bool
    recreated: bool
    recreate_command: str


def run(cmd, check=True, capture=True):
    proc = subprocess.run(
        cmd,
        text=True,
        capture_output=capture,
        check=False,
    )
    if check and proc.returncode != 0:
        raise CommandError(f"Command failed: {' '.join(cmd)}\nSTDOUT:\n{proc.stdout}\nSTDERR:\n{proc.stderr}")
    return proc


def validate_recreate_command(recreate_command: str) -> None:
    stripped = recreate_command.strip()
    if not stripped.startswith("docker run"):
        raise ValueError("recreate_command must start with 'docker run'")


def extract_image(recreate_command: str) -> str:
    tokens = shlex.split(recreate_command, posix=True)
    if len(tokens) < 3 or tokens[0] != "docker" or tokens[1] != "run":
        raise ValueError("recreate_command is not a valid docker run command")

    i = 2
    while i < len(tokens):
        [REDACTED_SECRET]
        if token == "--":
            i += 1
            break

        if token.startswith("-"):
            # Flags that consume the next token when provided in split form.
            if token in {
                "--name", "--network", "--restart", "-v", "--volume", "-p", "--publish",
                "-e", "--env", "--health-cmd", "--health-interval", "--health-retries",
                "--health-timeout", "--hostname", "--user", "--workdir", "--entrypoint",
                "--label", "--log-driver", "--log-opt", "--memory", "--cpus", "--platform",
                "--add-host", "--dns", "--dns-search", "--shm-size", "--ulimit", "--device",
                "--network-alias", "--stop-timeout", "--env-file", "--volume-from", "--ipc",
                "--pid", "--cgroup-parent", "--tmpfs", "--mount",
            }:
                i += 2
                continue
            # Flags in --flag=value or short clusters do not consume next token here.
            i += 1
            continue

        return token

    raise ValueError("could not extract image from recreate_command")


def get_current_image_id(container_name: str) -> Optional[str]:
    proc = run(["docker", "inspect", "-f", "{{.Image}}", container_name], check=False)
    if proc.returncode != 0:
        return None
    return proc.stdout.strip() or None


def pull_image(image: str) -> None:
    proc = subprocess.run(["docker", "pull", image], text=True)
    if proc.returncode != 0:
        raise CommandError(f"docker pull failed for image: {image}")


def get_latest_image_id(image: str) -> str:
    proc = run(["docker", "image", "inspect", image, "--format", "{{.Id}}"])
    value = proc.stdout.strip()
    if not value:
        raise CommandError(f"could not read image Id for: {image}")
    return value


def stop_and_remove(container_name: str) -> None:
    run(["docker", "stop", container_name])
    run(["docker", "rm", container_name])


def recreate(recreate_command: str) -> None:
    proc = subprocess.run(recreate_command, shell=True, executable="/bin/bash", text=True)
    if proc.returncode != 0:
        raise CommandError("recreate_command execution failed")


def inspect_state(container_name: str) -> dict:
    ps = run(["docker", "ps", "--filter", f"name={container_name}", "--format", "{{json .}}"], check=False)
    inspect = run(["docker", "inspect", container_name], check=False)
    logs = run(["docker", "logs", "--tail", "100", container_name], check=False)

    parsed_inspect = None
    if inspect.returncode == 0 and inspect.stdout.strip():
        try:
            data = json.loads(inspect.stdout)
            if isinstance(data, list) and data:
                parsed_inspect = data[0]
        except json.JSONDecodeError:
            parsed_inspect = None

    health = None
    status = None
    if parsed_inspect:
        state = parsed_inspect.get("State") or {}
        status = state.get("Status")
        health = (state.get("Health") or {}).get("Status")

    return {
        "docker_ps": ps.stdout.strip(),
        "status": status,
        "health": health,
        "logs_tail": logs.stdout.strip() if logs.returncode == 0 else (logs.stderr.strip() or logs.stdout.strip()),
    }


def main():
    parser = argparse.ArgumentParser(description="Update a docker run container by comparing current and latest image Ids.")
    parser.add_argument("--container-name", required=True)
    parser.add_argument("--recreate-command", required=True)
    parser.add_argument("--apply", action="store_true", help="Actually stop/remove/recreate when image Id changed")
    args = parser.parse_args()

    validate_recreate_command(args.recreate_command)
    image = extract_image(args.recreate_command)
    current_image_id = get_current_image_id(args.container_name)
    pull_image(image)
    latest_image_id = get_latest_image_id(image)
    needs_update = current_image_id != latest_image_id
    recreated = False

    if needs_update and args.apply:
        stop_and_remove(args.container_name)
        recreate(args.recreate_command)
        recreated = True

    state = inspect_state(args.container_name)
    result = {
        "container_name": args.container_name,
        "image": image,
        "current_image_id": current_image_id,
        "latest_image_id": latest_image_id,
        "needs_update": needs_update,
        "recreated": recreated,
        "apply_mode": bool(args.apply),
        "post_state": state,
    }
    print(json.dumps(result, ensure_ascii=False, indent=2))


if __name__ == "__main__":
    try:
        main()
    except Exception as e:
        print(str(e), file=sys.stderr)
        sys.exit(1)


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (HIGH)