From 69e3d34dd4079ee0796bc4e74098a5b788b81d48 Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Sun, 28 Dec 2025 02:21:52 +0100 Subject: [PATCH 1/6] feat: Python on whales & embedded docker CLI --- Dockerfile | 4 ++-- pyproject.toml | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 4dfb251..ba03149 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,7 +14,7 @@ ADD . /app RUN --mount=type=cache,target=/root/.cache/uv \ uv sync --locked -FROM python:3.12-alpine3.22 +FROM docker:29.1.3-cli ENV USER=karl ENV GROUPNAME=docker @@ -39,7 +39,7 @@ RUN addgroup \ $USER RUN apk update --no-cache \ - && apk add --no-cache git + && apk add --no-cache git python3 COPY --from=builder --chown=app:app /app/.venv /app/.venv COPY --from=builder --chown=app:app /app/src /app/src diff --git a/pyproject.toml b/pyproject.toml index ddbc958..043e28e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,6 +21,7 @@ dependencies = [ "keyring>=25.6.0", "keyring-backend>=0.1.0", "bubus>=1.5.6", + "python-on-whales>=0.79.0", ] [dependency-groups] From a31e1cdad96662dc77644ccb6b2453d09e92a345 Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Sun, 28 Dec 2025 02:25:26 +0100 Subject: [PATCH 2/6] fix: docker group is already defined --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index ba03149..aad1cc2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,7 +17,7 @@ RUN --mount=type=cache,target=/root/.cache/uv \ FROM docker:29.1.3-cli ENV USER=karl -ENV GROUPNAME=docker +ENV GROUPNAME=docker-host ENV UID=1000 ENV GID=994 ENV PYTHONPATH="/app" From e67ecb38d47cf008483fffb998f8a31a8a7ef09a Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Sat, 3 Jan 2026 21:43:34 +0100 Subject: [PATCH 3/6] feat: Python on whales test --- pyproject.toml | 1 - src/karl/services/containers.py | 48 +++------------------------------ 2 files changed, 4 insertions(+), 45 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 043e28e..b866a13 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,6 @@ dependencies = [ "pyyaml>=6.0.2", "gitpython>=3.1.45", "pykeepass>=4.1.1.post1", - "docker>=7.1.0", "injectable==4.0.1", "py-automapper>=2.2.0", "fastapi-utils>=0.8.0", diff --git a/src/karl/services/containers.py b/src/karl/services/containers.py index 3ec794c..c838f17 100644 --- a/src/karl/services/containers.py +++ b/src/karl/services/containers.py @@ -1,8 +1,7 @@ import logging from pathlib import Path -import docker -from docker.models.containers import Container +from python_on_whales import DockerClient from injectable import injectable from karl.model.containers import Tree, Compose, SimpleContainer @@ -13,47 +12,8 @@ logger = logging.getLogger(__name__) @injectable(singleton=True) class DockerService: def __init__(self): - self._client = docker.from_env() - # logger.info(f"Docker client initialized. Plugins: {self._client.plugins()}") - self._tree = self._init_tree() - - def _init_tree(self) -> Tree: - tree = Tree() - container: Container - for container in self._client.containers.list(): - labels = container.labels - working_dir = labels.get("com.docker.compose.project.working_dir") - if working_dir: - if tree.composes.get(working_dir) is None: - tree.composes[working_dir] = Compose(working_dir) - tree.composes[working_dir].containers.append(SimpleContainer.from_container(container)) - else: - tree.containers.append(SimpleContainer.from_container(container)) - return tree - - @property - def tree(self) -> Tree: - return self._tree + self._client = DockerClient() def reload(self, compose_path: Path): - # TODO: Won't work in docker container - cmd = ["sudo", "docker", "compose", "-f", str(compose_path), "up", "-d"] - import subprocess - try: - process = subprocess.run( - cmd, - capture_output=True, - text=True, - check=False - ) - if process.returncode != 0: - logger.error(f"Docker compose failed with code {process.returncode}") - logger.error(f"stderr: {process.stderr}") - raise Exception(f"Docker compose failed: {process.stderr}") - - logger.info(f"Docker compose executed successfully") - logger.debug(f"stdout: {process.stdout}") - return process.stdout, process.stderr, process.returncode - except Exception as e: - logger.error(f"Failed to execute docker compose command: {e}") - raise e + containers = self._client.compose.ps() + logger.info(f"Found {len(containers)} containers") From 24cc2c02391a9e461888dd320efb734c5eb8b784 Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Sat, 10 Jan 2026 00:44:00 +0100 Subject: [PATCH 4/6] fix: symlink python3 to /usr/local/bin/python3 --- Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index aad1cc2..f6f779b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -39,7 +39,8 @@ RUN addgroup \ $USER RUN apk update --no-cache \ - && apk add --no-cache git python3 + && apk add --no-cache git python3 \ + && ln -s /usr/bin/python3 /usr/local/bin/python3 COPY --from=builder --chown=app:app /app/.venv /app/.venv COPY --from=builder --chown=app:app /app/src /app/src From 092a791cc05cd73383dce1a9b7931abe11b49246 Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Mon, 26 Jan 2026 21:15:29 +0100 Subject: [PATCH 5/6] fix: Some logging --- src/karl/services/containers.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/karl/services/containers.py b/src/karl/services/containers.py index c838f17..5658cf2 100644 --- a/src/karl/services/containers.py +++ b/src/karl/services/containers.py @@ -15,5 +15,8 @@ class DockerService: self._client = DockerClient() def reload(self, compose_path: Path): - containers = self._client.compose.ps() - logger.info(f"Found {len(containers)} containers") + projects = self._client.compose.ps() + logger.info(f"Found {len(projects)} projects") + from python_on_whales.components.compose.models import ComposeProject + for project in projects: + logger.info(f"Found project {project}") From 1a1eeb77290909ed0baaa8a9332610d8b290c6aa Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Mon, 26 Jan 2026 22:55:32 +0100 Subject: [PATCH 6/6] fix: Docker reload --- src/karl/services/containers.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/karl/services/containers.py b/src/karl/services/containers.py index 5658cf2..cc7cf56 100644 --- a/src/karl/services/containers.py +++ b/src/karl/services/containers.py @@ -1,10 +1,9 @@ import logging +import os from pathlib import Path -from python_on_whales import DockerClient from injectable import injectable - -from karl.model.containers import Tree, Compose, SimpleContainer +from python_on_whales import DockerClient logger = logging.getLogger(__name__) @@ -15,8 +14,7 @@ class DockerService: self._client = DockerClient() def reload(self, compose_path: Path): - projects = self._client.compose.ps() - logger.info(f"Found {len(projects)} projects") - from python_on_whales.components.compose.models import ComposeProject - for project in projects: - logger.info(f"Found project {project}") + os.chdir(compose_path.parent) + self._client.compose.ps() + self._client.compose.down(remove_orphans=True) + self._client.compose.up()