diff --git a/kubernetes/README.md b/kubernetes/README.md new file mode 100644 index 0000000..3cd5c0d --- /dev/null +++ b/kubernetes/README.md @@ -0,0 +1,46 @@ +# Rallly Kubernetes Manifests + +This directory contains base Kubernetes manifests to self-host Rallly. It separates configuration (ConfigMaps) from sensitive data (Secrets) and uses a StatefulSet for the PostgreSQL database. + +## Prerequisites + +- A Kubernetes cluster. +- `kubectl` configured to talk to your cluster. +- An Ingress Controller (e.g., NGINX) installed. + +## Configuration + +1. **Secrets (`secrets.yaml`):** + - **Important:** Do not commit the `secrets.yaml` file with real credentials to version control. + - Update `POSTGRES_PASSWORD` and `SECRET_PASSWORD` (use `openssl rand -hex 32` to generate). + - Update `DATABASE_URL` to match your postgres password. + +2. **Config (`rallly-config.yaml`):** + - Update `NEXT_PUBLIC_BASE_URL` to match your domain. + - Configure your SMTP settings for emails. + +3. **Ingress (`ingress.yaml`):** + - Change `host: rallly.example.com` to your actual domain. + - Ensure `ingressClassName` matches your cluster's controller (default is set to `nginx`). + +## Deployment Order + +Apply the manifests in the following order to ensure dependencies are met: + +```bash +# 1. Apply Secrets and Config first +kubectl apply -f secrets.yaml +kubectl apply -f rallly-config.yaml + +# 2. Apply Database (StatefulSet) +kubectl apply -f postgres.yaml + +# 3. Apply Application (Deployment) +kubectl apply -f rallly.yaml + +# 4. Apply Ingress +kubectl apply -f ingress.yaml + +# 5. Check that the pods are running - should show '1/1 Running' for each pod. +kubectl get pods +``` diff --git a/kubernetes/README.pdf b/kubernetes/README.pdf new file mode 100644 index 0000000..8e44d78 Binary files /dev/null and b/kubernetes/README.pdf differ diff --git a/kubernetes/ingress.yaml b/kubernetes/ingress.yaml new file mode 100644 index 0000000..da28f3b --- /dev/null +++ b/kubernetes/ingress.yaml @@ -0,0 +1,30 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: rallly + namespace: default + annotations: + # Example for cert-manager (uncomment if using) + # cert-manager.io/cluster-issuer: letsencrypt-prod + + # Example for NGINX ingress controller size limit + # nginx.ingress.kubernetes.io/proxy-body-size: "10m" +spec: + # NOTE: Explicitly set to 'nginx'. Remove this line if using a different Ingress Controller + # or if you wish to use the cluster default. + ingressClassName: nginx + rules: + - host: rallly.example.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: rallly + port: + number: 80 + tls: + - hosts: + - rallly.example.com + secretName: rallly-tls diff --git a/kubernetes/postgres.yaml b/kubernetes/postgres.yaml new file mode 100644 index 0000000..f6bc0e7 --- /dev/null +++ b/kubernetes/postgres.yaml @@ -0,0 +1,97 @@ +apiVersion: v1 +kind: Service +metadata: + name: postgres + namespace: default +spec: + ports: + - port: 5432 + selector: + app: postgres +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: postgres + namespace: default +spec: + selector: + matchLabels: + app: postgres + serviceName: "postgres" + replicas: 1 + template: + metadata: + labels: + app: postgres + spec: + securityContext: + # Run as standard Postgres user (UID 999) + fsGroup: 999 + runAsNonRoot: true + runAsUser: 999 + containers: + - name: postgres + # Switched to 14-alpine to align with official docker-compose + image: postgres:14-alpine + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + env: + - name: POSTGRES_USER + valueFrom: + secretKeyRef: + name: rallly-secrets + key: POSTGRES_USER + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: rallly-secrets + key: POSTGRES_PASSWORD + - name: POSTGRES_DB + value: rallly + # Fix: Point PGDATA to a generic subpath to avoid mount errors (lost+found) + - name: PGDATA + value: /var/lib/postgresql/data/pgdata + ports: + - containerPort: 5432 + name: postgres + # Health Probes + livenessProbe: + exec: + command: + - /bin/sh + - -c + # Uses env var and adds timeout to prevent hanging + - pg_isready -U $POSTGRES_USER -t 5 + initialDelaySeconds: 30 + periodSeconds: 10 + readinessProbe: + exec: + command: + - /bin/sh + - -c + # Uses env var and adds timeout to prevent hanging + - pg_isready -U $POSTGRES_USER -t 5 + initialDelaySeconds: 10 + periodSeconds: 5 + volumeMounts: + - name: postgres-data + mountPath: /var/lib/postgresql/data + resources: + limits: + cpu: "2" + memory: 2Gi + requests: + cpu: 500m + memory: 1Gi + volumeClaimTemplates: + - metadata: + name: postgres-data + spec: + accessModes: ["ReadWriteOnce"] + resources: + requests: + storage: 1Gi diff --git a/kubernetes/rallly-config.yaml b/kubernetes/rallly-config.yaml new file mode 100644 index 0000000..edd96e2 --- /dev/null +++ b/kubernetes/rallly-config.yaml @@ -0,0 +1,21 @@ +# kubernetes/rallly-config.yaml +# Stores all non-secret configuration variables. +apiVersion: v1 +kind: ConfigMap +metadata: + name: rallly-config + namespace: default +data: + # Base URL for the application (must match Ingress host) + NEXT_PUBLIC_BASE_URL: "https://rallly.example.com" + + # Email Settings + SUPPORT_EMAIL: "admin@example.com" + EMAIL_LOGIN_ENABLED: "true" + # SECURITY: This allows ANY email to register. Restrict to "*@example.com" or specific emails for production. + ALLOWED_EMAILS: "*" + + # SMTP Settings (Credentials will be in the Secret file) + SMTP_HOST: "smtp.example.com" + SMTP_PORT: "587" + SMTP_SECURE: "false" diff --git a/kubernetes/rallly.yaml b/kubernetes/rallly.yaml new file mode 100644 index 0000000..4f76352 --- /dev/null +++ b/kubernetes/rallly.yaml @@ -0,0 +1,125 @@ +apiVersion: v1 +kind: Service +metadata: + name: rallly + namespace: default +spec: + selector: + app: rallly + ports: + - protocol: TCP + port: 80 + targetPort: 3000 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: rallly + namespace: default + labels: + app: rallly +spec: + # Note: For production, use replicas: 2 or more with a PodDisruptionBudget for HA. + replicas: 1 + selector: + matchLabels: + app: rallly + strategy: + type: RollingUpdate + template: + metadata: + labels: + app: rallly + spec: + securityContext: + fsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: rallly + # Pinned version for stability and reproducibility + image: lukevella/rallly:v4.5.4 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 3000 + name: http + protocol: TCP + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + env: + # 1. Configuration (from ConfigMap) + - name: NEXT_PUBLIC_BASE_URL + valueFrom: + configMapKeyRef: + name: rallly-config + key: NEXT_PUBLIC_BASE_URL + - name: SUPPORT_EMAIL + valueFrom: + configMapKeyRef: + name: rallly-config + key: SUPPORT_EMAIL + - name: ALLOWED_EMAILS + valueFrom: + configMapKeyRef: + name: rallly-config + key: ALLOWED_EMAILS + - name: EMAIL_LOGIN_ENABLED + valueFrom: + configMapKeyRef: + name: rallly-config + key: EMAIL_LOGIN_ENABLED + - name: SMTP_HOST + valueFrom: + configMapKeyRef: + name: rallly-config + key: SMTP_HOST + - name: SMTP_PORT + valueFrom: + configMapKeyRef: + name: rallly-config + key: SMTP_PORT + - name: SMTP_SECURE + valueFrom: + configMapKeyRef: + name: rallly-config + key: SMTP_SECURE + # 2. Secrets (from Secret) + - name: DATABASE_URL + valueFrom: + secretKeyRef: + name: rallly-secrets + key: DATABASE_URL + - name: SECRET_PASSWORD + valueFrom: + secretKeyRef: + name: rallly-secrets + key: SECRET_PASSWORD + - name: INITIAL_ADMIN_EMAIL + valueFrom: + secretKeyRef: + name: rallly-secrets + key: INITIAL_ADMIN_EMAIL + resources: + limits: + cpu: "1" + memory: 1Gi + requests: + cpu: 200m + memory: 512Mi + + livenessProbe: + httpGet: + path: / + port: 3000 + initialDelaySeconds: 30 + periodSeconds: 10 + readinessProbe: + httpGet: + path: / + port: 3000 + # Reduced delay so the pod becomes ready faster once running + initialDelaySeconds: 10 + periodSeconds: 5 diff --git a/kubernetes/secrets.yaml b/kubernetes/secrets.yaml new file mode 100644 index 0000000..193729f --- /dev/null +++ b/kubernetes/secrets.yaml @@ -0,0 +1,24 @@ +# kubernetes/secrets.yaml +# WARNING: This file uses 'stringData' for demonstration. +# For production, DO NOT commit this file to Git. +# Use SealedSecrets, ExternalSecrets, or manually create the secret on the cluster. +apiVersion: v1 +kind: Secret +metadata: + name: rallly-secrets + namespace: default +type: Opaque +stringData: + # Database Connection String (postgres://user:password@service:port/db_name) + # IMPORTANT: The username/password here MUST match POSTGRES_USER/POSTGRES_PASSWORD below. + DATABASE_URL: "postgres://rallly:CHANGE_ME_PASSWORD@postgres:5432/rallly" + + # Random string for session encryption (generate with 'openssl rand -hex 32') + SECRET_PASSWORD: "CHANGE_ME_TO_A_LONG_RANDOM_STRING" + + # The email of the first admin user + INITIAL_ADMIN_EMAIL: "admin@example.com" + + # Database Credentials (used by the postgres StatefulSet) + POSTGRES_USER: "rallly" + POSTGRES_PASSWORD: "CHANGE_ME_PASSWORD"