Claude
Skills
Sign in
Back

prepare-otterstack-deployment

Included with Lifetime
$97 forever

Analyzes a codebase and prepares it for OtterStack deployment. Use when preparing docker-compose projects, checking OtterStack compatibility, scanning for environment variables, validating compose files, or setting up zero-downtime deployments. Triggers on "prepare for otterstack", "validate compose file", "check deployment readiness", or "scan env vars".

Cloud & DevOps

What this skill does


# Prepare OtterStack Deployment

Analyze a codebase and validate it's ready for OtterStack deployment by checking Docker Compose compatibility, scanning for environment variables, and detecting common failure patterns.

## Quick Start

Run these three checks to verify OtterStack readiness:

```bash
# 1. Scan for environment variables
grep -rE '\$\{[A-Z_]+\}|\$[A-Z_]+' docker-compose.yml

# 2. Check compose compatibility
grep -E "container_name:|env_file:" docker-compose.yml  # Should be empty

# 3. Validate syntax
docker compose config --quiet
```

If all checks pass → ready to deploy. If issues found → follow the detailed workflow below.

## Environment Variable Discovery

### Scan Application Code

Different languages use different patterns for environment variables:

**Node.js / TypeScript:**
```bash
grep -r "process\.env\." --include="*.js" --include="*.ts"
```

**Python:**
```bash
grep -r "os\.getenv\|os\.environ" --include="*.py"
```

**Ruby:**
```bash
grep -r "ENV\[" --include="*.rb"
```

**Go:**
```bash
grep -r "os\.Getenv" --include="*.go"
```

### Scan Docker Compose File

Find all variables referenced in compose file:

```bash
grep -oE '\$\{[A-Z_][A-Z0-9_]*\}' docker-compose.yml | sort -u
```

### Network Detection

Detect networks defined in the Docker Compose file to ensure proper container connectivity:

**Find network definitions:**
```bash
grep -A 5 "^networks:" docker-compose.yml
```

**Extract network names:**
```bash
# Get all network names from the networks section
grep -A 10 "^networks:" docker-compose.yml | grep -E "^  [a-z]" | awk '{print $1}' | sed 's/:$//'
```

**Identify default network:**
1. If networks section exists, use the first network listed as default
2. If no networks section, Docker Compose creates a default network named `{project}_default`
3. Recommend explicit network definition for clarity

**Check service network attachments:**
```bash
# Find services that specify networks
grep -B 5 "networks:" docker-compose.yml | grep -E "^  [a-z]" | awk '{print $1}' | sed 's/:$//'
```

**Network configuration requirements:**
- All services should attach to the same network for inter-service communication
- Network name should use variable substitution: `${NETWORK_NAME:-app-network}`
- Network should be defined explicitly at the bottom of compose file

**Example network configuration:**
```yaml
services:
  web:
    networks:
      - ${NETWORK_NAME:-app-network}

  api:
    networks:
      - ${NETWORK_NAME:-app-network}

networks:
  app-network:
    name: ${NETWORK_NAME:-app-network}
    external: false
```

### Scan Dockerfile

Check for ARG and ENV declarations:

```bash
grep -E "^(ENV|ARG)\s+" Dockerfile
```

### Consolidate Results

For each variable found:
1. Determine if it's **required** (no default) or **optional** (has default)
2. Identify the **purpose** (database URL, API key, port, etc)
3. Flag **sensitive** variables (passwords, keys, tokens)
4. Note any **default values** from code

## Compose File Validation

### Critical OtterStack Requirements

#### ❌ 1. No Hardcoded Container Names

**Check:**
```bash
grep "container_name:" docker-compose.yml
```

**Why it fails**: OtterStack creates unique container names per deployment (e.g., `myapp-abc1234-web-1`). Hardcoded names prevent parallel deployments and zero-downtime updates.

**Fix**: Remove all `container_name:` directives.

**Before:**
```yaml
services:
  web:
    container_name: myapp-web  # ❌ Remove this
    image: myapp:latest
```

**After:**
```yaml
services:
  web:
    # ✅ Let Docker Compose generate names
    image: myapp:latest
```

#### ✅ 2. Use Environment Section (Not env_file)

**Check:**
```bash
grep "env_file:" docker-compose.yml
```

**Why it fails**: OtterStack passes `--env-file` to Docker Compose for variable substitution in the compose file itself. Variables must be in the `environment:` section to be injected into containers.

**Fix**: Move to `environment:` section with variable substitution.

**Before:**
```yaml
services:
  web:
    env_file: .env  # ❌ This doesn't work with OtterStack
```

**After:**
```yaml
services:
  web:
    environment:  # ✅ Use environment section
      DATABASE_URL: ${DATABASE_URL}
      SECRET_KEY: ${SECRET_KEY}
      LOG_LEVEL: ${LOG_LEVEL:-INFO}  # With default
```

#### ❌ 3. No Static Traefik Priority Labels

**Check:**
```bash
grep "traefik.http.routers.*.priority" docker-compose.yml
```

**Why it fails**: OtterStack manages Traefik priority labels automatically for zero-downtime deployments. Static priorities conflict with this mechanism.

**Fix**: Remove priority labels, keep other Traefik labels.

**Before:**
```yaml
labels:
  - "traefik.http.routers.myapp.rule=Host(`example.com`)"
  - "traefik.http.routers.myapp.priority=100"  # ❌ Remove this
```

**After:**
```yaml
labels:
  - "traefik.http.routers.myapp.rule=Host(`example.com`)"
  # ✅ OtterStack manages priorities automatically
```

#### ✅ 4. Health Checks Defined

**Check:**
```bash
grep -A5 "healthcheck:" docker-compose.yml
```

**Why it matters**: OtterStack waits for containers to be healthy before routing traffic. Without health checks, containers are immediately considered healthy (which may not be accurate).

**Best practice**: Define explicit health checks.

**Example:**
```yaml
services:
  web:
    healthcheck:
      test: ["CMD", "curl", "-f", "http://127.0.0.1:8080/health"]
      interval: 10s
      timeout: 3s
      retries: 3
      start_period: 30s
```

**Critical**: Use `127.0.0.1` not `localhost` to avoid IPv6 issues (see Common Failures below).

#### ✅ 5. Syntax Validation

**Check:**
```bash
docker compose config --quiet
```

If this command fails, the compose file has syntax errors that must be fixed before deployment.

## Common Failure Detection

### 1. Native Module Bindings (Node.js)

**Check:**
```bash
grep -E "node-gyp|native|binding|better-sqlite3|bcrypt|sharp" package.json
```

**Problem**: Native modules compiled on your dev machine won't work in the production container due to different architectures.

**Solution**: Use multi-stage build and rebuild in production stage.

**Example fix in Dockerfile:**
```dockerfile
# Production stage
FROM node:20-slim

# Install build tools for native modules
RUN apt-get update && \
    apt-get install -y build-essential python3 && \
    apt-get clean

# Copy node_modules from builder
COPY --from=builder /build/node_modules ./node_modules

# Rebuild native modules for production architecture
RUN npm rebuild better-sqlite3

# Rest of dockerfile...
```

### 2. Database Path Permissions

**Check:**
```bash
grep -A2 "volumes:" docker-compose.yml | grep -E "\.db|/data"
```

**Problem**: Container user may not have write permissions to database directory.

**Solution**: Use named volumes OR ensure directory ownership in Dockerfile.

**Named volume approach (recommended):**
```yaml
volumes:
  - db-data:/app/data  # Named volume with correct permissions

volumes:
  db-data:
    name: myapp-db-data
```

**Dockerfile ownership approach:**
```dockerfile
# Create directories with correct ownership
RUN mkdir -p /app/data && chown -R app:app /app/data

# Switch to non-root user
USER app
```

### 3. Migration File Paths

**Check:**
```bash
grep "COPY.*migrations\|COPY.*prisma\|COPY.*db" Dockerfile
```

**Problem**: Migration files not copied to container or copied to wrong location.

**Solution**: Ensure migrations are copied to where your application expects them.

**Example**:
```dockerfile
# If your app looks for migrations relative to dist/index.js:
COPY src/migrations ./dist/migrations

# Not:
COPY src/migrations ./src/migrations  # ❌ Wrong location
```

### 4. IPv6/IPv4 Health Check Conflicts

**Check:**
```bash
grep -A3 "healthcheck:" docker-compose.yml | grep "localhost"
```

**Problem**: BusyBox `wget` and some `curl` versions try IPv6 (::1) first when resolving `localhost`, but app may only bind to IPv4 (0.0.0.0).

**Solution**: Use `127.0.0.1` instead of `localhost` in health checks.

**Befor

Related in Cloud & DevOps