Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/arrozet/caret/llms.txt

Use this file to discover all available pages before exploring further.

Caret’s production environment is designed to be simple and cost-effective without sacrificing reliability. The entire backend stack runs on a single Hetzner Cloud VPS (roughly 9 EUR/month), managed by Coolify — a self-hosted open-source PaaS that handles deployments, domain bindings, TLS certificates, and container restarts. Docker Compose is the deployment unit, Cloudflare manages DNS for the caret.page domain, and Supabase Cloud provides managed PostgreSQL, authentication, and vector search outside the VPS. This setup gives a PaaS-grade developer experience at a fraction of the cost of AWS or managed Kubernetes.

Infrastructure Stack

LayerChoiceNotes
HostingHetzner Cloud VPSSingle VM, ~9 EUR/month; sufficient for the full stack
PaaSCoolify (self-hosted)Deployments, domains, TLS, logs, redeploys, and Docker Compose resources
ContainersDocker Compose (docker-compose.prod.yml)Production service topology
CI/CDGitHub Actions + CoolifyAutomatic deploys from the prod branch
DNSCloudflareManages caret.page and all subdomains
Database / AuthSupabase CloudManaged PostgreSQL, Auth, and pgvector — external to the VPS

Production Domains

All public traffic enters through Coolify’s built-in reverse proxy, which terminates TLS and routes requests to the appropriate container. No internal service ports are exposed directly to the internet.
DomainServiceNotes
caret.pageFrontendMain user-facing app
www.caret.pageFrontend aliasCNAME to caret.page
api.caret.pageAPI GatewayPublic REST API entrypoint for all /api/v1/ traffic
ws.caret.pageCollab ServicePublic WebSocket endpoint — bypasses the API Gateway
ops.caret.pageCoolify dashboardPaaS control plane for deployments and operations
The collaboration WebSocket (ws.caret.page) connects directly to the collab service and must not be routed through the API Gateway. The collab service uses a long-lived WebSocket protocol that is incompatible with the gateway’s HTTP routing layer. Misconfiguring this will break real-time editing for all users.

Setup Guide

1
Provision the Hetzner VPS and install Coolify
2
Create a new VPS on Hetzner Cloud (CX21 or higher recommended). SSH into the server and install Coolify using its official one-line installer:
3
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
4
Once installation finishes, Coolify’s dashboard is available at your server’s public IP on port 8000. Immediately secure it by setting up a domain (ops.caret.page) and enabling HTTPS. Do not expose port 8000 in the Hetzner firewall — access the dashboard only through the ops.caret.page domain once DNS and TLS are configured.
5
Point Cloudflare DNS to the VPS
6
In the Cloudflare dashboard for caret.page, create A records pointing each subdomain to the VPS public IPv4 address:
7
Record typeNameTargetA@ (caret.page)<VPS IPv4>CNAMEwwwcaret.pageAapi<VPS IPv4>Aws<VPS IPv4>Aops<VPS IPv4>
8
Use DNS-only (grey cloud) mode in Cloudflare unless you have a specific reason to enable Cloudflare proxying — Coolify handles TLS natively via ACME/Let’s Encrypt.
9
Create the Coolify project from GitHub
10
In the Coolify dashboard:
11
  • Navigate to Projects → New Project.
  • Add your GitHub repository as the source. Authorise Coolify’s GitHub app if prompted.
  • Set the Build Pack to Docker Compose.
  • Set the Compose file path to /docker-compose.prod.yml.
  • Set the Branch to prod.
  • 12
    Coolify will watch for pushes to prod and redeploy automatically.
    13
    Configure domain bindings per service
    14
    In the Coolify service settings for each container, assign the correct public domain:
    15
    ServiceDomain bindingfrontendhttps://caret.pageapi-gatewayhttps://api.caret.pagecollab-servicehttps://ws.caret.pageCoolify itselfhttps://ops.caret.page
    16
    Coolify will automatically provision TLS certificates via Let’s Encrypt for each domain once DNS propagates.
    17
    Set production environment variables in Coolify
    18
    In the Coolify environment variable editor for the project, add all required secrets. Never commit these values to the repository.
    19
    # Database — required by all backend services
    DATABASE_URL=postgresql://postgres.[ref]:[password]@aws-0-[region].pooler.supabase.com:6543/postgres
    
    # Supabase
    SUPABASE_URL=https://your-project.supabase.co
    SUPABASE_ANON_KEY=your-anon-key
    SUPABASE_SERVICE_ROLE_KEY=your-service-role-key  # auth-service and document-service
    SUPABASE_JWT_SECRET=your-jwt-secret              # collab-service
    
    # JWT signing secret shared across Node and AI services
    JWT_SECRET=a-long-random-secret
    
    # CORS — allow production origins only
    ALLOWED_ORIGINS=https://caret.page,https://www.caret.page
    
    # API Gateway rate limiting (optional — defaults shown)
    RATE_LIMIT_MAX=1000
    RATE_LIMIT_WINDOW_MINUTES=15
    
    # Frontend build-time vars (baked in at image build time)
    VITE_API_BASE_URL=https://api.caret.page/api/v1
    VITE_API_URL=https://api.caret.page/api/v1
    VITE_COLLABORATION_WS_URL=wss://ws.caret.page/document
    VITE_COLLAB_WS_URL=wss://ws.caret.page/document
    VITE_ENABLE_COLLABORATION=true
    VITE_APP_ORIGIN=https://caret.page
    VITE_SUPABASE_URL=https://your-project.supabase.co
    VITE_SUPABASE_ANON_KEY=your-anon-key
    
    # AI provider keys — at least one required
    OPENAI_API_KEY=sk-...
    ANTHROPIC_API_KEY=sk-ant-...
    OPENROUTER_API_KEY=sk-or-...
    OPENROUTER_MODEL=deepseek/deepseek-v4-flash  # default model if OPENROUTER_API_KEY is set
    
    20
    The production compose file (docker-compose.prod.yml) uses :? syntax for required variables — for example DATABASE_URL:?DATABASE_URL is required. Docker Compose will refuse to start a service if a required variable is missing or empty, preventing silent misconfiguration.
    21
    Deploy and verify health check endpoints
    22
    Trigger the first deployment from the Coolify dashboard. Once containers are running, verify each service is healthy by hitting its health endpoint:
    23
    curl https://api.caret.page/health
    curl https://ws.caret.page/health
    
    24
    Coolify’s service panel also shows container health status and recent logs for each service in real time.

    Hetzner Firewall Rules

    Configure the Hetzner Cloud firewall to allow only the required ingress. All other ports should be blocked by default.
    ProtocolPort / TypePurpose
    TCP22SSH administration
    TCP80HTTP (needed for ACME challenges)
    TCP443HTTPS / WSS public traffic
    ICMPBasic reachability / ping
    Internal service-to-service communication stays inside Docker’s private network and never needs firewall rules.

    Production Compose Differences

    The docker-compose.prod.yml file differs from the development compose in several important ways:
    • Services use expose instead of ports — no ports are bound to the host; Coolify’s reverse proxy handles ingress.
    • All containers set restart: unless-stopped for automatic recovery after crashes or reboots.
    • Health check intervals are 30s (vs 10s in development) to reduce overhead. The AI service start_period is 20s in production (vs 15s in development).
    • Required environment variables use :? error syntax to fail fast on misconfiguration (e.g. DATABASE_URL:?DATABASE_URL is required). AI provider keys remain optional with empty-string defaults.
    • The frontend builds from Dockerfile.prod with production API URLs baked in at build time as ARG values. In production, the frontend container serves on port 8080 (not 5173 as in development).
    • The API Gateway exposes additional rate-limiting configuration via RATE_LIMIT_MAX and RATE_LIMIT_WINDOW_MINUTES environment variables.

    Monitoring

    Coolify Logs

    View real-time container logs and historical output for every service from the Coolify dashboard under Services → Logs.

    Sentry

    Sentry is the error tracking solution for both frontend (React) and backend (Node/Python) services. Configure SENTRY_DSN per service to capture exceptions and performance traces.

    Deprecated Deployment Targets

    The following infrastructure options were evaluated and rejected during the architecture phase. They are documented here for historical context only. Do not attempt to deploy to these targets unless the architecture decision is explicitly revisited.
    TargetServiceStatus
    VercelFrontendConsidered but rejected — the Hetzner VPS has sufficient resources and keeps deployment centralised
    AWS LambdaAPI Gateway, Auth, DocumentRejected — AWS costs were too high for an academic-project budget
    AWS ECS FargateCollab, AI ServiceRejected — always-on containers are cheaper and simpler on Hetzner
    Supabase CloudPostgreSQL + Auth + pgvectorStill active — remains the managed database/auth/vector platform outside the VPS

    Build docs developers (and LLMs) love