Skip to main content

Server Configuration Documentation

Infrastructure Overview

Server Architecture

[Internet]

[77.42.82.4] Application Server (10.0.0.2)
├── Nginx (reverse proxy)
├── Backend (FastAPI)
├── PostgreSQL
├── Redis
└── Frontend (static files)
↓ SSH Connection
[65.109.236.163] LXD Server (10.0.0.3)
└── LXD Containers (Sandboxes & Exams)

Servers

Application Server (77.42.82.4)

  • Public IP: 77.42.82.4
  • Private IP: 10.0.0.2
  • OS: Ubuntu 22.04
  • Services:
    • Nginx (ports 80, 443)
    • Docker + Docker Compose
    • Application containers

LXD Server (65.109.236.163)

  • Public IP: 65.109.236.163
  • Private IP: 10.0.0.3
  • OS: Ubuntu 22.04
  • Services:
    • LXD daemon (port 8443)
    • SSH (port 22)
    • Container network bridge

Nginx Configuration

Location: /etc/nginx/sites-enabled/ or container /etc/nginx/conf.d/

Frontend (mvp.zafarsaidov.uz)

server {
listen 80;
server_name mvp.zafarsaidov.uz;

root /usr/share/nginx/html;
index index.html;

# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;

# SPA routing - all routes serve index.html
location / {
try_files $uri $uri/ /index.html;
}

# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}

# Gzip compression
gzip on;
gzip_vary on;
gzip_min_length 1000;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
}

Backend API (mvp-api.zafarsaidov.uz)

server {
listen 80;
server_name mvp-api.zafarsaidov.uz;

# API endpoints
location / {
# Handle CORS preflight requests
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '$http_origin' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization' always;
add_header 'Access-Control-Max-Age' 1728000 always;
add_header 'Content-Type' 'text/plain; charset=utf-8' always;
add_header 'Content-Length' 0 always;
return 204;
}

proxy_pass http://backend:8000;
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;

# CORS headers
add_header Access-Control-Allow-Origin $http_origin always;
add_header Access-Control-Allow-Credentials true always;

# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;

# Timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}

# WebSocket configuration for terminal
location /ws/ {
proxy_pass http://backend:8000;
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;

# WebSocket timeout settings
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
}

Docker Compose Configuration

File: docker-compose.prod.yml

version: '3.8'

services:
postgres:
image: postgres:15-alpine
container_name: platform-postgres-prod
restart: unless-stopped
environment:
POSTGRES_DB: platform
POSTGRES_USER: platform
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- platform-network

redis:
image: redis:7-alpine
container_name: platform-redis-prod
restart: unless-stopped
command: redis-server --appendonly yes
volumes:
- redis_data:/data
networks:
- platform-network

backend:
build: ./backend
container_name: platform-backend-prod
restart: unless-stopped
env_file: ./backend/.env
volumes:
- ./backend/.lxd:/app/.lxd:ro # LXD certificates
- ./private_key:/app/.ssh/lxd_key:ro # SSH key for LXD server
environment:
- CORS_ORIGINS=https://mvp.zafarsaidov.uz,https://mvp-api.zafarsaidov.uz
networks:
- platform-network
depends_on:
- postgres
- redis

nginx:
build:
context: .
dockerfile: ./nginx/Dockerfile
container_name: platform-nginx
restart: unless-stopped
ports:
- "80:80"
- "443:443"
networks:
- platform-network
depends_on:
- backend

volumes:
postgres_data:
redis_data:

networks:
platform-network:
driver: bridge

Deployment Process

Initial Setup

  1. Clone repository on application server
ssh root@77.42.82.4
cd /opt
git clone git@gitlab.com:educenter-platform/mvp-v2.git platform-v2
cd platform-v2
  1. Copy SSH private key
# Copy from local machine
scp private_key root@77.42.82.4:/opt/platform-v2/
  1. Create .env file
cd backend
cat > .env << 'EOF'
DATABASE_URL=postgresql+asyncpg://platform:secure_password@postgres:5432/platform
POSTGRES_PASSWORD=secure_password
REDIS_URL=redis://redis:6379/0
SECRET_KEY=your-very-secure-secret-key-here
ACCESS_TOKEN_EXPIRE_MINUTES=43200
CORS_ORIGINS=https://mvp.zafarsaidov.uz,https://mvp-api.zafarsaidov.uz
LXD_ENDPOINT=https://10.0.0.3:8443
LXD_SSH_JUMP_HOST=65.109.236.163
LXD_CERT_PATH=/app/.lxd/client.crt
LXD_KEY_PATH=/app/.lxd/client.key
LXD_VERIFY_SSL=false
CONTAINER_CPU_LIMIT=2
CONTAINER_MEMORY_LIMIT=2GB
EOF
  1. Build and start services
docker-compose -f docker-compose.prod.yml up -d --build
  1. Initialize database
# Run migrations
docker-compose -f docker-compose.prod.yml exec backend alembic upgrade head

# Seed initial data
docker-compose -f docker-compose.prod.yml exec backend python -m app.db.init_db

Update Deployment

cd /opt/platform-v2
git pull origin main
docker-compose -f docker-compose.prod.yml build backend
docker-compose -f docker-compose.prod.yml stop backend
docker-compose -f docker-compose.prod.yml rm -f backend
docker-compose -f docker-compose.prod.yml up -d backend

Rebuild Everything

cd /opt/platform-v2
git pull origin main
docker-compose -f docker-compose.prod.yml down
docker-compose -f docker-compose.prod.yml up -d --build

Database Management

Backup

# Create backup
docker-compose -f docker-compose.prod.yml exec postgres pg_dump -U platform platform > backup_$(date +%Y%m%d).sql

# Restore backup
docker-compose -f docker-compose.prod.yml exec -T postgres psql -U platform platform < backup_20260201.sql

Access Database

docker-compose -f docker-compose.prod.yml exec postgres psql -U platform platform

Reset Database

docker-compose -f docker-compose.prod.yml exec backend python -m app.db.init_db reset

Monitoring & Logs

View Logs

# All services
docker-compose -f docker-compose.prod.yml logs -f

# Specific service
docker-compose -f docker-compose.prod.yml logs -f backend
docker-compose -f docker-compose.prod.yml logs -f nginx

# Last 100 lines
docker logs platform-backend-prod --tail 100

Container Status

docker-compose -f docker-compose.prod.yml ps

Resource Usage

docker stats

Security

Firewall (UFW)

# Allow SSH
ufw allow 22/tcp

# Allow HTTP/HTTPS
ufw allow 80/tcp
ufw allow 443/tcp

# Enable firewall
ufw enable

SSL/TLS (Let's Encrypt)

# Install certbot
apt install certbot python3-certbot-nginx

# Obtain certificate
certbot --nginx -d mvp.zafarsaidov.uz -d mvp-api.zafarsaidov.uz

# Auto-renewal (already configured by certbot)
certbot renew --dry-run

SSH Key Management

# Backend needs two keys:
# 1. /app/.ssh/lxd_key - for LXD server access (mounted from private_key)
# 2. /app/.ssh/id_rsa - for container access (auto-generated)

# Verify keys exist
docker exec platform-backend-prod ls -la /app/.ssh/

Troubleshooting

Backend won't start

# Check logs
docker logs platform-backend-prod

# Common issues:
# - Database connection failed: Check POSTGRES_PASSWORD
# - LXD connection failed: Check LXD_ENDPOINT and certificates
# - Import errors: Rebuild image

Database connection errors

# Check postgres is running
docker-compose -f docker-compose.prod.yml ps postgres

# Check database exists
docker-compose -f docker-compose.prod.yml exec postgres psql -U platform -l

# Test connection
docker-compose -f docker-compose.prod.yml exec backend python -c "from app.database import engine; import asyncio; asyncio.run(engine.connect())"

Nginx errors

# Check nginx config
docker exec platform-nginx nginx -t

# Reload nginx
docker exec platform-nginx nginx -s reload

# Check nginx logs
docker logs platform-nginx

Maintenance

Update Docker Images

docker-compose -f docker-compose.prod.yml pull
docker-compose -f docker-compose.prod.yml up -d

Clean Up Docker

# Remove unused images
docker image prune -a

# Remove unused volumes
docker volume prune

# Remove stopped containers
docker container prune

Database Maintenance

# Vacuum database
docker-compose -f docker-compose.prod.yml exec postgres vacuumdb -U platform platform

# Analyze database
docker-compose -f docker-compose.prod.yml exec postgres psql -U platform platform -c "ANALYZE;"