1
0
mirror of https://github.com/lukevella/rallly-selfhosted.git synced 2025-12-10 02:42:49 +01:00

Compare commits

..

9 Commits

Author SHA1 Message Date
Luke Vella
ccb6dc4c0e
Merge pull request #30 from gab-dev-7/feature/k8s-base-manifests
Add official Kubernetes base manifests using ConfigMaps
2025-12-01 15:38:29 +00:00
Gabriel Windlin
dc0d543646 TLS, storage class, single replicas 2025-12-01 15:57:21 +01:00
Gabriel Windlin
3f8a67426e .gitignore reference 2025-12-01 15:50:51 +01:00
Gabriel Windlin
63d784f550 backup infos + wait command 2025-12-01 15:22:34 +01:00
Gabriel Windlin
ce86b19da3 expanded README to be more informative 2025-12-01 15:17:10 +01:00
Gabriel Windlin
c1ded052df fixed blank line, added more info to README, fixed spelling of a comment 2025-12-01 15:05:22 +01:00
Gabriel Windlin
6d47452796 fixes recognized by coderabbit 2025-11-28 14:48:57 +01:00
Gabriel Windlin
cd0b94cbe9 Addressing PR feedback: fix probes, image version, and add docs 2025-11-28 14:29:57 +01:00
Gabriel Windlin
1894faa7d2 Add official Kubernetes base manifests using ConfigMaps 2025-11-25 12:07:37 +01:00
6 changed files with 394 additions and 0 deletions

86
kubernetes/README.md Normal file
View File

@ -0,0 +1,86 @@
# 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.

34
kubernetes/ingress.yaml Normal file
View File

@ -0,0 +1,34 @@
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

97
kubernetes/postgres.yaml Normal file
View File

@ -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

View File

@ -0,0 +1,24 @@
# 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"

129
kubernetes/rallly.yaml Normal file
View File

@ -0,0 +1,129 @@
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

24
kubernetes/secrets.yaml Normal file
View File

@ -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"