|
4 | 4 |
|
5 | 5 | The Towlion platform runs on a single Debian server. Applications run as Docker containers and share a set of core infrastructure services. |
6 | 6 |
|
| 7 | +```mermaid |
| 8 | +graph TB |
| 9 | + User[User Browser] -->|HTTPS| Caddy |
| 10 | +
|
| 11 | + subgraph Server["Debian 12 Server"] |
| 12 | + subgraph Docker["Docker / towlion network"] |
| 13 | + Caddy["Caddy :80/:443"] |
| 14 | +
|
| 15 | + Caddy --> App1["App 1 :8000"] |
| 16 | + Caddy --> App2["App 2 :8000"] |
| 17 | + Caddy --> App3["App 3 :8000"] |
| 18 | + Caddy --> Grafana["Grafana :3000"] |
| 19 | +
|
| 20 | + App1 --> Postgres[("PostgreSQL :5432")] |
| 21 | + App2 --> Postgres |
| 22 | + App3 --> Postgres |
| 23 | + App1 --> Redis[("Redis :6379")] |
| 24 | + App1 --> MinIO["MinIO :9000"] |
| 25 | +
|
| 26 | + Promtail["Promtail"] --> Loki["Loki :3100"] |
| 27 | + Loki --> Grafana |
| 28 | +
|
| 29 | + Prometheus["Prometheus :9090"] -.->|optional| Grafana |
| 30 | + cAdvisor["cAdvisor :8080"] -.->|optional| Prometheus |
| 31 | + NodeExp["Node Exporter :9100"] -.->|optional| Prometheus |
| 32 | + end |
| 33 | + end |
7 | 34 | ``` |
8 | | - Internet |
9 | | - │ |
10 | | - ▼ |
11 | | - DNS |
12 | | - │ |
13 | | - ▼ |
14 | | - Reverse Proxy |
15 | | - (Caddy) |
16 | | - / | \ |
17 | | - ▼ ▼ ▼ |
18 | | - App 1 App 2 App 3 |
19 | | - │ |
20 | | - ▼ |
21 | | - Shared Services |
22 | | - │ |
23 | | - ┌────────┼─────────┬─────────┐ |
24 | | - ▼ ▼ ▼ ▼ |
25 | | -PostgreSQL Redis MinIO Workers |
26 | | -``` |
| 35 | + |
| 36 | +Dashed lines indicate optional metrics services (enabled via `COMPOSE_PROFILES=metrics`). |
27 | 37 |
|
28 | 38 | ## Technology Stack |
29 | 39 |
|
@@ -214,6 +224,71 @@ pg_dump → /data/backups |
214 | 224 |
|
215 | 225 | Backups can be synced to remote storage using `rclone`. |
216 | 226 |
|
| 227 | +## CI/CD Flow |
| 228 | + |
| 229 | +```mermaid |
| 230 | +graph LR |
| 231 | + Push["git push to main"] --> Actions["GitHub Actions"] |
| 232 | +
|
| 233 | + subgraph Actions["GitHub Actions"] |
| 234 | + Test["Test Job"] --> Deploy["Deploy Job"] |
| 235 | + end |
| 236 | +
|
| 237 | + Deploy -->|SSH| Server["Server"] |
| 238 | +
|
| 239 | + subgraph Server["Server Operations"] |
| 240 | + Pull["git pull"] --> Build["docker compose up -d --build"] |
| 241 | + Build --> Trivy["Trivy image scan"] |
| 242 | + Build --> Migrate["Alembic migrate"] |
| 243 | + Migrate --> CaddyWrite["Write Caddyfile"] |
| 244 | + CaddyWrite --> Reload["Caddy reload"] |
| 245 | + end |
| 246 | +``` |
| 247 | + |
| 248 | +## Backup and Restore Flow |
| 249 | + |
| 250 | +```mermaid |
| 251 | +graph LR |
| 252 | + subgraph Backup["Daily Backup (cron 02:00)"] |
| 253 | + Cron["cron"] --> Script["backup-postgres.sh"] |
| 254 | + Script --> PgDump["pg_dump per database"] |
| 255 | + PgDump --> Compress["gzip"] |
| 256 | + Compress --> Store["/data/backups/postgres/"] |
| 257 | + Store --> Prune["Prune backups > 7 days"] |
| 258 | + end |
| 259 | +
|
| 260 | + subgraph Restore["Manual Restore"] |
| 261 | + List["List backups"] --> Choose["Choose backup file"] |
| 262 | + Choose --> RestoreScript["restore-postgres.sh"] |
| 263 | + RestoreScript --> Drop["Drop + recreate DB"] |
| 264 | + Drop --> Import["Import from backup"] |
| 265 | + Import --> Verify["Verify data"] |
| 266 | + end |
| 267 | +``` |
| 268 | + |
| 269 | +## Preview Environment Flow |
| 270 | + |
| 271 | +```mermaid |
| 272 | +graph TB |
| 273 | + subgraph Create["PR Opened / Updated"] |
| 274 | + PR["Pull Request"] --> Actions["GitHub Actions"] |
| 275 | + Actions -->|SSH| Clone["Clone to /opt/apps/app-pr-N/"] |
| 276 | + Clone --> Schema["Create PR-specific DB schema"] |
| 277 | + Schema --> BuildPR["docker compose up -d --build"] |
| 278 | + BuildPR --> CaddyPR["Write pr-N.preview.domain.caddy"] |
| 279 | + CaddyPR --> ReloadPR["Caddy reload"] |
| 280 | + ReloadPR --> Comment["Comment preview URL on PR"] |
| 281 | + end |
| 282 | +
|
| 283 | + subgraph Cleanup["PR Closed / Merged"] |
| 284 | + Closed["PR closed"] --> StopContainers["Stop + remove containers"] |
| 285 | + StopContainers --> DropSchema["Drop PR schema"] |
| 286 | + DropSchema --> RemoveCaddy["Remove .caddy file"] |
| 287 | + RemoveCaddy --> ReloadClean["Caddy reload"] |
| 288 | + ReloadClean --> RemoveDir["Remove /opt/apps/app-pr-N/"] |
| 289 | + end |
| 290 | +``` |
| 291 | + |
217 | 292 | ## Docker Compose Services |
218 | 293 |
|
219 | 294 | Each application lives in its own GitHub repository under the `towlion` organization. The server runs two layers of Compose services: |
@@ -249,9 +324,8 @@ services: |
249 | 324 | env_file: .env |
250 | 325 | frontend: |
251 | 326 | build: ./frontend |
252 | | - celery-worker: |
253 | | - build: ./app |
254 | | - command: celery -A app.tasks worker |
255 | 327 | ``` |
256 | 328 |
|
| 329 | +Celery workers are opt-in. To add background task processing, add a `celery-worker` service to your compose file. See the [app-template README](https://github.com/towlion/app-template#background-tasks) for instructions. |
| 330 | + |
257 | 331 | For single-app self-hosting (fork scenario), a repository may bundle platform services in its own Compose file so it can run standalone. |
0 commit comments