diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..9987b60 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,54 @@ +# Arbiter Dockerfile +# Multi-stage build for smaller final image + +FROM python:3.12-slim as builder + +WORKDIR /app + +# Install build dependencies +RUN apt-get update && apt-get install -y --no-install-recommends \ + gcc \ + libpq-dev \ + && rm -rf /var/lib/apt/lists/* + +# Install Python dependencies +COPY pyproject.toml ./ +RUN pip install --no-cache-dir build && \ + pip wheel --no-cache-dir --wheel-dir /wheels -e . + +# Final stage +FROM python:3.12-slim + +WORKDIR /app + +# Install runtime dependencies +RUN apt-get update && apt-get install -y --no-install-recommends \ + libpq5 \ + && rm -rf /var/lib/apt/lists/* \ + && useradd --create-home --shell /bin/bash arbiter + +# Copy wheels and install +COPY --from=builder /wheels /wheels +RUN pip install --no-cache-dir /wheels/* && rm -rf /wheels + +# Copy application +COPY src/ ./src/ +COPY templates/ ./templates/ +COPY alembic.ini ./ + +# Set ownership +RUN chown -R arbiter:arbiter /app + +USER arbiter + +# Environment +ENV PYTHONPATH=/app/src \ + PYTHONUNBUFFERED=1 \ + ARBITER_TEMPLATES_DIR=/app/templates + +# Default command (API server) +CMD ["uvicorn", "arbiter.main:app", "--host", "0.0.0.0", "--port", "8000"] + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..dcd83cb --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,120 @@ +# Arbiter Docker Compose configuration +# Usage: +# Development: docker compose up +# Infrastructure only: docker compose up db redis +# Production: docker compose -f docker-compose.yml -f docker-compose.prod.yml up + +services: + # PostgreSQL database + db: + image: postgres:16-alpine + container_name: arbiter-db + environment: + POSTGRES_USER: arbiter + POSTGRES_PASSWORD: arbiter + POSTGRES_DB: arbiter + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U arbiter -d arbiter"] + interval: 10s + timeout: 5s + retries: 5 + + # Redis for job queue and caching + redis: + image: redis:7-alpine + container_name: arbiter-redis + ports: + - "6379:6379" + volumes: + - redis_data:/data + command: redis-server --appendonly yes + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 5 + + # API server + api: + build: + context: . + dockerfile: Dockerfile + container_name: arbiter-api + ports: + - "8000:8000" + environment: + ARBITER_DATABASE_URL: postgresql+asyncpg://arbiter:arbiter@db:5432/arbiter + ARBITER_REDIS_URL: redis://redis:6379/0 + ARBITER_TEMPLATES_DIR: /app/templates + depends_on: + db: + condition: service_healthy + redis: + condition: service_healthy + healthcheck: + test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"] + interval: 30s + timeout: 10s + start_period: 10s + retries: 3 + volumes: + - ./templates:/app/templates:ro + + # Background worker + worker: + build: + context: . + dockerfile: Dockerfile + container_name: arbiter-worker + command: ["python", "-m", "arq", "arbiter.worker.settings.WorkerSettings"] + environment: + ARBITER_DATABASE_URL: postgresql+asyncpg://arbiter:arbiter@db:5432/arbiter + ARBITER_REDIS_URL: redis://redis:6379/0 + ARBITER_TEMPLATES_DIR: /app/templates + depends_on: + db: + condition: service_healthy + redis: + condition: service_healthy + volumes: + - ./templates:/app/templates:ro + + # Dashboard UI + dashboard: + build: + context: ./dashboard + dockerfile: Dockerfile + container_name: arbiter-dashboard + ports: + - "3000:80" + depends_on: + api: + condition: service_healthy + healthcheck: + test: ["CMD", "wget", "-q", "--spider", "http://localhost:80"] + interval: 30s + timeout: 10s + retries: 3 + + # Database migrations (run once) + migrate: + build: + context: . + dockerfile: Dockerfile + container_name: arbiter-migrate + command: ["python", "-m", "alembic", "upgrade", "head"] + environment: + ARBITER_DATABASE_URL: postgresql+asyncpg://arbiter:arbiter@db:5432/arbiter + depends_on: + db: + condition: service_healthy + profiles: + - migrate + +volumes: + postgres_data: + redis_data: