fix(deploy): create component deployments with 0 replicas to prevent ImagePullBackOff
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

Components are scaffolded before CI builds their images. Previously deployments
started with 1 replica, causing ImagePullBackOff until the first build completed.
Now deployments start at 0 replicas; CI deploy steps scale to 1 after verifying
the image exists in the registry.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
jordan 2026-02-10 10:16:14 -07:00
parent 28ab98df0f
commit b7d0e84946
7 changed files with 12 additions and 2 deletions

View File

@ -91,7 +91,10 @@ func (d *Deployer) Deploy(ctx context.Context, spec domain.DeploySpec) error {
if spec.Port == 0 { if spec.Port == 0 {
spec.Port = 8080 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 spec.Replicas = d.config.DefaultReplicas
} }
if spec.Domain == "" { if spec.Domain == "" {

View File

@ -54,6 +54,7 @@ deploy-{{COMPONENT_NAME}}:
image: bitnami/kubectl:latest image: bitnami/kubectl:latest
commands: 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 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: when:
branch: main branch: main
event: push event: push

View File

@ -54,6 +54,7 @@ deploy-{{COMPONENT_NAME}}:
image: bitnami/kubectl:latest image: bitnami/kubectl:latest
commands: 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 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: when:
branch: main branch: main
event: push event: push

View File

@ -54,6 +54,7 @@ deploy-{{COMPONENT_NAME}}:
image: bitnami/kubectl:latest image: bitnami/kubectl:latest
commands: 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 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: when:
branch: main branch: main
event: push event: push

View File

@ -56,6 +56,7 @@ deploy-{{COMPONENT_NAME}}:
image: bitnami/kubectl:latest image: bitnami/kubectl:latest
commands: 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 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: when:
branch: main branch: main
event: push event: push

View File

@ -54,6 +54,7 @@ deploy-{{COMPONENT_NAME}}:
image: bitnami/kubectl:latest image: bitnami/kubectl:latest
commands: 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 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: when:
branch: main branch: main
event: push event: push

View File

@ -11,6 +11,8 @@ import (
// createInitialComponentDeployment creates a K8s Deployment for a newly added component. // createInitialComponentDeployment creates a K8s Deployment for a newly added component.
// This ensures the deployment exists before CI runs, so kubectl set image succeeds. // 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. // For monorepo projects, updates the project's unified Ingress with path-based routing.
// Failures are logged but don't fail the component creation. // Failures are logged but don't fail the component creation.
func (s *ComponentService) createInitialComponentDeployment( func (s *ComponentService) createInitialComponentDeployment(
@ -41,7 +43,7 @@ func (s *ComponentService) createInitialComponentDeployment(
Image: image, Image: image,
Domain: projectDomain, Domain: projectDomain,
Port: component.Port, Port: component.Port,
Replicas: 1, Replicas: -1, // Negative = create with 0 replicas (no pods until CI builds the image)
BasePath: basePath, BasePath: basePath,
SiblingServices: siblingServices, SiblingServices: siblingServices,
Secrets: secrets, Secrets: secrets,