Skip to main content

Deployment

GreekManage ships in three environments, each defined by a different set of files.

EnvDefined inPurpose
Local devdocker-compose.ymlHot-reload dev on a laptop
E2Edocker-compose.e2e.ymlIsolated stack for Playwright runs
Stagingdocker-compose.staging.yml + Cloudflare TunnelProduction-like, pre-prod gate
MiniPCdocker-compose.minipc.ymlOn-prem deployment for embedded systems
Productionk8s/ manifestsCustomer-facing

Production topology

Manifests

All in k8s/:

FileResource
namespace.yamlgreekmanage namespace
postgres-deployment.yaml + postgres-pvc.yaml + postgres-service.yamlPostgres 17 + pgvector, 10 Gi PVC, ClusterIP service
redis-deployment.yaml + redis-service.yamlRedis 7 (emptyDir, no persistence — ephemeral cache only)
backend-deployment.yaml + backend-service.yamlDjango + Daphne, 2 replicas
frontend-deployment.yaml + frontend-service.yamlNginx + static SPA, 2 replicas, read-only rootfs
ingress.yamlnginx-ingress with cert-manager annotations
configmap.yamlNon-secret env (allowed hosts, log level, etc.)
secret.yamlSealed / external-secrets references (raw secrets templated for examples)
network-policies.yamlPod-to-pod traffic restriction

Resource limits

PodCPU reqCPU limMem reqMem lim
backend200m500m256Mi512Mi
frontend100m250m128Mi256Mi
postgres200m500m256Mi512Mi
redis100m250m128Mi256Mi

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 --from=build /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 /ws path 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

  1. Backup: continuous (Postgres point-in-time) + daily snapshot + weekly archive
  2. Replication: cross-region read replica recommended (not in default manifests)
  3. Monitoring: alarms on backup failures, replication lag, certificate expiry
  4. Runbook: stored outside GreekManage (so it's accessible when the platform is down)
  5. 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/ and frontend/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.