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

14 KiB

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

# 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

# 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:

# ~/.env
REGISTRY_USERNAME=your_username
REGISTRY_EMAIL=your_email@example.com
KUBECONFIG=/path/to/your/kubeconfig

2. Project Environment (./.env)

Store project-wide defaults:

# ./.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)

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)

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)

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:

# ./.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:
cat > .env.dev << EOF
NODE_ENV=development
LOG_LEVEL=debug
DEBUG=true
IMAGE_TAG=dev
INGRESS_HOST=rxminder-dev.local
COUCHDB_PASSWORD=devpass123
EOF
  1. Generate configuration and deploy:
make generate-config-dev
make deploy-dev

Production Deployment with Secrets

  1. Set up your production environment:
# .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
  1. Deploy with environment variables:
make deploy-with-env-prod

Dynamic Configuration Updates

Update configuration without rebuilding:

# Update environment variable
export LOG_LEVEL=debug

# Deploy with updated configuration
./scripts/deploy-with-env.sh prod apply

Testing Different Configurations

# 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

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

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

# 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:

    cp .env.example .env.dev
    # Edit .env.dev with your settings
    
  2. Test configurations:

    make generate-config-dev
    make kustomize-dry-run-dev
    
  3. Validate before deploying:

    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

# 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

# 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

# Generate and validate configuration
make generate-config-dev
make validate-config

# Test deployment
make kustomize-dry-run-dev

Step 3: Deploy

# 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

# .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

# 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

# .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: