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

46 KiB

ServerKit Architecture

Deep dive into how ServerKit connects domains, apps, containers, and databases.


Table of Contents


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

# 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