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