Skip to content

Tutorial F: Production-Ready Cesivi Setup

Overview

This tutorial guides you through setting up a production-grade Cesivi Server with: - Authentication: OAuth2/OIDC with Azure AD or Keycloak - Storage: SQLite or PostgreSQL for persistence - Search: Lucene for full-text search - Security: HTTPS, proper secrets management - Monitoring: Health checks, metrics, logging - Platform: Docker with orchestration (Kubernetes-ready)

Use Case: Production deployment for QA environments, staging, or development infrastructure shared by teams.

Time Required: ~45 minutes


Prerequisites

  • Docker Desktop with Kubernetes enabled (or a K8s cluster)
  • kubectl configured
  • An OIDC provider (Azure AD, Keycloak, Okta, etc.)
  • SSL certificate (or use Let's Encrypt)

Architecture

                              ┌──────────────────┐
                              │   Load Balancer  │
                              │   (HTTPS :443)   │
                              └────────┬─────────┘
                                       │
              ┌────────────────────────┼────────────────────────┐
              │                        │                        │
     ┌────────┴────────┐      ┌────────┴────────┐     ┌────────┴────────┐
     │ Cesivi │      │ Cesivi │     │ Cesivi │
     │    Replica 1    │      │    Replica 2    │     │    Replica 3    │
     └────────┬────────┘      └────────┬────────┘     └────────┬────────┘
              │                        │                        │
              └────────────────────────┼────────────────────────┘
                                       │
                              ┌────────┴────────┐
                              │    PostgreSQL   │
                              │   (Persistent)  │
                              └─────────────────┘

Step 1: Create Namespace and Secrets

# Create namespace
kubectl create namespace cesivi

# Create secrets for database
kubectl create secret generic db-credentials \
  --namespace cesivi \
  --from-literal=POSTGRES_USER=sharepoint \
  --from-literal=POSTGRES_PASSWORD=$(openssl rand -base64 32)

# Create secret for OIDC
kubectl create secret generic oidc-config \
  --namespace cesivi \
  --from-literal=OIDC_CLIENT_ID=your-client-id \
  --from-literal=OIDC_CLIENT_SECRET=your-client-secret

# Create TLS secret (if you have certificates)
kubectl create secret tls sharepoint-tls \
  --namespace cesivi \
  --cert=path/to/tls.crt \
  --key=path/to/tls.key

Step 2: Create ConfigMap

Create configmap.yaml:

apiVersion: v1
kind: ConfigMap
metadata:
  name: cesivi-config
  namespace: cesivi
data:
  appsettings.Production.json: |
    {
      "Logging": {
        "LogLevel": {
          "Default": "Warning",
          "Cesivi": "Information"
        },
        "Console": {
          "FormatterName": "json"
        }
      },
      "Cesivi": {
        "HostName": "cesivi.example.com",
        "UseHttps": true,
        "HttpPort": 8080,
        "HttpsPort": 8443,

        "StorageProvider": "PostgreSQL",
        "PostgresConnectionString": "Host=postgres;Database=sharepoint;Username=${POSTGRES_USER};Password=${POSTGRES_PASSWORD}",

        "SearchEngine": "Lucene",
        "LuceneIndexPath": "/data/lucene-index",

        "Identity": {
          "Providers": {
            "OAuth2": {
              "Enabled": true,
              "Priority": 50,
              "Authority": "https://login.microsoftonline.com/your-tenant-id/v2.0",
              "Audience": "api://cesivi",
              "ValidateIssuer": true,
              "ValidateAudience": true,
              "ValidateLifetime": true,
              "ClockSkewMinutes": 5
            },
            "AcceptAll": {
              "Enabled": false
            }
          }
        },

        "Authentication": {
          "AcceptAllCredentials": false,
          "AllowAnonymous": false,
          "EnableNTLM": false,
          "EnableJWT": true,
          "EnableBasic": false
        },

        "Session": {
          "IdleTimeoutMinutes": 30,
          "MaxSessionCount": 5000,
          "MemoryPressureThresholdMB": 1024
        }
      }
    }

Apply:

kubectl apply -f configmap.yaml


Step 3: Deploy PostgreSQL

Create postgres.yaml:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgres-pvc
  namespace: cesivi
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: postgres
  namespace: cesivi
spec:
  replicas: 1
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
        - name: postgres
          image: postgres:15-alpine
          ports:
            - containerPort: 5432
          env:
            - name: POSTGRES_USER
              valueFrom:
                secretKeyRef:
                  name: db-credentials
                  key: POSTGRES_USER
            - name: POSTGRES_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: db-credentials
                  key: POSTGRES_PASSWORD
            - name: POSTGRES_DB
              value: sharepoint
          volumeMounts:
            - name: postgres-storage
              mountPath: /var/lib/postgresql/data
          resources:
            requests:
              memory: "256Mi"
              cpu: "250m"
            limits:
              memory: "512Mi"
              cpu: "500m"
      volumes:
        - name: postgres-storage
          persistentVolumeClaim:
            claimName: postgres-pvc
---
apiVersion: v1
kind: Service
metadata:
  name: postgres
  namespace: cesivi
spec:
  selector:
    app: postgres
  ports:
    - port: 5432
      targetPort: 5432

Apply:

kubectl apply -f postgres.yaml


Step 4: Deploy Cesivi

Create cesivi.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: cesivi
  namespace: cesivi
spec:
  replicas: 3
  selector:
    matchLabels:
      app: cesivi
  template:
    metadata:
      labels:
        app: cesivi
    spec:
      containers:
        - name: cesivi
          image: ghcr.io/your-org/cesivi:latest
          ports:
            - containerPort: 8080
              name: http
          env:
            - name: ASPNETCORE_ENVIRONMENT
              value: Production
            - name: POSTGRES_USER
              valueFrom:
                secretKeyRef:
                  name: db-credentials
                  key: POSTGRES_USER
            - name: POSTGRES_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: db-credentials
                  key: POSTGRES_PASSWORD
          volumeMounts:
            - name: config
              mountPath: /app/appsettings.Production.json
              subPath: appsettings.Production.json
            - name: lucene-index
              mountPath: /data/lucene-index
          resources:
            requests:
              memory: "512Mi"
              cpu: "500m"
            limits:
              memory: "1Gi"
              cpu: "1000m"
          livenessProbe:
            httpGet:
              path: /_vti_bin/health
              port: http
            initialDelaySeconds: 30
            periodSeconds: 10
          readinessProbe:
            httpGet:
              path: /_vti_bin/health
              port: http
            initialDelaySeconds: 5
            periodSeconds: 5
      volumes:
        - name: config
          configMap:
            name: cesivi-config
        - name: lucene-index
          emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
  name: cesivi
  namespace: cesivi
spec:
  selector:
    app: cesivi
  ports:
    - port: 80
      targetPort: 8080
      name: http
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: cesivi
  namespace: cesivi
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - cesivi.example.com
      secretName: sharepoint-tls
  rules:
    - host: cesivi.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: cesivi
                port:
                  number: 80

Apply:

kubectl apply -f cesivi.yaml


Step 5: Set Up Monitoring

Create monitoring.yaml:

apiVersion: v1
kind: ServiceMonitor
metadata:
  name: cesivi
  namespace: cesivi
  labels:
    release: prometheus
spec:
  selector:
    matchLabels:
      app: cesivi
  endpoints:
    - port: http
      path: /_metrics
      interval: 30s
---
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: cesivi-alerts
  namespace: cesivi
spec:
  groups:
    - name: cesivi
      rules:
        - alert: CesiviDown
          expr: up{job="cesivi"} == 0
          for: 5m
          labels:
            severity: critical
          annotations:
            summary: "Cesivi is down"

        - alert: CesiviHighMemory
          expr: container_memory_usage_bytes{container="cesivi"} > 900000000
          for: 10m
          labels:
            severity: warning
          annotations:
            summary: "Cesivi memory usage is high"

Step 6: Configure Azure AD (Example)

Register Application in Azure AD

  1. Go to Azure Portal → Azure Active Directory → App registrations
  2. Click "New registration"
  3. Name: "Cesivi"
  4. Supported account types: "Single tenant" or "Multitenant"
  5. Redirect URI: https://cesivi.example.com/signin-oidc

Configure API Permissions

  1. Add permission: Microsoft Graph → User.Read
  2. Grant admin consent

Create Client Secret

  1. Go to "Certificates & secrets"
  2. Create new client secret
  3. Copy the value (you won't see it again)

Update Configuration

# Update the secret
kubectl create secret generic oidc-config \
  --namespace cesivi \
  --from-literal=OIDC_CLIENT_ID=<application-id> \
  --from-literal=OIDC_CLIENT_SECRET=<client-secret> \
  --dry-run=client -o yaml | kubectl apply -f -

Step 7: Verify Deployment

# Check pods
kubectl get pods -n cesivi

# Check services
kubectl get svc -n cesivi

# Check ingress
kubectl get ingress -n cesivi

# View logs
kubectl logs -l app=cesivi -n cesivi

# Test health endpoint
curl https://cesivi.example.com/_vti_bin/health

Step 8: Test with OAuth2 Token

# Get token from Azure AD (example using MSAL)
TOKEN=$(az account get-access-token \
  --resource api://cesivi \
  --query accessToken -o tsv)

# Test API
curl https://cesivi.example.com/_api/web \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/json;odata=verbose"

Security Checklist

  • [ ] HTTPS enabled with valid certificate
  • [ ] OAuth2/OIDC configured with proper issuer validation
  • [ ] AcceptAll provider disabled
  • [ ] Anonymous access disabled
  • [ ] Database credentials in Kubernetes secrets
  • [ ] Resource limits configured
  • [ ] Network policies applied
  • [ ] Pod security policies enabled
  • [ ] Audit logging enabled
  • [ ] Backup strategy for PostgreSQL

Scaling

# Scale horizontally
kubectl scale deployment cesivi -n cesivi --replicas=5

# Configure HPA
kubectl autoscale deployment cesivi \
  -n cesivi \
  --min=3 --max=10 \
  --cpu-percent=70

Backup and Recovery

# Backup PostgreSQL
kubectl exec -n cesivi postgres-xxx -- \
  pg_dump -U sharepoint sharepoint > backup.sql

# Restore
kubectl exec -i -n cesivi postgres-xxx -- \
  psql -U sharepoint sharepoint < backup.sql

Summary

You have deployed a production-grade Cesivi Server with: - High Availability: 3 replicas with load balancing - Secure Auth: OAuth2/OIDC with Azure AD - Persistent Storage: PostgreSQL database - Full-Text Search: Lucene engine - Monitoring: Prometheus metrics and alerts - TLS: HTTPS with valid certificates


See Also