mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-04 02:32:57 +00:00
dev day #80
This commit is contained in:
@@ -0,0 +1,663 @@
|
||||
# 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** ✅
|
||||
Reference in New Issue
Block a user