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
- Source: Monorepo with pnpm workspaces and Turborepo
- CI/CD: GitHub Actions builds and pushes Docker images
- Registry: GitHub Container Registry (ghcr.io)
- Auto-Deploy: compose-updater detects new images
- Load Balancing: Traefik routes to healthy containers
- Reverse Proxy: Traefik with Let's Encrypt SSL
- 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 OKwhen healthy - Returns
503 Service Unavailablewhen unhealthy - Traefik uses this to determine if a container should receive traffic
Deployment Process
Automatic Deployment
When code is pushed to the main branch:
-
Build Phase (GitHub Actions):
- Multi-stage Docker build
- Image pushed to
ghcr.io/antialias/soroban-abacus-flashcards
-
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
-
Check container logs:
ssh nas.home.network "docker logs abaci-blue" -
Test health endpoint manually:
ssh nas.home.network "docker exec abaci-blue curl -sf http://localhost:3000/api/health" -
Check database connectivity
Container Not Updating
- Verify GitHub Actions completed successfully
- Check compose-updater is running:
ssh nas.home.network "docker ps | grep compose-updater" - 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:
- Stop the old
soroban-abacus-flashcardscontainer - Stop compose-updater temporarily
- Deploy the new docker-compose.yaml
- Start both blue and green containers
- 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