From dd0199c083663df5500681e00cef628ae4119820 Mon Sep 17 00:00:00 2001 From: Wuesteon Date: Wed, 10 Dec 2025 03:35:28 +0100 Subject: [PATCH] docs: add comprehensive staging environment setup guide MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Documents the complete staging setup including: - Hetzner VPS and Namecheap DNS configuration - Caddy reverse proxy with auto-SSL - SvelteKit runtime environment variable pattern - Docker Compose configuration - Domain mappings and URLs - Troubleshooting guide - Instructions for adding new apps 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/STAGING_SETUP.md | 441 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 441 insertions(+) create mode 100644 docs/STAGING_SETUP.md diff --git a/docs/STAGING_SETUP.md b/docs/STAGING_SETUP.md new file mode 100644 index 000000000..0ae580b97 --- /dev/null +++ b/docs/STAGING_SETUP.md @@ -0,0 +1,441 @@ +# Staging Environment Setup Guide + +This document describes the complete staging environment setup for ManaCore apps on Hetzner VPS with HTTPS via Caddy reverse proxy. + +## Overview + +| Component | Details | +|-----------|---------| +| **Server** | Hetzner VPS (46.224.108.214) | +| **Domain** | manacore.ai (Namecheap) | +| **Reverse Proxy** | Caddy (auto-SSL via Let's Encrypt) | +| **Container Runtime** | Docker Compose | +| **SSH Access** | `ssh -i ~/.ssh/hetzner_deploy_key deploy@46.224.108.214` | + +## Architecture + +``` + ┌─────────────────────────────────────────────┐ + │ Hetzner VPS (46.224.108.214) │ + │ │ + Internet │ ┌─────────────────────────────────────┐ │ + │ │ │ Caddy (ports 80/443) │ │ + │ │ │ Auto-SSL via Let's Encrypt │ │ + ▼ │ └──────────────┬──────────────────────┘ │ +┌──────────────┐ │ │ │ +│ Namecheap │ │ ▼ │ +│ DNS Records │────────────────────│ ┌─────────────────────────────────────┐ │ +│ │ │ │ Docker Compose Services │ │ +│ *.staging │ │ │ │ │ +│ A → IP │ │ │ mana-core-auth:3001 │ │ +└──────────────┘ │ │ chat-web:3000 / chat-backend:3002 │ │ + │ │ clock-web:5187 / clock-backend:3017│ │ + │ │ calendar-web:5186 / calendar-api:3016│ │ + │ │ todo-web:5188 / todo-backend:3018 │ │ + │ │ manacore-web:5173 │ │ + │ │ postgres:5432 / redis:6379 │ │ + │ └─────────────────────────────────────┘ │ + └─────────────────────────────────────────────┘ +``` + +## Domain Mapping + +### DNS Configuration (Namecheap) + +| Type | Host | Value | TTL | +|------|------|-------|-----| +| A | `staging` | 46.224.108.214 | Automatic | +| A | `*.staging` | 46.224.108.214 | Automatic | + +The wildcard record `*.staging` enables all subdomains like `auth.staging.manacore.ai`, `clock.staging.manacore.ai`, etc. + +### Staging URLs + +| Service | URL | Internal Port | +|---------|-----|---------------| +| **Auth** | https://auth.staging.manacore.ai | 3001 | +| **ManaCore Web** | https://staging.manacore.ai | 5173 | +| **Chat Web** | https://chat.staging.manacore.ai | 3000 | +| **Chat API** | https://chat-api.staging.manacore.ai | 3002 | +| **Clock Web** | https://clock.staging.manacore.ai | 5187 | +| **Clock API** | https://clock-api.staging.manacore.ai | 3017 | +| **Calendar Web** | https://calendar.staging.manacore.ai | 5186 | +| **Calendar API** | https://calendar-api.staging.manacore.ai | 3016 | +| **Todo Web** | https://todo.staging.manacore.ai | 5188 | +| **Todo API** | https://todo-api.staging.manacore.ai | 3018 | + +## Caddy Reverse Proxy + +### Installation (One-time setup) + +```bash +# SSH into server +ssh -i ~/.ssh/hetzner_deploy_key deploy@46.224.108.214 + +# Create Caddy data directory +mkdir -p ~/caddy_data ~/caddy_config + +# Run Caddy container +docker run -d \ + --name caddy \ + --network host \ + --restart unless-stopped \ + -v ~/Caddyfile:/etc/caddy/Caddyfile \ + -v ~/caddy_data:/data \ + -v ~/caddy_config:/config \ + caddy:2-alpine +``` + +### Configuration + +The Caddyfile is stored at: +- **Server**: `~/Caddyfile` +- **Repo**: `docker/caddy/Caddyfile.staging` + +```caddyfile +# ManaCore Staging Reverse Proxy + +auth.staging.manacore.ai { + reverse_proxy localhost:3001 +} + +chat.staging.manacore.ai { + reverse_proxy localhost:3000 +} + +chat-api.staging.manacore.ai { + reverse_proxy localhost:3002 +} + +staging.manacore.ai { + reverse_proxy localhost:5173 +} + +calendar.staging.manacore.ai { + reverse_proxy localhost:5186 +} + +calendar-api.staging.manacore.ai { + reverse_proxy localhost:3016 +} + +clock.staging.manacore.ai { + reverse_proxy localhost:5187 +} + +clock-api.staging.manacore.ai { + reverse_proxy localhost:3017 +} + +todo.staging.manacore.ai { + reverse_proxy localhost:5188 +} + +todo-api.staging.manacore.ai { + reverse_proxy localhost:3018 +} +``` + +### Updating Caddy Configuration + +```bash +# Copy updated config to server +scp -i ~/.ssh/hetzner_deploy_key docker/caddy/Caddyfile.staging deploy@46.224.108.214:~/Caddyfile + +# Reload Caddy (no downtime) +ssh -i ~/.ssh/hetzner_deploy_key deploy@46.224.108.214 "docker exec caddy caddy reload --config /etc/caddy/Caddyfile" +``` + +### Caddy Management Commands + +```bash +# View logs +docker logs caddy -f + +# Restart Caddy +docker restart caddy + +# Check Caddy status +docker exec caddy caddy validate --config /etc/caddy/Caddyfile +``` + +## SvelteKit Runtime Environment Variables + +### The Problem + +SvelteKit's `$env/static/public` variables are replaced at **build time**. When Docker images are built in CI, the environment variables are baked into the JavaScript bundles. This means containers cannot use different URLs for different environments. + +### The Solution + +Use `$env/dynamic/private` in `hooks.server.ts` to read environment variables at **runtime**, then inject them into the HTML for client-side access. + +### Implementation + +Each SvelteKit web app has a `hooks.server.ts` that: +1. Reads `_CLIENT` environment variables at runtime +2. Injects them into the HTML via ``; + return html.replace('', `${envScript}`); + }, + }); +}; +``` + +### Environment Variable Pattern + +Each web app container receives two sets of URLs: + +| Variable | Purpose | Example | +|----------|---------|---------| +| `PUBLIC_BACKEND_URL` | Server-side (Docker network) | `http://clock-backend:3017` | +| `PUBLIC_BACKEND_URL_CLIENT` | Client-side (browser) | `https://clock-api.staging.manacore.ai` | +| `PUBLIC_MANA_CORE_AUTH_URL` | Server-side auth | `http://mana-core-auth:3001` | +| `PUBLIC_MANA_CORE_AUTH_URL_CLIENT` | Client-side auth | `https://auth.staging.manacore.ai` | + +## Docker Compose Configuration + +### File Locations + +| File | Purpose | +|------|---------| +| `docker-compose.staging.yml` | Staging configuration (repo) | +| `~/manacore-staging/docker-compose.yml` | Server deployment | + +### Key Configuration Sections + +**Web App Environment Variables:** +```yaml +clock-web: + environment: + NODE_ENV: staging + PORT: 5187 + # Server-side URLs (Docker internal network) + PUBLIC_BACKEND_URL: http://clock-backend:3017 + PUBLIC_MANA_CORE_AUTH_URL: http://mana-core-auth:3001 + # Client-side URLs (browser access via HTTPS) + PUBLIC_BACKEND_URL_CLIENT: https://clock-api.staging.manacore.ai + PUBLIC_MANA_CORE_AUTH_URL_CLIENT: https://auth.staging.manacore.ai +``` + +**Backend CORS Configuration:** +```yaml +clock-backend: + environment: + CORS_ORIGINS: https://clock.staging.manacore.ai,https://staging.manacore.ai,http://localhost:5187 +``` + +**Auth Service CORS:** +```yaml +mana-core-auth: + environment: + CORS_ORIGINS: https://chat.staging.manacore.ai,https://staging.manacore.ai,https://calendar.staging.manacore.ai,https://clock.staging.manacore.ai,https://todo.staging.manacore.ai,http://localhost:3000,http://localhost:5173 +``` + +### Syncing Configuration to Server + +```bash +# Copy docker-compose to server +scp -i ~/.ssh/hetzner_deploy_key docker-compose.staging.yml deploy@46.224.108.214:~/manacore-staging/docker-compose.yml + +# Recreate containers with new config +ssh -i ~/.ssh/hetzner_deploy_key deploy@46.224.108.214 "cd ~/manacore-staging && docker compose up -d --force-recreate" +``` + +## Deployment Workflow + +### CI/CD Pipeline + +The GitHub Actions workflow (`.github/workflows/cd-staging.yml`): +1. Builds Docker images on push to `dev` branch +2. Pushes images to GitHub Container Registry (ghcr.io) +3. SSHs into staging server +4. Pulls latest images +5. Restarts containers + +### Manual Deployment + +```bash +# 1. Build and push images (from local) +docker build -t ghcr.io/memo-2023/clock-web:latest -f apps/clock/apps/web/Dockerfile . +docker push ghcr.io/memo-2023/clock-web:latest + +# 2. SSH into server +ssh -i ~/.ssh/hetzner_deploy_key deploy@46.224.108.214 + +# 3. Pull and restart +cd ~/manacore-staging +docker compose pull +docker compose up -d --force-recreate +``` + +### Updating Environment Variables + +1. Edit `docker-compose.staging.yml` locally +2. Copy to server: `scp -i ~/.ssh/hetzner_deploy_key docker-compose.staging.yml deploy@46.224.108.214:~/manacore-staging/docker-compose.yml` +3. Recreate affected containers: `docker compose up -d --force-recreate ` + +## Troubleshooting + +### Mixed Content Errors + +**Symptom:** Browser console shows "Mixed Content: The page was loaded over HTTPS, but requested an insecure resource" + +**Cause:** Client-side JavaScript is calling HTTP URLs instead of HTTPS + +**Solution:** +1. Check `_CLIENT` environment variables in docker-compose.yml +2. Ensure they use `https://` staging domains +3. Recreate web containers: `docker compose up -d --force-recreate ` + +### CORS Errors + +**Symptom:** Browser console shows "Access-Control-Allow-Origin" errors + +**Cause:** Backend CORS_ORIGINS doesn't include the HTTPS staging domain + +**Solution:** +1. Add the HTTPS domain to `CORS_ORIGINS` in docker-compose.yml +2. Recreate backend containers + +### Caddy SSL Certificate Issues + +**Symptom:** Browser shows SSL certificate warning + +**Solution:** +```bash +# Check Caddy logs +docker logs caddy + +# Force certificate renewal +docker exec caddy caddy reload --config /etc/caddy/Caddyfile +``` + +### Container Health Check Failures + +**Symptom:** Container shows "unhealthy" status + +**Solution:** +```bash +# Check container logs +docker logs + +# Check health status +docker inspect | grep -A 20 Health +``` + +## Adding a New App to Staging + +### 1. Update DNS (if needed) + +If using a new subdomain pattern, update Namecheap DNS. The `*.staging` wildcard should cover most cases. + +### 2. Update Caddyfile + +Add entries for web and API: +```caddyfile +newapp.staging.manacore.ai { + reverse_proxy localhost: +} + +newapp-api.staging.manacore.ai { + reverse_proxy localhost: +} +``` + +### 3. Update docker-compose.staging.yml + +Add the new services with proper environment variables: +```yaml +newapp-web: + image: ghcr.io/memo-2023/newapp-web:latest + environment: + PUBLIC_BACKEND_URL: http://newapp-backend: + PUBLIC_MANA_CORE_AUTH_URL: http://mana-core-auth:3001 + PUBLIC_BACKEND_URL_CLIENT: https://newapp-api.staging.manacore.ai + PUBLIC_MANA_CORE_AUTH_URL_CLIENT: https://auth.staging.manacore.ai + ports: + - ":" +``` + +### 4. Implement hooks.server.ts + +Copy the runtime env var pattern from an existing app: +```typescript +import type { Handle } from '@sveltejs/kit'; +import { env } from '$env/dynamic/private'; + +export const handle: Handle = async ({ event, resolve }) => { + const authUrlClient = env.PUBLIC_MANA_CORE_AUTH_URL_CLIENT || ''; + const backendUrlClient = env.PUBLIC_BACKEND_URL_CLIENT || ''; + + return resolve(event, { + transformPageChunk: ({ html }) => { + const envScript = ``; + return html.replace('', `${envScript}`); + }, + }); +}; +``` + +### 5. Deploy + +1. Sync Caddyfile: `scp ... Caddyfile.staging deploy@server:~/Caddyfile` +2. Reload Caddy: `docker exec caddy caddy reload --config /etc/caddy/Caddyfile` +3. Sync docker-compose: `scp ... docker-compose.staging.yml deploy@server:~/manacore-staging/docker-compose.yml` +4. Deploy containers: `docker compose up -d` + +## Quick Reference Commands + +```bash +# SSH into server +ssh -i ~/.ssh/hetzner_deploy_key deploy@46.224.108.214 + +# View all containers +docker ps + +# View container logs +docker logs -f + +# Restart a container +docker restart + +# Recreate containers with new config +cd ~/manacore-staging && docker compose up -d --force-recreate + +# Check Caddy SSL certificates +docker exec caddy caddy validate --config /etc/caddy/Caddyfile + +# Test HTTPS endpoint +curl -s https://auth.staging.manacore.ai/api/v1/health + +# Check container env vars +docker exec printenv | grep -E 'CLIENT|CORS' +``` + +## Related Documentation + +- [Local Development Guide](./LOCAL_DEVELOPMENT.md) +- [CI/CD Deployment Guide](./DEPLOYMENT.md) +- [Environment Variables](./ENVIRONMENT_VARIABLES.md)