Files
ServerKit/docs/ARCHITECTURE.md
T
Juan Denis 26da2c65e8 Revamp README and add architecture documentation
Redesigned the README with improved layout, badges, architecture overview, and updated quick start instructions. Added a comprehensive docs/ARCHITECTURE.md file detailing system design, request flow, template system, port allocation, database linking, file paths, and troubleshooting.
2026-01-19 18:53:13 -05:00

561 lines
46 KiB
Markdown

# ServerKit Architecture
> Deep dive into how ServerKit connects domains, apps, containers, and databases.
---
## Table of Contents
- [System Overview](#system-overview)
- [Request Flow](#request-flow)
- [Template System](#template-system)
- [Port Allocation](#port-allocation)
- [Database Linking](#database-linking)
- [File Paths](#file-paths)
---
## System Overview
```
┌─────────────────────────────────────────────────────────────┐
│ INTERNET │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ YOUR SERVER │
│ ┌───────────────────────────────────────────────────────────────────────────────────────────────┐ │
│ │ NGINX (Reverse Proxy) │ │
│ │ Port 80 / 443 │ │
│ │ │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ app1.com │ │ app2.com │ │ api.app3.com │ │ Private URLs │ │ │
│ │ │ :443 │ │ :443 │ │ :443 │ │ /p/abc123 │ │ │
│ │ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │ │
│ └──────────┼───────────────────┼───────────────────┼───────────────────┼────────────────────────┘ │
│ │ │ │ │ │
│ │ proxy_pass │ proxy_pass │ proxy_pass │ proxy_pass │
│ ▼ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────────────────────────┐ │
│ │ DOCKER CONTAINERS │ │
│ │ │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ WordPress │ │ Flask │ │ Node.js │ │ Custom │ │ │
│ │ │ Port 8001 │ │ Port 8002 │ │ Port 8003 │ │ Port 8004 │ │ │
│ │ │ │ │ │ │ │ │ │ │ │
│ │ │ ┌──────────┐ │ │ ┌──────────┐ │ │ ┌──────────┐ │ │ ┌──────────┐ │ │ │
│ │ │ │ Apache │ │ │ │ Gunicorn │ │ │ │ PM2 │ │ │ │ Your │ │ │ │
│ │ │ │ PHP-FPM │ │ │ │ Python │ │ │ │ Express │ │ │ │ App │ │ │ │
│ │ │ └──────────┘ │ │ └──────────┘ │ │ └──────────┘ │ │ └──────────┘ │ │ │
│ │ └──────┬───────┘ └──────────────┘ └──────────────┘ └──────────────┘ │ │
│ │ │ │ │
│ └──────────┼──────────────────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────────────────────────┐ │
│ │ DATABASES │ │
│ │ │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ MySQL │ │ PostgreSQL │ │ Redis │ │ MongoDB │ │ │
│ │ │ Port 3306 │ │ Port 5432 │ │ Port 6379 │ │ Port 27017 │ │ │
│ │ └──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘ │ │
│ │ │ │
│ └──────────────────────────────────────────────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────────────────────────────────────┘
```
---
## Request Flow
What happens when a user visits your app:
```
User Request What Happens
─────────────────────────────────────────────────────────────────────────────────
Browser 1. DNS resolves app1.com to your server IP
│
▼
┌─────────┐ 2. Request hits Nginx on port 80/443
│ Nginx │ Nginx checks server_name directives
│ :80/443 │ Matches "app1.com" → proxy_pass http://127.0.0.1:8001
└────┬────┘
│
▼
┌─────────┐ 3. Nginx forwards request to Docker container
│ Docker │ Container receives request on internal port
│ :8001 │ App processes and returns response
└────┬────┘
│
▼
┌─────────┐ 4. Response flows back through Nginx
│ Response│ SSL termination handled by Nginx
│ 200 OK │ User sees the page
└─────────┘
```
### Detailed Nginx → Container Flow
```
NGINX CONFIG DOCKER
/etc/nginx/sites-enabled/ CONTAINER
───────────────────────── ─────────────────
server {
listen 80;
server_name my-blog.com;
┌─────────────────┐
location / { │ WordPress │
proxy_pass ─────────────────────────► │ │
http://127.0.0.1:8001; │ 0.0.0.0:80 │
proxy_set_header Host $host; │ ▲ │
proxy_set_header X-Real-IP ...; │ │ │
} │ (mapped to │
} │ host:8001) │
└─────────────────┘
```
---
## Template System
### Template → App → Domain Lifecycle
```
┌─────────────────────────────────────────────────────────────────────────────────┐
│ TEMPLATE LIBRARY │
│ /etc/serverkit/templates/ │
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │WordPress│ │ Flask │ │ Node.js │ │ Grafana │ │ n8n │ ... 60+ │
│ │ .yaml │ │ .yaml │ │ .yaml │ │ .yaml │ │ .yaml │ │
│ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ │
│ │ │ │ │ │ │
└────────┼────────────┼────────────┼────────────┼────────────┼───────────────────┘
│ │ │ │ │
│ User clicks "Deploy" in UI │
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────────────────────────────┐
│ SERVERKIT BACKEND │
│ │
│ TemplateService.install_template() │
│ ├── 1. Parse template YAML │
│ ├── 2. Generate unique port (8000-60000) │
│ ├── 3. Substitute variables: │
│ │ ${APP_NAME} → "my-blog" │
│ │ ${HTTP_PORT} → "8247" │
│ │ ${DB_PASSWORD} → "auto_generated" │
│ ├── 4. Create /var/serverkit/apps/my-blog/docker-compose.yml │
│ ├── 5. Run: docker compose up -d --build │
│ └── 6. Store app record in database │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────────┐
│ APP CREATED │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ App Name: my-blog │ │
│ │ Type: docker │ │
│ │ Port: 8247 (auto-assigned) │ │
│ │ Status: running │ │
│ │ Container: my-blog │ │
│ │ Path: /var/serverkit/apps/my-blog/ │ │
│ │ │ │
│ │ Private URL: http://server-ip:8247 ◄── Works immediately! │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘
│
│ User clicks "Connect Domain"
▼
┌─────────────────────────────────────────────────────────────────────────────────┐
│ DOMAIN CONNECTED │
│ │
│ DomainService.create_domain() │
│ ├── 1. Validate domain DNS points to server │
│ ├── 2. Check container port is accessible │
│ ├── 3. Generate Nginx config: │
│ │ │
│ │ server { │
│ │ listen 80; │
│ │ server_name my-blog.com; │
│ │ │
│ │ location / { │
│ │ proxy_pass http://127.0.0.1:8247; ◄── Container port │
│ │ 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; │
│ │ } │
│ │ } │
│ │ │
│ ├── 4. Write to /etc/nginx/sites-available/my-blog │
│ ├── 5. Symlink to /etc/nginx/sites-enabled/my-blog │
│ ├── 6. Test config: nginx -t │
│ ├── 7. Reload: systemctl reload nginx │
│ └── 8. (Optional) Request SSL via Let's Encrypt │
│ │
│ Public URL: https://my-blog.com ◄── Now accessible worldwide! │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘
```
### Template YAML Structure
```yaml
# Example: flask-hello-world.yaml
id: flask-hello-world
name: Flask - Hello World
version: "1.0"
description: Simple Flask debug app
categories:
- development
- api
variables:
- name: HTTP_PORT # Variable name
type: port # Auto-generates available port
default: "5000" # Starting port to search from
hidden: true # Don't show in UI
compose: # Docker Compose configuration
services:
app:
image: python:3.12-slim
container_name: ${APP_NAME}
ports:
- "${HTTP_PORT}:5000" # Host:Container port mapping
environment:
- APP_NAME=${APP_NAME}
- EXTERNAL_PORT=${HTTP_PORT}
```
---
## Port Allocation
### How ServerKit Finds Available Ports
```
┌─────────────────────────────────────────────────────────────────────────────────┐
│ PORT ALLOCATION │
│ │
│ TemplateService._find_available_port(start=8000) │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ Step 1: Check Database │ │
│ │ SELECT port FROM applications WHERE port IS NOT NULL │ │
│ │ Result: [8001, 8002, 8005, 8010] │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ Step 2: Check Docker │ │
│ │ docker ps --format '{{.Ports}}' │ │
│ │ Parse: "0.0.0.0:8003->80/tcp" → 8003 │ │
│ │ Result: [8003, 8004] │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ Step 3: Socket Bind Test │ │
│ │ Try: socket.bind(('127.0.0.1', port)) │ │
│ │ If fails → port in use by system │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ Step 4: Return First Available │ │
│ │ │ │
│ │ Checking 8000... taken (DB) │ │
│ │ Checking 8001... taken (DB) │ │
│ │ Checking 8002... taken (DB) │ │
│ │ Checking 8003... taken (Docker) │ │
│ │ Checking 8004... taken (Docker) │ │
│ │ Checking 8005... taken (DB) │ │
│ │ Checking 8006... AVAILABLE ✓ │ │
│ │ │ │
│ │ Return: 8006 │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘
```
### Port Map Example
```
┌──────────┬────────────────────────────────────────┐
│ Port │ Service │
├──────────┼────────────────────────────────────────┤
│ 22 │ SSH │
│ 80 │ Nginx (HTTP) │
│ 443 │ Nginx (HTTPS) │
│ 3306 │ MySQL │
│ 5432 │ PostgreSQL │
│ 5000 │ ServerKit Backend API │
│ 6379 │ Redis │
├──────────┼────────────────────────────────────────┤
│ 8001 │ App: wordpress-blog │
│ 8002 │ App: flask-api │
│ 8003 │ App: node-frontend │
│ 8004 │ App: grafana-monitoring │
│ 8005 │ App: n8n-automation │
│ 8006 │ App: (next available) │
│ ... │ ... │
│ 60000 │ (max port range) │
└──────────┴────────────────────────────────────────┘
```
---
## Database Linking
### How Apps Connect to Databases
```
┌────────────────────────────────────────────────────────────────────────────────┐
│ │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ APP (Flask) │ │ DATABASE (MySQL) │ │
│ │ │ │ │ │
│ │ Needs DB access │ │ db: my_app_db │ │
│ │ │ │ user: app_user │ │
│ └────────┬─────────┘ └────────┬─────────┘ │
│ │ │ │
│ │ User clicks "Link Database" │ │
│ │ │ │
│ └──────────────┬───────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────────────────┐ │
│ │ SERVERKIT LINKS THEM │ │
│ │ │ │
│ │ 1. Creates database: my_app_db │ │
│ │ 2. Creates user with secure password │ │
│ │ 3. Grants permissions │ │
│ │ 4. Injects environment variables into app container: │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────────────┐ │ │
│ │ │ DB_HOST=localhost │ │ │
│ │ │ DB_PORT=3306 │ │ │
│ │ │ DB_NAME=my_app_db │ │ │
│ │ │ DB_USER=app_user │ │ │
│ │ │ DB_PASSWORD=xK9#mP2$vL7@nQ4 │ │ │
│ │ │ │ │ │
│ │ │ # Also provides connection URL format: │ │ │
│ │ │ DATABASE_URL=mysql://app_user:xK9#mP2$vL7@nQ4@localhost/db │ │ │
│ │ └─────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ 5. Restarts app container to pick up new env vars │ │
│ │ │ │
│ └────────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────────────────┐ │
│ │ APP CODE │ │
│ │ │ │
│ │ # Python/Flask example │ │
│ │ import os │ │
│ │ import mysql.connector │ │
│ │ │ │
│ │ db = mysql.connector.connect( │ │
│ │ host=os.environ['DB_HOST'], # localhost │ │
│ │ port=os.environ['DB_PORT'], # 3306 │ │
│ │ database=os.environ['DB_NAME'], # my_app_db │ │
│ │ user=os.environ['DB_USER'], # app_user │ │
│ │ password=os.environ['DB_PASSWORD'] │ │
│ │ ) │ │
│ │ │ │
│ │ # Or use the URL directly: │ │
│ │ # SQLAlchemy: create_engine(os.environ['DATABASE_URL']) │ │
│ │ │ │
│ └────────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘
```
---
## File Paths
### Where Everything Lives
```
SERVER FILESYSTEM
─────────────────────────────────────────────────────────────────────────────────
/var/serverkit/ # ServerKit data root
├── apps/ # All deployed applications
│ ├── my-blog/
│ │ ├── docker-compose.yml # Generated from template
│ │ ├── .env # Environment variables
│ │ └── data/ # Persistent volumes
│ ├── flask-api/
│ │ ├── docker-compose.yml
│ │ └── app/ # Application code
│ └── ...
│
├── backups/ # Database backups
│ ├── mysql/
│ └── postgres/
│
└── ssl/ # SSL certificates (if not using certbot)
/etc/serverkit/ # ServerKit configuration
├── templates/ # Template library (YAML files)
│ ├── wordpress.yaml
│ ├── flask-hello-world.yaml
│ ├── grafana.yaml
│ └── ...
└── config.yaml # Main config
/etc/nginx/ # Nginx configuration
├── sites-available/ # All site configs
│ ├── my-blog # Generated by ServerKit
│ ├── flask-api
│ └── default
├── sites-enabled/ # Enabled sites (symlinks)
│ ├── my-blog -> ../sites-available/my-blog
│ └── flask-api -> ../sites-available/flask-api
└── nginx.conf # Main nginx config
/var/log/nginx/ # Nginx logs (per-app)
├── my-blog.access.log
├── my-blog.error.log
├── flask-api.access.log
└── flask-api.error.log
/var/lib/mysql/ # MySQL data
/var/lib/postgresql/ # PostgreSQL data
```
---
## Component Diagram
```
┌─────────────────────────────────────────────────────────────────────────────────┐
│ SERVERKIT │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ FRONTEND (React) │ │
│ │ Served via Nginx :80/443 │ │
│ │ │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │Dashboard │ │ Apps │ │ Domains │ │ Docker │ │ Security │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │Databases │ │Templates │ │ Firewall │ │ Cron │ │ Settings │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │
│ │ │ │
│ └─────────────────────────────────┬────────────────────────────────────────┘ │
│ │ │
│ REST API + WebSocket │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ BACKEND (Flask) │ │
│ │ Port 5000 │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────────────────┐ │ │
│ │ │ SERVICES │ │ │
│ │ │ │ │ │
│ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │
│ │ │ │DockerService │ │ NginxService │ │TemplateServ. │ │ │ │
│ │ │ │ │ │ │ │ │ │ │ │
│ │ │ │ • compose_up │ │ • create_site│ │ • install │ │ │ │
│ │ │ │ • logs │ │ • enable_ssl │ │ • variables │ │ │ │
│ │ │ │ • stats │ │ • reload │ │ • validate │ │ │ │
│ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │ │
│ │ │ │ │ │
│ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │
│ │ │ │ DBService │ │ SSLService │ │SecurityServ. │ │ │ │
│ │ │ │ │ │ │ │ │ │ │ │
│ │ │ │ • create_db │ │ • certbot │ │ • ClamAV │ │ │ │
│ │ │ │ • users │ │ • renew │ │ • 2FA │ │ │ │
│ │ │ │ • backup │ │ • wildcard │ │ • firewall │ │ │ │
│ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │ │
│ │ │ │ │ │
│ │ └──────────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────┬────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ DATABASE (SQLite/PostgreSQL) │ │
│ │ │ │
│ │ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌─────────┐ │ │
│ │ │ Apps │ │ Domains │ │ Users │ │ Databases │ │ Settings│ │ │
│ │ └───────────┘ └───────────┘ └───────────┘ └───────────┘ └─────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────────────────┘
```
---
## Troubleshooting
### 502 Bad Gateway
```
Problem: Nginx can't reach the container
Check:
┌─────────────────────────────────────────────────────────────────┐
│ 1. Is container running? │
│ docker ps | grep <app-name> │
│ │
│ 2. Is port bound to host? │
│ docker port <container-name> │
│ Expected: 5000/tcp -> 0.0.0.0:8001 │
│ │
│ 3. Is port accessible? │
│ curl -I http://127.0.0.1:8001 │
│ Expected: HTTP/1.1 200 OK │
│ │
│ 4. Does nginx config have correct port? │
│ cat /etc/nginx/sites-enabled/<app-name> │
│ Check: proxy_pass http://127.0.0.1:8001; │
│ │
│ 5. Check nginx error log: │
│ tail -50 /var/log/nginx/<app-name>.error.log │
└─────────────────────────────────────────────────────────────────┘
```
### Container Won't Start
```
Problem: docker compose up fails
Check:
┌─────────────────────────────────────────────────────────────────┐
│ 1. Check compose logs: │
│ cd /var/serverkit/apps/<app-name> │
│ docker compose logs │
│ │
│ 2. Validate compose file: │
│ docker compose config │
│ │
│ 3. Check for port conflicts: │
│ docker ps --format "{{.Ports}}" │
│ netstat -tulpn | grep <port> │
└─────────────────────────────────────────────────────────────────┘
```
---
## See Also
- [Installation Guide](INSTALLATION.md)
- [API Reference](API.md)
- [Deployment Guide](DEPLOYMENT.md)