Architecture
Overview
The Towlion platform runs on a single Debian server. Applications run as Docker containers and share a set of core infrastructure services.
Internet
│
▼
DNS
│
▼
Reverse Proxy
(Caddy)
/ | \
▼ ▼ ▼
App 1 App 2 App 3
│
▼
Shared Services
│
┌────────┼─────────┬─────────┐
▼ ▼ ▼ ▼
PostgreSQL Redis MinIO Workers
Technology Stack
Infrastructure
- Debian — host operating system
- Docker — container runtime
- Docker Compose — service orchestration
Networking
- Caddy — reverse proxy with automatic TLS via Let's Encrypt
Application
- FastAPI — Python backend framework
- SQLAlchemy — ORM
- Pydantic — data validation
- Alembic — database migrations
Frontend
- Next.js — React framework
- TypeScript — type-safe frontend code
Data
- PostgreSQL — primary database
- Redis — caching and job queues
- MinIO — S3-compatible object storage
Background Jobs
- Celery — async task processing (backed by Redis)
CI/CD
- GitHub Actions — automated builds and deployments
Optional Observability
- Grafana — dashboards
- Loki — log aggregation
GitHub as the Control Plane
Traditional PaaS platforms use a dedicated control plane:
Towlion replaces this with GitHub:
GitHub provides:
- CI/CD (Actions)
- Configuration storage (Secrets)
- Access control (repository permissions)
- Workflow orchestration (Actions workflows)
This eliminates the need for custom deployment dashboards or orchestration systems.
Multi-Application Hosting
A single server hosts multiple applications. Each application runs in its own container, sharing the core infrastructure services.
Domain Routing
Each application gets its own subdomain. Caddy routes traffic to the correct container.
uku.towlion.com → uku-app container
timer.towlion.com → timer-app container
lyrics.towlion.com → lyrics-app container
Example Caddy configuration:
uku.towlion.com {
reverse_proxy uku-app:8000
}
timer.towlion.com {
reverse_proxy timer-app:8000
}
storage.towlion.com {
reverse_proxy minio:9000
}
Caddy automatically provisions TLS certificates and redirects HTTP to HTTPS.
Database Strategy
A single PostgreSQL instance runs on the server. Each application uses a dedicated database for logical isolation:
Persistent Storage
All persistent data is stored under /data on the host, ensuring data survives container redeployments.
/data
├── postgres # PostgreSQL data
├── redis # Redis data
├── minio # Object storage
├── caddy/ # Caddy TLS certs and config
│ ├── data
│ └── config
├── loki # Log aggregation data
├── grafana # Dashboard data
└── backups/postgres # Database backups
Docker containers mount these directories as volumes.
Object Storage
MinIO provides S3-compatible object storage. Applications interact with it using the standard S3 API.
S3_ENDPOINT=https://storage.example.com
S3_BUCKET=uploads
S3_ACCESS_KEY=app_user
S3_SECRET_KEY=secret
Storage data lives at /data/minio.
Background Jobs
Asynchronous tasks are processed by Celery workers backed by Redis.
Typical use cases:
- Sending transactional email
- Processing file uploads
- Scheduled background tasks
Workers run as separate containers.
Transactional Email
Email is sent via external providers (e.g., Postmark, Amazon SES). The application sends email through provider APIs, with delivery handled by Celery workers.
Logging and Backups
Logging: Applications write to stdout. Container logs are captured by the Docker runtime. Optionally, logs can be aggregated with Promtail, Loki, and Grafana.
Backups: Daily database backups via cron:
Backups can be synced to remote storage using rclone.
Docker Compose Services
Each application lives in its own GitHub repository under the towlion organization. The server runs two layers of Compose services:
Platform Services (server-level)
Shared infrastructure managed at the server level, independent of any application repository:
services:
caddy:
image: caddy:2
postgres:
image: postgres:16
volumes:
- /data/postgres:/var/lib/postgresql/data
redis:
image: redis
minio:
image: minio/minio
volumes:
- /data/minio:/data
Application Services (per-repo)
Each application repository defines its own containers. These connect to the shared platform services via Docker networking:
services:
app:
build: ./app
env_file: .env
frontend:
build: ./frontend
celery-worker:
build: ./app
command: celery -A app.tasks worker
For single-app self-hosting (fork scenario), a repository may bundle platform services in its own Compose file so it can run standalone.