🎉 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 [34mGenerating development configuration...[0m [0;34m[INFO][0m Kustomize Config Generator [0;34m[INFO][0m Environment: dev [0;34m[INFO][0m Loading environment variables... [1;33m[WARNING][0m File not found: /home/will/.env [0;34m[INFO][0m Loading: /home/will/Code/meds/.env [1;33m[WARNING][0m File not found: /home/will/Code/meds/.env.dev [1;33m[WARNING][0m File not found: /home/will/Code/meds/.env.local [0;34m[INFO][0m Generating base config.env... [0;32m[SUCCESS][0m Generated: /home/will/Code/meds/k8s-kustomize/base/config.env [0;34m[INFO][0m Generating environment-specific config for: dev [0;32m[SUCCESS][0m Generated development config: /home/will/Code/meds/k8s-kustomize/overlays/dev/config.env [0;34m[INFO][0m Validating generated configuration... [0;32m[SUCCESS][0m Configuration validation passed! [0;32m[SUCCESS][0m Configuration generation completed! [0;34m[INFO][0m 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 [34mDeploying to Kubernetes with Kustomize (dev)...[0m [34mDeploying to production with environment variables...[0m [0;34m[INFO][0m Kustomize Deployment with Environment Variables [0;34m[INFO][0m Environment: prod [0;34m[INFO][0m Action: apply [0;34m[INFO][0m Validating prerequisites... [0;32m[SUCCESS][0m Prerequisites validated [0;34m[INFO][0m Loading environment variables for: prod [0;34m[INFO][0m Loading: /home/will/Code/meds/.env [0;32m[SUCCESS][0m Environment loaded: prod [0;34m[INFO][0m Key variables: APP_NAME: rxminder NODE_ENV: production IMAGE_TAG: latest NAMESPACE: rxminder-prod INGRESS_HOST: rxminder.192.168.153.243.nip.io [0;34m[INFO][0m Generating dynamic configuration... [34mValidating Kustomize configuration (dev)...[0m 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) [34mValidating Kustomize configuration (prod)...[0m 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) [32mKustomize validation completed![0m [34mDry run Kustomize deployment (dev)...[0m 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>
539 lines
14 KiB
Markdown
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/)
|