GitHub Actions Build & Deploy Guide (GHCR)
Overview
The deployment system uses GitHub Actions to build Docker images and GitHub Container Registry (GHCR) to store them. This is 4x faster than the old Mac→VPS method.
Key Benefits:
- ⚡ 4x faster (~21 min total vs 45-60 min old method)
- 🔔 Email notifications when builds complete
- 🏗️ Native AMD64 builds (no emulation needed)
- 📦 Centralized registry (GHCR stores all versions)
- 🔄 Easy rollback (pull any previous version)
How It Works
You push code GitHub Actions GHCR stores You deploy
to main branch → builds AMD64 images → images centrally → to VPS via SSH
(1s) (~20 min) (instant) (~1 min)
Total time: ~21 minutes (vs 45-60 min old method)
Standard Deployment (3 Steps)
Step 1: Push to Main Branch
# Make your changes
git add .
git commit -m "feat: your changes here"
git push origin main
What happens automatically:
- GitHub Actions detects push to main
- Builds API image (AMD64, ~15-20 min)
- Builds Web image (AMD64, ~20 min)
- Pushes both images to GHCR
- Sends email notification to siteadmin@theblueline.com
You don't need to do anything during this step - go get coffee! ☕
Step 2: Wait for Email Notification
Subject: ✅ Sampo Deployment: blueline built successfully
Email contains:
- Deployment ID:
blueline - Build time: ~20 minutes
- Image names:
ghcr.io/nostoi/sampo-blueline-api:latestghcr.io/nostoi/sampo-blueline-web:latest
- Links to GitHub Actions run
If you don't receive email within 25 minutes:
# Check workflow status
gh run list --workflow=build-and-push.yml --limit 3
# View latest run
gh run view --log-failed
Step 3: Deploy to VPS
Once you receive the email, deploy the new images:
ssh root@23.235.204.208 /opt/sampo-blueline-alpha/deploy.sh blueline
What this script does:
- Pulls latest images from GHCR (~1-2 min)
- Validates Docker Compose configuration
- Stops old containers
- Starts new containers with fresh images
- Waits for health checks (30 seconds)
- Reports container status
Expected output:
[2026-02-11 15:57:29] 📥 Pulling latest images for deployment: blueline
[2026-02-11 15:57:29] 📦 Pulling API image: ghcr.io/nostoi/sampo-blueline-api:latest
[2026-02-11 15:57:30] 📦 Pulling Web image: ghcr.io/nostoi/sampo-blueline-web:latest
[2026-02-11 15:57:30] 🔍 Validating Docker Compose configuration...
[2026-02-11 15:57:34] 🚀 Starting containers with new images...
[2026-02-11 15:58:52] ✅ Deployment successful!
[2026-02-11 15:58:52] 📊 Running containers:
blueline-alpha-api Up 41 seconds (healthy)
blueline-alpha-web Up 10 seconds (healthy)
blueline-alpha-db Up About a minute (healthy)
Total deployment time: ~1 minute
Verification
Quick Health Check
# Check API health
curl https://alpha.theblueline.com/health | jq '.status'
# Expected: "ok"
# Check Web homepage
curl -I https://alpha.theblueline.com/
# Expected: HTTP/1.1 200 OK
Complete Verification
# 1. Container status
ssh root@23.235.204.208 'docker ps --filter "name=blueline-alpha" --format "✓ {{.Names}}: {{.Status}}"'
# Expected:
# ✓ blueline-alpha-api: Up X minutes (healthy)
# ✓ blueline-alpha-web: Up X minutes (healthy)
# ✓ blueline-alpha-db: Up X minutes (healthy)
# ✓ blueline-alpha-redis: Up X minutes (healthy)
# 2. Deployment ID verification
ssh root@23.235.204.208 'docker exec blueline-alpha-api printenv DEPLOYMENT_ID'
# Expected: blueline
# 3. Config files exist
ssh root@23.235.204.208 'docker exec blueline-alpha-api ls -lh /app/deployments/blueline/app.json'
# Expected: Shows file size (~6.6K)
ssh root@23.235.204.208 'docker exec blueline-alpha-web ls -lh /app/apps/web/public/deployments/blueline.json'
# Expected: Shows file size (~6.2K)
Understanding Image Names
⚠️ IMPORTANT: Deployment ID ≠ Environment Name
Deployment ID: blueline
↓ (from deployments/blueline/app.json)
↓
Image names: sampo-blueline-api, sampo-blueline-web
↓ (GHCR registry names)
↓
Environment: alpha.theblueline.com
↓ (staging environment URL)
↓
Container names: blueline-alpha-api, blueline-alpha-web
↓ (combines deployment + environment)
Why this matters:
- GitHub Actions builds images as
sampo-blueline-*(deployment ID) - VPS runs containers as
blueline-alpha-*(deployment + environment) - Both point to the same deployment config:
/app/deployments/blueline/app.json
Rollback Procedure
Quick Rollback (Previous Version)
ssh root@23.235.204.208 /opt/sampo-blueline-alpha/deploy.sh blueline --rollback previous
Rollback to Specific Version
# 1. Find previous commit SHA
git log --oneline -5
# Example output:
# a4c6788 (HEAD -> main) feat: add new feature
# 3b5e012 fix: resolve bug
# 2c4f901 chore: update dependencies
# 2. Deploy specific version (use first 7 chars of commit SHA)
ssh root@23.235.204.208 /opt/sampo-blueline-alpha/deploy.sh blueline --rollback sha-3b5e012
How rollback works:
- Stops current containers
- Pulls specified version from GHCR (or uses cached image)
- Starts containers with old version
- Verifies health
Time to rollback: <5 minutes
Database safety: Rollback does NOT revert database changes (migrations remain applied)
Monitoring Deployment Progress
View GitHub Actions Status
Browser:
- Go to: https://github.com/Nostoi/sampo/actions
- Look for workflow: "Build and Push to GHCR"
- Click latest run to see progress
Command Line:
# List recent runs
gh run list --workflow=build-and-push.yml --limit 3
# Watch current run in real-time
gh run watch
# View logs of specific run
gh run view <RUN_ID> --log
Build Stages
Typical GitHub Actions run:
1. Checkout code (10 seconds)
2. Set up Docker Buildx (5 seconds)
3. Log in to GHCR (3 seconds)
4. Build API image (15-20 minutes)
├─ Install dependencies (5 min)
├─ Compile TypeScript (3 min)
├─ Generate Prisma client (2 min)
└─ Create Docker layers (5-10 min)
5. Build Web image (15-20 minutes)
├─ Install dependencies (5 min)
├─ Build Next.js app (8-12 min)
└─ Create Docker layers (2-3 min)
6. Push to GHCR (1-2 minutes)
7. Send email notification (5 seconds)
Total: ~20 minutes (both images build in parallel)
Check VPS Deployment Logs
# View last deployment log
ssh root@23.235.204.208 'tail -50 /var/log/sampo/deploy-blueline.log'
# Follow deployment in real-time
ssh root@23.235.204.208 'tail -f /var/log/sampo/deploy-blueline.log'
# Check container logs
ssh root@23.235.204.208 'docker logs blueline-alpha-api --tail 50'
ssh root@23.235.204.208 'docker logs blueline-alpha-web --tail 50'
Troubleshooting
Build Failed on GitHub Actions
Symptom: Email not received, or workflow shows red X
Check:
# View failed run
gh run list --workflow=build-and-push.yml --limit 1
gh run view --log-failed
Common causes:
-
TypeScript errors
- Fix locally:
pnpm type-check - Commit fix, push again
- Fix locally:
-
Docker build fails
- Check Dockerfile syntax
- Verify dependencies in package.json
- Check Docker build logs in GitHub Actions
-
GHCR authentication failed
- Contact DevOps (GitHub PAT may need renewal)
Deployment Failed on VPS
Symptom: Deploy script shows error
Check:
# VPS is reachable
ssh root@23.235.204.208 'echo ok'
# Disk space available
ssh root@23.235.204.208 'df -h'
# Docker is running
ssh root@23.235.204.208 'docker ps'
Common causes:
-
VPS disk full
# Clean old images ssh root@23.235.204.208 'docker system prune -a -f' -
Network issues
- Wait 5 minutes, try again
- GHCR may be temporarily unavailable
-
Container won't start
# Check logs ssh root@23.235.204.208 'docker logs blueline-alpha-api --tail 100'
Containers Running But Site Down
Symptom: Deployment succeeded, containers healthy, but site unreachable
Check:
# 1. Test API directly on VPS
ssh root@23.235.204.208 'curl http://localhost:3003/health'
# Should return: {"status":"ok",...}
# 2. Test Web directly on VPS
ssh root@23.235.204.208 'curl http://localhost:3006/'
# Should return: HTML content
# 3. Check Nginx is running
ssh root@23.235.204.208 'systemctl status nginx'
# Should show: Active: active (running)
# 4. Check Nginx configuration
ssh root@23.235.204.208 'nginx -t'
# Should show: configuration file is ok
If Nginx is down:
# Start Nginx
ssh root@23.235.204.208 'systemctl start nginx'
# Enable on boot
ssh root@23.235.204.208 'systemctl enable nginx'
# Check ports
ssh root@23.235.204.208 'lsof -i :80 -i :443'
Wrong Version Deployed
Symptom: Containers running old code
Verify:
# Check running images
ssh root@23.235.204.208 'docker ps --filter "name=blueline-alpha" --format "{{.Image}}"'
# Should show:
# ghcr.io/nostoi/sampo-blueline-api:latest
# ghcr.io/nostoi/sampo-blueline-web:latest
# Check image digest (unique identifier)
ssh root@23.235.204.208 'docker inspect ghcr.io/nostoi/sampo-blueline-api:latest | grep Id'
If wrong images:
# Force pull latest (ignore cache)
ssh root@23.235.204.208 'docker pull --no-cache ghcr.io/nostoi/sampo-blueline-api:latest'
ssh root@23.235.204.208 'docker pull --no-cache ghcr.io/nostoi/sampo-blueline-web:latest'
# Redeploy
ssh root@23.235.204.208 /opt/sampo-blueline-alpha/deploy.sh blueline
Advanced Operations
Manual Container Restart (Without Redeployment)
ssh root@23.235.204.208 << 'REMOTE'
cd /opt/sampo-alpha
# Stop containers
docker compose -f docker-compose.base.yml \
-f docker-compose.blueline.override.yml down
# Start containers (uses existing images)
docker compose -f docker-compose.base.yml \
-f docker-compose.blueline.override.yml up -d
# Wait for healthy
sleep 15
docker ps --filter "name=blueline-alpha"
REMOTE
View GHCR Package Information
Browser:
- https://github.com/Nostoi?tab=packages
- Look for:
sampo-blueline-apiandsampo-blueline-web
Command Line:
# List available versions (requires packages:read scope)
gh api /user/packages/container/sampo-blueline-api/versions
# Note: Default GitHub CLI may not have packages:read scope
# Contact DevOps if you need this access
Check VPS Image Cache
# List all Sampo images on VPS
ssh root@23.235.204.208 'docker images | grep sampo-blueline'
# Expected output:
# ghcr.io/nostoi/sampo-blueline-api latest abc123 X hours ago 2.7GB
# ghcr.io/nostoi/sampo-blueline-web latest def456 X hours ago 339MB
# Clean old images (keep only latest)
ssh root@23.235.204.208 'docker image prune -a -f --filter "label=deployment=blueline" --filter "until=24h"'
Performance Comparison
| Metric | Old Method (Mac→VPS) | New Method (GHCR) | Improvement | | ------------------ | --------------------------- | ------------------------------- | -------------------- | | Build location | Local Mac (ARM64 emulated) | GitHub Actions (native AMD64) | 50-70% faster builds | | Build time | 15-20 min | 7-10 min per image | ~50% faster | | Transfer | SCP tar file (~1.5GB) | Docker pull (~500MB compressed) | 3x faster | | Transfer time | 5-10 min | 1-2 min | 5x faster | | Manual steps | Build, save, transfer, load | Push to main, deploy | Fewer steps | | Notifications | None | Email on completion | Better visibility | | Total time | 45-60 min | 21 min | 4x faster |
Deployment Checklist
Before Pushing to Main
- [ ] Changes tested locally
- [ ]
pnpm type-checkpasses - [ ]
pnpm testpasses - [ ] Changes committed with descriptive message
- [ ] Team notified (if major change)
After GitHub Actions Completes
- [ ] Email notification received
- [ ] Email shows "success" for both API and Web
- [ ] Ready to deploy (no peak traffic, maintenance window OK)
During VPS Deployment
- [ ] Deploy script completes without errors
- [ ] All containers show "healthy" status
- [ ] No errors in deployment log
After Deployment
- [ ] Health check returns 200:
curl https://alpha.theblueline.com/health - [ ] Web homepage loads:
curl https://alpha.theblueline.com/ - [ ] Test critical workflows in browser
- [ ] Monitor logs for 5-10 minutes:
docker logs blueline-alpha-api -f - [ ] Document deployment (time, version, any issues)
Best Practices
When to Deploy
✅ Good times:
- Off-peak hours (early morning, late evening)
- After team communication
- During scheduled maintenance windows
- After thorough local testing
❌ Avoid:
- Peak traffic hours (10am-4pm)
- Fridays after 3pm (limited weekend support)
- Without rollback plan ready
- Without testing changes locally first
Email Notification Settings
Current recipients:
- siteadmin@theblueline.com
To add yourself:
- Contact DevOps to add your email to
.github/workflows/build-and-push.yml
Maintaining GHCR
Images are automatically:
- Tagged with
:latest(most recent) - Tagged with
:sha-<commit>(specific version) - Retained indefinitely (until manually deleted)
To clean up old images (DevOps only):
- Use GitHub Packages UI
- Delete versions older than 30 days
- Keep at least last 10 versions for rollback
Port Reference
VPS Container Ports
| Service | Internal Port | External Port | Access | | -------- | ------------- | -------------- | --------------- | | Database | 5432 | 127.0.0.1:5436 | Localhost only | | Redis | 6379 | 127.0.0.1:6382 | Localhost only | | API | 3001 | 0.0.0.0:3003 | Via Nginx proxy | | Web | 3000 | 0.0.0.0:3006 | Via Nginx proxy |
Public URLs (via Nginx)
- Web: https://alpha.theblueline.com/
- API: https://alpha.theblueline.com/api/
- Health: https://alpha.theblueline.com/health
Quick Reference
Most Common Commands
# Check GitHub Actions status
gh run list --workflow=build-and-push.yml --limit 3
# Watch build progress
gh run watch
# Deploy after build completes
ssh root@23.235.204.208 /opt/sampo-blueline-alpha/deploy.sh blueline
# Verify deployment
curl https://alpha.theblueline.com/health | jq '.status'
# Rollback to previous version
ssh root@23.235.204.208 /opt/sampo-blueline-alpha/deploy.sh blueline --rollback previous
# Check container status
ssh root@23.235.204.208 'docker ps --filter "name=blueline-alpha"'
# View container logs
ssh root@23.235.204.208 'docker logs blueline-alpha-api --tail 50'
Related Documentation
Help Articles
- Deployment Overview - System architecture and features
- Troubleshooting Guide - Detailed issue resolution
- Build Optimization - Performance tuning
Technical Docs (Repository)
- Complete guide:
docs/GHCR_DEPLOYMENT_GUIDE.md - Quick reference:
docs/GHCR_QUICK_REFERENCE.md - Success report:
docs/GHCR_DEPLOYMENT_SUCCESS.md - Operations guide:
docs/operations/docker-deployment-quick-reference.md
Support
For deployment issues:
- Check troubleshooting section above
- Review GitHub Actions logs
- Check VPS status and logs
- Contact DevOps team
Emergency (site down):
- Rollback immediately:
./deploy.sh blueline --rollback previous - Verify site is back up
- Investigate root cause
- Document in post-mortem
Contact:
- DevOps Team: siteadmin@theblueline.com
- Repository: https://github.com/Nostoi/sampo
- GitHub Actions: https://github.com/Nostoi/sampo/actions
- GHCR Packages: https://github.com/Nostoi?tab=packages
Last updated: 2026-02-11 (GHCR deployment migration)