soroban-abacus-flashcards/DEPLOYMENT.md

6.1 KiB

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:

curl https://abaci.one/api/health

Response:

{
  "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

# 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:

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

# 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

# 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

# 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:

    ssh nas.home.network "docker logs abaci-blue"
    
  2. Test health endpoint manually:

    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:
    ssh nas.home.network "docker ps | grep compose-updater"
    
  3. Check compose-updater logs for errors

Both Containers Unhealthy

# 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:

./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