Ski Vesotel
Multi-tenant SaaS platform for comprehensive ski school management. It allows teachers to register their work shifts (private lessons, courses) and administration to manage payroll, dynamic rates, and users with granular role control.
System Architecture
The system follows a containerized microservices architecture with 4 main services orchestrated via Docker Compose:
Frontend
- Framework: Next.js 14 (App Router)
- Port: 3000 → 3010
- Build: GHCR Image
Backend API
- Framework: FastAPI + SQLAlchemy
- Database: PostgreSQL 16
- Auth: JWT RS256 + 2FA
Redis Cache
In-memory cache and session management. Configured with LRU (Least Recently Used) eviction policy and 256MB limit for rate limiting and temporary states.

Advanced SaaS Features
The system implements advanced multi-tenant SaaS patterns that allow multiple companies to operate independently on the same instance.
Data Structure (PostgreSQL 16)
Relational database managed via SQLAlchemy ORM and Alembic for schema version control. Makes extensive use of JSONB columns for dynamic configurations, allowing each company to customize rates, shift types, and features without modifying the schema.
Enumerated Types
Schema Tables
users
Central registered users table. Stores credentials, global role, 2FA status and default company. Each user has a UUID as primary key.
Schema Columns
| Column | Constraints / Type |
|---|---|
| id | PK, uuid |
| unique | |
| hashed_password | — |
| first_name | — |
| last_name | — |
| role | userrole enum |
| is_active | — |
| default_company_id | FK → companies |
| must_change_password | — |
| is_2fa_enabled | — |
| otp_secret | — |
| created_at | — |
| updated_at | — |
CI/CD Pipeline
Deployment is fully automated via GitHub Actions. Each push to the main branch triggers a 2-stage pipeline:
1. Build & Push (GitHub-hosted runner)
Docker images for frontend and backend are built and pushed to GitHub Container Registry (GHCR) as private images.
2. Deploy (Self-hosted runner)
A self-hosted runner on my home server (192.168.x.x) pulls the new images from GHCR, runs database migrations with Alembic, and brings up the updated containers.
3. Automatic Migrations
Before starting services, 'alembic upgrade head' is run in a temporary container to safely apply database schema changes.
4. Cleanup
After deployment, 'docker image prune' is executed to remove obsolete images and free disk space on the production server.
Images are hosted on GitHub Container Registry (GHCR), which allows storing private Docker images linked to the repository, eliminating the need for a self-hosted registry.
Security and Deployment
Public access follows a Defense in Depth scheme with 3 layers, designed to expose the service securely.
Layer 1: Reverse Proxy
Nginx Proxy Manager with Let's Encrypt SSL certificates. Terminates the secure connection and redirects to the internal service.
Layer 2: Isolated Network
Services operate in a dedicated MikroTik subnet, isolated from the home network. Traffic between VMs is managed through firewall rules.
Layer 3: Docker Networks
Isolated Docker bridge networks (app and db). Containers communicate with each other without exposing unnecessary ports. Sensitive variables injected via GitHub Secrets.
Note: This is a real production project used by a ski school. Complete documentation includes sensitive configurations that have been omitted for security.