# Production Deployment Guide ## Quick Production Deployment (15 Minutes) ### Prerequisites - Docker & Docker Compose installed - Domain name configured - SSL certificate ready (Let's Encrypt recommended) - PostgreSQL 14+ database --- ## Step 1: Clone & Configure (5 min) ```bash # Clone repository git clone fotbal-club-production cd fotbal-club-production # Copy environment template cp .env.example .env # Generate JWT secret (64 characters) openssl rand -hex 32 > jwt_secret.txt ``` ### Edit .env file: ```bash nano .env ``` **Critical settings to change:** ```env # Application APP_ENV=production DEBUG=false PORT=8080 # JWT - CHANGE THIS! JWT_SECRET= # Database DATABASE_URL=postgres://dbuser:dbpassword@localhost:5432/fotbal_club?sslmode=require # SMTP - Real email service SMTP_HOST=smtp.sendgrid.net SMTP_PORT=587 SMTP_USER=apikey SMTP_PASSWORD= SMTP_FROM=noreply@your-domain.cz SMTP_FROM_NAME="Your Club Name" # Migrations RUN_MIGRATIONS=true SEED_DATABASE=false # CORS ALLOWED_ORIGINS=https://your-domain.cz,https://www.your-domain.cz ``` --- ## Step 2: Database Setup (3 min) ```bash # Start PostgreSQL (if using Docker) docker-compose up -d db # Wait for database to be ready docker-compose exec db pg_isready # Run migrations docker-compose run --rm backend ./fotbal-club migrate # Verify migrations docker-compose exec db psql -U postgres -d fotbal_club -c "\dt" ``` --- ## Step 3: Build & Deploy (5 min) ```bash # Build frontend cd frontend npm install --production npm run build cd .. # Build backend docker-compose build backend # Start all services docker-compose up -d # Verify services are running docker-compose ps # Check logs docker-compose logs -f backend | head -50 ``` --- ## Step 4: Verify Deployment (2 min) ```bash # Health check curl http://localhost:8080/api/v1/health # Expected response: # {"status":"ok","database":"connected"} # Check metrics curl http://localhost:8080/metrics | grep "http_requests_total" # Test authentication curl -X POST http://localhost:8080/api/v1/auth/login \ -H "Content-Type: application/json" \ -d '{"email":"admin@example.com","password":"admin123"}' ``` --- ## Nginx Reverse Proxy Configuration ### Install Nginx ```bash sudo apt update sudo apt install nginx certbot python3-certbot-nginx ``` ### Configure Site ```bash sudo nano /etc/nginx/sites-available/fotbal-club ``` ```nginx # Backend API server { listen 80; server_name api.your-domain.cz; # Redirect to HTTPS return 301 https://$server_name$request_uri; } server { listen 443 ssl http2; server_name api.your-domain.cz; # SSL certificates (Let's Encrypt) ssl_certificate /etc/letsencrypt/live/api.your-domain.cz/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/api.your-domain.cz/privkey.pem; # SSL configuration ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; # Security headers (backend already sets these, but good to enforce) add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always; add_header X-Content-Type-Options "nosniff" always; add_header X-Frame-Options "SAMEORIGIN" always; # Rate limiting limit_req_zone $binary_remote_addr zone=api_limit:10m rate=100r/s; limit_req zone=api_limit burst=200 nodelay; # Proxy settings location / { proxy_pass http://127.0.0.1:8080; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_cache_bypass $http_upgrade; # Timeouts proxy_connect_timeout 60s; proxy_send_timeout 60s; proxy_read_timeout 60s; } # Uploads - longer timeout location ~ ^/(api/v1/upload|api/v1/admin/.*/(upload|image)) { client_max_body_size 10M; proxy_pass http://127.0.0.1:8080; proxy_request_buffering off; proxy_read_timeout 300s; } # Static files - long cache location ~ ^/(dist|uploads|cache)/ { proxy_pass http://127.0.0.1:8080; proxy_cache_valid 200 7d; add_header Cache-Control "public, max-age=604800, immutable"; } # Metrics endpoint - restrict access location /metrics { allow 127.0.0.1; allow ; deny all; proxy_pass http://127.0.0.1:8080; } # Access/error logs access_log /var/log/nginx/fotbal-club-access.log combined; error_log /var/log/nginx/fotbal-club-error.log warn; } # Frontend (static files) server { listen 80; server_name your-domain.cz www.your-domain.cz; return 301 https://$server_name$request_uri; } server { listen 443 ssl http2; server_name your-domain.cz www.your-domain.cz; ssl_certificate /etc/letsencrypt/live/your-domain.cz/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/your-domain.cz/privkey.pem; root /var/www/fotbal-club/frontend/build; index index.html; # Gzip compression gzip on; gzip_vary on; gzip_min_length 1024; gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json; # Security headers add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always; add_header X-Content-Type-Options "nosniff" always; add_header X-Frame-Options "SAMEORIGIN" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; # React Router (SPA) location / { try_files $uri $uri/ /index.html; add_header Cache-Control "no-cache"; } # Static assets - long cache location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ { expires 1y; add_header Cache-Control "public, immutable"; } # Proxy API requests to backend location /api { proxy_pass http://127.0.0.1:8080; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_cache_bypass $http_upgrade; } access_log /var/log/nginx/fotbal-club-frontend-access.log combined; error_log /var/log/nginx/fotbal-club-frontend-error.log warn; } ``` ### Enable Site & Get SSL ```bash # Enable site sudo ln -s /etc/nginx/sites-available/fotbal-club /etc/nginx/sites-enabled/ # Test configuration sudo nginx -t # Get SSL certificate sudo certbot --nginx -d your-domain.cz -d www.your-domain.cz -d api.your-domain.cz # Reload Nginx sudo systemctl reload nginx # Auto-renewal sudo certbot renew --dry-run ``` --- ## Database Backup Setup ### Automated Daily Backups ```bash # Create backup script sudo nano /usr/local/bin/backup-fotbal-db.sh ``` ```bash #!/bin/bash set -e # Configuration DB_NAME="fotbal_club" DB_USER="postgres" BACKUP_DIR="/var/backups/fotbal-club" RETENTION_DAYS=7 DATE=$(date +%Y%m%d_%H%M%S) BACKUP_FILE="$BACKUP_DIR/fotbal_club_$DATE.dump" # Create backup directory mkdir -p $BACKUP_DIR # Backup database pg_dump -U $DB_USER -Fc $DB_NAME > $BACKUP_FILE # Compress gzip $BACKUP_FILE # Delete old backups find $BACKUP_DIR -name "*.dump.gz" -mtime +$RETENTION_DAYS -delete # Upload to S3 (optional) # aws s3 cp $BACKUP_FILE.gz s3://your-bucket/backups/ echo "Backup completed: $BACKUP_FILE.gz" ``` ```bash # Make executable sudo chmod +x /usr/local/bin/backup-fotbal-db.sh # Add to crontab (daily at 2 AM) sudo crontab -e ``` Add line: ``` 0 2 * * * /usr/local/bin/backup-fotbal-db.sh >> /var/log/fotbal-backup.log 2>&1 ``` --- ## Monitoring Setup ### Prometheus Configuration ```yaml # prometheus.yml global: scrape_interval: 15s scrape_configs: - job_name: 'fotbal-club' static_configs: - targets: ['localhost:8080'] metrics_path: '/metrics' basic_auth: username: 'admin' password: '' ``` ### Grafana Dashboard Import Use dashboard ID: 6417 (Gin metrics) Modify for custom metrics --- ## Security Hardening Checklist ### Server Level ```bash # Update system sudo apt update && sudo apt upgrade -y # Enable firewall sudo ufw allow 22/tcp sudo ufw allow 80/tcp sudo ufw allow 443/tcp sudo ufw enable # Fail2ban for SSH sudo apt install fail2ban sudo systemctl enable fail2ban sudo systemctl start fail2ban # Disable root SSH login sudo nano /etc/ssh/sshd_config # Set: PermitRootLogin no sudo systemctl restart sshd ``` ### Application Level ```bash # Set file permissions sudo chown -R app:app /app/uploads sudo chmod 755 /app/uploads sudo chmod 644 /app/uploads/* # Secure environment files chmod 600 .env chown root:root .env # Rotate logs sudo nano /etc/logrotate.d/fotbal-club ``` ``` /var/log/nginx/fotbal-club-*.log { daily rotate 14 compress delaycompress notifempty create 0640 www-data adm sharedscripts postrotate [ -f /var/run/nginx.pid ] && kill -USR1 `cat /var/run/nginx.pid` endscript } ``` --- ## Performance Tuning ### PostgreSQL Optimization ```bash # Edit postgresql.conf sudo nano /etc/postgresql/14/main/postgresql.conf ``` ```conf # Memory settings (for 4GB RAM server) shared_buffers = 1GB effective_cache_size = 3GB maintenance_work_mem = 256MB work_mem = 32MB # Connections max_connections = 200 # Checkpoints checkpoint_completion_target = 0.9 wal_buffers = 16MB # Query planner random_page_cost = 1.1 # For SSD effective_io_concurrency = 200 # Logging log_min_duration_statement = 1000 # Log slow queries (1s+) ``` ### Docker Resource Limits ```yaml # docker-compose.yml services: backend: deploy: resources: limits: cpus: '2' memory: 1G reservations: cpus: '0.5' memory: 512M restart: unless-stopped db: deploy: resources: limits: cpus: '2' memory: 2G reservations: cpus: '1' memory: 1G restart: unless-stopped ``` --- ## Maintenance Scripts ### Health Check Script ```bash #!/bin/bash # /usr/local/bin/health-check.sh URL="https://your-domain.cz/api/v1/health" RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" $URL) if [ $RESPONSE -ne 200 ]; then echo "Health check failed! HTTP $RESPONSE" # Send alert curl -X POST "https://api.telegram.org/bot/sendMessage" \ -d "chat_id=" \ -d "text=⚠️ Fotbal Club Health Check Failed!" exit 1 fi echo "Health check OK" ``` ### Database Maintenance ```bash #!/bin/bash # Weekly database maintenance # Vacuum and analyze psql -U postgres -d fotbal_club -c "VACUUM ANALYZE;" # Reindex psql -U postgres -d fotbal_club -c "REINDEX DATABASE fotbal_club;" # Check table sizes psql -U postgres -d fotbal_club -c " SELECT schemaname, tablename, pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) AS size FROM pg_tables WHERE schemaname = 'public' ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC LIMIT 10; " ``` --- ## Troubleshooting ### Service Won't Start ```bash # Check logs docker-compose logs backend --tail=100 # Common issues: # 1. Port already in use sudo lsof -i :8080 # Kill process if needed # 2. Database connection failed docker-compose exec db pg_isready # 3. Permission denied sudo chown -R app:app /app ``` ### High Memory Usage ```bash # Check container stats docker stats # Restart services if needed docker-compose restart backend # Check for memory leaks docker-compose exec backend ps aux --sort=-%mem | head ``` ### Slow Queries ```bash # Enable query logging psql -U postgres -d fotbal_club -c " ALTER DATABASE fotbal_club SET log_min_duration_statement = 100; " # View slow queries sudo tail -f /var/log/postgresql/postgresql-14-main.log | grep "duration:" ``` --- ## Rollback Procedure ### Quick Rollback ```bash # Stop current version docker-compose down # Checkout previous version git checkout # Rollback database migrations (if needed) docker-compose run backend ./fotbal-club migrate down # Restart with old version docker-compose up -d # Verify curl http://localhost:8080/api/v1/health ``` --- ## Support & Contact ### Log Locations - **Backend:** `docker-compose logs backend` - **Database:** `/var/log/postgresql/` - **Nginx:** `/var/log/nginx/fotbal-club-*.log` - **System:** `/var/log/syslog` ### Useful Commands ```bash # View real-time logs docker-compose logs -f backend # Check resource usage docker stats # Database console docker-compose exec db psql -U postgres fotbal_club # Restart specific service docker-compose restart backend # Clean up old images docker system prune -a ``` --- ## Success Criteria After deployment, verify: - [ ] Health endpoint returns 200 - [ ] Homepage loads in < 2 seconds - [ ] Login works - [ ] Articles display correctly - [ ] File uploads work - [ ] Email sends successfully - [ ] SSL certificate valid - [ ] Metrics endpoint accessible - [ ] Database backups running - [ ] Logs are being written **Status: READY FOR PRODUCTION** ✅