mirror of
https://github.com/lukevella/rallly-selfhosted.git
synced 2025-12-10 10:52:50 +01:00
Compare commits
No commits in common. "ccb6dc4c0ed9dc9fca72916720499eb8ceadbcaf" and "297e04f69bcd32f311ed3928ef3141389b701bc9" have entirely different histories.
ccb6dc4c0e
...
297e04f69b
@ -1,86 +0,0 @@
|
|||||||
# 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. Consider adding `secrets.yaml` to your `.gitignore` file to prevent accidental commits.
|
|
||||||
- Update `POSTGRES_PASSWORD` and `SECRET_PASSWORD` (use `openssl rand -hex 32` to generate).
|
|
||||||
- **Critical:** Ensure the password in `DATABASE_URL` matches `POSTGRES_PASSWORD`. Both must use the same value.
|
|
||||||
- **Format:** The `DATABASE_URL` format should look like this: `postgres://<user>:<password>@<postgres-service-name>:5432/<db-name>`.
|
|
||||||
|
|
||||||
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`).
|
|
||||||
- **TLS:**
|
|
||||||
- **Option 1 (Manual):** Create a TLS Secret: `kubectl create secret tls rallly-tls --cert=path/to/cert --key=path/to/key`
|
|
||||||
- **Option 2 (cert-manager):** See comments in `ingress.yaml` for automatic certificate provisioning setup.
|
|
||||||
|
|
||||||
## 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
|
|
||||||
|
|
||||||
# Wait for database to be ready
|
|
||||||
kubectl wait --for=condition=ready pod -l app=postgres --timeout=300s
|
|
||||||
|
|
||||||
# 3. Apply Application (Deployment)
|
|
||||||
kubectl apply -f rallly.yaml
|
|
||||||
|
|
||||||
# 4. Apply Ingress
|
|
||||||
kubectl apply -f ingress.yaml
|
|
||||||
```
|
|
||||||
|
|
||||||
**Note:** If you update `secrets.yaml` or `rallly-config.yaml` _after_ deployment, you must restart the Rallly pods for changes to take effect:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
kubectl rollout restart deployment rallly
|
|
||||||
```
|
|
||||||
|
|
||||||
This performs a **rolling restart**, so there will be no downtime. However, ensure the new configuration is valid; if pods fail to start, check the logs with `kubectl logs -f deployment/rallly`.
|
|
||||||
|
|
||||||
**Note:** This assumes your Deployment has multiple replicas. If running a single Rallly instance (1 replica), there will be brief downtime during the restart.
|
|
||||||
|
|
||||||
## Verification
|
|
||||||
|
|
||||||
Check that the pods are running:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
kubectl get pods
|
|
||||||
```
|
|
||||||
|
|
||||||
The Postgres pod should show `1/1 Running` and the Rallly pod should eventually show `1/1 Running` once the liveness probe passes.
|
|
||||||
|
|
||||||
## Notes on Storage
|
|
||||||
|
|
||||||
The PostgreSQL StatefulSet requests a 1Gi PersistentVolume. Ensure your cluster has a default StorageClass configured, or update the `volumeClaimTemplates` in `postgres.yaml` to specify a StorageClass. If no StorageClass is available, the PersistentVolumeClaim will remain pending and the postgres pod will not start. Check your cluster's available StorageClasses with `kubectl get storageclass`.
|
|
||||||
|
|
||||||
**Quick check:** Run `kubectl get storageclass` before deployment. If the output is empty, ask your cluster administrator to configure a default StorageClass, or update `postgres.yaml` to reference an existing one.
|
|
||||||
|
|
||||||
## Notes on Backups
|
|
||||||
|
|
||||||
For production deployments, implement regular PostgreSQL backups. Consider using:
|
|
||||||
|
|
||||||
- Kubernetes-native backup tools (e.g., Velero)
|
|
||||||
- Scheduled pg_dump jobs within the cluster
|
|
||||||
- Cloud-provider managed backups (if using managed K8s)
|
|
||||||
|
|
||||||
Refer to your cluster provider's backup documentation for recommendations.
|
|
||||||
@ -1,34 +0,0 @@
|
|||||||
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
|
|
||||||
# Note: This Secret must be created separately. Options:
|
|
||||||
# 1. Use cert-manager (uncomment annotation above) to auto-provision
|
|
||||||
# 2. Manually create: kubectl create secret tls rallly-tls --cert=path/to/cert --key=path/to/key
|
|
||||||
# 3. Use an existing cluster-issued certificate secret
|
|
||||||
@ -1,97 +0,0 @@
|
|||||||
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
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
# 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
|
|
||||||
# Note: This is distinct from INITIAL_ADMIN_EMAIL (defined in secrets), which creates the admin account.
|
|
||||||
# SUPPORT_EMAIL is the user-facing support contact shown to users.
|
|
||||||
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"
|
|
||||||
@ -1,129 +0,0 @@
|
|||||||
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
|
|
||||||
# Zero-downtime deployment strategy
|
|
||||||
rollingUpdate:
|
|
||||||
maxUnavailable: 0
|
|
||||||
maxSurge: 1
|
|
||||||
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
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
# 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"
|
|
||||||
Loading…
x
Reference in New Issue
Block a user