Files
kaedwen ea141eb012
Build and Push Docker Image / build-and-push (push) Failing after 1m41s
initial
2026-07-02 20:09:44 +02:00

9.1 KiB

Docker Deployment Guide

Prerequisites

  • Docker or Podman installed
  • Gitea instance with Container Registry enabled
  • GITEA_TOKEN secret configured in repository settings

Building

Local Build

# Build image
docker build -t aitrade:local .

# Build with specific tag
docker build -t aitrade:v1.0.0 .

Automated Build (Gitea Actions)

The CI/CD pipeline automatically builds and pushes images on:

  • Push to main/master/develop: Creates latest and branch-specific tags
  • Git tags (v)*: Creates semantic version tags (v1.0.0, 1.0, 1)
  • Pull requests: Build-only, no push

Image Tags:

gitea.yourdomain.com/username/aitrade:latest
gitea.yourdomain.com/username/aitrade:main
gitea.yourdomain.com/username/aitrade:main-abc123def
gitea.yourdomain.com/username/aitrade:v1.0.0
gitea.yourdomain.com/username/aitrade:1.0
gitea.yourdomain.com/username/aitrade:1

Running

# Start application
docker-compose up -d

# View logs
docker-compose logs -f aitrade

# Stop application
docker-compose down

# Restart application
docker-compose restart aitrade

With Ollama (LLM Sentiment)

# Start both services
docker-compose --profile llm up -d

# Pull Mistral model (first time only)
docker exec ollama ollama pull mistral

# Verify Ollama is running
curl http://localhost:11434/api/version

# Enable LLM in aitrade (edit docker-compose.yaml)
# Set: LLM_SCORER_ENABLED: "true"

# Restart aitrade
docker-compose restart aitrade

Standalone Container

# Pull from registry
docker pull gitea.yourdomain.com/username/aitrade:latest

# Run with default settings
docker run -d \
  --name aitrade \
  -p 8080:8080 \
  -v $(pwd)/data:/app/data \
  gitea.yourdomain.com/username/aitrade:latest

# Run with custom configuration
docker run -d \
  --name aitrade \
  -p 8080:8080 \
  -v $(pwd)/data:/app/data \
  -v $(pwd)/config.yaml:/app/config.yaml:ro \
  -e CONFIG_FILE=/app/config.yaml \
  gitea.yourdomain.com/username/aitrade:latest

# Run with environment variables
docker run -d \
  --name aitrade \
  -p 8080:8080 \
  -v $(pwd)/data:/app/data \
  -e TRADING_STRATEGY=normal \
  -e DRY_RUN=true \
  -e TRADING_ENABLED=false \
  -e LLM_SCORER_ENABLED=false \
  gitea.yourdomain.com/username/aitrade:latest

Configuration

Option 1: Environment Variables

Pass environment variables via -e flag or Docker Compose environment: section.

docker run -d \
  -e TRADING_STRATEGY=aggressive \
  -e MAX_TRADES_PER_HOUR=12 \
  -e DRY_RUN=true \
  ...

Option 2: Config File (YAML)

Mount a config.yaml file into the container:

docker run -d \
  -v $(pwd)/config.yaml:/app/config.yaml:ro \
  -e CONFIG_FILE=/app/config.yaml \
  ...

Option 3: Docker Compose

Edit docker-compose.yaml and modify the environment: section.

Networking

IB Gateway on Host

If IB Gateway runs on the host machine, use host.docker.internal:

environment:
  IB_GATEWAY_HOST: host.docker.internal
  IB_GATEWAY_PORT: "4001"

Ollama on Host

environment:
  LLM_SCORER_ENABLED: "true"
  LLM_SCORER_ENDPOINT: http://host.docker.internal:11434

Custom Network

# Create network
docker network create trading-net

# Run IB Gateway container
docker run -d --name ib-gateway --network trading-net your-ib-image

# Run aitrade
docker run -d \
  --name aitrade \
  --network trading-net \
  -e IB_GATEWAY_HOST=ib-gateway \
  -e IB_GATEWAY_PORT=4001 \
  ...

Persistence

Database

Mount /app/data to persist SQLite database:

docker run -d \
  -v $(pwd)/data:/app/data \
  ...

Important: Ensure the directory is writable by UID 1000 (trader user).

mkdir -p data
chown -R 1000:1000 data

Config File

Mount config as read-only:

docker run -d \
  -v $(pwd)/config.yaml:/app/config.yaml:ro \
  -e CONFIG_FILE=/app/config.yaml \
  ...

Health Checks

The container includes a built-in health check at /health:

Docker Health Status

# Check health status
docker inspect --format='{{.State.Health.Status}}' aitrade
# Output: healthy, unhealthy, or starting

# View health check logs
docker inspect --format='{{range .State.Health.Log}}{{.Output}}{{end}}' aitrade

# Manual check
curl http://localhost:8080/health
# Response: {"status":"healthy"}

Custom Healthcheck Binary

The image includes a lightweight healthcheck binary (/app/healthcheck) for internal health checks:

# Run healthcheck from inside container
docker exec aitrade /app/healthcheck localhost 8080

# Custom host/port
docker exec aitrade /app/healthcheck 127.0.0.1 8080

# Exit code 0 = healthy, 1 = unhealthy

This allows health checks in Distroless without needing curl/wget.

Monitoring

Logs

# View logs
docker logs aitrade

# Follow logs
docker logs -f aitrade

# Last 100 lines
docker logs --tail 100 aitrade

# Docker Compose
docker-compose logs -f aitrade

Metrics

Access the web dashboard at http://localhost:8080:

  • Account balance
  • Active trades
  • Trade history
  • News sentiment
  • Whitelist management

Updating

Pull Latest Image

# Stop container
docker stop aitrade
docker rm aitrade

# Pull latest
docker pull gitea.yourdomain.com/username/aitrade:latest

# Start with same settings
docker run -d \
  --name aitrade \
  -p 8080:8080 \
  -v $(pwd)/data:/app/data \
  gitea.yourdomain.com/username/aitrade:latest

Docker Compose

# Pull latest
docker-compose pull aitrade

# Restart
docker-compose up -d aitrade

Zero-Downtime Update

# Pull new image
docker pull gitea.yourdomain.com/username/aitrade:latest

# Start new container with different name
docker run -d \
  --name aitrade-new \
  -p 8081:8080 \
  -v $(pwd)/data:/app/data \
  gitea.yourdomain.com/username/aitrade:latest

# Verify new container is healthy
curl http://localhost:8081/health

# Switch port mapping (update reverse proxy or load balancer)
# Then stop old container
docker stop aitrade
docker rm aitrade

# Rename new container
docker rename aitrade-new aitrade

Troubleshooting

Container Won't Start

# Check logs
docker logs aitrade

# Check health status
docker inspect --format='{{.State.Health.Status}}' aitrade

# Verify permissions
ls -la data/
# Should be owned by UID 1000

Database Errors

# Check database file
ls -la data/aitrade.db

# Reset database (deletes all data!)
docker stop aitrade
rm -f data/aitrade.db*
docker start aitrade

IB Gateway Connection

# Check IB Gateway is running
netstat -an | grep 4001

# Test from container
docker exec aitrade sh -c "nc -zv host.docker.internal 4001"

Ollama Connection

# Test Ollama from host
curl http://localhost:11434/api/version

# Test from container
docker exec aitrade sh -c "wget -qO- http://host.docker.internal:11434/api/version"

Security

Non-Root User

The container runs as user trader (UID 1000) by default.

Network Isolation

Run on a dedicated network:

docker network create --internal trading-net

Secrets

Never commit secrets to the repository. Use:

  • Docker secrets
  • Environment files (.env)
  • Kubernetes secrets
  • Vault
# Using .env file
docker run -d \
  --env-file .env \
  ...

Production Deployment

Systemd Service

[Unit]
Description=AI Trading Application
After=docker.service
Requires=docker.service

[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/opt/aitrade
ExecStartPre=-/usr/bin/docker stop aitrade
ExecStartPre=-/usr/bin/docker rm aitrade
ExecStart=/usr/bin/docker run -d \
  --name aitrade \
  --restart unless-stopped \
  -p 8080:8080 \
  -v /opt/aitrade/data:/app/data \
  -v /opt/aitrade/config.yaml:/app/config.yaml:ro \
  -e CONFIG_FILE=/app/config.yaml \
  gitea.yourdomain.com/username/aitrade:latest
ExecStop=/usr/bin/docker stop aitrade

[Install]
WantedBy=multi-user.target

Enable and start:

sudo systemctl enable aitrade
sudo systemctl start aitrade
sudo systemctl status aitrade

Kubernetes

See k8s/ directory for Kubernetes manifests (deployment, service, configmap, secrets).

Backup

Database Backup

Since the application uses SQLite in default mode (single file), backups are straightforward:

# Simple copy (application should be stopped)
docker stop aitrade
cp data/aitrade.db backups/aitrade-$(date +%Y%m%d).db
docker start aitrade

# Or use SQLite backup command (can run while app is running)
docker exec aitrade sqlite3 /app/data/aitrade.db ".backup '/app/data/backup-$(date +%Y%m%d).db'"

# Copy to host
docker cp aitrade:/app/data/backup-20260628.db ./backups/

# Automated backup (cron) - runs while app is running
0 2 * * * docker exec aitrade sqlite3 /app/data/aitrade.db ".backup '/app/data/backup-$(date +\%Y\%m\%d).db'"

Note: SQLite .backup command is safe to run while the application is running. Simple file copy should only be done when the application is stopped.

Full Backup

# Backup entire data directory (stop app first)
docker stop aitrade
tar -czf aitrade-backup-$(date +%Y%m%d).tar.gz data/
docker start aitrade