Vassiliy Yegorov e942c6c2b5
Some checks failed
Build Admin / Build image (push) Failing after 8s
Update build.yaml
2026-03-21 10:52:42 +07:00
2026-03-21 10:45:42 +07:00
2026-03-21 10:52:42 +07:00
2026-03-21 10:47:11 +07:00
2026-03-21 10:47:11 +07:00
2026-03-21 10:47:11 +07:00
2026-03-21 10:47:11 +07:00
2026-03-21 10:47:11 +07:00
2026-03-21 10:47:11 +07:00
2026-03-21 10:47:11 +07:00
2026-03-21 10:45:42 +07:00

API Proxy — HTTPS Reverse Proxy

Production-ready nginx reverse proxy in Docker for forwarding requests from an internal Kubernetes cluster to external APIs (ElevenLabs, OpenAI, etc.)

Quick Start

Prerequisites

  • Ubuntu 24.04 VM
  • Port 8080 open only to k8s cluster CIDR (firewall rule)

Installation

# On a fresh VM (as root):
sudo bash scripts/install.sh

Configuration

# Edit environment variables:
vi /opt/proxy/proxy-vm/.env

# Set your k8s cluster CIDR:
ALLOWED_CIDR=10.0.0.0/8,172.16.0.0/12

# Restart to apply:
cd /opt/proxy/proxy-vm
docker compose restart

Verify

# Health check (no auth required):
curl http://localhost:8080/health

# Test with auth:
curl -H "X-Proxy-Token: YOUR_SECRET" http://localhost:8080/elevenlabs/v1/voices

How to Add a New Upstream

Example: adding api.anthropic.com/anthropic/*

1. Add upstream block in nginx/nginx.conf:

upstream anthropic_backend {
    server api.anthropic.com:443;
    keepalive 32;
}

2. Add location block in the server section:

location /anthropic/ {
    if ($allowed_ip = 0) {
        return 403 '{"error":"ip_not_allowed"}';
    }
    if ($auth_ok = 0) {
        return 403 '{"error":"invalid_token"}';
    }

    rewrite ^/anthropic/(.*) /$1 break;

    proxy_pass https://anthropic_backend;
    proxy_ssl_server_name on;
    proxy_ssl_name api.anthropic.com;

    proxy_set_header Host api.anthropic.com;
    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 $scheme;
    proxy_set_header Connection "";
    proxy_set_header X-Proxy-Token "";

    proxy_http_version 1.1;

    proxy_buffering off;
    proxy_request_buffering off;
    proxy_read_timeout 120s;
    proxy_send_timeout 120s;
}

3. Rebuild and restart:

cd /opt/proxy/proxy-vm
docker compose up -d --build

K8s Side: How to Call the Proxy

Environment Variables (in your Deployment/ConfigMap)

env:
  - name: PROXY_BASE_URL
    value: "http://10.0.1.50:8080"  # proxy VM internal IP
  - name: PROXY_SECRET
    valueFrom:
      secretKeyRef:
        name: api-proxy
        key: token

TypeScript Example

// In tts-worker, replace ELEVENLABS_BASE_URL:
// Before: https://api.elevenlabs.io
// After:  http://<proxy-vm-ip>:8080/elevenlabs

const response = await fetch(
  `${process.env.PROXY_BASE_URL}/elevenlabs/v1/text-to-speech/${voiceId}`,
  {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'xi-api-key': process.env.ELEVENLABS_API_KEY,  // passed through to upstream
      'X-Proxy-Token': process.env.PROXY_SECRET,     // validated by proxy
    },
    body: JSON.stringify(payload),
  }
);
// Response is binary MP3, identical to direct ElevenLabs call
const buffer = Buffer.from(await response.arrayBuffer());

Security Checklist

  • PROXY_SECRET generated via openssl rand -hex 32
  • ALLOWED_CIDR restricted to cluster CIDR only
  • Port 8080 closed to the public (only k8s CIDR in firewall/NSG)
  • VM has no public IP or is behind NAT
  • Logs rotate (Docker logging driver configured: 50m x 5 files)

Adding TLS (Self-Signed)

If you need HTTPS between k8s and the proxy:

# Generate self-signed cert:
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout /opt/proxy/proxy-vm/nginx/ssl/proxy.key \
  -out /opt/proxy/proxy-vm/nginx/ssl/proxy.crt \
  -subj "/CN=api-proxy"

Then in nginx.conf, change the server block:

server {
    listen 8443 ssl;
    ssl_certificate     /etc/nginx/ssl/proxy.crt;
    ssl_certificate_key /etc/nginx/ssl/proxy.key;
    # ... rest of config
}

Mount the certs in docker-compose.yml:

volumes:
  - ./nginx/ssl:/etc/nginx/ssl:ro

Monitoring

Tail logs in JSON format

docker compose logs -f proxy | jq .

Count requests per upstream per minute

docker compose logs --since=1m proxy --no-log-prefix \
  | jq -r '.uri' \
  | cut -d'/' -f2 \
  | sort | uniq -c | sort -rn

Check container health

docker inspect --format='{{.State.Health.Status}}' api-proxy

How It Works

  1. A k8s pod sends an HTTP request to http://<proxy-vm>:8080/elevenlabs/v1/text-to-speech/... with the X-Proxy-Token header and the original API key (xi-api-key).
  2. Nginx checks the source IP against the configured ALLOWED_CIDR geo block and validates the X-Proxy-Token against PROXY_SECRET — rejecting with 403 if either fails.
  3. The /elevenlabs/ prefix is stripped via rewrite, and the request is forwarded over HTTPS to api.elevenlabs.io with SNI enabled and keepalive connections.
  4. The X-Proxy-Token header is removed before forwarding; all other headers (including xi-api-key) pass through unchanged.
  5. The response streams back unbuffered to the pod — critical for binary audio data from ElevenLabs TTS.
Description
No description provided
Readme 45 KiB
Languages
Shell 87.5%
Dockerfile 12.5%