# ezscale-website Helm Chart Production deployment for the EZSCALE billing platform Laravel app. ## Environments | Env | Values file | Notes | |---|---|---| | Local k3d | `values-local.yaml` | Self-contained: in-cluster MariaDB + Valkey. No ingress (use port-forward). | | US prod | `values-us-prod.yaml` | Connects to existing `mariadb` CR in `ezscale` ns; per-app Valkey on Longhorn. | ## Prerequisites (us-prod) The `ezscale` namespace must already have: - mariadb-operator installed cluster-wide (provides `MariaDB`/`Database`/`User`/`Grant` CRDs) - A `MariaDB` CR named `mariadb` - cert-manager `ClusterIssuer` named `letsencrypt` - Traefik with `cloudflarewarp` and `http-to-https` middlewares in `kube-system` - Image-pull `Secret` named `gitea-registry` These are all already provisioned per `infrastructure/kubernetes/`. ## First-time bootstrap (us-prod) This step is run **once** per environment, never repeated. The Secret it creates lives forever — re-running it would invalidate every encrypted DB column and every issued OAuth token. ```bash # 1. Generate APP_KEY locally (any Laravel checkout works) APP_KEY=$(cd /tmp && composer create-project laravel/laravel _key_gen --quiet --no-install \ && cd _key_gen && composer install --quiet \ && php artisan key:generate --show) # 2. Generate Passport keys TMP_KEYS=$(mktemp -d) openssl genrsa -out "$TMP_KEYS/oauth-private.key" 4096 openssl rsa -in "$TMP_KEYS/oauth-private.key" -pubout -out "$TMP_KEYS/oauth-public.key" # 3. Gather production secrets. Read each one and store in shell vars. DB_PASS=$(openssl rand -base64 32 | tr -d '/=+' | cut -c1-32) read -sp "STRIPE_KEY (pk_live_...): " STRIPE_KEY; echo read -sp "STRIPE_SECRET (sk_live_...): " STRIPE_SECRET; echo read -sp "STRIPE_WEBHOOK_SECRET (whsec_...): " STRIPE_WEBHOOK; echo read -sp "PAYPAL_CLIENT_ID: " PAYPAL_ID; echo read -sp "PAYPAL_CLIENT_SECRET: " PAYPAL_SECRET; echo read -sp "AWS_ACCESS_KEY_ID (Storj): " AWS_KEY; echo read -sp "AWS_SECRET_ACCESS_KEY (Storj): " AWS_SECRET; echo read -sp "MAIL_USERNAME (SMTP): " MAIL_USER; echo read -sp "MAIL_PASSWORD (SMTP): " MAIL_PASS; echo read -sp "VIRTFUSION_API_KEY: " VF_KEY; echo read -sp "PTERODACTYL_APP_KEY: " PTERO_KEY; echo # 4. Create the Secret in the ezscale namespace kubectl create secret generic ezscale-website-secrets \ --namespace ezscale \ --from-literal=APP_KEY="$APP_KEY" \ --from-literal=DB_PASSWORD="$DB_PASS" \ --from-literal=STRIPE_KEY="$STRIPE_KEY" \ --from-literal=STRIPE_SECRET="$STRIPE_SECRET" \ --from-literal=STRIPE_WEBHOOK_SECRET="$STRIPE_WEBHOOK" \ --from-literal=PAYPAL_CLIENT_ID="$PAYPAL_ID" \ --from-literal=PAYPAL_CLIENT_SECRET="$PAYPAL_SECRET" \ --from-literal=AWS_ACCESS_KEY_ID="$AWS_KEY" \ --from-literal=AWS_SECRET_ACCESS_KEY="$AWS_SECRET" \ --from-literal=MAIL_USERNAME="$MAIL_USER" \ --from-literal=MAIL_PASSWORD="$MAIL_PASS" \ --from-literal=VIRTFUSION_API_KEY="$VF_KEY" \ --from-literal=PTERODACTYL_APP_KEY="$PTERO_KEY" \ --from-file=oauth-private.key="$TMP_KEYS/oauth-private.key" \ --from-file=oauth-public.key="$TMP_KEYS/oauth-public.key" # 5. Wipe the local key copies rm -rf /tmp/_key_gen "$TMP_KEYS" ``` After this, `helm install` and every subsequent `helm upgrade` reference the existing Secret without ever rewriting it. ## Deploy ```bash helm upgrade --install ezscale-website ./helm/ezscale-website \ --namespace ezscale \ -f helm/ezscale-website/values-us-prod.yaml \ --set image.tag=v0.1.0 ``` ## Local dev cluster ```bash # 1. Create cluster with Longhorn (or local-path) and the operator k3d cluster create ezscale-local helm repo add mariadb-operator https://helm.mariadb.com/mariadb-operator helm install mariadb-operator -n mariadb-operator --create-namespace mariadb-operator/mariadb-operator # 2. Build the image into the cluster docker build --target app -t ezscale-website:app-local . docker build --target horizon -t ezscale-website:horizon-local . docker build --target scheduler -t ezscale-website:scheduler-local . k3d image import ezscale-website:app-local ezscale-website:horizon-local ezscale-website:scheduler-local -c ezscale-local # 3. Edit values-local.yaml — replace APP_KEY with `php artisan key:generate --show` output # 4. Install helm upgrade --install ezscale-website ./helm/ezscale-website \ --namespace ezscale --create-namespace \ -f helm/ezscale-website/values-local.yaml \ --set image.registry=docker.io \ --set image.repository=library/ezscale-website \ --set image.tag=local # 5. Port-forward and visit http://localhost:8080 kubectl port-forward -n ezscale svc/ezscale-website 8080:80 ``` ## Operations ```bash # Tail Horizon logs kubectl logs -n ezscale -l app.kubernetes.io/component=horizon -f # Run an artisan command in a one-off pod kubectl run -n ezscale --rm -it --image=git.ezscale.cloud/ezscale/website:app-v0.1.0 \ --env-from='[{"configMapRef":{"name":"ezscale-website-env"}},{"secretRef":{"name":"ezscale-website-secrets"}}]' \ artisan-shell -- bash # Force a Horizon rolling restart kubectl rollout restart deployment -n ezscale ezscale-website-horizon ``` ## What's NOT in this chart (intentionally) - Cloudflare Zero Trust for the admin panel — layered on later. - ExternalDNS records — DNS managed manually in Cloudflare Terraform. - Backup CronJob for the application database — covered by the existing `mariadb` instance's backup CronJob in `infrastructure/kubernetes/ezscale/mysql/`. - Backups for Storj uploads — Storj provides its own replication; per-bucket lifecycle policies live in the Storj console.