Files
rxminder/docs/ENVIRONMENT_VARIABLES.md
William Valentin 0ea1af91c9 feat: Complete Kustomize migration with environment variable integration
🎉 Major enhancement: Full migration from shell script deployment to Kustomize

## New Features

### Kustomize Infrastructure
-  Complete base resources for all Kubernetes manifests
-  Development overlay with optimized dev settings
-  Production overlay with enterprise-grade security and performance
-  ConfigMap and Secret generation from environment variables
-  Image tag and replica management per environment

### Environment Variable Integration
-  Multi-source environment loading (~/.env, .env.dev, .env.prod, .env.local)
-  Static configuration generation from environment variables
-  Dynamic runtime environment variable injection
-  Comprehensive variable documentation and examples
-  Secrets template generation for secure credential management

### Enhanced Makefile
-  20+ new Kustomize-specific deployment targets
-  Environment-aware configuration generation commands
-  Validation, dry-run, and debugging capabilities
-  Backward compatibility with legacy shell script deployment

### New Scripts & Tools
-  scripts/generate-config.sh - Environment variable to Kustomize config generator
-  scripts/deploy-with-env.sh - Runtime environment variable deployment tool
-  Comprehensive help and usage documentation

### Documentation
-  k8s-kustomize/README.md - Complete Kustomize deployment guide
-  docs/ENVIRONMENT_VARIABLES.md - Environment variable integration guide
-  KUSTOMIZE_MIGRATION.md - Migration summary and next steps

## Benefits
- 🚀 Simplified deployment: make deploy-dev vs complex shell scripts
- 🔒 Environment isolation: Clear dev/staging/prod separation
- 🔧 GitOps ready: Works seamlessly with ArgoCD, Flux
-  Better validation: Built-in YAML validation catches errors early
- 📈 Standard approach: Industry-standard Kubernetes deployment method
- 🛡️ Enhanced security: Production security contexts, network policies, TLS

## Usage Examples
Generating development configuration...
[INFO] Kustomize Config Generator
[INFO] Environment: dev
[INFO] Loading environment variables...
[WARNING] File not found: /home/will/.env
[INFO] Loading: /home/will/Code/meds/.env
[WARNING] File not found: /home/will/Code/meds/.env.dev
[WARNING] File not found: /home/will/Code/meds/.env.local
[INFO] Generating base config.env...
[SUCCESS] Generated: /home/will/Code/meds/k8s-kustomize/base/config.env
[INFO] Generating environment-specific config for: dev
[SUCCESS] Generated development config: /home/will/Code/meds/k8s-kustomize/overlays/dev/config.env
[INFO] Validating generated configuration...
[SUCCESS] Configuration validation passed!
[SUCCESS] Configuration generation completed!
[INFO] Next steps:
  1. Review generated files in k8s-kustomize/
  2. Update any environment-specific values
  3. Create secrets.env files for sensitive data
  4. Test with: make kustomize-dry-run-dev
Deploying to Kubernetes with Kustomize (dev)...
Deploying to production with environment variables...
[INFO] Kustomize Deployment with Environment Variables
[INFO] Environment: prod
[INFO] Action: apply
[INFO] Validating prerequisites...
[SUCCESS] Prerequisites validated
[INFO] Loading environment variables for: prod
[INFO] Loading: /home/will/Code/meds/.env
[SUCCESS] Environment loaded: prod
[INFO] Key variables:
  APP_NAME: rxminder
  NODE_ENV: production
  IMAGE_TAG: latest
  NAMESPACE: rxminder-prod
  INGRESS_HOST: rxminder.192.168.153.243.nip.io
[INFO] Generating dynamic configuration...
Validating Kustomize configuration (dev)...
configmap/rxminder-config-4229dg76t6 created (dry run)
secret/couchdb-secret-7ck2cc96g5 created (dry run)
service/rxminder-couchdb-service created (dry run)
service/rxminder-frontend-service created (dry run)
persistentvolumeclaim/rxminder-couchdb-pvc created (dry run)
deployment.apps/rxminder-frontend created (dry run)
statefulset.apps/rxminder-couchdb created (dry run)
horizontalpodautoscaler.autoscaling/rxminder-frontend-hpa created (dry run)
job.batch/rxminder-db-seed created (dry run)
ingress.networking.k8s.io/rxminder-ingress created (dry run)
networkpolicy.networking.k8s.io/rxminder-database-policy created (dry run)
networkpolicy.networking.k8s.io/rxminder-frontend-policy created (dry run)
Validating Kustomize configuration (prod)...
configmap/rxminder-config-2979gkcf9c created (dry run)
secret/couchdb-secret-6k9794bgg2 created (dry run)
service/rxminder-couchdb-service created (dry run)
service/rxminder-frontend-service created (dry run)
persistentvolumeclaim/rxminder-couchdb-pvc created (dry run)
deployment.apps/rxminder-frontend created (dry run)
statefulset.apps/rxminder-couchdb created (dry run)
horizontalpodautoscaler.autoscaling/rxminder-frontend-hpa created (dry run)
job.batch/rxminder-db-seed created (dry run)
ingress.networking.k8s.io/rxminder-ingress created (dry run)
networkpolicy.networking.k8s.io/rxminder-database-policy created (dry run)
networkpolicy.networking.k8s.io/rxminder-frontend-policy created (dry run)
Kustomize validation completed!
Dry run Kustomize deployment (dev)...
apiVersion: v1
items:
- apiVersion: v1
  data:
    APP_NAME: rxminder
    APP_VERSION: 1.0.0
    CACHE_TTL: "1800"
    CERT_MANAGER_ISSUER: letsencrypt-prod
    CORS_ORIGIN: '*'
    COUCHDB_DATABASE_NAME: meds_app
    DB_HOST: rxminder-couchdb-service
    DB_PORT: "5984"
    DEBUG: "true"
    DEV_MODE: "false"
    ENABLE_CORS: "true"
    ENABLE_METRICS: "false"
    ENABLE_MONITORING: "false"
    ENABLE_TRACING: "false"
    HEALTH_CHECK_INTERVAL: "30"
    HOT_RELOAD: "false"
    IMAGE_REPOSITORY: will/rxminder
    INGRESS_CLASS: nginx
    LOG_FORMAT: json
    LOG_LEVEL: debug
    LOG_TIMESTAMP: "true"
    MAX_CONNECTIONS: "100"
    METRICS_PORT: "9090"
    NODE_ENV: development
    REACT_APP_API_URL: http://rxminder-couchdb-service:5984
    READINESS_CHECK_TIMEOUT: "5"
    REGISTRY_URL: gitea-http.taildb3494.ts.net
    REQUEST_TIMEOUT: "30000"
  kind: ConfigMap
  metadata:
    annotations:
      kubectl.kubernetes.io/last-applied-configuration: |
        {"apiVersion":"v1","data":{"APP_NAME":"rxminder","APP_VERSION":"1.0.0","CACHE_TTL":"1800","CERT_MANAGER_ISSUER":"letsencrypt-prod","CORS_ORIGIN":"*","COUCHDB_DATABASE_NAME":"meds_app","DB_HOST":"rxminder-couchdb-service","DB_PORT":"5984","DEBUG":"true","DEV_MODE":"false","ENABLE_CORS":"true","ENABLE_METRICS":"false","ENABLE_MONITORING":"false","ENABLE_TRACING":"false","HEALTH_CHECK_INTERVAL":"30","HOT_RELOAD":"false","IMAGE_REPOSITORY":"will/rxminder","INGRESS_CLASS":"nginx","LOG_FORMAT":"json","LOG_LEVEL":"debug","LOG_TIMESTAMP":"true","MAX_CONNECTIONS":"100","METRICS_PORT":"9090","NODE_ENV":"development","REACT_APP_API_URL":"http://rxminder-couchdb-service:5984","READINESS_CHECK_TIMEOUT":"5","REGISTRY_URL":"gitea-http.taildb3494.ts.net","REQUEST_TIMEOUT":"30000"},"kind":"ConfigMap","metadata":{"annotations":{},"labels":{"app":"rxminder","environment":"dev","version":"v1.0.0"},"name":"rxminder-config-4229dg76t6","namespace":"rxminder-dev"}}
    labels:
      app: rxminder
      environment: dev
      version: v1.0.0
    name: rxminder-config-4229dg76t6
    namespace: rxminder-dev
- apiVersion: v1
  data:
    password: ZGV2cGFzczEyMw==
    username: YWRtaW4=
  kind: Secret
  metadata:
    annotations:
      kubectl.kubernetes.io/last-applied-configuration: |
        {"apiVersion":"v1","data":{"password":"ZGV2cGFzczEyMw==","username":"YWRtaW4="},"kind":"Secret","metadata":{"annotations":{},"labels":{"app":"rxminder","environment":"dev","version":"v1.0.0"},"name":"couchdb-secret-7ck2cc96g5","namespace":"rxminder-dev"},"type":"Opaque"}
    labels:
      app: rxminder
      environment: dev
      version: v1.0.0
    name: couchdb-secret-7ck2cc96g5
    namespace: rxminder-dev
  type: Opaque
- apiVersion: v1
  kind: Service
  metadata:
    annotations:
      kubectl.kubernetes.io/last-applied-configuration: |
        {"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"labels":{"app":"rxminder","component":"database","environment":"dev","version":"v1.0.0"},"name":"rxminder-couchdb-service","namespace":"rxminder-dev"},"spec":{"ports":[{"name":"couchdb","port":5984,"protocol":"TCP","targetPort":5984}],"selector":{"app":"rxminder","component":"database"},"type":"ClusterIP"}}
    labels:
      app: rxminder
      component: database
      environment: dev
      version: v1.0.0
    name: rxminder-couchdb-service
    namespace: rxminder-dev
  spec:
    ports:
    - name: couchdb
      port: 5984
      protocol: TCP
      targetPort: 5984
    selector:
      app: rxminder
      component: database
    type: ClusterIP
- apiVersion: v1
  kind: Service
  metadata:
    annotations:
      kubectl.kubernetes.io/last-applied-configuration: |
        {"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"labels":{"app":"rxminder","component":"frontend","environment":"dev","version":"v1.0.0"},"name":"rxminder-frontend-service","namespace":"rxminder-dev"},"spec":{"ports":[{"name":"http","port":80,"protocol":"TCP","targetPort":80}],"selector":{"app":"rxminder","component":"frontend"},"type":"ClusterIP"}}
    labels:
      app: rxminder
      component: frontend
      environment: dev
      version: v1.0.0
    name: rxminder-frontend-service
    namespace: rxminder-dev
  spec:
    ports:
    - name: http
      port: 80
      protocol: TCP
      targetPort: 80
    selector:
      app: rxminder
      component: frontend
    type: ClusterIP
- apiVersion: v1
  kind: PersistentVolumeClaim
  metadata:
    annotations:
      kubectl.kubernetes.io/last-applied-configuration: |
        {"apiVersion":"v1","kind":"PersistentVolumeClaim","metadata":{"annotations":{},"labels":{"app":"rxminder","component":"database","environment":"dev","version":"v1.0.0"},"name":"rxminder-couchdb-pvc","namespace":"rxminder-dev"},"spec":{"accessModes":["ReadWriteOnce"],"resources":{"requests":{"storage":"1Gi"}},"storageClassName":"standard"}}
    labels:
      app: rxminder
      component: database
      environment: dev
      version: v1.0.0
    name: rxminder-couchdb-pvc
    namespace: rxminder-dev
  spec:
    accessModes:
    - ReadWriteOnce
    resources:
      requests:
        storage: 1Gi
    storageClassName: standard
- apiVersion: apps/v1
  kind: Deployment
  metadata:
    annotations:
      kubectl.kubernetes.io/last-applied-configuration: |
        {"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{},"labels":{"app":"rxminder","component":"frontend","environment":"dev","version":"v1.0.0"},"name":"rxminder-frontend","namespace":"rxminder-dev"},"spec":{"replicas":1,"selector":{"matchLabels":{"component":"frontend"}},"template":{"metadata":{"labels":{"component":"frontend"}},"spec":{"containers":[{"env":[{"name":"NODE_ENV","value":"development"},{"name":"LOG_LEVEL","value":"debug"}],"envFrom":[{"configMapRef":{"name":"rxminder-config-4229dg76t6"}}],"image":"gitea-http.taildb3494.ts.net/will/rxminder:dev","livenessProbe":{"httpGet":{"path":"/","port":80},"initialDelaySeconds":30,"periodSeconds":30},"name":"frontend","ports":[{"containerPort":80}],"readinessProbe":{"httpGet":{"path":"/","port":80},"initialDelaySeconds":5,"periodSeconds":5},"resources":{"limits":{"cpu":"40m","memory":"32Mi"},"requests":{"cpu":"20m","memory":"16Mi"}}}],"imagePullSecrets":[{"name":"rxminder-registry-secret"}]}}}}
    labels:
      app: rxminder
      component: frontend
      environment: dev
      version: v1.0.0
    name: rxminder-frontend
    namespace: rxminder-dev
  spec:
    replicas: 1
    selector:
      matchLabels:
        component: frontend
    template:
      metadata:
        labels:
          component: frontend
      spec:
        containers:
        - env:
          - name: NODE_ENV
            value: development
          - name: LOG_LEVEL
            value: debug
          envFrom:
          - configMapRef:
              name: rxminder-config-4229dg76t6
          image: gitea-http.taildb3494.ts.net/will/rxminder:dev
          livenessProbe:
            httpGet:
              path: /
              port: 80
            initialDelaySeconds: 30
            periodSeconds: 30
          name: frontend
          ports:
          - containerPort: 80
          readinessProbe:
            httpGet:
              path: /
              port: 80
            initialDelaySeconds: 5
            periodSeconds: 5
          resources:
            limits:
              cpu: 40m
              memory: 32Mi
            requests:
              cpu: 20m
              memory: 16Mi
        imagePullSecrets:
        - name: rxminder-registry-secret
- apiVersion: apps/v1
  kind: StatefulSet
  metadata:
    annotations:
      kubectl.kubernetes.io/last-applied-configuration: |
        {"apiVersion":"apps/v1","kind":"StatefulSet","metadata":{"annotations":{},"labels":{"app":"rxminder","component":"database","environment":"dev","version":"v1.0.0"},"name":"rxminder-couchdb","namespace":"rxminder-dev"},"spec":{"replicas":1,"selector":{"matchLabels":{"app":"rxminder","component":"database"}},"serviceName":"rxminder-couchdb-service","template":{"metadata":{"labels":{"app":"rxminder","component":"database"}},"spec":{"containers":[{"env":[{"name":"COUCHDB_USER","valueFrom":{"secretKeyRef":{"key":"username","name":"couchdb-secret-7ck2cc96g5"}}},{"name":"COUCHDB_PASSWORD","valueFrom":{"secretKeyRef":{"key":"password","name":"couchdb-secret-7ck2cc96g5"}}}],"image":"couchdb:3.3.2","livenessProbe":{"httpGet":{"path":"/_up","port":5984},"initialDelaySeconds":60,"periodSeconds":30},"name":"couchdb","ports":[{"containerPort":5984}],"readinessProbe":{"httpGet":{"path":"/_up","port":5984},"initialDelaySeconds":10,"periodSeconds":5},"resources":{"limits":{"cpu":"60m","memory":"128Mi"},"requests":{"cpu":"30m","memory":"64Mi"}},"volumeMounts":[{"mountPath":"/opt/couchdb/data","name":"couchdb-data"}]}]}},"volumeClaimTemplates":[{"metadata":{"labels":{"app":"rxminder","component":"database"},"name":"couchdb-data"},"spec":{"accessModes":["ReadWriteOnce"],"resources":{"requests":{"storage":"1Gi"}},"storageClassName":"standard"}}]}}
    labels:
      app: rxminder
      component: database
      environment: dev
      version: v1.0.0
    name: rxminder-couchdb
    namespace: rxminder-dev
  spec:
    replicas: 1
    selector:
      matchLabels:
        app: rxminder
        component: database
    serviceName: rxminder-couchdb-service
    template:
      metadata:
        labels:
          app: rxminder
          component: database
      spec:
        containers:
        - env:
          - name: COUCHDB_USER
            valueFrom:
              secretKeyRef:
                key: username
                name: couchdb-secret-7ck2cc96g5
          - name: COUCHDB_PASSWORD
            valueFrom:
              secretKeyRef:
                key: password
                name: couchdb-secret-7ck2cc96g5
          image: couchdb:3.3.2
          livenessProbe:
            httpGet:
              path: /_up
              port: 5984
            initialDelaySeconds: 60
            periodSeconds: 30
          name: couchdb
          ports:
          - containerPort: 5984
          readinessProbe:
            httpGet:
              path: /_up
              port: 5984
            initialDelaySeconds: 10
            periodSeconds: 5
          resources:
            limits:
              cpu: 60m
              memory: 128Mi
            requests:
              cpu: 30m
              memory: 64Mi
          volumeMounts:
          - mountPath: /opt/couchdb/data
            name: couchdb-data
    volumeClaimTemplates:
    - metadata:
        labels:
          app: rxminder
          component: database
        name: couchdb-data
      spec:
        accessModes:
        - ReadWriteOnce
        resources:
          requests:
            storage: 1Gi
        storageClassName: standard
- apiVersion: autoscaling/v2
  kind: HorizontalPodAutoscaler
  metadata:
    annotations:
      kubectl.kubernetes.io/last-applied-configuration: |
        {"apiVersion":"autoscaling/v2","kind":"HorizontalPodAutoscaler","metadata":{"annotations":{},"labels":{"app":"rxminder","component":"frontend","environment":"dev","version":"v1.0.0"},"name":"rxminder-frontend-hpa","namespace":"rxminder-dev"},"spec":{"maxReplicas":3,"metrics":[{"resource":{"name":"cpu","target":{"averageUtilization":50,"type":"Utilization"}},"type":"Resource"}],"minReplicas":1,"scaleTargetRef":{"apiVersion":"apps/v1","kind":"Deployment","name":"rxminder-frontend"}}}
    labels:
      app: rxminder
      component: frontend
      environment: dev
      version: v1.0.0
    name: rxminder-frontend-hpa
    namespace: rxminder-dev
  spec:
    maxReplicas: 3
    metrics:
    - resource:
        name: cpu
        target:
          averageUtilization: 50
          type: Utilization
      type: Resource
    minReplicas: 1
    scaleTargetRef:
      apiVersion: apps/v1
      kind: Deployment
      name: rxminder-frontend
- apiVersion: batch/v1
  kind: Job
  metadata:
    annotations:
      kubectl.kubernetes.io/last-applied-configuration: |
        {"apiVersion":"batch/v1","kind":"Job","metadata":{"annotations":{},"labels":{"app":"rxminder","component":"database","environment":"dev","version":"v1.0.0"},"name":"rxminder-db-seed","namespace":"rxminder-dev"},"spec":{"backoffLimit":4,"template":{"metadata":{"labels":{"app":"rxminder","component":"database"}},"spec":{"containers":[{"args":["# Wait for CouchDB to be ready\necho \"Waiting for CouchDB to be ready...\"\nuntil curl -f http://couchdb-service:5984/_up 2\u003e/dev/null; do\n  sleep 2\ndone\n\n# Create databases\necho \"Creating databases...\"\ncurl -X PUT http://$COUCHDB_USER:$COUCHDB_PASSWORD@couchdb-service:5984/meds_app\n\n# Create default admin user\necho \"Creating default admin user...\"\ncurl -X PUT http://$COUCHDB_USER:$COUCHDB_PASSWORD@couchdb-service:5984/_users/org.couchdb.user:$COUCHDB_USER \\\n  -H \"Content-Type: application/json\" \\\n  -d \"{\n    \\\"name\\\": \\\"$COUCHDB_USER\\\",\n    \\\"password\\\": \\\"$COUCHDB_PASSWORD\\\",\n    \\\"roles\\\": [\\\"admin\\\"],\n    \\\"type\\\": \\\"user\\\"\n  }\"\n\n# Create design documents for views\necho \"Creating design documents...\"\ncurl -X PUT http://$COUCHDB_USER:$COUCHDB_PASSWORD@couchdb-service:5984/meds_app/_design/medications \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"views\": {\n      \"by_name\": {\n        \"map\": \"function(doc) { if (doc.type === \\\"medication\\\") emit(doc.name, doc); }\"\n      },\n      \"by_user\": {\n        \"map\": \"function(doc) { if (doc.type === \\\"medication\\\") emit(doc.userId, doc); }\"\n      }\n    }\n  }'\n\ncurl -X PUT http://$COUCHDB_USER:$COUCHDB_PASSWORD@couchdb-service:5984/meds_app/_design/reminders \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"views\": {\n      \"by_medication\": {\n        \"map\": \"function(doc) { if (doc.type === \\\"reminder\\\") emit(doc.medicationId, doc); }\"\n      },\n      \"by_user\": {\n        \"map\": \"function(doc) { if (doc.type === \\\"reminder\\\") emit(doc.userId, doc); }\"\n      }\n    }\n  }'\n\n# Create a sample user document for reference\n  # Create design document for authentication users\n  curl -X PUT http://$COUCHDB_USER:$COUCHDB_PASSWORD@couchdb-service:5984/meds_app/_design/auth \\\n    -H \"Content-Type: application/json\" \\\n    -d '{\n      \"views\": {\n        \"by_username\": {\n          \"map\": \"function(doc) { if (doc.type === \\\"user\\\" \u0026\u0026 doc.username) emit(doc.username, doc); }\"\n        },\n        \"by_email\": {\n          \"map\": \"function(doc) { if (doc.type === \\\"user\\\" \u0026\u0026 doc.email) emit(doc.email, doc); }\"\n        }\n      }\n    }'\necho \"Creating sample user document...\"\ncurl -X POST http://$COUCHDB_USER:$COUCHDB_PASSWORD@couchdb-service:5984/meds_app \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"type\": \"user\",\n    \"name\": \"sample_user\",\n    \"email\": \"user@example.com\",\n    \"createdAt\": \"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'\"\n  }'\n\necho \"Database seeding completed with default admin user\"\n"],"command":["/bin/sh","-c"],"env":[{"name":"COUCHDB_USER","valueFrom":{"secretKeyRef":{"key":"username","name":"couchdb-secret-7ck2cc96g5"}}},{"name":"COUCHDB_PASSWORD","valueFrom":{"secretKeyRef":{"key":"password","name":"couchdb-secret-7ck2cc96g5"}}}],"image":"couchdb:3.3.2","name":"db-seeder"}],"restartPolicy":"Never"}}}}
    labels:
      app: rxminder
      component: database
      environment: dev
      version: v1.0.0
    name: rxminder-db-seed
    namespace: rxminder-dev
  spec:
    backoffLimit: 4
    template:
      metadata:
        labels:
          app: rxminder
          component: database
      spec:
        containers:
        - args:
          - |
            # Wait for CouchDB to be ready
            echo "Waiting for CouchDB to be ready..."
            until curl -f http://couchdb-service:5984/_up 2>/dev/null; do
              sleep 2
            done

            # Create databases
            echo "Creating databases..."
            curl -X PUT http://$COUCHDB_USER:$COUCHDB_PASSWORD@couchdb-service:5984/meds_app

            # Create default admin user
            echo "Creating default admin user..."
            curl -X PUT http://$COUCHDB_USER:$COUCHDB_PASSWORD@couchdb-service:5984/_users/org.couchdb.user:$COUCHDB_USER \
              -H "Content-Type: application/json" \
              -d "{
                \"name\": \"$COUCHDB_USER\",
                \"password\": \"$COUCHDB_PASSWORD\",
                \"roles\": [\"admin\"],
                \"type\": \"user\"
              }"

            # Create design documents for views
            echo "Creating design documents..."
            curl -X PUT http://$COUCHDB_USER:$COUCHDB_PASSWORD@couchdb-service:5984/meds_app/_design/medications \
              -H "Content-Type: application/json" \
              -d '{
                "views": {
                  "by_name": {
                    "map": "function(doc) { if (doc.type === \"medication\") emit(doc.name, doc); }"
                  },
                  "by_user": {
                    "map": "function(doc) { if (doc.type === \"medication\") emit(doc.userId, doc); }"
                  }
                }
              }'

            curl -X PUT http://$COUCHDB_USER:$COUCHDB_PASSWORD@couchdb-service:5984/meds_app/_design/reminders \
              -H "Content-Type: application/json" \
              -d '{
                "views": {
                  "by_medication": {
                    "map": "function(doc) { if (doc.type === \"reminder\") emit(doc.medicationId, doc); }"
                  },
                  "by_user": {
                    "map": "function(doc) { if (doc.type === \"reminder\") emit(doc.userId, doc); }"
                  }
                }
              }'

            # Create a sample user document for reference
              # Create design document for authentication users
              curl -X PUT http://$COUCHDB_USER:$COUCHDB_PASSWORD@couchdb-service:5984/meds_app/_design/auth \
                -H "Content-Type: application/json" \
                -d '{
                  "views": {
                    "by_username": {
                      "map": "function(doc) { if (doc.type === \"user\" && doc.username) emit(doc.username, doc); }"
                    },
                    "by_email": {
                      "map": "function(doc) { if (doc.type === \"user\" && doc.email) emit(doc.email, doc); }"
                    }
                  }
                }'
            echo "Creating sample user document..."
            curl -X POST http://$COUCHDB_USER:$COUCHDB_PASSWORD@couchdb-service:5984/meds_app \
              -H "Content-Type: application/json" \
              -d '{
                "type": "user",
                "name": "sample_user",
                "email": "user@example.com",
                "createdAt": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"
              }'

            echo "Database seeding completed with default admin user"
          command:
          - /bin/sh
          - -c
          env:
          - name: COUCHDB_USER
            valueFrom:
              secretKeyRef:
                key: username
                name: couchdb-secret-7ck2cc96g5
          - name: COUCHDB_PASSWORD
            valueFrom:
              secretKeyRef:
                key: password
                name: couchdb-secret-7ck2cc96g5
          image: couchdb:3.3.2
          name: db-seeder
        restartPolicy: Never
- apiVersion: networking.k8s.io/v1
  kind: Ingress
  metadata:
    annotations:
      kubectl.kubernetes.io/last-applied-configuration: |
        {"apiVersion":"networking.k8s.io/v1","kind":"Ingress","metadata":{"annotations":{},"labels":{"app":"rxminder","component":"frontend","environment":"dev","version":"v1.0.0"},"name":"rxminder-ingress","namespace":"rxminder-dev"},"spec":{"ingressClassName":"nginx","rules":[{"host":"rxminder-dev.local","http":{"paths":[{"backend":{"service":{"name":"rxminder-frontend-service","port":{"number":80}}},"path":"/","pathType":"Prefix"}]}}]}}
    labels:
      app: rxminder
      component: frontend
      environment: dev
      version: v1.0.0
    name: rxminder-ingress
    namespace: rxminder-dev
  spec:
    ingressClassName: nginx
    rules:
    - host: rxminder-dev.local
      http:
        paths:
        - backend:
            service:
              name: rxminder-frontend-service
              port:
                number: 80
          path: /
          pathType: Prefix
- apiVersion: networking.k8s.io/v1
  kind: NetworkPolicy
  metadata:
    annotations:
      kubectl.kubernetes.io/last-applied-configuration: |
        {"apiVersion":"networking.k8s.io/v1","kind":"NetworkPolicy","metadata":{"annotations":{},"labels":{"app":"rxminder","component":"database","environment":"dev","version":"v1.0.0"},"name":"rxminder-database-policy","namespace":"rxminder-dev"},"spec":{"egress":[{"ports":[{"port":5984,"protocol":"TCP"}],"to":[{"podSelector":{"matchLabels":{"component":"database"}}}]}],"ingress":[{"from":[{"podSelector":{"matchLabels":{"component":"frontend"}}}],"ports":[{"port":5984,"protocol":"TCP"}]}],"podSelector":{"matchLabels":{"component":"database"}},"policyTypes":["Ingress","Egress"]}}
    labels:
      app: rxminder
      component: database
      environment: dev
      version: v1.0.0
    name: rxminder-database-policy
    namespace: rxminder-dev
  spec:
    egress:
    - ports:
      - port: 5984
        protocol: TCP
      to:
      - podSelector:
          matchLabels:
            component: database
    ingress:
    - from:
      - podSelector:
          matchLabels:
            component: frontend
      ports:
      - port: 5984
        protocol: TCP
    podSelector:
      matchLabels:
        component: database
    policyTypes:
    - Ingress
    - Egress
- apiVersion: networking.k8s.io/v1
  kind: NetworkPolicy
  metadata:
    annotations:
      kubectl.kubernetes.io/last-applied-configuration: |
        {"apiVersion":"networking.k8s.io/v1","kind":"NetworkPolicy","metadata":{"annotations":{},"labels":{"app":"rxminder","component":"frontend","environment":"dev","version":"v1.0.0"},"name":"rxminder-frontend-policy","namespace":"rxminder-dev"},"spec":{"egress":[{"ports":[{"port":5984,"protocol":"TCP"}],"to":[{"podSelector":{"matchLabels":{"component":"database"}}}]},{"ports":[{"port":80,"protocol":"TCP"}],"to":[{"podSelector":{"matchLabels":{"component":"frontend"}}}]}],"ingress":[{"from":[{"podSelector":{"matchLabels":{"component":"frontend"}}}],"ports":[{"port":80,"protocol":"TCP"}]}],"podSelector":{"matchLabels":{"component":"frontend"}},"policyTypes":["Ingress","Egress"]}}
    labels:
      app: rxminder
      component: frontend
      environment: dev
      version: v1.0.0
    name: rxminder-frontend-policy
    namespace: rxminder-dev
  spec:
    egress:
    - ports:
      - port: 5984
        protocol: TCP
      to:
      - podSelector:
          matchLabels:
            component: database
    - ports:
      - port: 80
        protocol: TCP
      to:
      - podSelector:
          matchLabels:
            component: frontend
    ingress:
    - from:
      - podSelector:
          matchLabels:
            component: frontend
      ports:
      - port: 80
        protocol: TCP
    podSelector:
      matchLabels:
        component: frontend
    policyTypes:
    - Ingress
    - Egress
kind: List
metadata: {}

## Migration Path
- Legacy shell scripts remain available for backward compatibility
- Gradual migration: dev → staging → production
- Zero-downtime deployment capability

Co-authored-by: Assistant <assistant@anthropic.com>
2025-09-07 20:47:10 -07:00

539 lines
14 KiB
Markdown

# Environment Variables Guide
This guide explains how to use environment variables with the rxminder Kustomize deployment system.
## Overview
The rxminder application supports multiple ways to configure deployments using environment variables:
1. **Static Configuration**: Pre-generated config files
2. **Dynamic Configuration**: Runtime environment variable injection
3. **Hybrid Approach**: Combination of both methods
## Environment Variable Sources
Variables are loaded in the following priority order (last wins):
1. `~/.env` - Global user environment
2. `./.env` - Project-wide environment
3. `./.env.{environment}` - Environment-specific (e.g., `.env.dev`, `.env.prod`)
4. `./.env.local` - Local overrides (git-ignored)
5. System environment variables
## Quick Start
### Method 1: Generate Static Configuration
```bash
# Generate configuration from your environment variables
make generate-config-dev
make generate-config-prod
# Deploy using generated config
make deploy-dev
```
### Method 2: Dynamic Environment Injection
```bash
# Deploy with runtime environment variable substitution
make deploy-with-env-dev
make deploy-with-env-prod
```
## Environment Files Setup
### 1. Global User Environment (`~/.env`)
Store your personal/machine-specific settings:
```bash
# ~/.env
REGISTRY_USERNAME=your_username
REGISTRY_EMAIL=your_email@example.com
KUBECONFIG=/path/to/your/kubeconfig
```
### 2. Project Environment (`./.env`)
Store project-wide defaults:
```bash
# ./.env
APP_NAME=rxminder
REGISTRY_URL=gitea-http.taildb3494.ts.net
IMAGE_REPOSITORY=will/rxminder
COUCHDB_DATABASE_NAME=meds_app
```
### 3. Environment-Specific Files
#### Development (`./.env.dev`)
```bash
NODE_ENV=development
LOG_LEVEL=debug
DEBUG=true
IMAGE_TAG=dev
INGRESS_HOST=rxminder-dev.local
DEV_MODE=true
ENABLE_MONITORING=false
# Development database
COUCHDB_USERNAME=admin
COUCHDB_PASSWORD=devpass123
# Development resource limits
FRONTEND_MEMORY_REQUEST=16Mi
FRONTEND_MEMORY_LIMIT=32Mi
COUCHDB_MEMORY_REQUEST=64Mi
COUCHDB_MEMORY_LIMIT=128Mi
STORAGE_SIZE=1Gi
```
#### Production (`./.env.prod`)
```bash
NODE_ENV=production
LOG_LEVEL=warn
DEBUG=false
IMAGE_TAG=v1.0.0
INGRESS_HOST=rxminder.yourdomain.com
ENABLE_MONITORING=true
ENABLE_METRICS=true
ENABLE_TRACING=true
# Production performance
CACHE_TTL=3600
REQUEST_TIMEOUT=30000
MAX_CONNECTIONS=200
# Production resources
FRONTEND_REPLICAS=3
FRONTEND_MEMORY_REQUEST=256Mi
FRONTEND_MEMORY_LIMIT=512Mi
COUCHDB_MEMORY_REQUEST=512Mi
COUCHDB_MEMORY_LIMIT=1Gi
STORAGE_SIZE=10Gi
STORAGE_CLASS=ssd
# Security
ENABLE_SECURITY_HEADERS=true
ENABLE_RATE_LIMITING=true
CORS_ORIGIN=https://rxminder.yourdomain.com
# TLS/SSL
ENABLE_TLS=true
CERT_MANAGER_ISSUER=letsencrypt-prod
```
#### Staging (`./.env.staging`)
```bash
NODE_ENV=staging
LOG_LEVEL=info
DEBUG=false
IMAGE_TAG=staging
INGRESS_HOST=staging.rxminder.yourdomain.com
ENABLE_MONITORING=true
# Staging resources (between dev and prod)
FRONTEND_REPLICAS=2
FRONTEND_MEMORY_REQUEST=128Mi
FRONTEND_MEMORY_LIMIT=256Mi
STORAGE_SIZE=5Gi
```
### 4. Local Overrides (`./.env.local`)
**Note**: This file should be in `.gitignore` and used for sensitive local settings:
```bash
# ./.env.local (DO NOT COMMIT)
COUCHDB_PASSWORD=your_secure_password
REGISTRY_PASSWORD=your_registry_token
API_SECRET_KEY=your_secret_key
JWT_SECRET=your_jwt_secret
# Local development overrides
INGRESS_HOST=localhost:8080
DEV_API_URL=http://localhost:5984
```
## Available Environment Variables
### Core Application Variables
| Variable | Default | Description |
| ------------- | ------------ | ------------------------------------------------------------ |
| `APP_NAME` | `rxminder` | Application name used in labels and resources |
| `NODE_ENV` | `production` | Runtime environment (`development`, `staging`, `production`) |
| `LOG_LEVEL` | `info` | Logging level (`debug`, `info`, `warn`, `error`) |
| `DEBUG` | `false` | Enable debug mode |
| `APP_VERSION` | `1.0.0` | Application version |
### Container Registry Variables
| Variable | Default | Description |
| ------------------- | ------------------------------ | -------------------------------- |
| `REGISTRY_URL` | `gitea-http.taildb3494.ts.net` | Container registry URL |
| `IMAGE_REPOSITORY` | `will/rxminder` | Image repository path |
| `IMAGE_TAG` | `latest` | Image tag to deploy |
| `REGISTRY_USERNAME` | - | Registry authentication username |
| `REGISTRY_PASSWORD` | - | Registry authentication password |
| `REGISTRY_EMAIL` | - | Registry authentication email |
### Database Variables
| Variable | Default | Description |
| ----------------------- | -------------------------- | ---------------------------- |
| `DB_HOST` | `rxminder-couchdb-service` | Database host |
| `DB_PORT` | `5984` | Database port |
| `COUCHDB_DATABASE_NAME` | `meds_app` | CouchDB database name |
| `COUCHDB_USERNAME` | `admin` | Database username |
| `COUCHDB_PASSWORD` | - | Database password (required) |
### Network & Ingress Variables
| Variable | Default | Description |
| --------------------- | -------------------- | -------------------- |
| `INGRESS_HOST` | Environment-specific | Ingress hostname |
| `INGRESS_CLASS` | `nginx` | Ingress class to use |
| `ENABLE_TLS` | `false` | Enable TLS/HTTPS |
| `CERT_MANAGER_ISSUER` | `letsencrypt-prod` | Certificate issuer |
| `CORS_ORIGIN` | `*` | CORS allowed origins |
### Performance Variables
| Variable | Default | Description |
| ------------------- | ------- | ------------------------------- |
| `FRONTEND_REPLICAS` | `1` | Number of frontend replicas |
| `CACHE_TTL` | `1800` | Cache time-to-live in seconds |
| `REQUEST_TIMEOUT` | `30000` | Request timeout in milliseconds |
| `MAX_CONNECTIONS` | `100` | Maximum concurrent connections |
### Resource Limits
| Variable | Default | Description |
| ------------------------- | ---------- | ----------------------- |
| `FRONTEND_MEMORY_REQUEST` | `32Mi` | Frontend memory request |
| `FRONTEND_MEMORY_LIMIT` | `64Mi` | Frontend memory limit |
| `FRONTEND_CPU_REQUEST` | `20m` | Frontend CPU request |
| `FRONTEND_CPU_LIMIT` | `40m` | Frontend CPU limit |
| `COUCHDB_MEMORY_REQUEST` | `64Mi` | CouchDB memory request |
| `COUCHDB_MEMORY_LIMIT` | `128Mi` | CouchDB memory limit |
| `STORAGE_SIZE` | `1Gi` | Storage volume size |
| `STORAGE_CLASS` | `standard` | Storage class |
### Monitoring & Observability
| Variable | Default | Description |
| ------------------- | ------- | -------------------------- |
| `ENABLE_MONITORING` | `false` | Enable monitoring features |
| `ENABLE_METRICS` | `false` | Enable metrics collection |
| `ENABLE_TRACING` | `false` | Enable distributed tracing |
| `METRICS_PORT` | `9090` | Metrics server port |
### Security Variables
| Variable | Default | Description |
| ------------------------- | ------- | ----------------------- |
| `ENABLE_SECURITY_HEADERS` | `false` | Enable security headers |
| `ENABLE_RATE_LIMITING` | `false` | Enable rate limiting |
| `API_SECRET_KEY` | - | API secret key |
| `JWT_SECRET` | - | JWT signing secret |
## Usage Examples
### Basic Development Setup
1. Create your development environment file:
```bash
cat > .env.dev << EOF
NODE_ENV=development
LOG_LEVEL=debug
DEBUG=true
IMAGE_TAG=dev
INGRESS_HOST=rxminder-dev.local
COUCHDB_PASSWORD=devpass123
EOF
```
2. Generate configuration and deploy:
```bash
make generate-config-dev
make deploy-dev
```
### Production Deployment with Secrets
1. Set up your production environment:
```bash
# .env.prod (commit this)
NODE_ENV=production
LOG_LEVEL=warn
IMAGE_TAG=v1.0.0
INGRESS_HOST=rxminder.yourdomain.com
ENABLE_TLS=true
# .env.local (DO NOT commit)
COUCHDB_PASSWORD=your_secure_production_password
REGISTRY_PASSWORD=your_registry_token
```
2. Deploy with environment variables:
```bash
make deploy-with-env-prod
```
### Dynamic Configuration Updates
Update configuration without rebuilding:
```bash
# Update environment variable
export LOG_LEVEL=debug
# Deploy with updated configuration
./scripts/deploy-with-env.sh prod apply
```
### Testing Different Configurations
```bash
# Test with different image tag
export IMAGE_TAG=feature-branch
make diff-with-env-dev
# Test with different resources
export FRONTEND_REPLICAS=5
./scripts/deploy-with-env.sh prod dry-run
```
## Available Commands
### Configuration Generation
```bash
make generate-config-dev # Generate dev config from env vars
make generate-config-prod # Generate prod config from env vars
make generate-config-staging # Generate staging config from env vars
make generate-config-all # Generate all environment configs
make validate-config # Validate generated configuration
make generate-secrets-template # Generate secrets template files
```
### Environment-Aware Deployment
```bash
make deploy-with-env-dev # Deploy dev with env vars
make deploy-with-env-prod # Deploy prod with env vars
make deploy-with-env-staging # Deploy staging with env vars
make undeploy-with-env-dev # Remove dev deployment
make diff-with-env-dev # Show dev diff with env vars
make status-with-env-dev # Show dev status with env vars
```
### Direct Script Usage
```bash
# Generate configuration
./scripts/generate-config.sh dev
./scripts/generate-config.sh prod --secrets
# Deploy with environment variables
./scripts/deploy-with-env.sh dev apply
./scripts/deploy-with-env.sh prod diff
./scripts/deploy-with-env.sh staging delete
```
## Best Practices
### Security
1. **Never commit sensitive data**:
- Add `.env.local` to `.gitignore`
- Use external secret management for production
- Rotate passwords regularly
2. **Use environment-specific files**:
- `.env.dev` for development settings
- `.env.prod` for production configuration
- `.env.local` for sensitive overrides
### Organization
1. **Group related variables**:
- Database settings together
- Resource limits together
- Feature flags together
2. **Use descriptive names**:
- `FRONTEND_MEMORY_LIMIT` vs `MEM_LIMIT`
- `ENABLE_DEBUG_MODE` vs `DEBUG`
3. **Document your variables**:
- Add comments explaining purpose
- Include example values
- Note required vs optional
### Development Workflow
1. **Start with examples**:
```bash
cp .env.example .env.dev
# Edit .env.dev with your settings
```
2. **Test configurations**:
```bash
make generate-config-dev
make kustomize-dry-run-dev
```
3. **Validate before deploying**:
```bash
make validate-config
make diff-with-env-dev
```
## Troubleshooting
### Common Issues
1. **Variables not being loaded**:
- Check file permissions (`chmod 644 .env.*`)
- Verify file format (no spaces around `=`)
- Check file encoding (UTF-8)
2. **Configuration not updating**:
- Regenerate config: `make generate-config-dev`
- Clear cached resources: `kubectl delete configmap -l app=rxminder`
3. **Deployment failures**:
- Validate syntax: `make validate-config`
- Check logs: `kubectl logs -l app=rxminder`
- Verify resources: `kubectl describe deployment rxminder-frontend`
### Debug Commands
```bash
# Check loaded environment variables
./scripts/generate-config.sh dev --dry-run
# Validate generated configuration
make validate-config
# Show what would be deployed
./scripts/deploy-with-env.sh dev dry-run
# Check current deployment status
make status-with-env-dev
```
## Migration from Legacy System
### Step 1: Extract Current Configuration
```bash
# Export current settings to environment file
echo "APP_NAME=rxminder" > .env.dev
echo "IMAGE_TAG=dev" >> .env.dev
# ... add other variables
```
### Step 2: Test New System
```bash
# Generate and validate configuration
make generate-config-dev
make validate-config
# Test deployment
make kustomize-dry-run-dev
```
### Step 3: Deploy
```bash
# Deploy using new system
make deploy-with-env-dev
# Verify deployment
make status-with-env-dev
```
## External Secret Management
For production environments, integrate with external secret management:
### HashiCorp Vault Integration
```bash
# .env.prod
VAULT_ADDR=https://vault.yourdomain.com
VAULT_ROLE=rxminder-prod
VAULT_SECRET_PATH=secret/rxminder/prod
# Use Vault Agent or CSI driver to inject secrets
```
### Kubernetes External Secrets
```yaml
# external-secret.yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: rxminder-secrets
spec:
secretStoreRef:
name: vault-backend
kind: SecretStore
target:
name: couchdb-secret
data:
- secretKey: password
remoteRef:
key: rxminder/prod
property: couchdb_password
```
### AWS Secrets Manager
```bash
# .env.prod
AWS_REGION=us-west-2
AWS_SECRET_NAME=rxminder/prod/couchdb
# Use AWS Load Balancer Controller or external-secrets-operator
```
## Summary
The environment variable system provides:
- **Flexibility**: Configure deployments without changing code
- **Security**: Keep sensitive data out of version control
- **Consistency**: Standardized configuration across environments
- **Maintainability**: Clear separation of configuration and code
Choose the approach that best fits your workflow:
- **Static generation** for stable, version-controlled configurations
- **Dynamic injection** for flexible, runtime configurations
- **Hybrid approach** for the best of both worlds
For more information, see:
- [Kustomize README](../k8s-kustomize/README.md)
- [Migration Guide](../KUSTOMIZE_MIGRATION.md)
- [Deployment Scripts](../scripts/)