mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-03 18:22:57 +00:00
664 lines
13 KiB
Markdown
664 lines
13 KiB
Markdown
# 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 <your-repo-url> 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=<paste-from-jwt_secret.txt>
|
|
|
|
# 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=<your-sendgrid-api-key>
|
|
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 <your-monitoring-server-ip>;
|
|
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: '<secure-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<TOKEN>/sendMessage" \
|
|
-d "chat_id=<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 <previous-commit-hash>
|
|
|
|
# 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** ✅
|