- Remove redundant manual setup (setup-vps.sh) - Ansible handles all VPS setup - Remove config templates directory - Ansible creates .env.production - Delete verbose DEPLOYMENT_SETUP.md - info consolidated into deployment/README.md - Rewrite deployment/README.md with clear 3-step workflow - Simplify deployment/ansible/README.md to quick reference - Reduce total documentation from 1159 lines to 225 lines The deployment process is now easier to understand: 1. Run Ansible playbook (one-time setup) 2. Push code to Gitea (auto-builds) 3. SSH and promote to production 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
MemberSyncPro Deployment
Simple deployment workflow using Ansible + Gitea Actions + Blue-Green deployment.
Overview
┌──────────────┐
│ 1. Ansible │ Run once to set up infrastructure
│ Setup │ (Gitea, PostgreSQL, Runners, Scripts)
└──────┬───────┘
│
┌──────▼───────┐
│ 2. Push Code │ Push to Gitea triggers automatic build
│ to Gitea │ and staging
└──────┬───────┘
│
┌──────▼───────┐
│ 3. Promote │ Manual promotion to production
│ to Prod │ (sudo promote.sh)
└──────────────┘
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
- Visit
http://your-vps-ip - Create admin account (first-time setup)
- Create repository:
membersyncpro
5. Add Git Remote
git remote add production http://your-vps-ip/your-username/membersyncpro.git
Daily Deployment Workflow
Deploy New Version
# 1. Push code
git push production main
# 2. Wait for build (check Gitea → Actions)
# - Builds binary
# - Runs tests
# - Stages to inactive slot (blue/green)
# 3. SSH to VPS and promote
ssh ubuntu@your-vps-ip
sudo /opt/api-artifacts/scripts/promote.sh
# Type 'yes' to confirm
# 4. Verify
curl http://localhost:8080/hello
curl http://localhost:8080/version
Rollback
If something goes wrong after promotion:
ssh ubuntu@your-vps-ip
sudo /opt/api-artifacts/scripts/rollback.sh
Instantly reverts to the previous deployment.
VPS Directory Structure
/opt/api-artifacts/
├── builds/
│ ├── abc123.../ # Previous build
│ └── def456.../ # Current 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
│ ├── rollback.sh # Emergency rollback
│ ├── health-check.sh # Health validation
│ └── migrate.sh # Database migrations
└── config/
└── .env.production # Environment variables
Common Tasks
View Service Logs
# Follow logs
journalctl -u api-server -f
# Last 100 lines
journalctl -u api-server -n 100
Check Active Deployment
# See which slot is active
readlink /opt/api-artifacts/current
# Full build path
readlink -f /opt/api-artifacts/current
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
- Current state:
current -> blue(active) - New push: Build creates
builds/new456/ - Staging:
green -> builds/new456/(inactive) - Migrations: Run on green slot
- Promotion:
current -> green(switch!) - Restart: Systemd loads from current
- 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 playbookansible/hosts.ini- Your VPS inventoryscripts/- 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:
- Package installation: Git, Nginx, PostgreSQL, Docker
- Gitea setup: Binary download, database, systemd service
- Database provisioning: Admin user, API server user, databases
- Runner deployment: 5 self-hosted runners with auto-registration
- API infrastructure: User, directories, scripts, systemd service
- Nginx configuration: Reverse proxy for Gitea
- 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.productionfile 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:
- Add SSL/TLS certificates (Let's Encrypt)
- Set up monitoring (Prometheus/Grafana)
- Configure alerting for deployment failures
- Automate PostgreSQL backups
- Add cron job to clean old builds