Skip to content

Multi-Environment Deployment

This example demonstrates how to deploy the same TypeKro application across multiple environments (development, staging, production) with environment-specific configurations, scaling, and deployment strategies.

What You'll Learn

  • Environment-specific configuration - Different settings per environment
  • Resource scaling strategies - CPU/memory allocation based on environment
  • Deployment strategies - Direct mode for dev, KRO for production
  • GitOps workflows - YAML generation for CD pipelines
  • Security considerations - Environment-specific secrets and access controls

Application Overview

We'll deploy a web application with a database across three environments:

Complete Example

1. Define Environment-Aware Schema

typescript
import { type } from 'arktype';
import { 
  toResourceGraph, 
  simpleDeployment, 
  simpleService,
  simpleConfigMap,
  simpleSecret,
  simplePvc
} from 'typekro';

// Environment configuration schema
const EnvironmentSpec = type({
  // Environment identifier
  environment: '"development" | "staging" | "production"',
  
  // Application configuration
  app: {
    name: 'string',
    image: 'string',
    version: 'string',
    replicas: 'number',
    resources: {
      cpu: 'string',
      memory: 'string'
    }
  },
  
  // Database configuration
  database: {
    image: 'string',
    storageSize: 'string',
    resources: {
      cpu: 'string', 
      memory: 'string'
    }
  },
  
  // Environment-specific settings
  settings: {
    logLevel: '"debug" | "info" | "warn" | "error"',
    enableMetrics: 'boolean',
    enableTracing: 'boolean',
    domain?: 'string'
  }
});

const EnvironmentStatus = type({
  appUrl: 'string',
  databaseReady: 'boolean',
  environment: 'string',
  version: 'string',
  healthStatus: '"healthy" | "degraded" | "unhealthy"'
});

2. Create Environment-Aware Resource Graph

typescript
export const multiEnvApp = toResourceGraph(
  {
    name: 'multi-env-app',
    apiVersion: 'deploy.example.com/v1alpha1',
    kind: 'MultiEnvApp',
    spec: EnvironmentSpec,
    status: EnvironmentStatus,
  },
  // ResourceBuilder function
  (schema) => {
    // Environment-specific namespace prefix
    const envPrefix = schema.spec.environment;
    const appName = Cel.expr(envPrefix, '-', schema.spec.app.name);
    
    return {
      // Application configuration
      appConfig: simpleConfigMap({
        name: Cel.expr(appName, '-config'),
        data: {
          ENVIRONMENT: schema.spec.environment,
          LOG_LEVEL: schema.spec.settings.logLevel,
          ENABLE_METRICS: schema.spec.settings.enableMetrics.toString(),
          ENABLE_TRACING: schema.spec.settings.enableTracing.toString(),
          APP_VERSION: schema.spec.app.version,
          
          // Database connection
          DB_HOST: Cel.expr(appName, '-db'),
          DB_PORT: '5432',
          DB_NAME: Cel.expr('app_', schema.spec.environment),
          
          // Environment-specific URLs
          ...(schema.spec.settings.domain && {
            APP_DOMAIN: schema.spec.settings.domain
          })
        }
      }),
      
      // Database secrets (different per environment)
      dbSecret: simpleSecret({
        name: Cel.expr(appName, '-db-secret'),
        data: {
          // Use different passwords per environment
          DB_PASSWORD: schema.spec.environment === 'production' 
            ? 'cHJvZF9zdXBlcl9zZWN1cmVfcGFzc3dvcmQ=' // prod_super_secure_password
            : schema.spec.environment === 'staging'
            ? 'c3RnX3NlY3VyZV9wYXNzd29yZA==' // stg_secure_password  
            : 'ZGV2X3Bhc3N3b3Jk', // dev_password
          DB_USER: 'YXBwdXNlcg==' // appuser
        }
      }),
      
      // Database persistent volume
      dbStorage: simplePvc({
        name: Cel.expr(appName, '-db-storage'),
        accessMode: 'ReadWriteOnce',
        size: schema.spec.database.storageSize,
        // Production uses faster storage class
        storageClass: schema.spec.environment === 'production' 
          ? 'fast-ssd' 
          : 'standard'
      }),
      
      // Database deployment
      database: simpleDeployment({
        name: Cel.expr(appName, '-db'),
        image: schema.spec.database.image,
        replicas: 1, // Single DB instance for all environments
        ports: [{ containerPort: 5432 }],
        env: [
          { name: 'POSTGRES_DB', value: Cel.expr('app_', schema.spec.environment) },
          { name: 'POSTGRES_USER', valueFrom: { secretKeyRef: { name: Cel.expr(appName, '-db-secret'), key: 'DB_USER' } } },
          { name: 'POSTGRES_PASSWORD', valueFrom: { secretKeyRef: { name: Cel.expr(appName, '-db-secret'), key: 'DB_PASSWORD' } } }
        ],
        volumeMounts: [{
          name: 'db-storage',
          mountPath: '/var/lib/postgresql/data'
        }],
        volumes: [{
          name: 'db-storage',
          persistentVolumeClaim: { claimName: Cel.expr(appName, '-db-storage') }
        }],
        resources: {
          requests: {
            cpu: schema.spec.database.resources.cpu,
            memory: schema.spec.database.resources.memory
          },
          limits: {
            cpu: schema.spec.database.resources.cpu,
            memory: schema.spec.database.resources.memory
          }
        },
        // Production has stricter health checks
        livenessProbe: {
          exec: { command: ['pg_isready', '-U', 'appuser'] },
          initialDelaySeconds: schema.spec.environment === 'production' ? 60 : 30,
          periodSeconds: schema.spec.environment === 'production' ? 10 : 30
        }
      }),
      
      // Database service
      dbService: simpleService({
        name: Cel.expr(appName, '-db'),
        selector: { app: Cel.expr(appName, '-db') },
        ports: [{ port: 5432, targetPort: 5432 }]
      }),
      
      // Application deployment
      app: simpleDeployment({
        name: appName,
        image: Cel.template('%s:%s', schema.spec.app.image, schema.spec.app.version),
        replicas: schema.spec.app.replicas,
        ports: [{ containerPort: 3000 }],
        envFrom: [{ configMapRef: { name: Cel.expr(appName, '-config') } }],
        env: [
          { name: 'DB_PASSWORD', valueFrom: { secretKeyRef: { name: Cel.expr(appName, '-db-secret'), key: 'DB_PASSWORD' } } },
          { name: 'DB_USER', valueFrom: { secretKeyRef: { name: Cel.expr(appName, '-db-secret'), key: 'DB_USER' } } }
        ],
        resources: {
          requests: {
            cpu: schema.spec.app.resources.cpu,
            memory: schema.spec.app.resources.memory
          },
          limits: {
            // Production gets 2x limits
            cpu: schema.spec.environment === 'production' 
              ? Cel.template('%dm', parseInt(schema.spec.app.resources.cpu) * 2)
              : schema.spec.app.resources.cpu,
            memory: schema.spec.environment === 'production'
              ? Cel.template('%dMi', parseInt(schema.spec.app.resources.memory.replace('Mi', '')) * 2)
              : schema.spec.app.resources.memory
          }
        },
        livenessProbe: { httpGet: { path: '/health', port: 3000 } },
        readinessProbe: { httpGet: { path: '/ready', port: 3000 } },
        
        // Production has more sophisticated startup
        ...(schema.spec.environment === 'production' && {
          startupProbe: { 
            httpGet: { path: '/startup', port: 3000 },
            initialDelaySeconds: 30,
            periodSeconds: 10,
            timeoutSeconds: 5,
            failureThreshold: 10
          }
        })
      }),
      
      // Application service
      appService: simpleService({
        name: appName,
        selector: { app: appName },
        ports: [{ port: 80, targetPort: 3000 }],
        // Production and staging get LoadBalancer
        type: schema.spec.environment === 'development' ? 'ClusterIP' : 'LoadBalancer'
      })
    };
  },
  
  // Status aggregation across resources
  (schema, resources) => ({
    appUrl: schema.spec.settings.domain 
      ? Cel.template('https://%s', schema.spec.settings.domain)
      : Cel.template('http://%s-%s', schema.spec.environment, schema.spec.app.name),
    databaseReady: resources.database.status.readyReplicas === 1,
    environment: schema.spec.environment,
    version: schema.spec.app.version,
    healthStatus: (() => {
      const appReady = resources.app.status.readyReplicas === schema.spec.app.replicas;
      const dbReady = resources.database.status.readyReplicas === 1;
      
      if (appReady && dbReady) return 'healthy';
      if (appReady || dbReady) return 'degraded';
      return 'unhealthy';
    })()
  })
);

3. Environment Configurations

typescript
// Development environment - local development
const developmentConfig = {
  environment: 'development' as const,
  app: {
    name: 'myapp',
    image: 'myapp',
    version: 'latest',
    replicas: 1,
    resources: {
      cpu: '100m',
      memory: '128Mi'
    }
  },
  database: {
    image: 'postgres:15-alpine',
    storageSize: '1Gi',
    resources: {
      cpu: '100m',
      memory: '128Mi'
    }
  },
  settings: {
    logLevel: 'debug' as const,
    enableMetrics: false,
    enableTracing: false
  }
};

// Staging environment - pre-production testing
const stagingConfig = {
  environment: 'staging' as const,
  app: {
    name: 'myapp',
    image: 'myapp',
    version: '1.2.0',
    replicas: 2,
    resources: {
      cpu: '200m',
      memory: '256Mi'
    }
  },
  database: {
    image: 'postgres:15',
    storageSize: '10Gi',
    resources: {
      cpu: '200m',
      memory: '512Mi'
    }
  },
  settings: {
    logLevel: 'info' as const,
    enableMetrics: true,
    enableTracing: true,
    domain: 'myapp-staging.company.com'
  }
};

// Production environment - high availability
const productionConfig = {
  environment: 'production' as const,
  app: {
    name: 'myapp',
    image: 'myapp',
    version: '1.2.0',
    replicas: 5,
    resources: {
      cpu: '500m',
      memory: '512Mi'
    }
  },
  database: {
    image: 'postgres:15',
    storageSize: '100Gi',
    resources: {
      cpu: '1000m',
      memory: '2Gi'
    }
  },
  settings: {
    logLevel: 'warn' as const,
    enableMetrics: true,
    enableTracing: true,
    domain: 'api.company.com'
  }
};

4. Environment-Specific Deployment

typescript
import { factory } from 'typekro';

// Development - Direct deployment for fast iteration
console.log('Deploying to development...');
await factory('direct').deploy(multiEnvApp, developmentConfig);

// Staging - KRO deployment for testing orchestration
console.log('Deploying to staging...');
await factory('kro').deploy(multiEnvApp, stagingConfig);

// Production - Generate YAML for GitOps pipeline
console.log('Generating production YAML...');
const prodYaml = await factory('yaml').generate(multiEnvApp, productionConfig);
await writeFile('./production-deploy.yaml', prodYaml);

GitOps Integration

For production deployments, generate YAML for your CI/CD pipeline:

typescript
// scripts/generate-manifests.ts
import { writeFile, mkdir } from 'fs/promises';
import { factory } from 'typekro';
import { multiEnvApp } from './app';

async function generateManifests() {
  await mkdir('./manifests', { recursive: true });
  
  // Generate staging manifests
  const stagingYaml = await factory('yaml').generate(multiEnvApp, stagingConfig);
  await writeFile('./manifests/staging.yaml', stagingYaml);
  
  // Generate production manifests  
  const prodYaml = await factory('yaml').generate(multiEnvApp, productionConfig);
  await writeFile('./manifests/production.yaml', prodYaml);
  
  console.log('Generated manifests for GitOps deployment');
}

generateManifests();

Environment Promotion Pipeline

yaml
# .github/workflows/deploy.yml
name: Deploy Multi-Environment App

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  deploy-dev:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: oven-sh/setup-bun@v1
      - run: bun install
      - run: bun run deploy:dev
      
  deploy-staging:
    needs: deploy-dev
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: oven-sh/setup-bun@v1
      - run: bun install
      - run: bun run generate:staging
      - run: kubectl apply -f manifests/staging.yaml
      
  deploy-production:
    needs: deploy-staging
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4
      - uses: oven-sh/setup-bun@v1  
      - run: bun install
      - run: bun run generate:production
      - run: kubectl apply -f manifests/production.yaml

Environment-Specific Features

Development

  • Fast iteration - Direct deployment for immediate feedback
  • Debug logging - Verbose output for troubleshooting
  • Minimal resources - Cost-effective local development
  • Relaxed health checks - Faster startup times

Staging

  • Production parity - Mirrors production configuration
  • Performance testing - Load testing and optimization
  • Integration testing - End-to-end workflow validation
  • Monitoring enabled - Metrics and tracing for debugging

Production

  • High availability - Multiple replicas and health checks
  • Security hardened - Strict access controls and secrets
  • Performance optimized - Resource limits and fast storage
  • Comprehensive monitoring - Full observability stack

Configuration Management

Environment Variables

typescript
// Environment-specific configuration
const envConfig = {
  development: { logLevel: 'debug', replicas: 1 },
  staging: { logLevel: 'info', replicas: 2 },
  production: { logLevel: 'warn', replicas: 5 }
};

Secrets Management

typescript
// Different secret handling per environment
const secretData = {
  development: 'simple_password',
  staging: 'secure_password',
  production: 'enterprise_grade_secret'
};

Resource Scaling

typescript
// Progressive resource allocation
const resources = {
  development: { cpu: '100m', memory: '128Mi' },
  staging: { cpu: '200m', memory: '256Mi' },
  production: { cpu: '500m', memory: '512Mi' }
};

Learn More

Released under the Apache 2.0 License.