DEV Community

Propfirmkey
Propfirmkey

Posted on

Docker Containerization for Trading Bots: Best Practices

Running trading bots in production requires reliability, isolation, and easy deployment. Docker is the perfect fit. Let's build a production-grade containerized trading bot from scratch.

Why Docker for Trading Bots?

  1. Reproducibility — same environment in dev and prod
  2. Isolation — one bot crashing doesn't take down others
  3. Easy scaling — run multiple strategies in parallel
  4. Quick rollbacks — previous image tag = instant rollback

Project Structure

trading-bot/
├── Dockerfile
├── docker-compose.yml
├── .env.example
├── requirements.txt
├── src/
│   ├── __init__.py
│   ├── bot.py
│   ├── strategy.py
│   ├── risk_manager.py
│   └── data_feed.py
├── config/
│   └── config.yaml
├── logs/
└── data/
Enter fullscreen mode Exit fullscreen mode

The Dockerfile

# Multi-stage build for smaller final image
FROM python:3.12-slim AS builder

WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --user -r requirements.txt

FROM python:3.12-slim AS runtime

# Security: non-root user
RUN groupadd -r trader && useradd -r -g trader trader

WORKDIR /app

# Copy installed packages from builder
COPY --from=builder /root/.local /home/trader/.local

# Copy application
COPY src/ ./src/
COPY config/ ./config/

# Ensure scripts are in PATH
ENV PATH=/home/trader/.local/bin:$PATH
ENV PYTHONUNBUFFERED=1
ENV PYTHONDONTWRITEBYTECODE=1

# Health check
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
  CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8080/health')" || exit 1

# Volumes for persistent data
VOLUME ["/app/logs", "/app/data"]

USER trader

ENTRYPOINT ["python", "-m", "src.bot"]
Enter fullscreen mode Exit fullscreen mode

Docker Compose Configuration

version: '3.8'

services:
  trading-bot:
    build: .
    container_name: trading-bot-es
    restart: unless-stopped
    env_file:
      - .env
    volumes:
      - ./logs:/app/logs
      - ./data:/app/data
      - ./config:/app/config:ro
    ports:
      - "8080:8080"  # Health/metrics endpoint
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 512M
        reservations:
          cpus: '0.25'
          memory: 128M
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"
    networks:
      - trading-net

  redis:
    image: redis:7-alpine
    container_name: trading-redis
    restart: unless-stopped
    volumes:
      - redis-data:/data
    networks:
      - trading-net

  prometheus:
    image: prom/prometheus:latest
    container_name: trading-prometheus
    volumes:
      - ./config/prometheus.yml:/etc/prometheus/prometheus.yml:ro
    ports:
      - "9090:9090"
    networks:
      - trading-net

volumes:
  redis-data:

networks:
  trading-net:
    driver: bridge
Enter fullscreen mode Exit fullscreen mode

The Bot Core with Health Endpoint

# src/bot.py
import asyncio
import signal
import logging
from aiohttp import web
from datetime import datetime

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s [%(levelname)s] %(name)s: %(message)s',
    handlers=[
        logging.FileHandler('/app/logs/bot.log'),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger('trading-bot')

class TradingBot:
    def __init__(self):
        self.running = False
        self.start_time = None
        self.trades_today = 0
        self.pnl_today = 0.0

    async def start(self):
        self.running = True
        self.start_time = datetime.now()
        logger.info("Trading bot started")

        while self.running:
            try:
                await self.trading_loop()
            except Exception as e:
                logger.error(f"Trading loop error: {e}")
                await asyncio.sleep(5)

    async def trading_loop(self):
        # Your strategy logic here
        await asyncio.sleep(1)  # Tick interval

    async def stop(self):
        logger.info("Shutting down trading bot...")
        self.running = False
        # Close positions, save state, etc.

    def health_status(self):
        return {
            "status": "running" if self.running else "stopped",
            "uptime_seconds": (datetime.now() - self.start_time).total_seconds() if self.start_time else 0,
            "trades_today": self.trades_today,
            "pnl_today": self.pnl_today,
            "timestamp": datetime.now().isoformat(),
        }

# Health endpoint
bot = TradingBot()

async def health_handler(request):
    return web.json_response(bot.health_status())

async def metrics_handler(request):
    status = bot.health_status()
    metrics = f"""# HELP trading_bot_uptime_seconds Bot uptime
# TYPE trading_bot_uptime_seconds gauge
trading_bot_uptime_seconds {status['uptime_seconds']}
# HELP trading_bot_trades_total Total trades today
# TYPE trading_bot_trades_total counter
trading_bot_trades_total {status['trades_today']}
# HELP trading_bot_pnl_dollars Current PnL
# TYPE trading_bot_pnl_dollars gauge
trading_bot_pnl_dollars {status['pnl_today']}
"""
    return web.Response(text=metrics, content_type='text/plain')

async def main():
    app = web.Application()
    app.router.add_get('/health', health_handler)
    app.router.add_get('/metrics', metrics_handler)

    runner = web.AppRunner(app)
    await runner.setup()
    site = web.TCPSite(runner, '0.0.0.0', 8080)
    await site.start()

    # Handle graceful shutdown
    loop = asyncio.get_event_loop()
    for sig in (signal.SIGINT, signal.SIGTERM):
        loop.add_signal_handler(sig, lambda: asyncio.create_task(bot.stop()))

    await bot.start()

if __name__ == '__main__':
    asyncio.run(main())
Enter fullscreen mode Exit fullscreen mode

Secret Management

# .env.example - NEVER commit actual .env
DATA_FEED_KEY=your_key_here
TRADING_ACCOUNT_ID=your_account
BROKER_API_KEY=your_broker_key
REDIS_URL=redis://redis:6379
Enter fullscreen mode Exit fullscreen mode
# In production, use Docker secrets or environment injection
docker run --env-file .env trading-bot
Enter fullscreen mode Exit fullscreen mode

Deployment Script

#!/bin/bash
set -e

echo "Building trading bot..."
docker compose build --no-cache

echo "Stopping current bot (closing positions first)..."
docker compose exec trading-bot python -c "
import requests
requests.post('http://localhost:8080/shutdown')
" 2>/dev/null || true

sleep 5

echo "Deploying new version..."
docker compose up -d

echo "Checking health..."
sleep 10
curl -s http://localhost:8080/health | python -m json.tool

echo "Deployment complete!"
Enter fullscreen mode Exit fullscreen mode

Key Takeaways

  1. Always use multi-stage builds to keep images small
  2. Never run as root — create a dedicated user
  3. Resource limits prevent runaway processes from crashing your server
  4. Health checks enable orchestrators to auto-restart failed bots
  5. Persistent volumes protect logs and state across restarts

Whether you're running bots for personal trading or managing a funded account from a prop firm, Docker provides the reliability you need. If you're looking for firms that support algorithmic trading, PropFirmKey has detailed info on which firms like Alpha Futures are algo-friendly.

Top comments (0)