Deployment
GreekManage ships in three environments, each defined by a different set of files.
| Env | Defined in | Purpose |
|---|---|---|
| Local dev | docker-compose.yml | Hot-reload dev on a laptop |
| E2E | docker-compose.e2e.yml | Isolated stack for Playwright runs |
| Staging | docker-compose.staging.yml + Cloudflare Tunnel | Production-like, pre-prod gate |
| MiniPC | docker-compose.minipc.yml | On-prem deployment for embedded systems |
| Production | k8s/ manifests | Customer-facing |
Production topology
Manifests
All in k8s/:
| File | Resource |
|---|---|
namespace.yaml | greekmanage namespace |
postgres-deployment.yaml + postgres-pvc.yaml + postgres-service.yaml | Postgres 17 + pgvector, 10 Gi PVC, ClusterIP service |
redis-deployment.yaml + redis-service.yaml | Redis 7 (emptyDir, no persistence — ephemeral cache only) |
backend-deployment.yaml + backend-service.yaml | Django + Daphne, 2 replicas |
frontend-deployment.yaml + frontend-service.yaml | Nginx + static SPA, 2 replicas, read-only rootfs |
ingress.yaml | nginx-ingress with cert-manager annotations |
configmap.yaml | Non-secret env (allowed hosts, log level, etc.) |
secret.yaml | Sealed / external-secrets references (raw secrets templated for examples) |
network-policies.yaml | Pod-to-pod traffic restriction |
Resource limits
| Pod | CPU req | CPU lim | Mem req | Mem lim |
|---|---|---|---|---|
| backend | 200m | 500m | 256Mi | 512Mi |
| frontend | 100m | 250m | 128Mi | 256Mi |
| postgres | 200m | 500m | 256Mi | 512Mi |
| redis | 100m | 250m | 128Mi | 256Mi |
Tune for your tenant size; defaults work for a single national org of ~200 chapters.
Probes
readinessProbe:
httpGet: { path: /api/health/, port: 8000 }
initialDelaySeconds: 10
periodSeconds: 5
livenessProbe:
httpGet: { path: /api/health/, port: 8000 }
initialDelaySeconds: 15
periodSeconds: 10
Backend exposes /api/health/ returning 200 if DB and Redis are reachable.
Network policies
- Backend: ingress from frontend + celery; egress to postgres + redis + external HTTPS (LLM, payments, email)
- Postgres: ingress from backend + celery only
- Redis: ingress from backend + celery + beat only
- Frontend: ingress from world (via ingress controller); egress to backend + DNS
Apply
kubectl apply -f k8s/namespace.yaml
kubectl apply -f k8s/postgres-pvc.yaml
kubectl apply -f k8s/postgres-deployment.yaml
kubectl apply -f k8s/postgres-service.yaml
kubectl apply -f k8s/redis-deployment.yaml
kubectl apply -f k8s/redis-service.yaml
kubectl apply -f k8s/configmap.yaml
kubectl apply -f k8s/secret.yaml
kubectl apply -f k8s/backend-deployment.yaml
kubectl apply -f k8s/backend-service.yaml
kubectl apply -f k8s/frontend-deployment.yaml
kubectl apply -f k8s/frontend-service.yaml
kubectl apply -f k8s/ingress.yaml
kubectl apply -f k8s/network-policies.yaml
For repeated deploys, wrap in a Helm chart or Kustomize overlay (not yet checked into the repo — open issue #TBD).
Migration on deploy
Run as a one-off Job before pod rollout:
apiVersion: batch/v1
kind: Job
metadata:
name: migrate
namespace: greekmanage
spec:
template:
spec:
containers:
- name: migrate
image: greekmanage-backend:0.57.0
command: ["python", "manage.py", "migrate", "--noinput"]
envFrom:
- configMapRef: { name: greekmanage-config }
- secretRef: { name: greekmanage-secrets }
restartPolicy: OnFailure
Apply, wait, then patch the backend Deployment to the new image tag.
Image building
Backend (backend/Dockerfile):
FROM python:3.13-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc libpq-dev xmlsec1 libreoffice-impress libreoffice-core
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . /app
WORKDIR /app
CMD ["gunicorn", "greekmanage.wsgi", "-b", "0.0.0.0:8000"]
Frontend (frontend/Dockerfile):
# Stage 1 — dev
FROM node:22-alpine AS dev
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
CMD ["npm", "run", "dev"]
# Stage 2 — build
FROM node:22-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
ARG VITE_API_URL
ENV VITE_API_URL=$VITE_API_URL
RUN npm run build
# Stage 3 — prod
FROM nginx:alpine AS production
COPY /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
Build:
docker build -t ghcr.io/<org>/greekmanage-backend:0.57.0 backend/
docker build -t ghcr.io/<org>/greekmanage-frontend:0.57.0 \
--target production --build-arg VITE_API_URL=https://app.greekmanage.com \
frontend/
docker push ghcr.io/<org>/greekmanage-backend:0.57.0
docker push ghcr.io/<org>/greekmanage-frontend:0.57.0
Staging environment
docker-compose.staging.yml + Cloudflare Tunnel:
- Separate Postgres on 5434, separate Redis DB 2
- Production-like images (no volume mounts, frozen at build)
- HTTPS via Cloudflare Tunnel (no public IP needed)
DJANGO_DEBUG=False- Suitable for E2E + manual QA of release candidates
Domain + TLS
- DNS: A record for
app.<customer>.com→ ingress controller's external IP / load balancer - TLS: cert-manager + Let's Encrypt via HTTP-01 challenge automatically
- WebSocket support: nginx-ingress passes the
/wspath through to backend Daphne
Storage configuration
Per-org StorageConfig model points to the bucket the org uses. Most orgs use the platform-managed bucket; enterprise customers can configure their own.
→ Storage configuration (org admin)
Backups
celery-backups deployment runs the backups Celery queue. Two scheduled tasks:
- Daily: incremental DB snapshot to S3
- Weekly: full DB snapshot to S3 (retained 1 year)
→ Backups & export (platform admin)
Disaster recovery
- Backup: continuous (Postgres point-in-time) + daily snapshot + weekly archive
- Replication: cross-region read replica recommended (not in default manifests)
- Monitoring: alarms on backup failures, replication lag, certificate expiry
- Runbook: stored outside GreekManage (so it's accessible when the platform is down)
- Test: quarterly restore-to-staging exercise
Mobile app distribution
- Capacitor 8 wraps the same React frontend
- Fastlane automates TestFlight + Play Store uploads from
frontend/ios/andfrontend/android/ - Native config in those directories (Firebase, etc.)
- App icons updated separately from web branding (requires native rebuild + store re-submission)
Observability
Currently minimal — see Observability for the gap analysis and roadmap.