Documentation Index Fetch the complete documentation index at: https://mintlify.com/egeuysall/ryva-archive/llms.txt
Use this file to discover all available pages before exploring further.
Infrastructure Architecture
Ryva’s infrastructure is designed for simplicity, reliability, and security. The platform uses Docker for containerization, Caddy as a reverse proxy, and PostgreSQL for data persistence.
Infrastructure Overview
Containerization
Docker - Container runtime
Docker Compose - Multi-container orchestration
Multi-stage builds - Optimized image sizes
Health checks - Container monitoring
Reverse Proxy
Caddy - Automatic HTTPS
Rate limiting - DDoS protection
Load balancing - Round-robin distribution
Security headers - HSTS, CSP, etc.
Database
PostgreSQL - Relational database
Supabase - Managed PostgreSQL (optional)
Connection pooling - pgxpool
Automatic migrations - On startup
Monitoring
Sentry - Error tracking
Health checks - Caddy + Docker
Logging - Structured JSON logs
Metrics - Request/response metrics
System Architecture Diagram
Internet
│
│ HTTPS (443)
│
┌──────────▼──────────┐
│ Caddy Proxy │
│ (Port 80, 443) │
│ │
│ • SSL Termination │
│ • Rate Limiting │
│ • Load Balancing │
│ • Health Checks │
│ • Security Headers │
│ • Compression │
└──────────┬──────────┘
│
│ HTTP (8080)
│
┌──────────▼──────────┐
│ Go API Backend │
│ (Port 8080) │
│ │
│ • Chi Router │
│ • JWT Auth │
│ • Business Logic │
│ • API Endpoints │
└──────────┬──────────┘
│
│ pgx/v5
│
┌──────────▼──────────┐
│ PostgreSQL │
│ (Port 5432) │
│ │
│ • User data │
│ • Organizations │
│ • Subscriptions │
└─────────────────────┘
External Services
─────────────────
│ Supabase Auth
│ Stripe Payments
│ Resend Email
│ Sentry Monitoring
Docker Architecture
Multi-Stage Builds
Both frontend and backend use multi-stage Docker builds for optimal image sizes.
Backend Dockerfile
The API Dockerfile (apps/api/Dockerfile:1-50) produces a minimal ~20MB image:
# Build stage
FROM golang:1.25-alpine AS builder
RUN apk add --no-cache git
WORKDIR /app
# Cache dependencies
COPY go.mod go.sum ./
RUN go mod download && go mod verify
# Build binary
COPY . .
ARG BUILD_VERSION=1.0.0
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -ldflags= "-s -w -X 'main.version=${BUILD_VERSION}'" \
-o api ./cmd/server/main.go
# Runtime stage
FROM alpine:3.20
# Install runtime dependencies
RUN apk add --no-cache ca-certificates curl \
&& rm -rf /var/cache/apk/*
# Create non-root user
RUN adduser -D -s /bin/sh appuser
USER appuser
WORKDIR /app
# Copy binary from builder
COPY --from=builder /app/api .
# Health check
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1
STOPSIGNAL SIGTERM
CMD [ "./api" ]
Key Features :
Two-stage build : Separates build and runtime environments
Small image : Alpine-based final image (~20MB)
Security : Runs as non-root user
Health checks : Built-in health monitoring
Layer caching : Optimized for fast rebuilds
Docker Compose Configurations
Ryva provides two Docker Compose configurations:
docker-compose.local.yml - Local Development
Used for local development with hot reload: services :
backend :
build :
context : ./apps/api
dockerfile : Dockerfile
image : ryva-backend:local
restart : always
env_file :
- ./apps/api/.env
- .env.docker
environment :
GO_ENV : development
PORT : 8080
healthcheck :
test : [ 'CMD' , 'curl' , '-f' , 'http://localhost:8080/health' ]
interval : 10s
timeout : 5s
retries : 3
mem_limit : 512m
cpus : 0.5
networks :
- app_net
caddy :
build :
context : ./infra/caddy
dockerfile : Dockerfile
ports :
- '80:80'
- '443:443'
- '443:443/udp' # HTTP/3
volumes :
- ./infra/caddy/Caddyfile.local:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
depends_on :
backend :
condition : service_healthy
networks :
- app_net
networks :
app_net :
driver : bridge
volumes :
caddy_data :
caddy_config :
Usage :docker compose -f docker-compose.local.yml up -d
docker-compose.yml - Production Deployment
Used for production with pre-built images from GHCR: services :
backend :
image : ghcr.io/${GITHUB_REPOSITORY_OWNER}/ryva-backend:${IMAGE_TAG}
restart : always
env_file :
- ./apps/api/.env
environment :
GO_ENV : production
PORT : 8080
healthcheck :
test : [ 'CMD' , 'wget' , '--quiet' , '--tries=1' , '-O' , '-' , 'http://localhost:8080/health' ]
interval : 10s
timeout : 5s
retries : 3
start_period : 10s
mem_limit : 512m
cpus : 0.5
networks :
- app_net
logging :
driver : json-file
options :
max-size : '10m'
max-file : '3'
caddy :
image : ghcr.io/${GITHUB_REPOSITORY_OWNER}/ryva-caddy:${CADDY_IMAGE_TAG:-latest}
restart : always
ports :
- '80:80'
- '443:443'
- '443:443/udp'
volumes :
- ./infra/caddy/Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
- caddy_logs:/var/log/caddy
depends_on :
backend :
condition : service_healthy
networks :
- app_net
Usage :
Health Checks
Both backend and Caddy containers have built-in health checks to ensure service availability.
Backend Health Check :
healthcheck :
test : [ 'CMD' , 'curl' , '-f' , 'http://localhost:8080/health' ]
interval : 10s
timeout : 5s
retries : 3
start_period : 10s
Health Endpoint (/health):
r . Get ( "/health" , func ( w http . ResponseWriter , r * http . Request ) {
httputil . Success ( w , r , map [ string ] string {
"status" : "healthy" ,
})
})
Caddy Reverse Proxy
Why Caddy?
Automatic HTTPS Caddy automatically obtains and renews SSL certificates from Let’s Encrypt
Simple Configuration Caddyfile syntax is more intuitive than Nginx or Apache
Built-in Rate Limiting Native rate limiting without additional modules
Modern Protocols HTTP/2 and HTTP/3 (QUIC) support out of the box
Caddyfile Configuration
The production Caddyfile (infra/caddy/Caddyfile:1-134) provides comprehensive proxy features:
# Global options
{
email hi@ryva.dev
grace_period 30s
admin off # Disable admin API in production
}
# API domain
api.ryva.dev {
# Rate limiting per endpoint
rate_limit {
distributed {
read_interval 5s
write_interval 5s
}
# Waitlist endpoint (more restrictive)
zone api_waitlist {
match {
path /v1/waitlist*
}
key {remote_host}
events 5
window 1m
burst 10
}
# General API (less restrictive)
zone api_general {
match {
not path /v1/waitlist*
}
key {remote_host}
events 100
window 1m
burst 150
}
jitter 0.1
}
# Reverse proxy to Go API
reverse_proxy backend:8080 {
# Health checks
health_uri /health
health_interval 10s
health_timeout 5s
health_status 2xx
# Load balancing
lb_policy round_robin
lb_try_duration 5s
lb_try_interval 1s
# Failure handling
fail_duration 30s
max_fails 3
unhealthy_status 5xx
# Timeouts
transport http {
dial_timeout 5s
response_header_timeout 30s
read_timeout 30s
write_timeout 30s
keepalive 90s
max_conns_per_host 100
}
# Request headers
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
header_up X-Request-ID {request_id}
}
# Security headers
header {
# CORS
Access-Control-Allow-Origin "https://ryva.dev"
Access-Control-Allow-Methods "GET, POST, PUT, PATCH, DELETE, OPTIONS"
Access-Control-Allow-Headers "Content-Type, Authorization, X-Request-ID"
Access-Control-Max-Age 86400
Access-Control-Allow-Credentials true
# Security
X-Content-Type-Options "nosniff"
X-Frame-Options "DENY"
X-XSS-Protection "1; mode=block"
Referrer-Policy "strict-origin-when-cross-origin"
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
# Remove server identification
-Server
# CSP
Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; object-src 'none'"
}
# Request body limit (10MB)
request_body {
max_size 10MB
}
# Compression
encode zstd gzip
# Logging
log {
output file /var/log/caddy/api-access.log {
roll_size 100mb
roll_keep 5
roll_keep_for 720h
}
format json
level INFO
}
}
Rate Limiting Strategy
Caddy’s rate limiting is applied at the edge before requests reach the backend, providing DDoS protection.
Waitlist Endpoint (more restrictive):
5 requests per minute per IP
Burst allowance: 10 requests
Purpose: Prevent spam signups
General API (less restrictive):
100 requests per minute per IP
Burst allowance: 150 requests
Purpose: Normal API usage
Configuration :
rate_limit {
zone api_waitlist {
match { path /v1/waitlist* }
key {remote_host}
events 5
window 1m
burst 10
}
}
Load Balancing
Caddy supports multiple backend instances with health checks:
reverse_proxy backend:8080 backend2:8080 {
lb_policy round_robin # Distribute requests evenly
lb_try_duration 5s # Retry for 5 seconds
lb_try_interval 1s # Wait 1 second between retries
fail_duration 30s # Mark unhealthy for 30 seconds
max_fails 3 # After 3 consecutive failures
health_uri /health # Health check endpoint
health_interval 10s # Check every 10 seconds
}
Caddy automatically adds security headers to all responses:
Header Value Purpose Strict-Transport-Securitymax-age=31536000Force HTTPS for 1 year X-Content-Type-OptionsnosniffPrevent MIME sniffing X-Frame-OptionsDENYPrevent clickjacking X-XSS-Protection1; mode=blockEnable XSS filter Referrer-Policystrict-origin-when-cross-originControl referrer info Content-Security-Policy(see config) Prevent XSS attacks
Database Infrastructure
PostgreSQL Options
Ryva supports two PostgreSQL deployment options:
Pros :
Fully managed (no DevOps required)
Built-in auth with JWT
Automatic backups
Real-time subscriptions
Connection pooling (pgBouncer)
Free tier available
Configuration :DATABASE_URL=postgresql://user:pass@db.supabase.co:5432/postgres
SUPABASE_URL=https://xxx.supabase.co
SUPABASE_ANON_KEY=xxx
JWT_SECRET=xxx
Pros :
Full control
Lower cost at scale
Custom extensions
No vendor lock-in
Docker Compose :services :
postgres :
image : postgres:16-alpine
restart : always
environment :
POSTGRES_USER : ryva
POSTGRES_PASSWORD : ${DB_PASSWORD}
POSTGRES_DB : ryva
volumes :
- postgres_data:/var/lib/postgresql/data
healthcheck :
test : [ 'CMD' , 'pg_isready' , '-U' , 'ryva' ]
interval : 10s
timeout : 5s
retries : 5
networks :
- app_net
volumes :
postgres_data :
Connection Pooling
The Go API uses pgxpool for efficient connection management:
func NewPool ( ctx context . Context , config Config ) ( * pgxpool . Pool , error ) {
poolConfig , err := pgxpool . ParseConfig ( getDatabaseURL ())
// Connection pool settings
poolConfig . MaxConns = 25 // Maximum connections
poolConfig . MinConns = 5 // Minimum idle connections
poolConfig . MaxConnLifetime = time . Hour // Recycle after 1 hour
poolConfig . MaxConnIdleTime = 30 * time . Minute // Close idle after 30 min
// Disable prepared statements in development (for compatibility)
if config . IsDevelopment {
poolConfig . ConnConfig . DefaultQueryExecMode = pgx . QueryExecModeSimpleProtocol
}
pool , err := pgxpool . NewWithConfig ( ctx , poolConfig )
if err != nil {
return nil , err
}
// Verify connection
if err := pool . Ping ( ctx ); err != nil {
return nil , err
}
return pool , nil
}
Automatic Migrations
Migrations run automatically when the API starts:
API reads db/migrations/ directory
Checks migration status table
Runs pending .up.sql files in order
Records completed migrations
Creating a Migration :
make db-create-migration name=add_users_table
This creates:
001_add_users_table.up.sql - Forward migration
001_add_users_table.down.sql - Rollback migration
Monitoring & Logging
Sentry Integration
Backend (Go):
import " github.com/getsentry/sentry-go "
func main () {
err := sentry . Init ( sentry . ClientOptions {
Dsn : os . Getenv ( "SENTRY_DSN" ),
Environment : getEnvironment (),
TracesSampleRate : 1.0 ,
})
defer sentry . Flush ( 2 * time . Second )
}
Frontend (Next.js):
import * as Sentry from '@sentry/nextjs' ;
Sentry . init ({
dsn: process . env . NEXT_PUBLIC_SENTRY_DSN ,
environment: process . env . NODE_ENV ,
tracesSampleRate: 1.0 ,
});
Structured Logging
Backend :
logger . Info ( ctx , "Server starting" , map [ string ] any {
"port" : port ,
"environment" : env ,
})
logger . Error ( ctx , "Failed to process request" , err , map [ string ] any {
"user_id" : userID ,
"request_id" : requestID ,
})
Caddy (JSON logs):
{
"level" : "info" ,
"ts" : 1234567890 ,
"msg" : "handled request" ,
"request" : {
"method" : "GET" ,
"uri" : "/v1/auth/me" ,
"proto" : "HTTP/2.0"
},
"duration" : 0.123 ,
"status" : 200
}
Deployment Workflow
CI/CD Pipeline (GitHub Actions)
name : Deploy
on :
push :
branches : [ master ]
jobs :
deploy :
runs-on : ubuntu-latest
steps :
- uses : actions/checkout@v4
# Build and push Docker images
- name : Build Backend
run : |
docker build -t ghcr.io/${{ github.repository }}/backend:${{ github.sha }} apps/api
docker push ghcr.io/${{ github.repository }}/backend:${{ github.sha }}
# Deploy to production
- name : Deploy
run : |
ssh ${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }} \
'cd /app && docker compose pull && docker compose up -d'
Deployment Steps
Build : GitHub Actions builds Docker images
Push : Images pushed to GHCR
Deploy : SSH to server, pull images, restart containers
Health Check : Caddy verifies backend health
Monitor : Sentry tracks errors
Make Commands
The root Makefile provides infrastructure commands:
make docker-build # Build all Docker images
make docker-up # Start containers (production)
make docker-down # Stop containers
make docker-logs # View container logs
make docker-clean # Remove containers and volumes
Best Practices
Run containers as non-root users
Use minimal base images (Alpine)
Scan images for vulnerabilities
Keep images up to date
Use multi-stage builds
Set memory limits (mem_limit)
Set CPU limits (cpus)
Configure health checks
Monitor resource usage
Use multiple backend instances
Enable Caddy health checks
Configure automatic restarts
Implement graceful shutdown
Regular database backups
Store backups off-site
Test restore procedures
Version control migrations
Next Steps
Architecture Overview Return to high-level architecture overview
Deployment Guide Detailed Docker deployment guide