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?
- Reproducibility — same environment in dev and prod
- Isolation — one bot crashing doesn't take down others
- Easy scaling — run multiple strategies in parallel
- 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/
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"]
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
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())
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
# In production, use Docker secrets or environment injection
docker run --env-file .env trading-bot
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!"
Key Takeaways
- Always use multi-stage builds to keep images small
- Never run as root — create a dedicated user
- Resource limits prevent runaway processes from crashing your server
- Health checks enable orchestrators to auto-restart failed bots
- 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)