🎉 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>
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:
- Static Configuration: Pre-generated config files
- Dynamic Configuration: Runtime environment variable injection
- Hybrid Approach: Combination of both methods
Environment Variable Sources
Variables are loaded in the following priority order (last wins):
~/.env- Global user environment./.env- Project-wide environment./.env.{environment}- Environment-specific (e.g.,.env.dev,.env.prod)./.env.local- Local overrides (git-ignored)- 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
- 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
- Generate configuration and deploy:
make generate-config-dev
make deploy-dev
Production Deployment with Secrets
- 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
- 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
-
Never commit sensitive data:
- Add
.env.localto.gitignore - Use external secret management for production
- Rotate passwords regularly
- Add
-
Use environment-specific files:
.env.devfor development settings.env.prodfor production configuration.env.localfor sensitive overrides
Organization
-
Group related variables:
- Database settings together
- Resource limits together
- Feature flags together
-
Use descriptive names:
FRONTEND_MEMORY_LIMITvsMEM_LIMITENABLE_DEBUG_MODEvsDEBUG
-
Document your variables:
- Add comments explaining purpose
- Include example values
- Note required vs optional
Development Workflow
-
Start with examples:
cp .env.example .env.dev # Edit .env.dev with your settings -
Test configurations:
make generate-config-dev make kustomize-dry-run-dev -
Validate before deploying:
make validate-config make diff-with-env-dev
Troubleshooting
Common Issues
-
Variables not being loaded:
- Check file permissions (
chmod 644 .env.*) - Verify file format (no spaces around
=) - Check file encoding (UTF-8)
- Check file permissions (
-
Configuration not updating:
- Regenerate config:
make generate-config-dev - Clear cached resources:
kubectl delete configmap -l app=rxminder
- Regenerate config:
-
Deployment failures:
- Validate syntax:
make validate-config - Check logs:
kubectl logs -l app=rxminder - Verify resources:
kubectl describe deployment rxminder-frontend
- Validate syntax:
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: