Skip to main content

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:
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
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:
docker compose up -d

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
}

Security Headers

Caddy automatically adds security headers to all responses:
HeaderValuePurpose
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:
  1. API reads db/migrations/ directory
  2. Checks migration status table
  3. Runs pending .up.sql files in order
  4. 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

  1. Build: GitHub Actions builds Docker images
  2. Push: Images pushed to GHCR
  3. Deploy: SSH to server, pull images, restart containers
  4. Health Check: Caddy verifies backend health
  5. 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