Deployment Guide

Complete guide for deploying B12 SIS to production environments.

Architecture Overview

┌─────────────────────────────────────────────────────────────┐
│                     Load Balancer                           │
│                    (HTTPS Termination)                      │
└─────────────────────┬───────────────────────────────────────┘
                      │
        ┌─────────────┴─────────────┐
        ▼                           ▼
┌───────────────┐           ┌───────────────┐
│   Frontend    │           │   Backend     │
│   (Nginx)     │──────────▶│   (Go/Gin)    │
│   Port 80     │           │   Port 8080   │
└───────────────┘           └───────┬───────┘
                                    │
                    ┌───────────────┼───────────────┐
                    ▼               ▼               ▼
             ┌──────────┐    ┌──────────┐    ┌──────────┐
             │  MySQL   │    │  Redis   │    │   S3     │
             │ Database │    │  Cache   │    │ Storage  │
             └──────────┘    └──────────┘    └──────────┘

Prerequisites

  • Docker & Docker Compose

  • Kubernetes cluster (for k8s deployment)

  • Helm 3+ (for k8s deployment)

  • MySQL 8.0+

  • Redis (optional, for caching)

  • S3-compatible storage (AWS S3, MinIO)

Environment Configuration

Backend Environment Variables

Create .env file in b12-backend/:

# Server
PORT=8080
GIN_MODE=release

# Database
DB_HOST=mysql
DB_PORT=3306
DB_USER=b12
DB_PASSWORD=secure_password
DB_NAME=b12_sis

# JWT Authentication
JWT_SECRET=your-secure-jwt-secret-min-32-chars
JWT_ACCESS_EXPIRY=15m
JWT_REFRESH_EXPIRY=7d

# S3 Storage
AWS_ACCESS_KEY_ID=your-access-key
AWS_SECRET_ACCESS_KEY=your-secret-key
AWS_REGION=us-east-1
S3_BUCKET=b12-uploads
S3_ENDPOINT=https://s3.amazonaws.com

# CORS
ALLOWED_ORIGINS=https://sis.school.edu

# Email (optional)
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=noreply@school.edu
SMTP_PASSWORD=app-password

# Canvas LMS (optional)
CANVAS_API_URL=https://school.instructure.com
CANVAS_API_TOKEN=canvas-api-token

# LTI 1.3 (optional)
LTI_ISSUER=https://sis.school.edu
LTI_PLATFORM_ID=canvas.instructure.com
LTI_CLIENT_ID=your-client-id
LTI_DEPLOYMENT_ID=deployment-id
LTI_JWK_URL=https://sis.school.edu/.well-known/jwks.json
LTI_AUTH_URL=https://school.instructure.com/api/lti/authorize_redirect
LTI_TOKEN_URL=https://school.instructure.com/login/oauth2/token

Frontend Environment

Configure b12-frontend/src/environments/environment.prod.ts:

export const environment = {
  production: true,
  apiUrl: 'https://api.school.edu/api'
};

Docker Deployment

Backend Dockerfile

# b12-backend/Dockerfile
FROM golang:1.23-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o main ./cmd/api

FROM alpine:latest
RUN apk --no-cache add ca-certificates tzdata
WORKDIR /root/
COPY --from=builder /app/main .
COPY --from=builder /app/locales ./locales
EXPOSE 8080
CMD ["./main"]

Frontend Dockerfile

# b12-frontend/Dockerfile
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build:prod

FROM nginx:alpine
COPY --from=builder /app/dist/b12-frontend /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Frontend Nginx Config

# b12-frontend/nginx.conf
server {
    listen 80;
    server_name _;
    root /usr/share/nginx/html;
    index index.html;

    # Gzip compression
    gzip on;
    gzip_types text/plain text/css application/json application/javascript;

    # Angular routing
    location / {
        try_files $uri $uri/ /index.html;
    }

    # API proxy (if same domain)
    location /api {
        proxy_pass http://backend:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

    # Cache static assets
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
}

Docker Compose

# docker-compose.yml
version: '3.8'

services:
  frontend:
    build: ./b12-frontend
    ports:
      - "80:80"
    depends_on:
      - backend
    networks:
      - b12-network

  backend:
    build: ./b12-backend
    ports:
      - "8080:8080"
    env_file:
      - ./b12-backend/.env
    depends_on:
      - mysql
      - redis
    networks:
      - b12-network

  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: root_password
      MYSQL_DATABASE: b12_sis
      MYSQL_USER: b12
      MYSQL_PASSWORD: secure_password
    volumes:
      - mysql_data:/var/lib/mysql
    networks:
      - b12-network

  redis:
    image: redis:alpine
    networks:
      - b12-network

volumes:
  mysql_data:

networks:
  b12-network:

Deploy with Docker Compose

# Build and start
docker-compose up -d --build

# View logs
docker-compose logs -f

# Stop services
docker-compose down

Kubernetes Deployment

Directory Structure

k8s/
├── helm/
│   ├── b12-backend/
│   │   ├── Chart.yaml
│   │   ├── values.yaml
│   │   └── templates/
│   │       ├── deployment.yaml
│   │       ├── service.yaml
│   │       ├── configmap.yaml
│   │       └── secret.yaml
│   └── b12-frontend/
│       ├── Chart.yaml
│       ├── values.yaml
│       └── templates/
│           ├── deployment.yaml
│           ├── service.yaml
│           └── ingress.yaml

Backend Helm Values

# k8s/helm/b12-backend/values.yaml
replicaCount: 3

image:
  repository: your-registry/b12-backend
  tag: latest
  pullPolicy: Always

service:
  type: ClusterIP
  port: 8080

resources:
  limits:
    cpu: 500m
    memory: 512Mi
  requests:
    cpu: 100m
    memory: 128Mi

env:
  GIN_MODE: release
  DB_HOST: mysql.database.svc.cluster.local

secrets:
  JWT_SECRET: ""
  DB_PASSWORD: ""
  AWS_SECRET_ACCESS_KEY: ""

Frontend Helm Values

# k8s/helm/b12-frontend/values.yaml
replicaCount: 2

image:
  repository: your-registry/b12-frontend
  tag: latest
  pullPolicy: Always

service:
  type: ClusterIP
  port: 80

ingress:
  enabled: true
  className: nginx
  hosts:
    - host: sis.school.edu
      paths:
        - path: /
          pathType: Prefix
  tls:
    - secretName: sis-tls
      hosts:
        - sis.school.edu

resources:
  limits:
    cpu: 200m
    memory: 128Mi
  requests:
    cpu: 50m
    memory: 64Mi

Deploy with Helm

# Backend
cd b12-backend
make deploy
# Select environment when prompted

# Frontend
cd b12-frontend
make deploy
# Select environment when prompted

Manual Helm Commands

# Create namespace
kubectl create namespace b12-sis

# Deploy backend
helm upgrade --install b12-backend ./k8s/helm/b12-backend \
  --namespace b12-sis \
  --values ./k8s/helm/b12-backend/values-prod.yaml

# Deploy frontend
helm upgrade --install b12-frontend ./k8s/helm/b12-frontend \
  --namespace b12-sis \
  --values ./k8s/helm/b12-frontend/values-prod.yaml

Database Setup

Initial Migration

The backend automatically runs migrations on startup. For manual migration:

# Connect to backend container
kubectl exec -it deployment/b12-backend -n b12-sis -- sh

# Or with Docker
docker exec -it b12-backend sh

# Migrations run automatically, but can force:
./main migrate

Database Backup

# MySQL backup
mysqldump -h $DB_HOST -u $DB_USER -p$DB_PASSWORD $DB_NAME > backup.sql

# Kubernetes CronJob for automated backups
kubectl apply -f k8s/backup-cronjob.yaml

SSL/TLS Configuration

Using cert-manager

# k8s/certificate.yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: sis-tls
  namespace: b12-sis
spec:
  secretName: sis-tls
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
  dnsNames:
    - sis.school.edu
    - api.school.edu

Manual TLS Secret

kubectl create secret tls sis-tls \
  --cert=fullchain.pem \
  --key=privkey.pem \
  -n b12-sis

Health Checks

Backend Health Endpoint

# Health check
curl http://localhost:8080/api/health

# Response
{"status":"ok","database":"connected","redis":"connected"}

Kubernetes Probes

livenessProbe:
  httpGet:
    path: /api/health
    port: 8080
  initialDelaySeconds: 30
  periodSeconds: 10

readinessProbe:
  httpGet:
    path: /api/health
    port: 8080
  initialDelaySeconds: 5
  periodSeconds: 5

Monitoring & Logging

Application Logs

# Kubernetes logs
kubectl logs -f deployment/b12-backend -n b12-sis

# Docker logs
docker logs -f b12-backend

Metrics (Prometheus)

Backend exposes metrics at /metrics:

curl http://localhost:8080/metrics

Log Aggregation

Configure Fluentd/Filebeat to ship logs to Elasticsearch or similar.

Scaling

Horizontal Pod Autoscaler

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: b12-backend-hpa
  namespace: b12-sis
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: b12-backend
  minReplicas: 2
  maxReplicas: 10
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70

Database Scaling

For high availability:

  • Use MySQL replication (primary-replica)

  • Consider Amazon RDS or Google Cloud SQL

  • Implement read replicas for query distribution

Troubleshooting

Common Issues

Backend won’t start

# Check logs
kubectl logs deployment/b12-backend -n b12-sis

# Common causes:
# - Database connection failed
# - Missing environment variables
# - Port already in use

Frontend returns 404

# Ensure nginx config has try_files for Angular routing
location / {
    try_files $uri $uri/ /index.html;
}

CORS errors

# Check ALLOWED_ORIGINS in backend .env
ALLOWED_ORIGINS=https://sis.school.edu,https://www.school.edu

Database connection issues

# Test connectivity
kubectl run mysql-client --rm -it --image=mysql:8.0 -- \
  mysql -h mysql.database.svc.cluster.local -u b12 -p

Debug Mode

# Enable debug logging in backend
GIN_MODE=debug

# Check nginx config
docker exec b12-frontend nginx -t

Rollback

Kubernetes Rollback

# View history
kubectl rollout history deployment/b12-backend -n b12-sis

# Rollback to previous
kubectl rollout undo deployment/b12-backend -n b12-sis

# Rollback to specific revision
kubectl rollout undo deployment/b12-backend -n b12-sis --to-revision=2

Helm Rollback

# View history
helm history b12-backend -n b12-sis

# Rollback
helm rollback b12-backend 1 -n b12-sis

Security Checklist

  • Use HTTPS everywhere

  • Set secure JWT secret (32+ characters)

  • Configure CORS properly

  • Use Kubernetes secrets for sensitive data

  • Enable network policies

  • Regular security updates

  • Database backups encrypted

  • Access logs enabled

  • Rate limiting configured