GitHub Actions Build & Deploy Guide (GHCR)

Last updated: February 11, 2026
Admin Tools

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:

  1. GitHub Actions detects push to main
  2. Builds API image (AMD64, ~15-20 min)
  3. Builds Web image (AMD64, ~20 min)
  4. Pushes both images to GHCR
  5. 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:latest
    • ghcr.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:

  1. Pulls latest images from GHCR (~1-2 min)
  2. Validates Docker Compose configuration
  3. Stops old containers
  4. Starts new containers with fresh images
  5. Waits for health checks (30 seconds)
  6. 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:

  1. Stops current containers
  2. Pulls specified version from GHCR (or uses cached image)
  3. Starts containers with old version
  4. 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:

  1. Go to: https://github.com/Nostoi/sampo/actions
  2. Look for workflow: "Build and Push to GHCR"
  3. 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:

  1. TypeScript errors

    • Fix locally: pnpm type-check
    • Commit fix, push again
  2. Docker build fails

    • Check Dockerfile syntax
    • Verify dependencies in package.json
    • Check Docker build logs in GitHub Actions
  3. 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:

  1. VPS disk full

    # Clean old images
    ssh root@23.235.204.208 'docker system prune -a -f'
    
  2. Network issues

    • Wait 5 minutes, try again
    • GHCR may be temporarily unavailable
  3. 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-api and sampo-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-check passes
  • [ ] pnpm test passes
  • [ ] 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

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:

  1. Check troubleshooting section above
  2. Review GitHub Actions logs
  3. Check VPS status and logs
  4. Contact DevOps team

Emergency (site down):

  1. Rollback immediately: ./deploy.sh blueline --rollback previous
  2. Verify site is back up
  3. Investigate root cause
  4. 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)

Was this article helpful?

Your feedback helps us improve our support content.

Still need assistance?

Our support team is ready to help you with more complex issues.

Contact Support