---
name: beanstalk-deploy
description: "Robust deployment patterns for Elastic Beanstalk with GitHub Actions, Pulumi, and edge case handling"
globs:
  - "**/.github/workflows/*deploy*.yml"
  - "**/.github/workflows/*beanstalk*.yml"
  - "**/infra/**/*.ts"
  - "**/infrastructure/**/*.ts"
  - "**/Pulumi*.yaml"
  - "**/package.json"
alwaysApply: false
---

# AWS Elastic Beanstalk Deployment Best Practices

Apply these production-tested patterns when working with Elastic Beanstalk deployments, especially with GitHub Actions and Pulumi infrastructure.

## 🎯 Core Principles

1. **Always verify infrastructure health** before deploying
2. **Never assume resources are ready** - implement retry logic
3. **Handle terminated environments** gracefully with state cleanup
4. **Use concurrency control** to prevent deployment conflicts
5. **Pre-install dependencies** for faster, more reliable deploys
6. **Implement comprehensive error handling** with fallbacks

## 🏗️ Infrastructure Health Checks

**ALWAYS check infrastructure status before deploying:**

```yaml
- name: Check infrastructure status
  run: |
    echo "🔍 Checking infrastructure status..."

    # Get environment name from Pulumi state (without deploying)
    EB_ENVIRONMENT_NAME=$(pulumi stack output ebEnvironmentName 2>/dev/null || echo "")

    if [ -z "$EB_ENVIRONMENT_NAME" ]; then
      echo "🔍 No environment found in Pulumi state. Will deploy infrastructure..."
    else
      echo "🔍 Checking environment status: $EB_ENVIRONMENT_NAME"

      # Check if environment exists and is healthy
      EB_ENV_STATUS=$(aws elasticbeanstalk describe-environments \
        --environment-names "$EB_ENVIRONMENT_NAME" \
        --query "Environments[0].Status" --output text 2>/dev/null || echo "NOT_FOUND")

      if [ "$EB_ENV_STATUS" = "Terminated" ] || [ "$EB_ENV_STATUS" = "NOT_FOUND" ]; then
        echo "⚠️  Environment is $EB_ENV_STATUS. Deleting from Pulumi state..."

        # Delete environment from Pulumi state
        EB_URN=$(pulumi stack --show-urns | awk '/aws:elasticbeanstalk\/environment:Environment/ {print $1; exit}')
        if [ -n "$EB_URN" ]; then
          echo "🔧 Deleting: $EB_URN"
          pulumi state delete "$EB_URN" --force
        fi

        echo "🔄 Infrastructure will be recreated..."
      else
        echo "✅ Environment exists: $EB_ENV_STATUS"

        # Check if infrastructure changes needed
        if pulumi preview --diff --expect-no-changes 2>/dev/null; then
          echo "✅ No infrastructure changes needed"
        else
          echo "🔄 Infrastructure changes detected"
        fi
      fi
    fi
```

**Why**: Prevents deploying to orphaned resources, automatically recovers from terminated environments, saves money on zombie resources.

## ⏳ Beanstalk Readiness Verification

**ALWAYS wait for environment to be fully ready:**

```yaml
- name: Verify Elastic Beanstalk environment exists
  run: |
    echo "🔍 Verifying Elastic Beanstalk environment..."
    EB_ENVIRONMENT_NAME="${{ steps.get-resources.outputs.eb_environment_name }}"

    # Wait until environment exists
    echo "⏳ Waiting for environment to exist..."
    aws elasticbeanstalk wait environment-exists \
      --environment-names "$EB_ENVIRONMENT_NAME" || true

    # Wait until environment is Ready (with 30 retries)
    for i in {1..30}; do
      ENV_STATUS=$(aws elasticbeanstalk describe-environments \
        --environment-names "$EB_ENVIRONMENT_NAME" \
        --query "Environments[0].Status" --output text 2>/dev/null || echo "NOT_FOUND")
      ENV_HEALTH=$(aws elasticbeanstalk describe-environments \
        --environment-names "$EB_ENVIRONMENT_NAME" \
        --query "Environments[0].Health" --output text 2>/dev/null || echo "UNKNOWN")

      echo "⏳ EB Status: $ENV_STATUS, Health: $ENV_HEALTH (attempt $i/30)"

      if [ "$ENV_STATUS" = "Ready" ]; then
        echo "✅ Environment is Ready"
        break
      fi
      sleep 20  # Wait 20 seconds between checks (10 minutes total)
    done

    if [ "$ENV_STATUS" != "Ready" ]; then
      echo "⚠️  Environment not Ready after 10 minutes. Continuing with caution..."
    fi
```

**Why**: Prevents timing-related failures, ensures environment is provisioned before app deployment, provides visibility into provisioning progress.

## 🔒 HTTPS/SSL Configuration

**CRITICAL: Classic Load Balancer vs Application Load Balancer**

Elastic Beanstalk environments can use either Classic Load Balancer (CLB) or Application Load Balancer (ALB). The HTTPS listener configuration is **completely different** between them.

### Checking Your Load Balancer Type

```bash
# Check load balancer type
aws elasticbeanstalk describe-configuration-settings \
  --environment-name <env-name> \
  --application-name <app-name> \
  --query 'ConfigurationSettings[0].OptionSettings[?Namespace==`aws:elasticbeanstalk:environment`]' \
  --output json | grep LoadBalancerType
```

**Outputs:**
- `"Value": "classic"` → Use Classic Load Balancer config
- `"Value": "application"` → Use Application Load Balancer config

### Classic Load Balancer HTTPS Configuration

**Use namespace: `aws:elb:listener:443`**

```typescript
// Pulumi configuration for Classic Load Balancer
...(certificate && certValidationComplete
  ? [
      {
        namespace: "aws:elb:listener:443",
        name: "ListenerProtocol",
        value: "HTTPS",
      },
      {
        namespace: "aws:elb:listener:443",
        name: "InstancePort",
        value: "80",
      },
      {
        namespace: "aws:elb:listener:443",
        name: "InstanceProtocol",
        value: "HTTP",
      },
      {
        namespace: "aws:elb:listener:443",
        name: "SSLCertificateId",
        value: certValidationComplete.certificateArn,
      },
      {
        namespace: "aws:elb:listener:443",
        name: "ListenerEnabled",
        value: "true",
      },
    ]
  : [])
```

### Application Load Balancer (ALBv2) HTTPS Configuration

**Use namespace: `aws:elbv2:listener:443`**

```typescript
// Pulumi configuration for Application Load Balancer
...(certificate && certValidationComplete
  ? [
      {
        namespace: "aws:elbv2:listener:443",
        name: "Protocol",
        value: "HTTPS",
      },
      {
        namespace: "aws:elbv2:listener:443",
        name: "SSLCertificateArns",
        value: certValidationComplete.certificateArn,
      },
      {
        namespace: "aws:elbv2:listener:443",
        name: "SSLPolicy",
        value: "ELBSecurityPolicy-TLS13-1-2-2021-06",
      },
    ]
  : [])
```

### Common Symptoms of Misconfiguration

**Problem:** `https://your-domain.com` times out or refuses connection, but `http://` works fine

**Diagnosis:**
```bash
# 1. Check if HTTPS listener exists
aws elasticbeanstalk describe-configuration-settings \
  --environment-name <env-name> \
  --application-name <app-name> \
  --query 'ConfigurationSettings[0].OptionSettings[?contains(Namespace, `listener`)]'

# 2. Check certificate status
aws acm list-certificates --region <region> \
  --query 'CertificateSummaryList[?contains(DomainName, `your-domain.com`)]'

# 3. Verify DNS resolution
nslookup your-domain.com

# 4. Test load balancer directly
curl -I http://<load-balancer-endpoint>/health
```

**Root Cause:** Using `aws:elbv2:listener:443` config on a Classic Load Balancer (or vice versa)

**Fix:** Update Pulumi infrastructure code with correct namespace based on load balancer type, then run:
```bash
cd infrastructure
pulumi up
```

**Why**: Mixing Classic and ALBv2 configuration namespaces silently fails - the HTTPS listener is never created, leaving port 443 closed while the environment appears healthy.

See full documentation for complete deployment patterns, Pulumi configuration, monitoring, and production checklist.