240 lines
6.1 KiB
Markdown
240 lines
6.1 KiB
Markdown
# Soroban Abacus Flashcards - Production Deployment System
|
|
|
|
## Overview
|
|
|
|
The Soroban Abacus Flashcards application is deployed to production at `https://abaci.one` using a blue-green deployment strategy with zero-downtime updates.
|
|
|
|
## Architecture
|
|
|
|
```
|
|
User Request → Cloudflare → abaci.one (DDNS) → Synology NAS → Traefik → Docker (Blue + Green)
|
|
```
|
|
|
|
### Components
|
|
|
|
1. **Source**: Monorepo with pnpm workspaces and Turborepo
|
|
2. **CI/CD**: GitHub Actions builds and pushes Docker images
|
|
3. **Registry**: GitHub Container Registry (ghcr.io)
|
|
4. **Auto-Deploy**: compose-updater detects new images
|
|
5. **Load Balancing**: Traefik routes to healthy containers
|
|
6. **Reverse Proxy**: Traefik with Let's Encrypt SSL
|
|
7. **DNS**: Porkbun DDNS for dynamic IP updates
|
|
|
|
## Blue-Green Deployment
|
|
|
|
Two containers (`abaci-blue` and `abaci-green`) run simultaneously:
|
|
|
|
- **Shared resources**: Both containers mount the same data volumes
|
|
- **Health checks**: Traefik only routes to healthy containers
|
|
- **Zero downtime**: When one container restarts, the other serves traffic
|
|
- **Automatic updates**: compose-updater pulls new images and restarts containers
|
|
|
|
### How It Works
|
|
|
|
```
|
|
1. Push to main branch
|
|
↓
|
|
2. GitHub Actions builds and pushes Docker image to ghcr.io
|
|
↓
|
|
3. compose-updater detects new image (checks every 5 minutes)
|
|
↓
|
|
4. compose-updater restarts containers one at a time
|
|
↓
|
|
5. Traefik health checks ensure traffic only goes to ready containers
|
|
```
|
|
|
|
### Health Check Endpoint
|
|
|
|
The `/api/health` endpoint verifies container readiness:
|
|
|
|
```bash
|
|
curl https://abaci.one/api/health
|
|
```
|
|
|
|
Response:
|
|
|
|
```json
|
|
{
|
|
"status": "healthy",
|
|
"timestamp": "2025-01-14T12:00:00.000Z",
|
|
"checks": {
|
|
"database": {
|
|
"status": "ok",
|
|
"latencyMs": 2
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
- Returns `200 OK` when healthy
|
|
- Returns `503 Service Unavailable` when unhealthy
|
|
- Traefik uses this to determine if a container should receive traffic
|
|
|
|
## Deployment Process
|
|
|
|
### Automatic Deployment
|
|
|
|
When code is pushed to the `main` branch:
|
|
|
|
1. **Build Phase** (GitHub Actions):
|
|
- Multi-stage Docker build
|
|
- Image pushed to `ghcr.io/antialias/soroban-abacus-flashcards`
|
|
|
|
2. **Deploy Phase** (compose-updater on NAS):
|
|
- Detects new image within 5 minutes
|
|
- Pulls new image
|
|
- Restarts containers (one at a time)
|
|
- Traefik routes traffic to healthy containers
|
|
|
|
### Manual Deployment
|
|
|
|
```bash
|
|
# From local machine (with SSH access to NAS)
|
|
./nas-deployment/deploy.sh
|
|
```
|
|
|
|
This script handles migration from the old single-container setup to blue-green.
|
|
|
|
## File Structure
|
|
|
|
```
|
|
/
|
|
├── Dockerfile # Multi-stage build configuration
|
|
├── .github/workflows/deploy.yml # CI/CD pipeline (build + push)
|
|
├── apps/web/
|
|
│ └── src/app/api/health/route.ts # Health check endpoint
|
|
├── nas-deployment/
|
|
│ ├── docker-compose.yaml # Blue-green container config
|
|
│ ├── deploy.sh # Manual deployment/migration script
|
|
│ └── .env # Environment variables (not committed)
|
|
└── DEPLOYMENT.md # This documentation
|
|
```
|
|
|
|
## Docker Compose Configuration
|
|
|
|
Both containers share the same volumes and Traefik service:
|
|
|
|
```yaml
|
|
services:
|
|
blue:
|
|
image: ghcr.io/antialias/soroban-abacus-flashcards:latest
|
|
container_name: abaci-blue
|
|
volumes:
|
|
- ./data:/app/apps/web/data # Shared database
|
|
- ./uploads:/app/uploads # Shared uploads
|
|
healthcheck:
|
|
test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"]
|
|
labels:
|
|
- "traefik.http.services.abaci.loadbalancer.server.port=3000"
|
|
- "traefik.http.services.abaci.loadbalancer.healthcheck.path=/api/health"
|
|
|
|
green:
|
|
# Same configuration as blue, shares volumes
|
|
```
|
|
|
|
Traefik automatically load balances between both containers, routing only to healthy ones.
|
|
|
|
## Monitoring and Maintenance
|
|
|
|
### Check Deployment Status
|
|
|
|
```bash
|
|
# Check running containers
|
|
ssh nas.home.network "docker ps | grep abaci"
|
|
|
|
# Check health of both containers
|
|
curl https://abaci.one/api/health
|
|
|
|
# Check compose-updater logs
|
|
ssh nas.home.network "docker logs --tail 50 compose-updater"
|
|
```
|
|
|
|
### View Logs
|
|
|
|
```bash
|
|
# Blue container logs
|
|
ssh nas.home.network "docker logs -f abaci-blue"
|
|
|
|
# Green container logs
|
|
ssh nas.home.network "docker logs -f abaci-green"
|
|
```
|
|
|
|
### Force Immediate Update
|
|
|
|
```bash
|
|
# Restart compose-updater to trigger immediate check
|
|
ssh nas.home.network "cd /volume1/homes/antialias/projects/abaci.one && docker-compose -f docker-compose.updater.yaml restart"
|
|
|
|
# Or manually pull and restart
|
|
ssh nas.home.network "cd /volume1/homes/antialias/projects/abaci.one && docker-compose pull && docker-compose up -d"
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
### Health Check Failing
|
|
|
|
1. Check container logs:
|
|
|
|
```bash
|
|
ssh nas.home.network "docker logs abaci-blue"
|
|
```
|
|
|
|
2. Test health endpoint manually:
|
|
|
|
```bash
|
|
ssh nas.home.network "docker exec abaci-blue curl -sf http://localhost:3000/api/health"
|
|
```
|
|
|
|
3. Check database connectivity
|
|
|
|
### Container Not Updating
|
|
|
|
1. Verify GitHub Actions completed successfully
|
|
2. Check compose-updater is running:
|
|
```bash
|
|
ssh nas.home.network "docker ps | grep compose-updater"
|
|
```
|
|
3. Check compose-updater logs for errors
|
|
|
|
### Both Containers Unhealthy
|
|
|
|
```bash
|
|
# Force restart both
|
|
ssh nas.home.network "cd /volume1/homes/antialias/projects/abaci.one && docker-compose restart"
|
|
```
|
|
|
|
## Migration from Single Container
|
|
|
|
If upgrading from the old single-container setup:
|
|
|
|
```bash
|
|
./nas-deployment/deploy.sh
|
|
```
|
|
|
|
This script will:
|
|
|
|
1. Stop the old `soroban-abacus-flashcards` container
|
|
2. Stop compose-updater temporarily
|
|
3. Deploy the new docker-compose.yaml
|
|
4. Start both blue and green containers
|
|
5. Restart compose-updater
|
|
|
|
## Security
|
|
|
|
- Non-root user in Docker container
|
|
- Minimal Alpine Linux base image
|
|
- GitHub Container Registry with token authentication
|
|
- Traefik handles SSL termination
|
|
- Health check doesn't expose sensitive data
|
|
|
|
## Performance
|
|
|
|
- Zero-downtime deployments via load balancing
|
|
- Health checks prevent routing to unhealthy containers
|
|
- Both containers share data - no sync needed
|
|
- SQLite WAL mode handles concurrent access
|
|
|
|
---
|
|
|
|
_Last updated: January 2025_
|