31 — Deployment¶
Server Architecture¶
PyroRadius runs on a single VPS. There is no load balancer, no staging environment, and no auto-scaling. All production traffic goes directly to this server.
| Component | Details |
|---|---|
| Cloud Provider | AWS EC2 (Frankfurt region) |
| IP Address | your-server-ip |
| OS | Ubuntu 22.04 LTS |
| Web Server | Nginx |
| PHP | PHP 8.3 + php8.3-fpm |
| Database | PostgreSQL 16 |
| Cache / Queue | Redis 7 |
| Queue Runner | Supervisor |
| App Path | /var/www/pyroradius/ |
| Node.js | Node 20 |
| Process Manager | systemd + Supervisor |
SSH Access¶
The private key is at ~/.ssh/your-ssh-key on the developer's local machine. If you do not have this key, ask the PyroNet Solutions administrator.
Once connected, all work is done in /var/www/pyroradius/.
Git Workflow¶
PyroRadius uses a feature branch workflow. The master branch always reflects production. You must never push directly to master.
Branch Naming¶
feature/<short-description>
feature/production-sync-<YYYY-MM-DD> # for production-only hotfixes
fix/<short-description>
chore/<short-description>
Standard Workflow¶
# 1. Create a feature branch from master
git checkout master
git pull origin master
git checkout -b feature/my-feature
# 2. Make changes, commit
git add -p # stage specific changes (never git add -A blindly)
git commit -m "feat: describe the change"
# 3. Push the branch
git push origin feature/my-feature
# 4. Create a Pull Request on GitHub → review → merge to master
# NEVER: git push origin master
# NEVER: git checkout master && git merge feature/... && git push origin master
Emergency Hotfix¶
Even for urgent fixes, use a branch:
git checkout -b feature/production-sync-$(date +%Y-%m-%d)
# make fix
git push origin feature/production-sync-$(date +%Y-%m-%d)
# merge via PR or get explicit sign-off before merging
Full Deploy Procedures¶
PHP-Only Changes (no new migrations, no JS changes)¶
cd /var/www/pyroradius
# 1. Pull latest code
git pull origin master
# 2. Install/update Composer dependencies (if composer.json changed)
composer install --no-dev --optimize-autoloader
# 3. Clear and rebuild caches
php artisan optimize:clear
php artisan optimize
# 4. Restart PHP-FPM to reload opcache
systemctl restart php8.3-fpm
# 5. Verify the app is up
curl -s -o /dev/null -w "%{http_code}" https://pyroradius.pyronet.com.pk/login
# Should return 200
JS-Only Changes (React/Inertia changes, no PHP or DB changes)¶
cd /var/www/pyroradius
# 1. Pull latest code
git pull origin master
# 2. Install/update npm packages (if package.json changed)
npm install
# 3. Build production assets
npm run build
# 4. Restart PHP-FPM (Inertia manifest is re-read by PHP)
systemctl restart php8.3-fpm
# 5. Clear view cache if needed
php artisan view:clear
Database Migrations Only (no code changes)¶
cd /var/www/pyroradius
# 1. Pull latest code
git pull origin master
# 2. REVIEW the migration before running
cat database/migrations/$(ls -t database/migrations | head -1)
# Read it. Understand what it does. Never run blind.
# 3. Run the migration
php artisan migrate
# 4. If the migration modifies tables used by the app, restart PHP-FPM
systemctl restart php8.3-fpm
WARNING: Never run
php artisan migrate:freshorphp artisan migrate:reseton production. These are destructive. Always write additive migrations.
Combined Deploy (PHP + JS + Migrations)¶
This is the most common full deploy. Do it in this exact order:
cd /var/www/pyroradius
# 1. Pull latest code
git pull origin master
# 2. Install Composer packages
composer install --no-dev --optimize-autoloader
# 3. Install npm packages
npm install
# 4. Run migrations (review first)
php artisan migrate
# 5. Clear all caches
php artisan optimize:clear
# 6. Rebuild config/route/view caches
php artisan optimize
# 7. Build frontend assets
npm run build
# 8. Restart PHP-FPM
systemctl restart php8.3-fpm
# 9. Restart queue workers via Supervisor
supervisorctl restart all
# 10. Verify
tail -n 50 storage/logs/laravel.log
curl -s -o /dev/null -w "%{http_code}" https://pyroradius.pyronet.com.pk/login
Supervisor — Queue Workers¶
Supervisor manages Laravel queue workers. Workers process jobs like RADIUS sync, WhatsApp messages, invoice generation, and audit logging.
Check Status¶
Expected output — all workers should show RUNNING:
laravel-worker:laravel-worker_00 RUNNING pid 12345, uptime 2:30:15
laravel-worker:laravel-worker_01 RUNNING pid 12346, uptime 2:30:15
Restart Workers¶
# Restart all workers
supervisorctl restart all
# Restart only queue workers
supervisorctl restart laravel-worker:*
Force Stop and Restart (if workers are stuck)¶
Supervisor Config Location¶
Typical contents:
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/pyroradius/artisan queue:work redis --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=2
redirect_stderr=true
stdout_logfile=/var/www/pyroradius/storage/logs/worker.log
stopwaitsecs=3600
Reload Supervisor Config (after editing conf file)¶
Nginx Configuration Notes¶
Nginx config is at:
Key settings to know:
- Root:
/var/www/pyroradius/public try_files $uri $uri/ /index.php?$query_string;— routes all requests through Laravel- SSL terminated at Nginx (Let's Encrypt certificate)
client_max_body_size 10M;— for file uploads- FastCGI proxy passes to
unix:/run/php/php8.3-fpm.sock
To test and reload Nginx config:
PHP-FPM Pool Settings¶
Pool config at: /etc/php/8.3/fpm/pool.d/www.conf
Key parameters:
pm = dynamic
pm.max_children = 20
pm.start_servers = 5
pm.min_spare_servers = 3
pm.max_spare_servers = 10
pm.max_requests = 500
Restart PHP-FPM:
Environment Variable Management¶
Environment variables are stored in /var/www/pyroradius/.env. This file is not in git (it is in .gitignore).
Editing Environment Variables¶
After changing any env variable:
Critical Environment Variables¶
| Variable | Purpose |
|---|---|
APP_KEY |
Laravel encryption key — never change in production |
DB_DATABASE |
Main PostgreSQL database |
RADIUS_DB_DATABASE |
RADIUS PostgreSQL database |
REDIS_HOST |
Redis connection |
QUEUE_CONNECTION |
Should be redis in production |
SESSION_DRIVER |
Should be redis in production |
SESSION_LIFETIME |
Session expiry in minutes |
WHATSAPP_ENABLED |
true/false — controls WhatsApp dispatch |
ACS_ENABLED |
false — TR-069 is not active |
MIKROTIK_API_HOST |
MikroTik router host |
MIKROTIK_API_USER |
RouterOS API username |
MIKROTIK_API_PASSWORD |
RouterOS API password |
Backup Strategy¶
Backups are managed by the BackupRun Artisan command, which uses spatie/laravel-backup.
# Run backup manually
php artisan backup:run
# List backups
php artisan backup:list
# Clean old backups (respects retention policy in config/backup.php)
php artisan backup:clean
Backups are stored in the backup disk configured in config/filesystems.php. Confirm the backup destination (local or remote S3/similar) is configured correctly.
Backup what matters:
- PostgreSQL
pyroradiusdatabase — full pg_dump - PostgreSQL
radiusdatabase — full pg_dump .envfile — store separately and securelystorage/directory — uploaded files
Post-Deploy Verification Checklist¶
Run this after every deploy before considering it complete:
- [ ]
tail -n 100 storage/logs/laravel.log— no new ERROR or CRITICAL entries - [ ]
supervisorctl status all— all workers RUNNING - [ ]
systemctl status php8.3-fpm— active (running) - [ ]
systemctl status nginx— active (running) - [ ]
redis-cli ping— returns PONG - [ ] Open browser →
https://pyroradius.pyronet.com.pk/login— page loads - [ ] Log in as super_admin → dashboard loads with correct stats
- [ ] Log in as a dealer account → correct menu items visible (no admin items)
- [ ] Navigate to Customers → list loads
- [ ] Open one customer → all tabs load (Profile, Invoices, Ledger, ONT)
- [ ] Check online count on dashboard — not 0 and not NULL
- [ ] If JS was built: hard refresh browser (Ctrl+Shift+R) to confirm new assets load
Rollback Procedure¶
PyroRadius does not have a one-click rollback. Roll back manually:
Code Rollback¶
cd /var/www/pyroradius
# Find the previous commit hash
git log --oneline -10
# Reset to previous commit (does NOT affect the database)
git reset --hard <previous-commit-hash>
# Rebuild caches and restart
php artisan optimize:clear
php artisan optimize
npm run build
systemctl restart php8.3-fpm
supervisorctl restart all
Migration Rollback¶
Only roll back if the migration has not been in production long and no data depends on it.
If a migration cannot be safely rolled back (it deleted or renamed columns with data), write a compensating migration instead:
php artisan make:migration revert_some_column_change
# Write the inverse operation
php artisan migrate
Database Restore from Backup¶
Last resort — use only if data is corrupted: