Sandbox

Architecture Diagram

Local Machine
macOS (Bash)
Development Host
VS Code
IDE + Kilo Agent
Source Control
GitHub
Bagilov/bagilov_tech · master
Python 3.12
UV · .venv
Deploy
Deployment Pipeline:
Git Push
GitHub origin/master
scp Sync
docker-deploy.sh
docker compose
build & up -d
Health Check
curl localhost:8000
SSH: macOS → Yandex Cloud (158.160.201.2):
SSH + scp
kluch + strict-checking
Remote Dir
~/bagilov_tech_docker
Containers
6 services
Kilo Code Agent
Kilo Code Wrapper
AI Coding Assistant with Custom Prompts
System Core identity & rules
Task Specific coding tasks
Context Project structure
Tool Available tools
Style Code conventions
Security Security constraints
Python Stack
Python 3.12
Latest LTS
UV Package Manager
Fast pip alternative
.venv
Virtual Environment
Core Libraries:
FastAPI
Web Framework
uvicorn
ASGI Server
Jinja2
Templates
starlette
ASGI Framework
pydantic
Data Validation
Coding Principles:
  • if __name__ == "__main__" — all entry points
  • Type hints — full annotation
  • Async/await — where applicable
  • Dependency injection — FastAPI Depends
  • Environment variables — .env files only
  • No secrets in code — gitignore enforced
  • Logging over print — structured logs
  • Test coverage >80% — pytest + coverage
Connection
SSH Tunnel: macOS → Yandex Cloud (158.160.201.2)
scp: File sync (docker-deploy.sh)
Port forward: 80, 443 → nginx → app:8000
Yandex Cloud
Compute VM
Ubuntu 22.04
nginx
Reverse Proxy
SSL/TLS
GlobalSign
bagilov.tech
Production
Hosting
Yandex Compute Cloud
VM: 2 vCPU, 2GB RAM
Region: ru-central1-d
IP: 158.160.201.2
Domain
bagilov.tech
SSL Certificates:
Primary Certificate
cert.crt (nginx volume · GlobalSign)
Intermediate CA
intermediate_pem_globalsign_ssl_dv_free_1.crt
Root CA
root_pem_globalsign_ssl_dv_free_1.crt
Private Key
key.txt (secured)
Deployment Workflow:
  • ./docker-deploy.sh → scp files + docker compose to Yandex Cloud VM
  • nginx reload → Apply configuration changes
  • SSL termination → nginx container (GlobalSign certs)
  • Port 8000 → FastAPI app via nginx proxy_pass
  • Firewall → Only ports 80, 443, 22 open
Docker Deployment (Production)
Nginx Container
Reverse Proxy + SSL
Ports: 80, 443
SSL Termination
Proxy to app:8000
Volume: nginx-ssl
App Container
FastAPI + Python 3.12
Port: 8000 (internal)
Python 3.12 + UV
Volumes: static, logs, db
Health check
app-static
Static files
app-logs
Application logs
blog-admin-data
Auth (.env)
dd-engine-db
SQLite database
nginx-ssl
SSL certificates
nginx-logs
JSON access logs
loki-data
Log storage (7d)
grafana-data
Dashboards + users
Supporting Services:
Grafana
:3000 · dashboards
Prometheus
:9090 · metrics
Netdata
:19999 · monitoring
Vision
:8100 · fixtures
Loki
:3100 · log store
Promtail
log shipper → Loki
ntfy
push alerts → phone
AiTunnel LLM
mistral-nemo · tags & chat
Docker Commands:
$ docker compose build
$ docker compose up -d
$ docker compose logs -f
$ docker compose down
Observability & Analytics
A request-visibility pipeline was added, reusing Grafana as the single dashboard without shipping data to a third party:
nginx (JSON log) → Promtail → Loki → Grafana
Structured access logs: nginx writes one JSON object per request (path, status, referer, user-agent, latency) to a persistent nginx-logs volume instead of stdout.
Log aggregation: Promtail tails the log and ships it to Loki (filesystem storage, 7-day retention), labelled by method and status.
Dashboards as code: the Loki datasource and the Nginx Analytics dashboard (unique visitors, top pages, status codes, referrers, live request log) are provisioned from files in grafana/provisioning/.
Single pane of glass: metrics (Prometheus), logs (Loki) and system stats (Netdata) are all reachable from one Grafana at /grafana/.
Alerts to the phone: Grafana routes alerts to a self-hosted ntfy server on ntfy.bagilov.tech; a small relay turns the webhook JSON into a readable push message.
AI: tagging, digest & page chat — AiTunnel
publish → app → AiTunnel LLM → auto_tags → UI (purple line)
One key, many models: an AiTunnel key (sk-aitunnel-…) is kept in the server .env only, never committed.
On publish: the model picks tags from our existing vocabulary in the background; author tags win, and additions are stored as auto_tags.
Cheap by design: mistral-nemo (~3.8/6 ₽ per 1M tokens) with provider: {sort: price}, truncated text, temperature 0 and a content-hash cache.