From dcba9e74d9488dd08c0848d51114ba72e5dfc42b Mon Sep 17 00:00:00 2001 From: Prathamesh Musale Date: Tue, 10 Mar 2026 10:38:24 +0000 Subject: [PATCH] feat(k8s): support init containers via compose labels Add support for k8s init containers defined in docker-compose files using the `laconic.init-container` label. Services with this label set to "true" are built as init containers instead of regular containers in the pod spec. This enables stacks to fetch runtime-created ConfigMaps (e.g. from deployer jobs) before the main containers start, without requiring manual operator steps between deployments. Example compose usage: services: fetch-config: image: bitnami/kubectl:latest labels: laconic.init-container: "true" command: ["sh", "-c", "kubectl get configmap ..."] volumes: - shared-config:/config Co-Authored-By: Claude Opus 4.6 --- stack_orchestrator/deploy/k8s/cluster_info.py | 32 +++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/stack_orchestrator/deploy/k8s/cluster_info.py b/stack_orchestrator/deploy/k8s/cluster_info.py index 8c530fc9..1f6d14f2 100644 --- a/stack_orchestrator/deploy/k8s/cluster_info.py +++ b/stack_orchestrator/deploy/k8s/cluster_info.py @@ -446,12 +446,16 @@ class ClusterInfo: ) -> tuple: """Build k8s container specs from parsed compose YAML. - Returns a tuple of (containers, services, volumes) where: + Returns a tuple of (containers, init_containers, services, volumes) + where: - containers: list of V1Container objects + - init_containers: list of V1Container objects for init containers + (compose services with label ``laconic.init-container: "true"``) - services: the last services dict processed (used for annotations/labels) - volumes: list of V1Volume objects """ containers = [] + init_containers = [] services = {} global_resources = self.spec.get_container_resources() if not global_resources: @@ -571,15 +575,29 @@ class ClusterInfo: ), resources=to_k8s_resource_requirements(container_resources), ) - containers.append(container) + # Services with laconic.init-container label become + # k8s init containers instead of regular containers. + svc_labels = service_info.get("labels", {}) + if isinstance(svc_labels, list): + # docker-compose labels can be a list of "key=value" + svc_labels = dict( + l.split("=", 1) for l in svc_labels + ) + is_init = str( + svc_labels.get("laconic.init-container", "") + ).lower() in ("true", "1", "yes") + if is_init: + init_containers.append(container) + else: + containers.append(container) volumes = volumes_for_pod_files( parsed_yaml_map, self.spec, self.app_name ) - return containers, services, volumes + return containers, init_containers, services, volumes # TODO: put things like image pull policy into an object-scope struct def get_deployment(self, image_pull_policy: Optional[str] = None): - containers, services, volumes = self._build_containers( + containers, init_containers, services, volumes = self._build_containers( self.parsed_pod_yaml_map, image_pull_policy ) registry_config = self.spec.get_image_registry_config() @@ -650,6 +668,7 @@ class ClusterInfo: metadata=client.V1ObjectMeta(annotations=annotations, labels=labels), spec=client.V1PodSpec( containers=containers, + init_containers=init_containers or None, image_pull_secrets=image_pull_secrets, volumes=volumes, affinity=affinity, @@ -695,8 +714,8 @@ class ClusterInfo: for job_file in self.parsed_job_yaml_map: # Build containers for this single job file single_job_map = {job_file: self.parsed_job_yaml_map[job_file]} - containers, _services, volumes = self._build_containers( - single_job_map, image_pull_policy + containers, init_containers, _services, volumes = ( + self._build_containers(single_job_map, image_pull_policy) ) # Derive job name from file path: docker-compose-.yml -> @@ -722,6 +741,7 @@ class ClusterInfo: ), spec=client.V1PodSpec( containers=containers, + init_containers=init_containers or None, image_pull_secrets=image_pull_secrets, volumes=volumes, restart_policy="Never",