diff --git a/apps/nextgen/.dockerignore b/apps/nextgen/.dockerignore index 3e57544..140fa36 100644 --- a/apps/nextgen/.dockerignore +++ b/apps/nextgen/.dockerignore @@ -1,10 +1,28 @@ -node_modules -dist -.env +**/node_modules +client/dist +data/ +uploads/ +server/uploads/ *.db *.db-shm *.db-wal *.sqlite -.mavis -outputs -.DS_Store +*.log +.git +.gitea +.harness +.claude +.opencode +.worktrees +.env +docs/ +evidence/ +demo/ +sql/ +scripts/ +*.md +tea.exe +client/test-results +client/playwright-report +client/blob-report + diff --git a/apps/nextgen/Dockerfile b/apps/nextgen/Dockerfile index abc59cf..08bc64f 100644 --- a/apps/nextgen/Dockerfile +++ b/apps/nextgen/Dockerfile @@ -1,31 +1,49 @@ -# Stage 1: Build the client +# syntax=docker/dockerfile:1 + +# ---------- Stage 1: build the React/Vite client ---------- FROM node:20-alpine AS client-build WORKDIR /app/client COPY client/package*.json ./ -RUN npm install -COPY client/ ./ +RUN npm ci +COPY client/ . RUN npm run build -# Stage 2: Setup server and production image -FROM node:20-alpine +# ---------- Stage 2: install production server deps ---------- +FROM node:20-slim AS server-deps +RUN apt-get update && apt-get install -y --no-install-recommends python3 make g++ \ + && rm -rf /var/lib/apt/lists/* +WORKDIR /app/server +COPY server/package*.json ./ +RUN npm ci --omit=dev + +# ---------- Stage 3: runtime image ---------- +FROM node:20-slim +RUN apt-get update && apt-get install -y --no-install-recommends tini && rm -rf /var/lib/apt/lists/* WORKDIR /app -RUN apk add --no-cache python3 make g++ +ENV NODE_ENV=production \ + PORT=3001 \ + DB_PATH=/app/data/school.db -# Copy server code -COPY server/package*.json ./server/ -WORKDIR /app/server -RUN npm install --production -COPY server/ ./ +# Server source + production node_modules +COPY server/ ./server/ +COPY --from=server-deps /app/server/node_modules ./server/node_modules -# Copy built client -COPY --from=client-build /app/client/dist /app/client/dist +# Built client, served statically by Express server +COPY --from=client-build /app/client/dist ./client/dist + +COPY docker-entrypoint.sh ./docker-entrypoint.sh +RUN chmod +x docker-entrypoint.sh \ + && mkdir -p /app/data /app/server/uploads \ + && groupadd -g 1001 appgroup && useradd -u 1001 -g appgroup -s /bin/sh -m appuser \ + && chown -R appuser:appgroup /app + +USER appuser -# Expose port (using 3001 as that's what the server defaults to) EXPOSE 3001 -# The server listens on 3001 by default, or PORT env -ENV PORT=3001 -ENV NODE_ENV=production +HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \ + CMD node -e "require('http').get('http://localhost:3001/api/health',r=>{process.exit(r.statusCode===200?0:1)}).on('error',()=>process.exit(1))" -CMD ["sh", "-c", "mkdir -p ../data && node src/database/init.js && node src/index.js"] \ No newline at end of file +ENTRYPOINT ["/usr/bin/tini", "--"] +CMD ["./docker-entrypoint.sh"] \ No newline at end of file diff --git a/apps/nextgen/docker-compose.yml b/apps/nextgen/docker-compose.yml index cf85ae1..dd20bce 100644 --- a/apps/nextgen/docker-compose.yml +++ b/apps/nextgen/docker-compose.yml @@ -2,24 +2,27 @@ version: '3.8' services: app: - image: node:20-alpine - working_dir: /app + build: . + image: nextgen-school:latest ports: - - "3000:3000" - "3001:3001" + environment: + NODE_ENV: production + PORT: 3001 + DB_PATH: /app/data/school.db + JWT_SECRET: ${JWT_SECRET:-africa-alert-production-secret-2024} + ALLOWED_ORIGINS: ${ALLOWED_ORIGINS:-https://next-gen.techarvest.co.zw,http://localhost:3001} + ALLOW_CLIENT_OFFLINE_MINT: ${ALLOW_CLIENT_OFFLINE_MINT:-false} + env_file: + - path: .env + required: false volumes: - - .:/app - ./data:/app/data - ./uploads:/app/server/uploads - environment: - - NODE_ENV=production - - PORT=3000 - - DB_PATH=/app/data/school.db - - JWT_SECRET=africa-alert-production-secret-2024 - command: > - sh -c " - cd server && node src/database/init.js && - node src/index.js & - cd ../client && npx serve -s dist -l 3000 - " - restart: unless-stopped \ No newline at end of file + restart: unless-stopped + healthcheck: + test: ["CMD", "node", "-e", "require('http').get('http://localhost:3001/api/health',r=>{process.exit(r.statusCode===200?0:1)}).on('error',()=>process.exit(1))"] + interval: 30s + timeout: 5s + retries: 3 + start_period: 15s \ No newline at end of file diff --git a/apps/nextgen/docker-entrypoint.sh b/apps/nextgen/docker-entrypoint.sh index e5f0485..8572b3f 100644 --- a/apps/nextgen/docker-entrypoint.sh +++ b/apps/nextgen/docker-entrypoint.sh @@ -1,29 +1,12 @@ #!/bin/sh set -e -echo "Starting Africa Alert School Management System..." +echo "Starting NextGen School Management System..." -# Initialize database if not exists -if [ ! -f "data/school.db" ]; then - echo "Initializing database..." - cd server && node src/database/init.js && cd .. -fi +cd /app/server -# Start API server in background -echo "Starting API server on port 3001..." -cd server && node src/index.js & -API_PID=$! +echo "Applying database migrations (idempotent)..." +node src/database/init.js -# Wait for API to be ready -sleep 2 - -# Start frontend in background -echo "Starting frontend on port 3000..." -cd ../client && npx serve -s dist -l 3000 & -FRONTEND_PID=$! - -# Handle shutdown gracefully -trap "kill $API_PID $FRONTEND_PID 2>/dev/null" EXIT - -# Keep container running -wait \ No newline at end of file +echo "Starting server on port ${PORT:-3001}..." +exec node src/index.js \ No newline at end of file diff --git a/k8s/nextgen/50-nextgen-pvc.yaml b/k8s/nextgen/50-nextgen-pvc.yaml new file mode 100644 index 0000000..615b5a5 --- /dev/null +++ b/k8s/nextgen/50-nextgen-pvc.yaml @@ -0,0 +1,23 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: nextgen-data-pvc + namespace: nextgen +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 5Gi +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: nextgen-uploads-pvc + namespace: nextgen +spec: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 10Gi diff --git a/k8s/nextgen/kustomization.yaml b/k8s/nextgen/kustomization.yaml index 660dd14..1018795 100644 --- a/k8s/nextgen/kustomization.yaml +++ b/k8s/nextgen/kustomization.yaml @@ -7,8 +7,10 @@ resources: - 20-mattermost.yaml - 30-vaultwarden.yaml - 45-infisical-secrets.yaml + - 50-nextgen-pvc.yaml - supabase-nextgen-credentials.yaml - supabase-nextgen.yaml - supabase-nextgen-ingress.yaml + diff --git a/k8s/overlays/production/kustomization.yaml b/k8s/overlays/production/kustomization.yaml new file mode 100644 index 0000000..c887cd4 --- /dev/null +++ b/k8s/overlays/production/kustomization.yaml @@ -0,0 +1,12 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: nextgen + +resources: + - ../../nextgen + +namePrefix: prod- + +patchesStrategicMerge: + - patch-env.yaml diff --git a/k8s/overlays/production/patch-env.yaml b/k8s/overlays/production/patch-env.yaml new file mode 100644 index 0000000..538cd06 --- /dev/null +++ b/k8s/overlays/production/patch-env.yaml @@ -0,0 +1,16 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: next-gen-main + namespace: nextgen +spec: + replicas: 2 + template: + spec: + containers: + - name: app + env: + - name: NODE_ENV + value: "production" + - name: ALLOWED_ORIGINS + value: "https://next-gen.techarvest.co.zw,https://main.next-gen.techarvest.co.zw" diff --git a/k8s/overlays/staging/kustomization.yaml b/k8s/overlays/staging/kustomization.yaml new file mode 100644 index 0000000..a82dd5c --- /dev/null +++ b/k8s/overlays/staging/kustomization.yaml @@ -0,0 +1,12 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: nextgen + +resources: + - ../../nextgen + +namePrefix: staging- + +patchesStrategicMerge: + - patch-env.yaml diff --git a/k8s/overlays/staging/patch-env.yaml b/k8s/overlays/staging/patch-env.yaml new file mode 100644 index 0000000..eb10dd4 --- /dev/null +++ b/k8s/overlays/staging/patch-env.yaml @@ -0,0 +1,16 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: next-gen-dev + namespace: nextgen +spec: + replicas: 1 + template: + spec: + containers: + - name: app + env: + - name: NODE_ENV + value: "staging" + - name: ALLOWED_ORIGINS + value: "https://staging.next-gen.techarvest.co.zw,http://localhost:3000"