Production operations
Reverse proxy, Postgres tuning, storage retention, Kubernetes, and rollout checks.
This page covers production deployment patterns for the standalone Node collector backed by Postgres and S3-compatible object storage. The Cloudflare Workers path remains supported, but the Node collector is the clearest fit for container platforms and private infrastructure.
Network and reverse proxy
The recommended layout places the collector behind TLS, keeps OTLP ingest routes reachable, and protects dashboard/internal query routes with your normal auth boundary.
| Route | Backend | Purpose |
|---|---|---|
/v1/* | Collector on :8790 | Public OTLP and SDK ingest |
/internal/* | Collector on :8790 | Dashboard query APIs |
/ | Dashboard static server on :5173 | React dashboard |
Nginx
server {
listen 443 ssl http2;
server_name obs.my-app.com;
ssl_certificate /etc/letsencrypt/live/obs.my-app.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/obs.my-app.com/privkey.pem;
gzip on;
gzip_types application/json application/x-protobuf text/plain text/css;
location /v1/ {
proxy_pass http://127.0.0.1:8790;
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 $scheme;
client_max_body_size 10m;
}
location /internal/ {
proxy_pass http://127.0.0.1:8790;
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 $scheme;
}
location / {
proxy_pass http://127.0.0.1:5173;
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 $scheme;
try_files $uri $uri/ /index.html;
}
}Caddy
obs.my-app.com {
reverse_proxy /v1/* 127.0.0.1:8790 {
header_up Host {host}
header_up X-Real-IP {remote}
}
reverse_proxy /internal/* 127.0.0.1:8790 {
header_up Host {host}
header_up X-Real-IP {remote}
}
reverse_proxy /* 127.0.0.1:5173 {
header_up Host {host}
header_up X-Real-IP {remote}
}
}Postgres tuning
Each collector instance uses a persistent client-side pool. Start with the default PG_POOL_MAX=10 for low to moderate traffic. For high-throughput environments, scale to 30 or 50, making sure the total across replicas stays below the database server's max_connections.
Set a statement timeout so expensive dashboard or analysis queries cannot lock the database indefinitely:
PG_STATEMENT_TIMEOUT=30000Object storage retention
Replay chunks and pprof profiles live in S3-compatible object storage. Match bucket lifecycle retention to the database retention window. For a 72-hour hot-debugging window, use a three-day expiration rule.
{
"Rules": [
{
"ID": "AutoDeleteOldTelemetryBlobs",
"Status": "Enabled",
"Filter": {
"Prefix": ""
},
"Expiration": {
"Days": 3
}
}
]
}Apply it with:
aws s3api put-bucket-lifecycle-configuration \
--bucket obs-unified-storage-bucket \
--lifecycle-configuration file://lifecycle-policy.jsonKubernetes shape
Run collectors as stateless replicas against managed Postgres and S3-compatible storage.
apiVersion: v1
kind: ConfigMap
metadata:
name: obs-config
namespace: observability
data:
BLOB_STORE: "s3"
S3_REGION: "us-east-1"
S3_BUCKET: "my-production-obs-bucket"
PG_POOL_MAX: "30"
PORT: "8790"
---
apiVersion: v1
kind: Secret
metadata:
name: obs-secrets
namespace: observability
type: Opaque
data:
DATABASE_URL: cG9zdGdyZXM6Ly91c2VyOnBhc3NAcGctaG9zdDo1NDMyL29ic191bmlmaWVk
S3_ACCESS_KEY_ID: QUtJQVhYWFhYWFhYWFhYWFhYWFg=
S3_SECRET_ACCESS_KEY: c2VjcmV0LWtleS12YWx1ZS1nb2VzLWhlcmU=
INGEST_KEY: bXktc2VjdXJlLXdyaXRlLWtleQ==
DASHBOARD_PASSWORD: bXktc3VwZXItc2VjdXJlLXBhc3N3b3JkapiVersion: apps/v1
kind: Deployment
metadata:
name: obs-collector
namespace: observability
spec:
replicas: 2
selector:
matchLabels:
app: obs-collector
template:
metadata:
labels:
app: obs-collector
spec:
containers:
- name: collector
image: obs-unified/collector:latest
ports:
- containerPort: 8790
envFrom:
- configMapRef:
name: obs-config
- secretRef:
name: obs-secrets
resources:
requests:
cpu: 250m
memory: 512Mi
limits:
cpu: "1"
memory: 1GiOperational checks
Before opening ingest traffic:
- Run database migrations for the target collector storage.
- Confirm
/healthresponds through the reverse proxy. - Verify CORS from the browser origin with
obs-unified doctor. - Send one synthetic trace, one log, and one usage event.
- Confirm dashboard login and internal query routes are not publicly exposed without your intended auth.