Deployment Strategies

Haloy supports two deployment strategies: rolling deployment (default) and replace deployment.

Rolling Deployment (Default)

Gradually replaces old containers with new ones, ensuring zero downtime.

How It Works

  1. Start new container(s)
  2. Wait for health checks to pass
  3. Route traffic to new containers
  4. Stop old containers
  5. Repeat for all replicas

Configuration

name: "my-app" deployment_strategy: "rolling" # Default, can be omitted replicas: 3 domains: - domain: "my-app.com"

Benefits

  • Zero downtime: Always have containers running
  • Safe rollouts: Can catch issues before all containers update
  • Automatic rollback: If health checks fail, old containers remain

Use Cases

  • Production applications requiring high availability
  • Services with multiple replicas
  • Applications where downtime is not acceptable

Example

name: "high-availability-app" deployment_strategy: "rolling" replicas: 5 health_check_path: "/health" image: repository: "my-org/ha-app" tag: "v2.0.0" domains: - domain: "ha-app.com" acme_email: "admin@ha-app.com"

Replace Deployment

Stops all old containers before starting new ones.

How It Works

  1. Stop all old containers
  2. Start new container(s)
  3. Wait for health checks
  4. Route traffic to new containers

Configuration

name: "my-app" deployment_strategy: "replace" replicas: 3 domains: - domain: "my-app.com"

Benefits

  • Clean state: No overlap between old and new versions
  • Resource efficiency: Lower peak resource usage
  • Simpler: Easier to reason about deployment state

Considerations

  • Brief downtime: Service unavailable during container swap
  • All-or-nothing: All containers update at once

Use Cases

  • Development/staging environments
  • Batch processing applications
  • Services where brief downtime is acceptable
  • Applications requiring clean state between versions
  • Single-replica deployments

Example

name: "batch-processor" deployment_strategy: "replace" replicas: 2 health_check_path: "/status" image: repository: "my-org/batch-app" tag: "v1.5.0" env: - name: "BATCH_SIZE" value: "1000"

Comparison

AspectRollingReplace
DowntimeZeroBrief (during swap)
Resource usageHigher (overlapping containers)Lower
RiskLower (gradual rollout)Higher (all at once)
RollbackAutomatic on failureManual
ComplexityHigherLower
Best forProduction HA servicesDev/staging, batch jobs

Per-Target Strategies

Use different strategies for different environments:

name: "my-app" # Default for all targets deployment_strategy: "rolling" replicas: 3 targets: production: server: prod.haloy.com deployment_strategy: "rolling" # High availability replicas: 5 domains: - domain: "my-app.com" staging: server: staging.haloy.com deployment_strategy: "replace" # Faster deployments replicas: 2 domains: - domain: "staging.my-app.com" development: server: dev.haloy.com deployment_strategy: "replace" # Clean state replicas: 1 domains: - domain: "dev.my-app.com"

Health Checks

Both strategies rely on health checks to determine when containers are ready:

name: "my-app" health_check_path: "/api/health" port: "8080" # Your app should respond with 200 OK when healthy # Example health endpoint response: # { # "status": "healthy", # "uptime": 42, # "database": "connected" # }

Health Check Requirements

Your application should:

  1. Respond to GET requests at the health check path
  2. Return HTTP 200 when healthy
  3. Return non-200 when unhealthy or not ready
  4. Check critical dependencies (database, cache, etc.)

Example Health Check Implementation

Node.js/Express:

app.get('/health', async (req, res) => { try { // Check database connection await db.ping(); // Check other dependencies const cacheConnected = await cache.isConnected(); if (cacheConnected) { res.status(200).json({ status: 'healthy' }); } else { res.status(503).json({ status: 'unhealthy', reason: 'cache unavailable' }); } } catch (error) { res.status(503).json({ status: 'unhealthy', reason: error.message }); } });

Go:

func healthHandler(w http.ResponseWriter, r *http.Request) { // Check database if err := db.Ping(); err != nil { w.WriteHeader(http.StatusServiceUnavailable) json.NewEncoder(w).Encode(map[string]string{ "status": "unhealthy", "reason": "database unavailable", }) return } w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(map[string]string{ "status": "healthy", }) }

Deployment Process

Monitor your deployment:

# Deploy with logs haloy deploy # Deploy without logs (faster) haloy deploy --no-logs # Check status after deployment haloy status # View application logs haloy logs

Troubleshooting

Rolling Deployment Stuck

If a rolling deployment gets stuck:

  1. Check health check endpoint:

    curl https://my-app.com/health
  2. View application logs:

    haloy logs
  3. Verify health check path is correct in config

Replace Deployment Downtime Too Long

If replace deployment takes too long:

  1. Optimize container startup time
  2. Use health checks to detect readiness faster
  3. Pre-warm caches and connections on startup
  4. Consider switching to rolling deployment

Best Practices

  1. Use rolling for production: Ensures high availability
  2. Implement robust health checks: Critical for safe deployments
  3. Test in staging first: Verify deployment strategy works
  4. Monitor during deployment: Watch logs and health metrics
  5. Document health check endpoint: Ensure team knows the requirements
  6. Set appropriate replica counts: More replicas = smoother rolling deployments

Next Steps