This document outlines the process for deploying the Universal application to various environments, including staging and production.
Universal is designed with a modern deployment architecture:
- Web Server: Nginx for serving static assets and routing requests
- Application Server: PHP-FPM for handling application code
- Database: MySQL/PostgreSQL for relational data and event store
- WebSocket Server: Laravel Reverb for real-time updates
- Cache: Redis for application caching and session storage
- Queue Workers: For handling background processing
Before deploying, ensure you have:
- A server with at least 2GB RAM (4GB recommended)
- PHP 8.1+ installed with required extensions
- Composer installed globally
- MySQL 8.0+ or PostgreSQL 14+
- Nginx web server
- Redis server
- Git access to the repository
- SSL certificate for secure HTTPS connections
# Update system packages
sudo apt update && sudo apt upgrade -y
# Install required packages
sudo apt install -y nginx mysql-server redis-server git curl zip unzip
# Install PHP and extensions
sudo apt install -y php8.1-fpm php8.1-cli php8.1-mysql php8.1-pgsql \
php8.1-mbstring php8.1-xml php8.1-curl php8.1-zip php8.1-bcmath \
php8.1-intl php8.1-gd php8.1-redis# For MySQL
sudo mysql_secure_installation
# Create database and user
sudo mysql -u root -pCREATE DATABASE universal;
CREATE USER 'universal'@'localhost' IDENTIFIED BY 'your-secure-password';
GRANT ALL PRIVILEGES ON universal.* TO 'universal'@'localhost';
FLUSH PRIVILEGES;
EXIT;Create a new Nginx server block configuration:
# /etc/nginx/sites-available/universal
server {
listen 80;
server_name your-domain.com;
root /var/www/universal/public;
add_header X-Frame-Options "SAMEORIGIN";
add_header X-Content-Type-Options "nosniff";
index index.php;
charset utf-8;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
error_page 404 /index.php;
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\.(?!well-known).* {
deny all;
}
}
Enable the site and restart Nginx:
sudo ln -s /etc/nginx/sites-available/universal /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginxUse Certbot to obtain and configure SSL:
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d your-domain.comcd /var/www
sudo git clone https://github.com/davorminchorov/universal.git
cd universal
sudo chown -R www-data:www-data .sudo -u www-data composer install --no-dev --optimize-autoloader
sudo -u www-data npm install
sudo -u www-data npm run buildsudo -u www-data cp .env.example .env
sudo -u www-data php artisan key:generateEdit the .env file with your production settings:
APP_ENV=production
APP_DEBUG=false
APP_URL=https://your-domain.com
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=universal
DB_USERNAME=universal
DB_PASSWORD=your-secure-password
CACHE_DRIVER=redis
SESSION_DRIVER=redis
QUEUE_CONNECTION=redis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
REVERB_APP_ID=universal
REVERB_APP_KEY=your-reverb-app-key
REVERB_APP_SECRET=your-reverb-app-secret
REVERB_HOST=reverb.your-domain.com
sudo -u www-data php artisan migrate --forcesudo -u www-data php artisan optimize
sudo -u www-data php artisan route:cache
sudo -u www-data php artisan view:cache
sudo -u www-data php artisan event:cacheCreate a Supervisor configuration for Laravel Reverb:
# /etc/supervisor/conf.d/reverb.conf
[program:reverb]
process_name=%(program_name)s
command=php /var/www/universal/artisan reverb:start
autostart=true
autorestart=true
user=www-data
redirect_stderr=true
stdout_logfile=/var/www/universal/storage/logs/reverb.log
stopwaitsecs=3600
Create a Supervisor configuration for queue workers:
# /etc/supervisor/conf.d/universal-worker.conf
[program:universal-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/universal/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/universal/storage/logs/worker.log
stopwaitsecs=3600
Start Supervisor processes:
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start reverb:*
sudo supervisorctl start universal-worker:*Add Laravel scheduler to crontab:
sudo -u www-data crontab -eAdd the following line:
* * * * * cd /var/www/universal && php artisan schedule:run >> /dev/null 2>&1
Create a .github/workflows/deploy.yml file:
name: Deploy
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up SSH
uses: webfactory/ssh-agent@v0.7.0
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
- name: Deploy to Production
run: |
ssh -o StrictHostKeyChecking=no user@your-domain.com "cd /var/www/universal && \
git pull origin main && \
composer install --no-dev --optimize-autoloader && \
npm install && \
npm run build && \
php artisan migrate --force && \
php artisan optimize && \
php artisan route:cache && \
php artisan view:cache && \
php artisan event:cache && \
sudo supervisorctl restart reverb:* && \
sudo supervisorctl restart universal-worker:*"If using Laravel Forge for deployment:
- Create a new site in Forge
- Connect your GitHub repository
- Configure deployment script:
cd $FORGE_SITE
git pull origin main
composer install --no-dev --optimize-autoloader
npm install
npm run build
php artisan migrate --force
php artisan optimize
php artisan route:cache
php artisan view:cache
php artisan event:cache
php artisan queue:restart- Set up SSL certificate
- Configure environment variables
- Configure Daemon for Laravel Reverb
For zero-downtime deployment, consider using a blue-green deployment strategy:
- Set up two identical production environments (blue and green)
- Deploy to the inactive environment
- Run tests to ensure everything works
- Switch traffic from active to inactive environment
- The previously active environment becomes inactive
Laravel Envoyer automates zero-downtime deployments:
- Create a project in Envoyer
- Configure your repository and server
- Set up your deployment hooks:
# Install Composer dependencies
composer install --no-dev --optimize-autoloader
# Install and compile npm assets
npm ci
npm run build
# Laravel commands
php artisan migrate --force
php artisan optimize
php artisan route:cache
php artisan view:cache
php artisan event:cache- Configure activation hook:
php artisan queue:restartIf deployment fails, follow these rollback procedures:
cd /var/www/universal
git reset --hard HEAD~1
composer install --no-dev --optimize-autoloader
npm ci
npm run build
php artisan optimize
php artisan route:cache
php artisan view:cache
php artisan event:cache
php artisan queue:restartSimply activate a previous deployment in the Envoyer dashboard.
Set up log monitoring using Papertrail or ELK stack:
# Install Filebeat (for ELK)
curl -L -O https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-8.6.0-amd64.deb
sudo dpkg -i filebeat-8.6.0-amd64.deb
# Configure Filebeat
sudo nano /etc/filebeat/filebeat.ymlSet up Laravel Telescope in a secure production environment:
composer require laravel/telescope
php artisan telescope:installConfigure .env for Telescope:
TELESCOPE_ENABLED=true
TELESCOPE_PATH=telescope-dashboard
Secure Telescope with authentication middleware.
Set up automated backups:
# Install Laravel Backup Package
composer require spatie/laravel-backup
# Configure backup
php artisan vendor:publish --provider="Spatie\Backup\BackupServiceProvider"Configure .env for backups:
BACKUP_ARCHIVE_PASSWORD=your-secure-password
BACKUP_DESTINATION=s3
AWS_ACCESS_KEY_ID=your-aws-key
AWS_SECRET_ACCESS_KEY=your-aws-secret
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=your-backup-bucket
Schedule backups:
// app/Console/Kernel.php
protected function schedule(Schedule $schedule)
{
$schedule->command('backup:clean')->daily()->at('01:00');
$schedule->command('backup:run')->daily()->at('02:00');
}When deploying schema changes to the event store:
# Create migration for stored_events table
php artisan make:migration add_new_field_to_stored_events_table
# Apply migration with caution
php artisan migrate --path=database/migrations/xxxx_xx_xx_add_new_field_to_stored_events_table.phpAfter deployment, you may need to rebuild projections:
# Rebuild all projections
php artisan event-sourcing:replay
# Rebuild specific projector
php artisan event-sourcing:replay "App\\Domain\\Subscriptions\\Projections\\SubscriptionProjector"To minimize downtime during rebuilds:
- Create temporary projections tables
- Rebuild projections to temporary tables
- Swap tables when complete
When deploying changes to event schemas:
- First deploy event upcasters
- Then deploy code that produces new event versions
After deployment, perform these security hardening steps:
- File Permissions:
sudo find /var/www/universal -type f -exec chmod 644 {} \;
sudo find /var/www/universal -type d -exec chmod 755 {} \;
sudo chmod -R ug+rwx /var/www/universal/storage /var/www/universal/bootstrap/cache- Firewall Configuration:
sudo ufw allow 'Nginx Full'
sudo ufw allow ssh
sudo ufw enable- Fail2Ban Setup:
sudo apt install fail2ban
sudo systemctl enable fail2ban
sudo systemctl start fail2ban-
500 Server Error:
- Check storage directory permissions
- Review Laravel logs at
/var/www/universal/storage/logs/laravel.log
-
WebSocket Connection Issues:
- Verify Reverb daemon is running:
sudo supervisorctl status reverb - Check Reverb logs:
/var/www/universal/storage/logs/reverb.log
- Verify Reverb daemon is running:
-
Database Connection Issues:
- Confirm database credentials in
.env - Check MySQL/PostgreSQL service:
sudo systemctl status mysql
- Confirm database credentials in
-
Cache Issues:
- Clear application cache:
php artisan cache:clear - Rebuild optimized classes:
php artisan optimize:clear && php artisan optimize
- Clear application cache:
-
Queue Not Processing:
- Check queue worker status:
sudo supervisorctl status universal-worker - Restart queue workers:
sudo supervisorctl restart universal-worker:*
- Check queue worker status:
Before deploying to production, complete this checklist:
- Review all code changes and merge to main branch
- Run all tests and ensure they pass
- Update
.envwith production settings - Ensure database backups are in place
- Verify supervisor configurations
- Configure proper logging
- Set up monitoring
- Enable HTTPS with valid SSL certificate
- Set up firewall rules
- Update documentation if needed
- Perform a test deployment to staging environment
- Check the deployed application for any issues
- Execute deployment to production
- Verify critical functionality after deployment