From a173aba1110166affcdfe2cdf97de4379c26f6ce Mon Sep 17 00:00:00 2001 From: Harshit Jain Date: Sat, 27 Dec 2025 10:07:22 +0530 Subject: [PATCH 1/6] feat: add Docker containerization (#455) --- .dockerignore | 41 +++++++++ .github/workflows/docker.yml | 52 +++++++++++ Dockerfile | 53 +++++++++++ README.md | 13 +++ docker-compose.yml | 91 +++++++++++++++++++ docs/guides/docker-deployment.md | 49 ++++++++++ src/frontend/.dockerignore | 4 + src/frontend/Dockerfile | 31 +++++++ src/frontend/nginx.conf | 46 ++++++++++ .../src/components/chat/chat-messages.tsx | 14 +-- 10 files changed, 381 insertions(+), 13 deletions(-) create mode 100644 .dockerignore create mode 100644 .github/workflows/docker.yml create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100644 docs/guides/docker-deployment.md create mode 100644 src/frontend/.dockerignore create mode 100644 src/frontend/Dockerfile create mode 100644 src/frontend/nginx.conf diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..0e19a1d1 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,41 @@ +# Git +.git +.gitignore + +# Docs (except README) +docs/ +notebooks/ +assets/ +CHANGELOG.md +CONTRIBUTING.md +SECURITY.md +*.md +!README.md + +# Dev files +.vscode/ +.github/ +.env +.env.* +!.env.example + +# Python cache +__pycache__/ +*.py[cod] +.pytest_cache/ +.ruff_cache/ +.coverage +*.egg-info/ +dist/ +.venv/ + +# Frontend (built separately) +src/frontend/ + +# Tests +tests/ + +# Logs/data +*.log +logs/ +.var/ diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 00000000..3439958e --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,52 @@ +# Docker Build & Push Workflow +name: Docker + +on: + push: + branches: [main] + tags: ["v*"] + pull_request: + branches: [main] + +permissions: + contents: read + packages: write + +env: + REGISTRY: ghcr.io + +jobs: + build: + name: Build & Push + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GHCR + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build backend + uses: docker/build-push-action@v6 + with: + context: . + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ env.REGISTRY }}/${{ github.repository }}:${{ github.sha }},${{ env.REGISTRY }}/${{ github.repository }}:latest + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Build frontend + uses: docker/build-push-action@v6 + with: + context: ./src/frontend + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ env.REGISTRY }}/${{ github.repository }}-ui:${{ github.sha }},${{ env.REGISTRY }}/${{ github.repository }}-ui:latest + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..301c5ea2 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,53 @@ +# syntax=docker/dockerfile:1 +# ============================================================================ +# AgenticFleet Backend Dockerfile +# Optimized multi-stage build for production +# ============================================================================ + +FROM python:3.12-slim AS builder + +# Install uv +COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv + +WORKDIR /app + +# Copy only dependency files first (better caching) +COPY pyproject.toml uv.lock ./ + +# Sync dependencies WITHOUT installing the project itself +ENV UV_COMPILE_BYTECODE=1 UV_LINK_MODE=copy +RUN uv sync --frozen --no-install-project --no-dev + +# Copy source and install project +COPY src/agentic_fleet ./src/agentic_fleet +COPY README.md LICENSE ./ +RUN uv sync --frozen --no-dev + +# ----------------------------------------------------------------------------- +# Runtime stage - minimal image +# ----------------------------------------------------------------------------- +FROM python:3.12-slim + +RUN apt-get update && apt-get install -y --no-install-recommends curl \ + && rm -rf /var/lib/apt/lists/* \ + && useradd -m -s /bin/bash app + +WORKDIR /app + +# Copy only the virtual environment and source +COPY --from=builder --chown=app:app /app/.venv ./.venv +COPY --from=builder --chown=app:app /app/src ./src + +# Create data directories with correct permissions +RUN mkdir -p .var/logs .var/cache .var/data && chown -R app:app .var + +ENV PATH="/app/.venv/bin:$PATH" \ + PYTHONUNBUFFERED=1 + +USER app +EXPOSE 8000 + +HEALTHCHECK --interval=30s --timeout=10s --start-period=15s --retries=3 \ + CMD curl -f http://localhost:8000/health || exit 1 + +CMD ["python", "-m", "uvicorn", "agentic_fleet.main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/README.md b/README.md index 644393f7..6b26766a 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,19 @@ agentic-fleet run -m "Research the latest advances in AI agents" --verbose agentic-fleet dev ``` +### 🐳 Docker + +```bash +# Quick start with Docker Compose +git clone https://github.com/Qredence/agentic-fleet.git && cd agentic-fleet +cp .env.example .env # Add your OPENAI_API_KEY +docker compose up -d + +# Access: Frontend http://localhost:3000, API http://localhost:8000 +``` + +See [Docker Deployment Guide](docs/guides/docker-deployment.md) for production setup. + ## 📖 Usage ### CLI diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..cd1ad586 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,91 @@ +# ============================================================================ +# AgenticFleet Docker Compose +# Production-ready setup matching the project architecture +# ============================================================================ +# +# The project uses: +# - Backend (FastAPI) on port 8000 for API endpoints +# - Frontend (Vite/React) proxied through nginx on port 3000 +# +# Usage: +# docker compose up -d # Start full stack +# docker compose up -d backend # Backend only +# docker compose --profile tracing up -d # With Jaeger +# +# ============================================================================ + +services: + # -------------------------------------------------------------------------- + # Backend API (FastAPI + uvicorn) + # -------------------------------------------------------------------------- + backend: + build: + context: . + dockerfile: Dockerfile + image: ghcr.io/qredence/agentic-fleet:latest + container_name: agentic-fleet-backend + restart: unless-stopped + ports: + - "8000:8000" + environment: + # Required + - OPENAI_API_KEY=${OPENAI_API_KEY:-} + # Optional API keys + - TAVILY_API_KEY=${TAVILY_API_KEY:-} + - AZURE_OPENAI_ENDPOINT=${AZURE_OPENAI_ENDPOINT:-} + - AZURE_OPENAI_API_KEY=${AZURE_OPENAI_API_KEY:-} + # Settings + - LOG_LEVEL=${LOG_LEVEL:-INFO} + # Tracing + - ENABLE_OTEL=${ENABLE_OTEL:-false} + - OTLP_ENDPOINT=http://jaeger:4317 + volumes: + - backend-data:/app/.var + networks: + - agentic-net + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8000/health"] + interval: 30s + timeout: 10s + start_period: 20s + retries: 3 + + # -------------------------------------------------------------------------- + # Frontend (Vite production build served by nginx) + # -------------------------------------------------------------------------- + frontend: + build: + context: ./src/frontend + dockerfile: Dockerfile + image: ghcr.io/qredence/agentic-fleet-ui:latest + container_name: agentic-fleet-frontend + restart: unless-stopped + ports: + - "3000:80" + depends_on: + backend: + condition: service_healthy + networks: + - agentic-net + + # -------------------------------------------------------------------------- + # Jaeger Tracing + # -------------------------------------------------------------------------- + jaeger: + image: jaegertracing/jaeger:2.13.0 + container_name: agentic-fleet-jaeger + profiles: [tracing] + environment: + - COLLECTOR_OTLP_ENABLED=true + ports: + - "4317:4317" + - "16686:16686" + networks: + - agentic-net + +networks: + agentic-net: + driver: bridge + +volumes: + backend-data: diff --git a/docs/guides/docker-deployment.md b/docs/guides/docker-deployment.md new file mode 100644 index 00000000..30f883f3 --- /dev/null +++ b/docs/guides/docker-deployment.md @@ -0,0 +1,49 @@ +# Docker Deployment + +## Quick Start + +```bash +# 1. Configure environment +cp .env.example .env +# Edit .env and add OPENAI_API_KEY + +# 2. Start services +docker compose up -d + +# 3. Access +# Frontend: http://localhost:3000 +# API Docs: http://localhost:8000/docs +``` + +## Commands + +```bash +docker compose up -d # Start all +docker compose up -d backend # Backend only +docker compose --profile tracing up -d # With Jaeger tracing +docker compose logs -f # View logs +docker compose down # Stop +``` + +## Environment Variables + +| Variable | Required | Description | +|----------|----------|-------------| +| `OPENAI_API_KEY` | Yes | OpenAI API key | +| `TAVILY_API_KEY` | No | Web search | +| `AZURE_OPENAI_*` | No | Azure OpenAI config | +| `ENABLE_OTEL` | No | Enable tracing | + +## Production + +For production, use pre-built images: + +```bash +docker pull ghcr.io/qredence/agentic-fleet:latest +docker pull ghcr.io/qredence/agentic-fleet-ui:latest +``` + +Or build locally: +```bash +docker compose build +``` diff --git a/src/frontend/.dockerignore b/src/frontend/.dockerignore new file mode 100644 index 00000000..e9184df4 --- /dev/null +++ b/src/frontend/.dockerignore @@ -0,0 +1,4 @@ +node_modules/ +dist/ +*.log +.env* diff --git a/src/frontend/Dockerfile b/src/frontend/Dockerfile new file mode 100644 index 00000000..923fa80d --- /dev/null +++ b/src/frontend/Dockerfile @@ -0,0 +1,31 @@ +# ============================================================================ +# AgenticFleet Frontend Dockerfile +# Lightweight nginx-based production image +# ============================================================================ + +FROM node:20-alpine AS builder + +WORKDIR /app +COPY package*.json ./ +RUN npm ci --legacy-peer-deps +COPY . . +RUN npm run build + +# ----------------------------------------------------------------------------- +# Production - nginx alpine (~25MB) +# ----------------------------------------------------------------------------- +FROM nginx:alpine + +# Custom nginx config for SPA + API proxy +RUN rm /etc/nginx/conf.d/default.conf +COPY nginx.conf /etc/nginx/conf.d/default.conf + +# Copy built assets +COPY --from=builder /app/dist /usr/share/nginx/html + +EXPOSE 80 + +HEALTHCHECK --interval=30s --timeout=5s --retries=3 \ + CMD wget -q --spider http://localhost/ || exit 1 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/src/frontend/nginx.conf b/src/frontend/nginx.conf new file mode 100644 index 00000000..2ea2d13b --- /dev/null +++ b/src/frontend/nginx.conf @@ -0,0 +1,46 @@ +# Nginx config for AgenticFleet frontend +# Serves SPA with API proxy to backend + +upstream api { + server backend:8000; +} + +server { + listen 80; + root /usr/share/nginx/html; + index index.html; + + # Gzip + gzip on; + gzip_types text/plain text/css application/json application/javascript text/xml image/svg+xml; + + # Proxy API to backend + location /api { + proxy_pass http://api; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_read_timeout 300s; + proxy_buffering off; + } + + # Health/docs endpoints + location ~ ^/(health|ready|docs|redoc|openapi.json) { + proxy_pass http://api; + proxy_set_header Host $host; + } + + # Static assets with cache + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + # SPA fallback + location / { + try_files $uri $uri/ /index.html; + } +} diff --git a/src/frontend/src/components/chat/chat-messages.tsx b/src/frontend/src/components/chat/chat-messages.tsx index b1bbd475..37829e11 100644 --- a/src/frontend/src/components/chat/chat-messages.tsx +++ b/src/frontend/src/components/chat/chat-messages.tsx @@ -12,18 +12,6 @@ import { MessageContent, } from "@/components/message"; -/** - * Prepare message content for display. - * - * @param content - Message content to display; non-string values will be serialized - * @param isStreaming - Whether to append a trailing streaming cursor - * @returns The formatted message string; includes a trailing ` ▍` when `isStreaming` is true - */ -function formatMessageContent(content: unknown, isStreaming: boolean): string { - const baseContent = typeof content === "string" ? content : JSON.stringify(content); - return isStreaming ? baseContent + " ▍" : baseContent; -} - export type ChatMessagesProps = { messages: ChatMessage[]; isLoading?: boolean; @@ -141,4 +129,4 @@ export function ChatMessages({ ); -} \ No newline at end of file +} From 3ef1119aee022c6a3713d93c4c5d287eb21ae26b Mon Sep 17 00:00:00 2001 From: Harshit Jain Date: Sat, 27 Dec 2025 10:25:43 +0530 Subject: [PATCH 2/6] fix: address code review feedback --- Dockerfile | 7 +++---- docker-compose.yml | 11 ++--------- src/frontend/Dockerfile | 4 ++-- 3 files changed, 7 insertions(+), 15 deletions(-) diff --git a/Dockerfile b/Dockerfile index 301c5ea2..609a240d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,8 +6,8 @@ FROM python:3.12-slim AS builder -# Install uv -COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv +# Install uv (pinned version for reproducibility) +COPY --from=ghcr.io/astral-sh/uv:0.5.14 /uv /usr/local/bin/uv WORKDIR /app @@ -34,9 +34,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends curl \ WORKDIR /app -# Copy only the virtual environment and source +# Copy only the virtual environment (source is installed in venv) COPY --from=builder --chown=app:app /app/.venv ./.venv -COPY --from=builder --chown=app:app /app/src ./src # Create data directories with correct permissions RUN mkdir -p .var/logs .var/cache .var/data && chown -R app:app .var diff --git a/docker-compose.yml b/docker-compose.yml index cd1ad586..2b7c8e39 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -43,12 +43,7 @@ services: - backend-data:/app/.var networks: - agentic-net - healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:8000/health"] - interval: 30s - timeout: 10s - start_period: 20s - retries: 3 + # Healthcheck defined in Dockerfile # -------------------------------------------------------------------------- # Frontend (Vite production build served by nginx) @@ -72,11 +67,9 @@ services: # Jaeger Tracing # -------------------------------------------------------------------------- jaeger: - image: jaegertracing/jaeger:2.13.0 + image: jaegertracing/all-in-one:1.59 container_name: agentic-fleet-jaeger profiles: [tracing] - environment: - - COLLECTOR_OTLP_ENABLED=true ports: - "4317:4317" - "16686:16686" diff --git a/src/frontend/Dockerfile b/src/frontend/Dockerfile index 923fa80d..f7b12293 100644 --- a/src/frontend/Dockerfile +++ b/src/frontend/Dockerfile @@ -16,8 +16,8 @@ RUN npm run build # ----------------------------------------------------------------------------- FROM nginx:alpine -# Custom nginx config for SPA + API proxy -RUN rm /etc/nginx/conf.d/default.conf +# Install wget for healthcheck and remove default config +RUN apk add --no-cache wget && rm /etc/nginx/conf.d/default.conf COPY nginx.conf /etc/nginx/conf.d/default.conf # Copy built assets From 4b2ccd0ad2492b72362b74cceb439990fd8db589 Mon Sep 17 00:00:00 2001 From: Harshit Jain Date: Sat, 27 Dec 2025 11:44:13 +0530 Subject: [PATCH 3/6] Add build fix for docker repo name in lowercase --- .github/workflows/docker.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 3439958e..df2e2800 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -33,12 +33,15 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Set lowercase image name + run: echo "IMAGE_NAME=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV + - name: Build backend uses: docker/build-push-action@v6 with: context: . push: ${{ github.event_name != 'pull_request' }} - tags: ${{ env.REGISTRY }}/${{ github.repository }}:${{ github.sha }},${{ env.REGISTRY }}/${{ github.repository }}:latest + tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }},${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest cache-from: type=gha cache-to: type=gha,mode=max @@ -47,6 +50,6 @@ jobs: with: context: ./src/frontend push: ${{ github.event_name != 'pull_request' }} - tags: ${{ env.REGISTRY }}/${{ github.repository }}-ui:${{ github.sha }},${{ env.REGISTRY }}/${{ github.repository }}-ui:latest + tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ui:${{ github.sha }},${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ui:latest cache-from: type=gha cache-to: type=gha,mode=max From b4f3ea4f3ea4acd456d6caf41d5e3e05d62af00c Mon Sep 17 00:00:00 2001 From: Zachary BENSALEM Date: Mon, 29 Dec 2025 18:16:44 +0100 Subject: [PATCH 4/6] Update Dockerfile Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Zachary BENSALEM --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 609a240d..c0c04afb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,7 @@ FROM python:3.12-slim AS builder # Install uv (pinned version for reproducibility) -COPY --from=ghcr.io/astral-sh/uv:0.5.14 /uv /usr/local/bin/uv +COPY --from=ghcr.io/astral-sh/uv:0.8.0 /uv /usr/local/bin/uv WORKDIR /app From 29629735dad7b69261572f81d521c1be0f5380d5 Mon Sep 17 00:00:00 2001 From: Zachary BENSALEM Date: Mon, 29 Dec 2025 18:17:12 +0100 Subject: [PATCH 5/6] Update README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Zachary BENSALEM --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6b26766a..d2e8bae5 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,9 @@ agentic-fleet dev ```bash # Quick start with Docker Compose -git clone https://github.com/Qredence/agentic-fleet.git && cd agentic-fleet +# Clone the repo (skip if you're already in the project directory) +git clone https://github.com/Qredence/agentic-fleet.git +cd agentic-fleet cp .env.example .env # Add your OPENAI_API_KEY docker compose up -d From d121deb4561f974612f7b568e381329272c58331 Mon Sep 17 00:00:00 2001 From: Zachary BENSALEM Date: Mon, 29 Dec 2025 18:17:37 +0100 Subject: [PATCH 6/6] Update src/frontend/nginx.conf Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Zachary BENSALEM --- src/frontend/nginx.conf | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/frontend/nginx.conf b/src/frontend/nginx.conf index 2ea2d13b..51a64b66 100644 --- a/src/frontend/nginx.conf +++ b/src/frontend/nginx.conf @@ -13,6 +13,10 @@ server { # Gzip gzip on; gzip_types text/plain text/css application/json application/javascript text/xml image/svg+xml; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 5; + gzip_min_length 1024; # Proxy API to backend location /api {