Skip to content

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

ssh -i ~/.ssh/your-key root@your-server-ip

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/.

cd /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:fresh or php artisan migrate:reset on 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

supervisorctl status all

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)

supervisorctl stop all
supervisorctl start all

Supervisor Config Location

/etc/supervisor/conf.d/laravel-worker.conf

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)

supervisorctl reread
supervisorctl update
supervisorctl start all

Nginx Configuration Notes

Nginx config is at:

/etc/nginx/sites-available/pyroradius
/etc/nginx/sites-enabled/pyroradius  (symlink)

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:

nginx -t       # test config syntax
systemctl reload nginx   # apply without downtime

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:

systemctl restart php8.3-fpm
systemctl status php8.3-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

nano /var/www/pyroradius/.env
# or
vim /var/www/pyroradius/.env

After changing any env variable:

php artisan optimize:clear
php artisan optimize
systemctl restart php8.3-fpm

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:

  1. PostgreSQL pyroradius database — full pg_dump
  2. PostgreSQL radius database — full pg_dump
  3. .env file — store separately and securely
  4. storage/ 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.

php artisan migrate:rollback --step=1
# Review what this does before running 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:

# Stop the app first
systemctl stop nginx

# Restore from backup
pg_restore -U pyroradius -d pyroradius /path/to/backup.dump

# Restart
systemctl start nginx
supervisorctl restart all