Shipping a voice agent is 40% code and 60% operations. This chapter is the operations half — six platforms, one Dockerfile, one CI pipeline, and a production checklist you can print and pin above your monitor.
1. Railway — Fastest Path
- Push repo to GitHub.
- Railway → New Project → Deploy from GitHub → pick repo.
- Variables tab → paste all keys from .env.
- Set start command node dist/index.js. Deploy.
2. Render — Almost Identical
Same flow, slightly cheaper for always-on workers. Good WebSocket support.
3. Docker — the Universal Package
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
RUN npm run build
EXPOSE 3000
HEALTHCHECK CMD wget -qO- http://localhost:3000/health || exit 1
CMD ["node", "dist/index.js"]docker build -t voice-agent .
docker run -p 3000:3000 --env-file .env voice-agent4. AWS — Production Scale
- ECS Fargate for stateless containers.
- ALB in front for TLS + WebSocket routing.
- RDS Postgres for durable state.
- Secrets Manager for keys.
5. Cloudflare Workers
Edge functions with global latency. Great for the HTTP webhook layer. Cannot hold long-lived Twilio WebSockets — pair with a Durable Object or an origin server.
6. Vercel
Perfect for the dashboard front-end. Serverless functions are OK for short jobs; use a dedicated worker (Railway) for long-lived voice sockets.
7. Logging
import pino from "pino";
export const log = pino({ level: process.env.LOG_LEVEL ?? "info" });
log.info({ call_id, ms_total }, "turn.done");8. Monitoring
- Uptime — BetterStack pinging /health every 30s.
- Metrics — Grafana Cloud dashboards (latency p50/p95, cost/call).
- Errors — Sentry with source maps.
- Alerts — PagerDuty on p95 > 2s for 5 minutes.
9. Secrets Management
- Never commit .env. Ever.
- Store secrets in the platform (Railway Variables, AWS SM, Cloudflare Secrets).
- Rotate every 90 days. Automate rotation for Twilio + ElevenLabs.
10. API Keys — Per Environment
Separate keys for dev, staging, prod. Never share across environments.
11. Security
- Verify every webhook signature (Twilio, Stripe, ElevenLabs).
- Rate limit per IP + per phone number.
- Strip PII from logs (mask phone middle 4 digits).
- TLS only. HSTS on the dashboard domain.
12. Rate Limits
import rateLimit from "express-rate-limit";
app.use("/api", rateLimit({ windowMs: 60_000, max: 60 })); // 60 req/min/IP13. Scaling
- Autoscale on CPU or on active WebSocket count.
- Sticky sessions for calls (or hand off state via Redis on reconnect).
- Warm pool of 2 instances always up to avoid cold-start on first ring.
14. CI/CD
- PR opens → run test, lint, typecheck.
- Merge to main → build Docker image → push to registry.
- Deploy to staging automatically. Prod behind a manual approval.
- Smoke test after deploy: place one synthetic call, assert audio returned.
15. GitHub Actions — Complete Pipeline
name: deploy
on: { push: { branches: [main] } }
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npm ci
- run: npm test
- run: npm run build16. Production Checklist
- Health endpoint returning 200.
- Structured logs shipping to a real log store.
- Alerts on error rate, p95 latency, cost/day.
- Secrets in platform vault, not repo.
- Webhook signature verification.
- Rate limits at the edge and app layer.
- Docker image reproducible from any commit.
- Rollback tested — one command back to last green build.
- Synthetic call in CI on every deploy.