Build Your Own Trading Terminal with AI — Part 6 of 6

Deployment — Ship It to Production

You've built the architecture, the data pipeline, the signal engine, the dashboard, and the AI layer. Now it's time to ship it. Docker, VPS, monitoring, security, and cost optimization — everything you need to run your terminal 24/7 for under $50/month.

Docker VPS Monitoring Security
Build Your Own Trading Terminal with AI6/6
VPS SetupDocker ComposeCron OrchestrationMonitoringSecurityCost BreakdownScalingKey Takeaways
VPS Setup

Choosing Your Hosting Provider

Your terminal needs a server that's always on, close to market data sources (US East Coast), and cheap enough to run 24/7. Here's a comparison of the top options:

ProviderPlanCPURAMStorageCost/moLocation
HetznerCX312 vCPU8 GB80 GB NVMe$8.50US East, EU
DigitalOceanBasic2 vCPU4 GB80 GB SSD$24NYC, SFO
RailwayProShared8 GBEphemeral$20+usageUS East
Fly.ioPerformance2 CPU4 GBVolumes$20Global
AWS Lightsail4GB2 vCPU4 GB80 GB SSD$20us-east-1

Our Pick: Hetzner CX31

Hetzner CX31 at $8.50/month is the best value. 8 GB RAM is enough for TimescaleDB + Redis + all your services. NVMe storage is fast. The US East datacenter (Ashburn, VA) puts you <10ms from NYSE/NASDAQ. European traders can use Falkenstein or Helsinki. For comparison, the same specs on DigitalOcean costs 3x more.

Bash — Initial Server Setup # 1. SSH into your new server ssh root@your-server-ip # 2. Create a non-root user adduser terminal usermod -aG sudo terminal su - terminal # 3. Install Docker & Docker Compose curl -fsSL https://get.docker.com | sh sudo usermod -aG docker $USER # 4. Install Docker Compose plugin sudo apt install docker-compose-plugin # 5. Clone your repo git clone https://github.com/you/trading-terminal.git cd trading-terminal # 6. Create .env file (NEVER commit this) cp .env.example .env nano .env # Fill in API keys # 7. Launch everything docker compose up -d # 8. Verify all services are running docker compose ps docker compose logs --tail=50 ingestion
Docker Containerization

The Complete Docker Compose File

Docker Compose orchestrates all your services: database, cache, ingestion, signals, AI, and dashboard. One file, one command, everything runs.

docker-compose.yml — Production Configuration version: '3.8' services: # === DATABASE LAYER === timescaledb: image: timescale/timescaledb:latest-pg16 restart: always ports: ["5432:5432"] environment: POSTGRES_PASSWORD: ${DB_PASSWORD} POSTGRES_DB: trading_terminal volumes: - tsdb_data:/var/lib/postgresql/data - ./db/init.sql:/docker-entrypoint-initdb.d/init.sql healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 10s retries: 5 redis: image: redis:7-alpine restart: always ports: ["6379:6379"] command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru --appendonly yes volumes: ["redis_data:/data"] # === INGESTION LAYER === ingestion: build: ./services/ingestion restart: always depends_on: timescaledb: { condition: service_healthy } redis: { condition: service_started } env_file: .env environment: DATABASE_URL: postgresql://postgres:${DB_PASSWORD}@timescaledb:5432/trading_terminal REDIS_URL: redis://redis:6379 # === SIGNAL ENGINE === signals: build: ./services/signals restart: always depends_on: [timescaledb] env_file: .env # === AI LAYER (MCP SERVER) === ai: build: ./services/ai restart: always depends_on: [timescaledb, signals] env_file: .env # === DASHBOARD === dashboard: build: ./services/dashboard restart: always ports: ["3000:3000"] depends_on: [timescaledb, redis, signals] env_file: .env # === REVERSE PROXY === caddy: image: caddy:2-alpine restart: always ports: ["80:80", "443:443"] volumes: - ./Caddyfile:/etc/caddy/Caddyfile - caddy_data:/data volumes: tsdb_data: redis_data: caddy_data:
Caddyfile — Automatic HTTPS terminal.yourdomain.com { reverse_proxy dashboard:3000 encode gzip header { X-Content-Type-Options nosniff X-Frame-Options DENY Strict-Transport-Security "max-age=31536000; includeSubDomains" } }
Cron Orchestration

Scheduling All the Moving Parts

Your terminal has multiple time-critical jobs that need to run reliably. Here's the complete cron schedule:

JobSchedule (ET)ServiceDurationCritical?
Morning briefing06:45 Mon-FriAI~30sYes
Pre-market scan09:00 Mon-FriSignals~2 minYes
Intraday quotesEvery 5 min (9:30-16:00)Ingestion~10sNo
Daily bars (US)17:30 Mon-FriIngestion~3 minYes
Daily bars (EU)12:00 Mon-FriIngestion~1 minNo
Signal generation18:00 Mon-FriSignals~5 minYes
Evening scanner18:30 Mon-FriSignals+AI~3 minYes
Crypto quotesEvery 5 minIngestion~10sNo
Macro data08:00 DailyIngestion~30sNo
Weekly financials02:00 SundayIngestion~20 minNo
DB backup03:00 DailyTimescaleDB~5 minYes
Health checkEvery 10 minAll~5sYes
Monitoring & Alerting

Keeping the Lights On

A terminal that goes down on a volatile day is worse than no terminal. You need monitoring at three levels:

Uptime Monitoring

External ping every 60s. UptimeRobot (free) or Better Stack ($10/mo). Alert via Telegram if dashboard goes down.

Data Freshness

Check that bars_daily was updated within the last 24h. Alert if stale data detected. Prevents trading on old prices.

Error Alerting

Log all errors to a rotating file. Alert on >5 errors/hour or any critical failure (DB down, API key expired).

Python — Health Check Endpoint from datetime import datetime, timedelta async def health_check() -> dict: """Comprehensive health check for all services.""" checks = {} # 1. Database connectivity try: await db.execute("SELECT 1") checks["database"] = "ok" except: checks["database"] = "FAIL" # 2. Redis connectivity try: await cache.redis.ping() checks["redis"] = "ok" except: checks["redis"] = "FAIL" # 3. Data freshness latest = await db.get_latest_bar_timestamp() stale = (datetime.utcnow() - latest) > timedelta(hours=26) checks["data_fresh"] = "STALE" if stale else "ok" # 4. Disk space disk = shutil.disk_usage("/") pct_free = disk.free / disk.total * 100 checks["disk"] = "LOW" if pct_free < 10 else "ok" # 5. Overall status checks["status"] = "healthy" if all( v == "ok" for v in checks.values() if v != checks["status"] ) else "degraded" return checks
Security

API Key Management and Access Control

Your terminal handles API keys worth money and potentially trading credentials. Security is not optional.

ThreatMitigationImplementation
API key leak.env file + Docker secrets.gitignore on day 1, rotate quarterly
Dashboard exposureCaddy + HTTP auth or Cloudflare TunnelPassword-protect dashboard, no public access
DB accessNetwork isolationDB only accessible from Docker network, never expose port 5432
SSH bruteforceKey-only auth + fail2banDisable password SSH, install fail2ban
Supply chainPin Docker image versionsUse image:tag@sha256:hash for critical services

The .env Checklist

Your .env file should contain: database password, Yahoo cookie, Alpha Vantage key, Polygon API key, Anthropic API key, Telegram bot token + chat ID, Discord webhook URL. Back it up offline (encrypted USB or password manager). If your VPS dies, you need these keys to redeploy in minutes, not hours.

Cost Breakdown

Total Monthly Cost: $30-50

ItemProviderCost/moNotes
VPSHetzner CX31$8.508 GB RAM, 2 vCPU, 80 GB NVMe
Data APIPolygon Starter$29.0015-min delayed, unlimited calls
Claude APIAnthropic~$15.0010 analyses/day + briefings
Uptime monitorUptimeRobot$0Free tier (50 monitors)
Domain + DNSCloudflare$0Free tier (DNS + proxy)
Telegram botTelegram$0Free forever
Discord webhookDiscord$0Free forever
TOTAL~$52.50vs. Bloomberg $2,000/mo
Cost Savings vs. Bloomberg
($24,000 - $630) / year = $23,370 saved = 97.4% cheaper
Scaling: From 50 to 500+ Symbols

Growing Your Universe

Your terminal starts with 50 symbols. As you grow confidence, you'll want to cover the full S&P 500, European markets, and crypto. Here's how each layer scales:

Component50 Symbols500 SymbolsBottleneckSolution
Ingestion3 min/day30 min/dayAPI rate limitsParallel providers, batch requests
TimescaleDB200 MB2 GBQuery speed at scaleCompression, indexes, materialized views
Redis50 MB256 MBMemory capLRU eviction, TTL tuning
Signal engine30 sec5 minCPU for indicatorsVectorized numpy, pre-compute on ingest
Dashboard50 rows500 rowsDOM renderingVirtual scrolling, pagination

When to Upgrade Your VPS

Monitor RAM usage. When TimescaleDB + Redis + all services consistently use >75% of your RAM, upgrade to the next tier. On Hetzner, going from CX31 (8 GB) to CX41 (16 GB) is $15.90/month — still 99% cheaper than Bloomberg. You won't need this until you're running 1,000+ symbols with real-time streaming.

Key Takeaways

Key Takeaways — Part 6 (Final)

Congratulations — You've Completed the Series!

You now have the complete blueprint to build, deploy, and operate a personal trading terminal with AI. From architecture to production in 6 parts. Go build it.

Re-read Part 1 Back to Home
Build Your Own Trading Terminal with AI6/6