diff --git a/internal/adapter/deployer/deployer.go b/internal/adapter/deployer/deployer.go index a49ac85..77b63d7 100644 --- a/internal/adapter/deployer/deployer.go +++ b/internal/adapter/deployer/deployer.go @@ -91,7 +91,10 @@ func (d *Deployer) Deploy(ctx context.Context, spec domain.DeploySpec) error { if spec.Port == 0 { spec.Port = 8080 } - if spec.Replicas == 0 { + switch { + case spec.Replicas < 0: + spec.Replicas = 0 // Explicitly zero: create deployment with no pods + case spec.Replicas == 0: spec.Replicas = d.config.DefaultReplicas } if spec.Domain == "" { diff --git a/internal/adapter/templates/templates/components/app-astro/.woodpecker.step.yml.tmpl b/internal/adapter/templates/templates/components/app-astro/.woodpecker.step.yml.tmpl index 4e7bdd2..c7022cc 100644 --- a/internal/adapter/templates/templates/components/app-astro/.woodpecker.step.yml.tmpl +++ b/internal/adapter/templates/templates/components/app-astro/.woodpecker.step.yml.tmpl @@ -54,6 +54,7 @@ deploy-{{COMPONENT_NAME}}: image: bitnami/kubectl:latest commands: - kubectl set image deployment/{{PROJECT_NAME}}-{{COMPONENT_NAME}} {{COMPONENT_NAME}}=registry.threesix.ai/{{PROJECT_NAME}}/{{COMPONENT_NAME}}:${CI_COMMIT_SHA:0:8} -n projects || echo "Deployment not found, skipping" + - kubectl scale deployment/{{PROJECT_NAME}}-{{COMPONENT_NAME}} --replicas=1 -n projects 2>/dev/null || true when: branch: main event: push diff --git a/internal/adapter/templates/templates/components/app-nextjs/.woodpecker.step.yml.tmpl b/internal/adapter/templates/templates/components/app-nextjs/.woodpecker.step.yml.tmpl index 9c3b44c..1364629 100644 --- a/internal/adapter/templates/templates/components/app-nextjs/.woodpecker.step.yml.tmpl +++ b/internal/adapter/templates/templates/components/app-nextjs/.woodpecker.step.yml.tmpl @@ -54,6 +54,7 @@ deploy-{{COMPONENT_NAME}}: image: bitnami/kubectl:latest commands: - kubectl set image deployment/{{PROJECT_NAME}}-{{COMPONENT_NAME}} {{COMPONENT_NAME}}=registry.threesix.ai/{{PROJECT_NAME}}/{{COMPONENT_NAME}}:${CI_COMMIT_SHA:0:8} -n projects || echo "Deployment not found, skipping" + - kubectl scale deployment/{{PROJECT_NAME}}-{{COMPONENT_NAME}} --replicas=1 -n projects 2>/dev/null || true when: branch: main event: push diff --git a/internal/adapter/templates/templates/components/app-react/.woodpecker.step.yml.tmpl b/internal/adapter/templates/templates/components/app-react/.woodpecker.step.yml.tmpl index 5ae6355..d0c92b2 100644 --- a/internal/adapter/templates/templates/components/app-react/.woodpecker.step.yml.tmpl +++ b/internal/adapter/templates/templates/components/app-react/.woodpecker.step.yml.tmpl @@ -54,6 +54,7 @@ deploy-{{COMPONENT_NAME}}: image: bitnami/kubectl:latest commands: - kubectl set image deployment/{{PROJECT_NAME}}-{{COMPONENT_NAME}} {{COMPONENT_NAME}}=registry.threesix.ai/{{PROJECT_NAME}}/{{COMPONENT_NAME}}:${CI_COMMIT_SHA:0:8} -n projects || echo "Deployment not found, skipping" + - kubectl scale deployment/{{PROJECT_NAME}}-{{COMPONENT_NAME}} --replicas=1 -n projects 2>/dev/null || true when: branch: main event: push diff --git a/internal/adapter/templates/templates/components/service/.woodpecker.step.yml.tmpl b/internal/adapter/templates/templates/components/service/.woodpecker.step.yml.tmpl index d5afcd1..3471ab7 100644 --- a/internal/adapter/templates/templates/components/service/.woodpecker.step.yml.tmpl +++ b/internal/adapter/templates/templates/components/service/.woodpecker.step.yml.tmpl @@ -56,6 +56,7 @@ deploy-{{COMPONENT_NAME}}: image: bitnami/kubectl:latest commands: - kubectl set image deployment/{{PROJECT_NAME}}-{{COMPONENT_NAME}} {{COMPONENT_NAME}}=registry.threesix.ai/{{PROJECT_NAME}}/{{COMPONENT_NAME}}:${CI_COMMIT_SHA:0:8} -n projects || echo "Deployment not found, skipping" + - kubectl scale deployment/{{PROJECT_NAME}}-{{COMPONENT_NAME}} --replicas=1 -n projects 2>/dev/null || true when: branch: main event: push diff --git a/internal/adapter/templates/templates/components/worker/.woodpecker.step.yml.tmpl b/internal/adapter/templates/templates/components/worker/.woodpecker.step.yml.tmpl index d4057ca..1ce6f81 100644 --- a/internal/adapter/templates/templates/components/worker/.woodpecker.step.yml.tmpl +++ b/internal/adapter/templates/templates/components/worker/.woodpecker.step.yml.tmpl @@ -54,6 +54,7 @@ deploy-{{COMPONENT_NAME}}: image: bitnami/kubectl:latest commands: - kubectl set image deployment/{{PROJECT_NAME}}-{{COMPONENT_NAME}} {{COMPONENT_NAME}}=registry.threesix.ai/{{PROJECT_NAME}}/{{COMPONENT_NAME}}:${CI_COMMIT_SHA:0:8} -n projects || echo "Deployment not found, skipping" + - kubectl scale deployment/{{PROJECT_NAME}}-{{COMPONENT_NAME}} --replicas=1 -n projects 2>/dev/null || true when: branch: main event: push diff --git a/internal/service/component_deploy.go b/internal/service/component_deploy.go index 6382130..7a8c7fe 100644 --- a/internal/service/component_deploy.go +++ b/internal/service/component_deploy.go @@ -11,6 +11,8 @@ import ( // createInitialComponentDeployment creates a K8s Deployment for a newly added component. // This ensures the deployment exists before CI runs, so kubectl set image succeeds. +// Deployments start with 0 replicas to avoid ImagePullBackOff (image doesn't exist yet). +// The CI deploy step scales to 1 after building and verifying the image. // For monorepo projects, updates the project's unified Ingress with path-based routing. // Failures are logged but don't fail the component creation. func (s *ComponentService) createInitialComponentDeployment( @@ -41,7 +43,7 @@ func (s *ComponentService) createInitialComponentDeployment( Image: image, Domain: projectDomain, Port: component.Port, - Replicas: 1, + Replicas: -1, // Negative = create with 0 replicas (no pods until CI builds the image) BasePath: basePath, SiblingServices: siblingServices, Secrets: secrets,