From 36d932fc5c5beb0214be17329f3c3857f712f558 Mon Sep 17 00:00:00 2001 From: DevDoshi19 Date: Mon, 22 Jun 2026 13:22:27 +0530 Subject: [PATCH] feat: containerize backend and frontend --- backend/.dockerignore | 33 +++++++++++++ backend/Dockerfile | 78 +++++++++++++++++++++++++++++++ docker-compose.yml | 98 +++++++++++++++++++++++++++++++++++++++ frontend/.dockerignore | 22 +++++++++ frontend/Dockerfile | 57 +++++++++++++++++++++++ frontend/streamlit_app.py | 3 +- 6 files changed, 290 insertions(+), 1 deletion(-) create mode 100644 backend/.dockerignore create mode 100644 backend/Dockerfile create mode 100644 docker-compose.yml create mode 100644 frontend/.dockerignore create mode 100644 frontend/Dockerfile diff --git a/backend/.dockerignore b/backend/.dockerignore new file mode 100644 index 0000000..04808ff --- /dev/null +++ b/backend/.dockerignore @@ -0,0 +1,33 @@ +# Python cache — at any depth +**/__pycache__/ +**/*.pyc +**/*.pyo +**/*.pyd + +# Virtual environment +.venv/ +venv/ +env/ + +# Secrets +.env + +# ChromaDB and data — mounted as volumes +chroma_db/ +data/ + +# Git +.git/ +.gitignore + +# Dev tools +.pytest_cache/ +.ragas_eval_cache/ +**/*.egg-info/ + +# Editor +.vscode/ +.idea/ + +# Learning notes +_readme/ \ No newline at end of file diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..7391b63 --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,78 @@ +# backend/Dockerfile +# +# Builds the VaultMind backend service. +# Contains: FastAPI + LangGraph pipeline + ChromaDB + BM25 +# +# Build: docker build -t vaultmind-backend . (from backend/) +# Run: docker run -p 8000:8000 vaultmind-backend (standalone) +# Preferred: docker compose up (with frontend) (create both the image at once) + +# -- Base image ------------------------------------------------------------------------- +# python:3.11-slim = ~130MB vs ~900MB for full python:3.11 +# Slim strips docs, test files, and unused packages. + +FROM python:3.11-slim + +# -- Python env vars ------------------------------------------------------------------- +# PYTHONDONTWRITEBYTECODE - skip .pyc files, saves space inside the container +# PYTHONUNBUFFERED - flush stdout/stderr immediately, logs show in real time +ENV PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 + +# -- Working directory ---------------------------------------------------------------- +# All subsequent commands run from /app inside the container. +# This is where your backend/ code will live. +WORKDIR /app + +# -- System dependencies -------------------------------------------------------------- +# build-essential — C compiler needed by some Python packages (chromadb, tokenizers) +# curl — useful for health checks inside the container +# && rm -rf /var/lib/apt/lists/* — cleans apt cache, keeps image smaller +RUN apt-get update && apt-get install -y \ + build-essential \ + curl \ + && rm -rf /var/lib/apt/lists/* + +# -- Python dependencies -------------------------------------------------------------- +# Copy requirements FIRST, install, THEN copy code. +# Reason: Docker caches each layer. If requirements.txt hasn't changed, +# this entire pip install step is skipped on rebuild — saves minutes. +# If you copy all code first, any code change invalidates this cache. +COPY requirements.txt . +RUN pip install --no-cache-dir --upgrade pip && \ + pip install --no-cache-dir -r requirements.txt + +# -- Application code ----------------------------------------------------------------- +# Copy everything in backend/ into /app inside the container. +# .dockerignore (next file) prevents __pycache__, .env, chroma_db from being copied. +COPY . . + +# -- Non-root user ---------------------------------------------------------------------- +# Running as root inside a container is a security risk. +# If the app is exploited, attacker gets root — we limit that here. +RUN useradd --create-home --shell /bin/bash vaultmind +USER vaultmind + +# -- Port -------------------------------------------------------------------------------- +# Documents which port the container listens on. +# EXPOSE doesn't publish the port — docker-compose.yml does that. +EXPOSE 8000 + +# -- Healthcheck ------------------------------------------------------------------------- +# Docker checks this every 30s. If it fails 3 times, container is marked unhealthy. +# Uses the shallow /health endpoint — just proves HTTP is responding. +# --interval : how often to check +# --timeout : how long to wait for a response +# --retries : how many failures before marking unhealthy +# --start-period : grace period after startup before checks begin (graph compile takes ~2s) +HEALTHCHECK --interval=30s --timeout=10s --retries=3 --start-period=15s \ + CMD curl -f http://localhost:8000/health || exit 1 + +# -- Start command ---------------------------------------------------------------------- +# Run uvicorn from inside /app (which is backend/). +# So "api.main:app" resolves to /app/api/main.py → app instance. +# --host 0.0.0.0 : listen on all interfaces inside the container +# (127.0.0.1 would only be accessible inside the container itself) +# --port 8000 : match EXPOSE above +# No --reload : never use reload in production — restarts on any file change +CMD ["uvicorn", "api.main:app", "--host", "0.0.0.0", "--port", "8000"] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..7b7d1c9 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,98 @@ +# docker-compose.yml +# Place this at the project root — VaultMind/docker-compose.yml +# +# Commands: +# docker compose up --build → build images and start both containers +# docker compose up → start with existing images (no rebuild) +# docker compose down → stop and remove containers +# docker compose logs -f → stream logs from both services +# docker compose logs -f backend → logs from backend only + +services: + + # ── Backend ──────────────────────────────────────────────────────────────── + backend: + build: + context: ./backend # Docker sees everything inside backend/ + dockerfile: Dockerfile # uses backend/Dockerfile + + container_name: vaultmind-backend + + # Passes every variable from .env into the container at runtime. + # Secrets never baked into the image — they live only on your machine. + env_file: + - .env + + # Publish port 8000 inside the container to port 8000 on your machine. + # Format: "host_port:container_port" + # Access the API at http://localhost:8000 from your browser or curl. + ports: + - "8000:8000" + + # Mount host directories into the container. + # chroma_db/ and data/ survive container restarts and rebuilds. + # Without volumes, every `docker compose down` wipes your vector store. + volumes: + - ./backend/chroma_db:/app/chroma_db # vector store persists + - ./backend/data:/app/data # resume.pdf + chunks.json + + # Docker checks this every 30s using the /health endpoint we built. + # unhealthy containers are visible in `docker compose ps`. + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8000/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 15s # grace period for graph compilation + + # Always restart if the container crashes — except if you manually stop it. + restart: unless-stopped + + # Connect to the shared private network. + networks: + - vaultmind-network + + + # ── Frontend ─────────────────────────────────────────────────────────────── + frontend: + build: + context: ./frontend + dockerfile: Dockerfile + + container_name: vaultmind-frontend + + # BACKEND_URL override — inside Docker, containers reach each other + # by service name, not localhost. "backend" is the service name above. + # This overrides whatever BACKEND_URL is set to in streamlit_app.py. + environment: + - BACKEND_URL=http://backend:8000 + + ports: + - "8501:8501" + + # Don't start frontend until backend container is running. + # Note: "running" doesn't mean "healthy" — just that the process started. + # For strict ordering (wait for /health to pass), you'd use a wait script. + # This is sufficient for development. + depends_on: + - backend + + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8501/_stcore/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 10s + + restart: unless-stopped + + networks: + - vaultmind-network + + +# ── Network ──────────────────────────────────────────────────────────────── +# bridge is the default driver — creates a private network between containers. +# Both services are on this network so frontend can call backend by service name. +networks: + vaultmind-network: + driver: bridge \ No newline at end of file diff --git a/frontend/.dockerignore b/frontend/.dockerignore new file mode 100644 index 0000000..09af79a --- /dev/null +++ b/frontend/.dockerignore @@ -0,0 +1,22 @@ +# frontend/.dockerignore + +# Python cache +__pycache__/ +*.pyc +*.pyo + +# Virtual environment +.venv/ +venv/ +env/ + +# Secrets — not needed, frontend has no API keys +.env + +# Git +.git/ +.gitignore + +# Editor +.vscode/ +.idea/ diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..97fe9e2 --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,57 @@ +# frontend/Dockerfile +# +# Builds the VaultMind frontend service. +# Contains: Streamlit + httpx only — no LangGraph, no ML libraries. +# This image is intentionally small — frontend is a thin HTTP client. +# +# Build: docker build -t vaultmind-frontend . (from frontend/) +# Preferred: docker compose up (with backend) + +# ── Base image ──────────────────────────────────────────────────────────────── +# Same slim base as backend for consistency. +# Frontend needs no C compiler or system libs — pip install is pure Python. +FROM python:3.11-slim + +# ── Python env vars ─────────────────────────────────────────────────────────── +ENV PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 + +# ── Working directory ───────────────────────────────────────────────────────── +WORKDIR /app + +# ── Python dependencies ─────────────────────────────────────────────────────── +# Only two packages — streamlit + httpx. +# No system deps needed unlike the backend (no chromadb, no tokenizers). +COPY requirements.txt . +RUN pip install --no-cache-dir --upgrade pip && \ + pip install --no-cache-dir -r requirements.txt + +# ── Application code ────────────────────────────────────────────────────────── +COPY . . + +# ── Non-root user ───────────────────────────────────────────────────────────── +RUN useradd --create-home --shell /bin/bash vaultmind +USER vaultmind + +# ── Port ────────────────────────────────────────────────────────────────────── +EXPOSE 8501 + +# ── Healthcheck ─────────────────────────────────────────────────────────────── +# Streamlit exposes a /_stcore/health endpoint built-in — no custom route needed. +# start-period is shorter than backend — no graph compilation, starts in ~2s. +HEALTHCHECK --interval=30s --timeout=10s --retries=3 --start-period=10s \ + CMD curl -f http://localhost:8501/_stcore/health || exit 1 + +# ── Streamlit config ────────────────────────────────────────────────────────── +# These env vars configure Streamlit without needing a config.toml file. +# STREAMLIT_SERVER_PORT : match EXPOSE above +# STREAMLIT_SERVER_ADDRESS : listen on all interfaces +# STREAMLIT_SERVER_HEADLESS : true = no browser auto-open, correct for containers +# STREAMLIT_BROWSER_GATHER_USAGE_STATS : false = no telemetry pings on startup +ENV STREAMLIT_SERVER_PORT=8501 \ + STREAMLIT_SERVER_ADDRESS=0.0.0.0 \ + STREAMLIT_SERVER_HEADLESS=true \ + STREAMLIT_BROWSER_GATHER_USAGE_STATS=false + +# ── Start command ───────────────────────────────────────────────────────────── +CMD ["streamlit", "run", "streamlit_app.py"] \ No newline at end of file diff --git a/frontend/streamlit_app.py b/frontend/streamlit_app.py index 10e453a..ad72174 100644 --- a/frontend/streamlit_app.py +++ b/frontend/streamlit_app.py @@ -11,11 +11,12 @@ import streamlit as st import httpx +import os ASSETS_DIR = Path(__file__).parent / "assets" LOGO_PNG = ASSETS_DIR / "logo.png" LOGO_DATA_URI = f"data:image/png;base64,{b64encode(LOGO_PNG.read_bytes()).decode('ascii')}" -BACKEND_URL = "http://127.0.0.1:8000" +BACKEND_URL = os.getenv("BACKEND_URL", "http://127.0.0.1:8000") # ---------------------------------------------------- # PAGE CONFIG # ----------------------------------------------------