Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions backend/.dockerignore
Original file line number Diff line number Diff line change
@@ -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/
78 changes: 78 additions & 0 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -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"]
98 changes: 98 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -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
22 changes: 22 additions & 0 deletions frontend/.dockerignore
Original file line number Diff line number Diff line change
@@ -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/
57 changes: 57 additions & 0 deletions frontend/Dockerfile
Original file line number Diff line number Diff line change
@@ -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"]
3 changes: 2 additions & 1 deletion frontend/streamlit_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
# ----------------------------------------------------
Expand Down
Loading