Farid Siddiqi a1360919f0
Some checks failed
Build and Deploy API Server & Frontend / deploy (push) Blocked by required conditions
Build and Deploy API Server & Frontend / build-backend (push) Has been cancelled
Build and Deploy API Server & Frontend / build-frontend (push) Has been cancelled
Add frontend React dashboard with CI/CD deployment pipeline
2025-12-25 22:08:03 -08:00

9.0 KiB

NitroKite Deployment

Simple deployment workflow using Ansible + Gitea Actions + Blue-Green deployment.

Overview

┌──────────────┐
│ 1. Ansible   │  Run once to set up infrastructure
│   Setup      │  (Gitea, PostgreSQL, Runners, Nginx, Scripts)
└──────┬───────┘
       │
┌──────▼───────┐
│ 2. Push Code │  Push to Gitea triggers automatic build
│   to Gitea   │  - Backend (Go API)
│              │  - Frontend (React App)
└──────┬───────┘
       │
┌──────▼───────┐
│ 3. Promote   │  Manual promotion to production
│   to Prod    │  (sudo promote.sh <commit-sha>)
└──────────────┘

Architecture

Deployed Applications

  1. Marketing Website - Static Astro site

    • Domain: https://nitrokite.com
    • Directory: /var/www/html/
    • Deployment: Manual via Ansible synchronize task
  2. Frontend App - React TypeScript dashboard

    • Domain: https://app.nitrokite.com
    • Directory: /var/www/app/
    • Deployment: Automated via Gitea Actions → staging → promotion
    • Features: TanStack Query, Router, Forms, shadcn/ui
  3. Backend API - Go Gin server

    • Domain: https://api.nitrokite.com
    • Directory: /opt/api-artifacts/current/
    • Deployment: Blue-Green with systemd service
    • Database: PostgreSQL with separate user/database
  4. Gitea - Git hosting + CI/CD

One-Time Setup

1. Prepare Ansible Inventory

cd deployment/ansible
cp hosts.ini.example hosts.ini

Edit hosts.ini:

[git]
your-vps-ip ansible_user=ubuntu ansible_ssh_private_key_file=~/.ssh/your-key.pem ansible_python_interpreter=/usr/bin/python3

2. Set Database Passwords

export POSTGRES_ADMIN_PASSWORD="your-strong-password"
export API_SERVER_DB_PASSWORD="your-api-db-password"

3. Run Ansible Playbook

cd deployment/ansible

# Test connection
ansible -i hosts.ini git -m ping

# Deploy everything
ansible-playbook -i hosts.ini site.yml

This sets up:

  • Gitea server (accessible at http://your-vps-ip)
  • PostgreSQL databases (gitea + apiserver)
  • 5 Gitea Actions runners
  • Nginx reverse proxy
  • API server deployment infrastructure at /opt/api-artifacts/
  • Systemd service for the API server

4. Create Gitea Repository

  1. Visit http://your-vps-ip
  2. Create admin account (first-time setup)
  3. Create repository: membersyncpro

5. Add Git Remote

git remote add production http://your-vps-ip/your-username/nitrokite.git

Daily Deployment Workflow

Deploy New Version

# 1. Push code (triggers both backend & frontend builds)
git push production main

# 2. Wait for build (check Gitea → Actions)
#    - Builds Go backend binary
#    - Builds React frontend bundle
#    - Stages backend to inactive slot (blue/green)
#    - Stages frontend to /opt/frontend-artifacts/staging/<commit-sha>

# 3. SSH to VPS and promote (pass the commit SHA)
ssh ubuntu@your-vps-ip
sudo /opt/api-artifacts/scripts/promote.sh <commit-sha>
# Type 'yes' to confirm

# This will:
# - Switch backend to new version (systemd reload)
# - Deploy frontend to /var/www/app/ (nginx serves it)
# - Run health checks
# - Backup previous versions

# 4. Verify
curl https://api.nitrokite.com/hello
curl https://api.nitrokite.com/version
curl https://app.nitrokite.com  # Frontend should load

Note: Get the commit SHA from the Gitea Actions output or via git rev-parse HEAD

Rollback

If something goes wrong after promotion:

ssh ubuntu@your-vps-ip
sudo /opt/api-artifacts/scripts/rollback.sh

Instantly reverts the backend to the previous deployment. For frontend, restore from backup:

# List backups
ls -l /opt/frontend-artifacts/backups/

# Restore specific backup
sudo rm -rf /var/www/app/*
sudo cp -r /opt/frontend-artifacts/backups/<timestamp>/* /var/www/app/
sudo chown -R www-data:www-data /var/www/app

VPS Directory Structure

/opt/api-artifacts/
├── builds/
│   ├── abc123.../              # Previous backend build
│   └── def456.../              # Current backend build
├── blue -> builds/abc123/      # Previous deployment
├── green -> builds/def456/     # Current deployment
├── current -> green            # Active (systemd uses this)
├── scripts/
│   ├── deploy.sh               # Auto-called by CI
│   ├── promote.sh              # Manual promotion (now handles frontend)
│   ├── rollback.sh             # Emergency rollback
│   ├── health-check.sh         # Health validation
│   └── migrate.sh              # Database migrations
└── config/
    └── .env.production         # Environment variables

/opt/frontend-artifacts/
├── staging/
│   ├── abc123.../              # Built frontend for commit abc123
│   └── def456.../              # Built frontend for commit def456
└── backups/
    └── 20251225_120000/        # Backup of previous frontend

/var/www/
├── html/                       # Marketing website (nitrokite.com)
└── app/                        # Frontend app (app.nitrokite.com)

Common Tasks

View Service Logs

# Backend API logs
journalctl -u api-server -f

# Nginx logs (frontend & marketing)
tail -f /var/log/nginx/access.log
tail -f /var/log/nginx/error.log

# Last 100 lines
journalctl -u api-server -n 100

Check Active Deployment

# Backend: See which slot is active
readlink /opt/api-artifacts/current

# Backend: Full build path
readlink -f /opt/api-artifacts/current

# Frontend: Check current files
ls -lh /var/www/app/

# Frontend: Check staging builds available
ls -l /opt/frontend-artifacts/staging/

Check Runners

# All runners
systemctl status gitea-runner-{1..5}

# Individual runner logs
journalctl -u gitea-runner-1 -f

List All Builds

ls -lh /opt/api-artifacts/builds/

Clean Old Builds

# Keep only last 10 builds
cd /opt/api-artifacts/builds
ls -t | tail -n +11 | xargs -I {} sudo rm -rf {}

Configuration

Environment Variables (on VPS)

Edit /opt/api-artifacts/config/.env.production:

DATABASE_URL=postgresql://apiserver:PASSWORD@localhost:5432/apiserver?sslmode=disable
AUTO_MIGRATE=false
PORT=8080
GIN_MODE=release

Ansible Variables

Edit deployment/ansible/site.yml:

vars:
  create_separate_apiserver_db: true  # Separate vs shared DB
  runner_count: 5                     # Number of runners
  gitea_version: "1.23.3"            # Gitea version

Blue-Green Deployment Flow

  1. Current state: current -> blue (active)
  2. New push: Build creates builds/new456/
  3. Staging: green -> builds/new456/ (inactive)
  4. Migrations: Run on green slot
  5. Promotion: current -> green (switch!)
  6. Restart: Systemd loads from current
  7. Rollback available: Blue slot still has previous build

Troubleshooting

Build Not Triggering

# Check runners
systemctl status gitea-runner-1

# Runner logs
journalctl -u gitea-runner-1 -f

Deployment Fails

# Check deploy script logs
journalctl -u api-server -n 50

# Verify permissions
ls -la /opt/api-artifacts/

# Check database connection
cat /opt/api-artifacts/config/.env.production

Service Won't Start

# Service logs
journalctl -u api-server -n 50

# Verify binary exists
ls -la /opt/api-artifacts/current/api-server

# Check symlinks
readlink /opt/api-artifacts/current
readlink /opt/api-artifacts/blue
readlink /opt/api-artifacts/green

Files Reference

  • ansible/site.yml - Complete infrastructure setup playbook
  • ansible/hosts.ini - Your VPS inventory
  • scripts/ - Deployment scripts (copied to VPS by Ansible)
  • systemd/api-server.service - Service configuration
  • .gitea/workflows/build-deploy.yml - CI/CD workflow

Ansible Playbook Details

The site.yml playbook handles:

  1. Package installation: Git, Nginx, PostgreSQL, Docker
  2. Gitea setup: Binary download, database, systemd service
  3. Database provisioning: Admin user, API server user, databases
  4. Runner deployment: 5 self-hosted runners with auto-registration
  5. API infrastructure: User, directories, scripts, systemd service
  6. Nginx configuration: Reverse proxy for Gitea
  7. Firewall: UFW with SSH, HTTP, PostgreSQL ports

Run it multiple times safely (idempotent).

Security Notes

  • Change default passwords (use environment variables)
  • Use SSH keys for VPS access
  • Keep .env.production file permissions at 600
  • Review UFW firewall rules
  • Consider adding SSL/TLS with Let's Encrypt
  • PostgreSQL is exposed on 0.0.0.0 - restrict if not needed externally

Next Steps

After basic deployment:

  1. Add SSL/TLS certificates (Let's Encrypt)
  2. Set up monitoring (Prometheus/Grafana)
  3. Configure alerting for deployment failures
  4. Automate PostgreSQL backups
  5. Add cron job to clean old builds