Revel Infrastructure
June 23, 2026 ยท View on GitHub
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.
๐ Related Repositories
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
Overview
This is a consolidated Docker Compose setup that includes:
Application Services
- 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
Infrastructure Services
- Caddy - Reverse proxy with automatic HTTPS
- PostgreSQL (PostGIS) - Primary database
- PgBouncer - Connection pooler for PostgreSQL
- Redis - Cache and message broker
Observability Stack
- 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
Security
- ClamAV - Antivirus scanning for uploaded files
Quick Start
-
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]
Configuration
Environment Variables
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)
Domain Configuration
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.
Optional services (Compose profiles)
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.
Directory Structure
.
โโโ 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)
Apple Wallet Pass Certificates
The application supports generating Apple Wallet passes for event tickets. This requires Apple Developer certificates to be placed in the certs/ directory.
Required Files
certs/
โโโ pass_certificate.pem # Apple Pass Type ID certificate
โโโ pass_key.pem # Private key for the certificate
โโโ wwdr.pem # Apple Worldwide Developer Relations certificate
Setting File Permissions
IMPORTANT: 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.pem
Why 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.
Volumes
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
Networking
All services run on a dedicated bridge network called revel_network.
Deployment
Server Specifications
Revel is deployed on a Hetzner CCX33 instance:
- CPU: 8 vCPU
- RAM: 32 GB
- Disk: 240 GB
Initial Deployment
- 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
Updates
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 update
๐จ Critical: SvelteKit Streaming SSR
IMPORTANT: The Revel frontend uses SvelteKit with streaming Server-Side Rendering (SSR). Improper Caddy configuration will cause pages to hang indefinitely with "eternal loading" states.
โ ๏ธ Reverse Proxy Configuration
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
}
}
}
Why This Matters
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
Symptoms of Misconfiguration
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
Testing Your Configuration
# 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!)
Verified Working Configuration
See the Caddyfile in this repository for the correct production configuration. All reverse_proxy blocks for the frontend must not include flush_interval -1.
Monitoring
Access monitoring tools:
- Grafana: https://grafana.letsrevel.io
- Prometheus (metrics): Available internally at
revel_prometheus:9090
Maintenance
Database Backups
docker compose exec revel_postgres pg_dump -U $DB_USER $DB_NAME > backup.sql
Logs
View logs for a specific service:
docker compose logs -f [service_name]
View all logs:
docker compose logs -f
Scaling Celery Workers
To scale up celery workers:
docker compose up -d --scale celery_default=4
Troubleshooting
Certificate Permission Errors
If 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/*.pem
Verify permissions are correct:
ls -la /path/to/revel/infra/certs/
All files should show -rw-r--r-- (644 permissions).
Check service health
docker compose ps
Restart a service
docker compose restart [service_name]
Reset everything (CAUTION: destroys data)
docker compose down -v
Security Notes
- Change all default passwords in
.env - Never commit
.envto version control - Regularly update Docker images
- Monitor Grafana for security alerts
- Review Pushover alert notifications
Differences from Old Setup
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
License
See LICENSE file in the repository root.