This repository contains the complete infrastructure configuration for the Revel project, including all services, observability stack, and reverse proxy setup.
Self-hosting Revel? Clone this repo and run
./setup.sh— an interactive wizard that takes a fresh VPS to a running instance (slim ~2 vCPU/4 GB, or the full stack). Full walkthrough: https://docs.letsrevel.io/self-hosting. The sections below document our own production deployment and the repo mechanics.
This repository contains the infrastructure and deployment configurations for Revel. The complete platform consists of:
- revel-backend - Django REST API, business logic, database models
- revel-frontend - SvelteKit web application, user interface
- infra (this repository) - Docker Compose setup, reverse proxy, observability stack, deployment configurations
This is a consolidated Docker Compose setup that includes:
- Web - Django application (Gunicorn + Uvicorn workers)
- Frontend - Next.js/SvelteKit frontend application
- Celery Workers - Background task processing (default queue)
- Beat - Celery scheduler for periodic tasks
- Telegram Bot - Telegram bot service
- Caddy - Reverse proxy with automatic HTTPS
- PostgreSQL (PostGIS) - Primary database
- PgBouncer - Connection pooler for PostgreSQL
- Redis - Cache and message broker
- Grafana - Metrics and logs visualization
- Prometheus - Metrics collection
- Loki - Log aggregation
- Tempo - Distributed tracing
- Pyroscope - Continuous profiling
- Alloy - eBPF-based profiling collector
- postgres-exporter - PostgreSQL metrics exporter
- ClamAV - Antivirus scanning for uploaded files
-
Clone the repository
cd /path/to/revel/infra -
Create environment file
cp .env.example .env # Edit .env with your actual configuration -
Start all services
docker compose up -d
-
Check service status
docker compose ps
-
View logs
docker compose logs -f [service_name]
All configuration is done through the .env file. See .env.example for all available options.
Key variables to configure:
- Database credentials (
DB_*) - Django secret key (
SECRET_KEY) - Google SSO for the Django admin login (
GOOGLE_SSO_*) - Grafana admin credentials (
GRAFANA_ADMIN_*) - Pushover notifications (
PUSHOVER_USER_KEY,PUSHOVER_APP_TOKEN)
Domains are parameterized via .env (FRONTEND_DOMAIN, API_DOMAIN, DOCS_DOMAIN,
GRAFANA_DOMAIN) and consumed by the Caddyfile. Three Caddyfile variants ship:
Caddyfile— Cloudflare-aware + legacy redirects (production default)Caddyfile.cloudflare— self-host behind Cloudflare's proxyCaddyfile.generic— self-host with Caddy as the edge (no Cloudflare)
Select a variant with CADDYFILE_PATH in .env (the wizard sets it). The defaults in
Caddyfile resolve to the production domains, so production needs no .env change.
DNS setup and the Cloudflare orange-cloud caveat are documented at
https://docs.letsrevel.io/self-hosting.
The core (web, frontend, celery, beat, postgres, pgbouncer, redis) always runs.
Observability, antivirus (ClamAV), and the Telegram bot are gated behind Compose
profiles via COMPOSE_PROFILES in .env (e.g. observability,antivirus,telegram).
An empty value runs the slim core only.
.
├── docker-compose.yml # Main compose file
├── Caddyfile # Reverse proxy configuration
├── .env # Environment variables (create from .env.example)
├── .env.example # Environment variables template
├── observability/ # Observability stack configurations
│ ├── alloy-config.alloy
│ ├── grafana-datasources.yaml
│ ├── loki-config.yaml
│ ├── prometheus-config.yml
│ └── tempo-config.yaml
├── media/ # User-uploaded media files
├── geo-data/ # Geographic data files
├── sentinel/ # LLM sentinel data
└── certs/ # Apple Wallet certificates (optional)
The application supports generating Apple Wallet passes for event tickets. This requires Apple Developer certificates to be placed in the certs/ directory.
certs/
├── pass_certificate.pem # Apple Pass Type ID certificate
├── pass_key.pem # Private key for the certificate
└── wwdr.pem # Apple Worldwide Developer Relations certificateIMPORTANT: All certificate files must be readable by the Docker container (which runs as non-root user appuser):
chmod 644 /path/to/revel/infra/certs/pass_certificate.pem
chmod 644 /path/to/revel/infra/certs/pass_key.pem
chmod 644 /path/to/revel/infra/certs/wwdr.pemWhy 644 for all files (including the private key)?
644(rw-r--r--): Owner can read/write, others can read- The Docker container runs as
appuser(non-root), which needs read access to mounted files - While
600would be ideal for private keys in traditional setups, Docker volume mounts require readable permissions when the container user differs from the host file owner - The files are only accessible within the server's filesystem (not exposed externally)
If you see errors like PermissionError: [Errno 13] Permission denied: '/app/certs/pass_certificate.pem', verify the file permissions are set correctly.
The following Docker volumes are created for persistent data:
caddy_data- Caddy data (SSL certificates)caddy_config- Caddy configuration cacherevel_postgres_data- PostgreSQL databaseredis_data- Redis persistenceloki_data- Log storagetempo_data- Trace storageprometheus_data- Metrics storagepyroscope_data- Profiling dataalloy_data- Alloy configurationgrafana_data- Grafana dashboards and settings
All services run on a dedicated bridge network called revel_network.
Revel is deployed on a Hetzner CCX33 instance:
- CPU: 8 vCPU
- RAM: 32 GB
- Disk: 240 GB
- Set up your server with Docker and Docker Compose
- Clone this repository
- Configure your
.envfile - Ensure DNS records point to your server:
- beta.letsrevel.io
- beta-api.letsrevel.io
- grafana.letsrevel.io
- Start services:
docker compose up -d - Caddy will automatically provision SSL certificates
To update a service to the latest image:
docker compose pull [service_name]
docker compose up -d [service_name]To update all services:
./deploy.sh updateIMPORTANT: The Revel frontend uses SvelteKit with streaming Server-Side Rendering (SSR). Improper Caddy configuration will cause pages to hang indefinitely with "eternal loading" states.
The Caddyfile MUST NOT buffer responses. Buffering breaks SvelteKit's streaming SSR.
❌ WRONG - DO NOT USE:
beta.letsrevel.io {
reverse_proxy revel_frontend:3000 {
flush_interval -1 # ❌ Buffers entire response - BREAKS SVELTEKIT!
}
}✅ CORRECT - Streaming enabled:
beta.letsrevel.io {
encode zstd gzip
reverse_proxy revel_frontend:3000 {
# No flush_interval = streaming enabled by default ✅
transport http {
keepalive 90s
keepalive_idle_conns 32
max_conns_per_host 100
}
}
}With flush_interval -1 (buffering enabled):
- ❌ Caddy holds the ENTIRE HTML response in memory
- ❌ Browser receives nothing until SSR fully completes
- ❌ Pages appear to "hang" or show eternal loading spinner
- ❌ Poor user experience, especially on slow connections
- ❌ Potential timeout issues on large pages
Without buffering (default):
- ✅ Caddy streams HTML as SvelteKit generates it
- ✅ Browser receives and renders content progressively
- ✅ Faster perceived load times (better TTFB)
- ✅ Better user experience
- ✅ Support for large pages without timeouts
If you experience these issues, check the Caddyfile for response buffering:
- Pages show eternal loading spinner
- Network tab shows request pending for many seconds
- HTML arrives all at once after a long delay
- Time-to-first-byte (TTFB) equals total response time
# Test response timing - should see progressive delivery
curl -w "\nTTFB: %{time_starttransfer}s\nTotal: %{time_total}s\n" \
-o /dev/null \
"https://beta.letsrevel.io/events"
# Good: TTFB and Total are close (streaming)
# Bad: TTFB ≈ Total time (buffering!)See the Caddyfile in this repository for the correct production configuration. All reverse_proxy blocks for the frontend must not include flush_interval -1.
Access monitoring tools:
- Grafana: https://grafana.letsrevel.io
- Prometheus (metrics): Available internally at
revel_prometheus:9090
docker compose exec revel_postgres pg_dump -U $DB_USER $DB_NAME > backup.sqlView logs for a specific service:
docker compose logs -f [service_name]View all logs:
docker compose logs -fTo scale up celery workers:
docker compose up -d --scale celery_default=4If you see errors like:
PermissionError: [Errno 13] Permission denied: '/app/certs/pass_certificate.pem'
Fix the certificate file permissions:
chmod 644 /path/to/revel/infra/certs/*.pemVerify permissions are correct:
ls -la /path/to/revel/infra/certs/All files should show -rw-r--r-- (644 permissions).
docker compose psdocker compose restart [service_name]docker compose down -v- Change all default passwords in
.env - Never commit
.envto version control - Regularly update Docker images
- Monitor Grafana for security alerts
- Review Pushover alert notifications
This consolidated setup differs from the previous multi-repo setup:
- All services are in a single
docker-compose.yml - Uses a dedicated
revel_networkinstead of an externalsharednetwork - Includes Redis directly (was previously in shared services)
- Simplified volume management
- All observability services are included
- Comprehensive alerting via Prometheus and Pushover
See LICENSE file in the repository root.