> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ringlyra.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Google Cloud (Existing Server)

> Deploy Ringlyra on a Google Cloud Compute Engine VM that already runs nginx alongside other applications

This guide is for teams deploying Ringlyra onto a **Google Cloud Compute Engine (GCE) VM that already has nginx and other applications running**. Instead of using Ringlyra's built-in nginx container, you will add a virtual-host block to your existing nginx and point your domain `www.ringlyra.com` at it.

## Architecture overview

```
Internet
  │
  ▼
GCE VM (your existing nginx on port 80 / 443)
  ├── www.ringlyra.com  ──►  nginx virtual host (this guide)
  │       ├── /api/v1/  ──►  localhost:8000  (Ringlyra API container)
  │       ├── /voice-audio/  ──►  localhost:9000  (MinIO container)
  │       └── /         ──►  localhost:3010  (Ringlyra UI container)
  │
  └── your-other-apps  ──►  their own virtual hosts (unchanged)
```

Ringlyra runs via Docker Compose **without** its own nginx container — the `remote` profile is intentionally skipped.

***

## Step 1 — GCP firewall rules

Open the required ports in your GCP project firewall. Run these from Cloud Shell or your local `gcloud` CLI:

```bash theme={null}
# HTTP + HTTPS for web traffic
gcloud compute firewall-rules create ringlyra-web \
  --direction=INGRESS --action=ALLOW \
  --rules=tcp:80,tcp:443 \
  --target-tags=ringlyra-server

# WebRTC (TURN server for voice calls)
gcloud compute firewall-rules create ringlyra-webrtc \
  --direction=INGRESS --action=ALLOW \
  --rules=tcp:3478,tcp:5349,udp:3478,udp:5349,udp:49152-49200 \
  --target-tags=ringlyra-server
```

Then add the `ringlyra-server` network tag to your VM:

```bash theme={null}
gcloud compute instances add-tags YOUR_INSTANCE_NAME \
  --zone=YOUR_ZONE \
  --tags=ringlyra-server
```

### Reserve a static external IP

A static IP prevents the VM's address from changing on restart, which would break DNS.

```bash theme={null}
# Reserve a static IP (if you don't already have one)
gcloud compute addresses create ringlyra-ip --region=YOUR_REGION

# Assign it to your instance
gcloud compute instances delete-access-config YOUR_INSTANCE_NAME \
  --access-config-name "External NAT" --zone=YOUR_ZONE

gcloud compute instances add-access-config YOUR_INSTANCE_NAME \
  --access-config-name "External NAT" \
  --address=$(gcloud compute addresses describe ringlyra-ip \
    --region=YOUR_REGION --format='get(address)') \
  --zone=YOUR_ZONE
```

Note the IP — you need it for DNS in the next step.

***

## Step 2 — DNS

Log in to your domain registrar and add two A records pointing to your GCE VM's static IP:

| Type | Name       | Value        | TTL |
| ---- | ---------- | ------------ | --- |
| A    | `@` (root) | `YOUR_VM_IP` | 300 |
| A    | `www`      | `YOUR_VM_IP` | 300 |

Verify propagation before continuing:

```bash theme={null}
nslookup www.ringlyra.com
# Expected: YOUR_VM_IP
```

***

## Step 3 — Deploy Ringlyra containers

SSH into your GCE VM.

### 3a — Clone the repo and create the environment file

```bash theme={null}
cd /opt   # or wherever you keep applications
git clone https://github.com/MohitMaliFtechiz/RingLyra.git ringlyra
cd ringlyra
```

Copy the example env file and fill in your values:

```bash theme={null}
cp api/.env.example .env
nano .env
```

Minimum required values for a production deployment:

```bash theme={null}
ENVIRONMENT="production"
SERVER_IP="YOUR_VM_IP"
PUBLIC_HOST="www.ringlyra.com"
PUBLIC_BASE_URL="https://www.ringlyra.com"
BACKEND_API_ENDPOINT="https://www.ringlyra.com"
UI_APP_URL="https://www.ringlyra.com"
MINIO_PUBLIC_ENDPOINT="https://www.ringlyra.com"

# Generate with: openssl rand -hex 32
OSS_JWT_SECRET="REPLACE_WITH_STRONG_SECRET"

# WebRTC TURN relay for browser voice calls
TURN_HOST="www.ringlyra.com"
# API container uses Docker DNS to reach the built-in coturn service
TURN_INTERNAL_HOST="coturn"
TURN_SECRET="REPLACE_WITH_TURN_SECRET"
# Recommended while validating remote audio paths
FORCE_TURN_RELAY="true"

DATABASE_URL="postgresql+asyncpg://postgres:postgres@localhost:5432/postgres"
REDIS_URL="redis://:redissecret@localhost:6379"

MINIO_ENDPOINT=localhost:9000
MINIO_ACCESS_KEY=minioadmin
MINIO_SECRET_KEY=minioadmin
MINIO_BUCKET=voice-audio

# SMTP (for OTP / password reset emails)
SMTP_HOST=smtppro.zoho.in
SMTP_PORT=465
SMTP_USER=notifications@ftechiz.com
SMTP_PASSWORD=YOUR_SMTP_PASSWORD
SMTP_FROM_EMAIL=notifications@ftechiz.com
SMTP_FROM_NAME=Ringlyra
```

### 3b — Start the stack (without the built-in nginx)

The `remote` Docker Compose profile starts Ringlyra's own nginx container, which conflicts with your existing nginx on ports 80/443. Use the deploy script instead; it starts the app services and starts coturn automatically when `TURN_HOST` and `TURN_SECRET` are present:

```bash theme={null}
bash infra/deploy.sh --build
```

This starts the API on `localhost:8000`, the UI on `localhost:3010`, MinIO on `localhost:9000`, and coturn on the TURN ports from Step 1.

Verify all containers are healthy:

```bash theme={null}
docker compose ps
# postgres, redis, minio, api, ui, and coturn should show "healthy" or "running"
```

### 3c — Database migrations

`infra/deploy.sh` runs Alembic migrations automatically after the containers are healthy. If you start the services manually instead of using the deploy script, run:

```bash theme={null}
docker compose exec api python -m alembic -c api/alembic.ini upgrade head
```

***

## Step 4 — SSL certificate with Let's Encrypt

Install Certbot on the VM (if not already installed):

```bash theme={null}
sudo apt update && sudo apt install -y certbot python3-certbot-nginx
```

Stop your existing nginx temporarily so Certbot can bind to port 80 for the ACME challenge:

```bash theme={null}
sudo systemctl stop nginx
```

Obtain the certificate:

```bash theme={null}
sudo certbot certonly --standalone \
  -d ringlyra.com \
  -d www.ringlyra.com \
  --email YOUR_EMAIL@example.com \
  --agree-tos --non-interactive
```

Certificates are stored in `/etc/letsencrypt/live/ringlyra.com/`.

Restart nginx:

```bash theme={null}
sudo systemctl start nginx
```

***

## Step 5 — nginx virtual host

Create a new site config for Ringlyra. This sits alongside your existing sites and does **not** touch them.

```bash theme={null}
sudo nano /etc/nginx/sites-available/ringlyra
```

Paste the following (replace `www.ringlyra.com` / `ringlyra.com` if your domain differs):

```nginx theme={null}
# Redirect bare domain → www
server {
    listen 80;
    listen 443 ssl;
    server_name ringlyra.com;

    ssl_certificate     /etc/letsencrypt/live/ringlyra.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/ringlyra.com/privkey.pem;

    return 301 https://www.ringlyra.com$request_uri;
}

# Redirect HTTP → HTTPS
server {
    listen 80;
    server_name www.ringlyra.com;
    return 301 https://www.ringlyra.com$request_uri;
}

# Main HTTPS virtual host
server {
    listen 443 ssl;
    server_name www.ringlyra.com;

    ssl_certificate     /etc/letsencrypt/live/ringlyra.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/ringlyra.com/privkey.pem;
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;

    # ── Backend API + WebSocket (audio streaming, signaling) ───────────────
    location /api/v1/ {
        proxy_pass http://localhost:8000;
        proxy_http_version 1.1;

        proxy_set_header Upgrade    $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host               $host;
        proxy_set_header X-Real-IP          $remote_addr;
        proxy_set_header X-Forwarded-For    $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto  https;

        # Long-lived WebSocket connections for voice calls
        proxy_read_timeout  3600s;
        proxy_send_timeout  3600s;
        proxy_buffering     off;
        client_max_body_size 100M;
    }

    # ── MinIO audio file storage ───────────────────────────────────────────
    location /voice-audio/ {
        proxy_pass http://localhost:9000/voice-audio/;
        proxy_http_version 1.1;

        proxy_set_header Host               $host;
        proxy_set_header X-Real-IP          $remote_addr;
        proxy_set_header X-Forwarded-For    $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto  https;

        proxy_buffering off;
        client_max_body_size 100M;
    }

    # ── Next.js UI (catch-all) ─────────────────────────────────────────────
    location / {
        proxy_pass http://localhost:3010;
        proxy_http_version 1.1;

        proxy_set_header Upgrade    $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host               $host;
        proxy_set_header X-Real-IP          $remote_addr;
        proxy_set_header X-Forwarded-For    $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto  https;

        # Rewrite internal MinIO URLs in API responses to use the public domain
        sub_filter 'http://localhost:9000/voice-audio/' 'https://www.ringlyra.com/voice-audio/';
        sub_filter_once off;
        sub_filter_types application/json text/html;
    }
}
```

Enable the site and reload nginx:

```bash theme={null}
sudo ln -s /etc/nginx/sites-available/ringlyra /etc/nginx/sites-enabled/ringlyra
sudo nginx -t          # verify — must say "syntax is ok"
sudo systemctl reload nginx
```

***

## Step 6 — Verify the deployment

```bash theme={null}
# API health check
curl https://www.ringlyra.com/api/v1/health

# UI loads
curl -I https://www.ringlyra.com
# Expected: HTTP/2 200
```

Open `https://www.ringlyra.com` in your browser — you should see the Ringlyra dashboard with a valid SSL certificate.

***

## Step 7 — Automatic certificate renewal

Certbot installs a renewal timer automatically, but it needs to reload nginx after renewing. Create a deploy hook:

```bash theme={null}
sudo nano /etc/letsencrypt/renewal-hooks/deploy/ringlyra-reload.sh
```

```bash theme={null}
#!/bin/bash
systemctl reload nginx
```

```bash theme={null}
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/ringlyra-reload.sh

# Test renewal dry-run
sudo certbot renew --dry-run
```

***

## Step 8 — GitHub Actions auto-deploy

If you set up the [GitHub Actions deploy workflow](/deployment/github-actions), add these secrets in your repo settings so the workflow can SSH into this VM and restart the containers:

| Secret              | Value                                    |
| ------------------- | ---------------------------------------- |
| `GCP_PROJECT_ID`    | Your GCP project ID                      |
| `GCP_SA_KEY`        | Service account JSON key                 |
| `GCP_REGION`        | VM region (e.g. `us-central1`)           |
| `AR_REPOSITORY`     | Artifact Registry repo name              |
| `GCE_INSTANCE_NAME` | VM instance name                         |
| `GCE_ZONE`          | VM zone (e.g. `us-central1-a`)           |
| `DEPLOY_PATH`       | `/opt/ringlyra` (or wherever you cloned) |

On every push to `main`, the workflow will build fresh Docker images, push them to Artifact Registry, SSH in via IAP, pull the images, and restart only the `api` and `ui` containers — leaving your existing nginx and other applications untouched.

***

## Troubleshooting

### `502 Bad Gateway` from nginx

The API or UI container is not running. Check:

```bash theme={null}
docker compose ps
docker compose logs api --tail=50
docker compose logs ui  --tail=50
```

### Voice calls connect but no audio

WebRTC relay ports may be blocked. Confirm the `ringlyra-webrtc` firewall rule is applied to your instance tag and that coturn is running:

```bash theme={null}
docker compose ps coturn
docker compose logs coturn --tail=80
```

If coturn is not running, make sure `.env` includes `TURN_HOST`, `TURN_INTERNAL_HOST`, `TURN_SECRET`, and `SERVER_IP`, then re-run:

```bash theme={null}
bash infra/deploy.sh --build
```

If calls connect but audio still fails, temporarily keep `FORCE_TURN_RELAY=true` so browser media uses relay candidates only while you verify the firewall and coturn logs.

### Certificate renewal fails

Ensure port 80 is reachable from the internet (required for HTTP-01 ACME challenge) and that the `ringlyra` firewall rule is active:

```bash theme={null}
gcloud compute firewall-rules describe ringlyra-web
```
