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
77 changes: 77 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Python cache and build artifacts
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
dist/
*.egg-info/
*.egg

# Virtual environments (we build our own in the container)
.venv/
venv/
ENV/
env/

# Testing artifacts
.coverage
.pytest_cache/
htmlcov/
.tox/
.nox/
tests/
*.test.py

# Type checking and linting cache
.mypy_cache/
.ruff_cache/

# IDE and editor files
.idea/
.vscode/
.devcontainer/
*.swp
*.swo
*~
.DS_Store
Thumbs.db

# Git
.git/
.gitignore
.gitattributes
.github/

# Environment files (CRITICAL: Never include secrets)
.env
.env.*
!.env.example

# Documentation (excluding docs/ directory, but keeping markdown files in root)
docs/

# CI/CD
.gitlab-ci.yml
.travis.yml
Jenkinsfile

# Database and data files (use volumes instead)
*.db
*.db-shm
*.db-wal
*.sqlite3
data/
documents/

# Development scripts
scripts/

# Docker files (avoid recursive copying)
Dockerfile
docker-compose.yml
.dockerignore

# Plan files
.claude/
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
python-version: "3.13"

- name: Install dependencies
run: uv sync --all-extras
Expand Down Expand Up @@ -63,7 +63,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
python-version: "3.13"

- name: Install dependencies
run: uv sync --all-extras
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ AI-powered Q&A system for shelter volunteers, using RAG to answer questions from
**Architecture:** Modular monolith with clean architecture principles (see [Documentation Index](#documentation-index) for targeted docs)

**Tech Stack:**
- Python 3.12+, FastAPI, Pydantic 2.x
- Python 3.13+, FastAPI, Pydantic 2.x
- LLM: OpenRouter (Claude via OpenAI-compatible API)
- Vector DB: Chroma (embedded)
- Frontend: Jinja2 + HTMX + Tailwind CSS
Expand Down
29 changes: 26 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Get up and running in 5 minutes:
git clone https://github.com/YOUR-USERNAME/retriever.git
cd retriever

# Install dependencies (requires Python 3.12+)
# Install dependencies (requires Python 3.13+)
uv sync --extra dev

# Copy environment template and add your API keys
Expand All @@ -32,7 +32,7 @@ uv run pytest

### Requirements

- **Python 3.12+** — We use modern Python features
- **Python 3.13+** — We use modern Python features
- **[uv](https://docs.astral.sh/uv/)** — Fast Python package manager (recommended over pip)
- **API Keys** — OpenRouter (for LLM) and OpenAI (for embeddings/moderation)

Expand All @@ -53,6 +53,29 @@ uv run pytest

Visit [http://localhost:8000](http://localhost:8000) to see the app running.

### Docker Development (Optional)

If you prefer Docker for development:

```bash
# Build the image
docker build -t retriever:latest .

# Run with docker-compose
docker-compose up -d

# View logs
docker-compose logs -f retriever

# Create a user (inside container)
docker-compose exec retriever uv run python scripts/create_user.py

# Stop
docker-compose down
```

**Note:** Active development is typically done on the host with `uv run uvicorn --reload` for faster iteration. Docker is mainly for testing the production build locally.

### Understanding the Codebase

- **Quick patterns & conventions** → [CLAUDE.md](CLAUDE.md)
Expand Down Expand Up @@ -312,7 +335,7 @@ Before submitting, ensure:
### Setup Issues?

- Verify `.env` has all required API keys (check `.env.example`)
- Make sure you're using Python 3.12+
- Make sure you're using Python 3.13+
- Try `uv sync --extra dev` to reinstall dependencies
- Check the [README](README.md) configuration section

Expand Down
91 changes: 91 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# ============================================
# Stage 1: Builder
# ============================================
FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim AS builder

# Install build dependencies for compiling C/C++ extensions
RUN apt-get update && \
apt-get install -y --no-install-recommends \
build-essential \
python3-dev \
&& rm -rf /var/lib/apt/lists/*

# Set working directory
WORKDIR /app

# Copy dependency files and README (needed for package metadata) for layer caching
COPY pyproject.toml uv.lock README.md ./

# Install dependencies using uv with locked versions
# --frozen: Use exact versions from uv.lock (reproducible builds)
# --no-cache: Don't cache downloads (reduces image size)
# --no-dev: Exclude development dependencies
RUN uv sync --frozen --no-cache --no-dev

# ============================================
# Stage 2: Runtime
# ============================================
FROM python:3.13-slim-bookworm

# Set environment variables
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
# Default port (can be overridden)
PORT=8000 \
# Python path
PYTHONPATH=/app

# Install runtime dependencies only
RUN apt-get update && \
apt-get install -y --no-install-recommends \
# SQLite library for better performance
libsqlite3-0 \
# curl for health checks
curl \
# ChromaDB native dependencies
libstdc++6 \
libgomp1 \
&& rm -rf /var/lib/apt/lists/*

# Create non-root user for security
RUN groupadd -r appuser && \
useradd -r -g appuser -u 1000 appuser

# Set working directory
WORKDIR /app

# Copy virtual environment from builder
COPY --from=builder --chown=appuser:appuser /app/.venv /app/.venv

# Copy application code
COPY --chown=appuser:appuser src/ /app/src/
COPY --chown=appuser:appuser pyproject.toml /app/

# Copy and set permissions for entrypoint script
COPY --chown=appuser:appuser entrypoint.sh /app/
RUN chmod +x /app/entrypoint.sh

# Create data directories with proper permissions
# These will be mounted as volumes in production
RUN mkdir -p /app/data/chroma /app/documents && \
chown -R appuser:appuser /app

# Switch to non-root user
USER appuser

# Add virtual environment to PATH
ENV PATH="/app/.venv/bin:$PATH"

# Health check
# Checks if the application is responding on the configured port
# Using shell form to allow PORT variable substitution
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD sh -c 'curl -f http://localhost:${PORT:-8000}/health || exit 1'

# Expose port (documentation only, actual port set by PORT env var)
EXPOSE 8000

# Start command using entrypoint script
# The entrypoint script uses 'exec' to ensure uvicorn receives signals directly
# This enables proper SIGTERM handling for graceful shutdowns
CMD ["/app/entrypoint.sh"]
115 changes: 99 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Retriever can be adapted for any organization with documentation that users need

### Prerequisites

- Python 3.12+
- Python 3.13+
- [uv](https://docs.astral.sh/uv/) (recommended) or pip
- API keys for:
- [OpenRouter](https://openrouter.ai/keys) (for LLM access)
Expand Down Expand Up @@ -146,30 +146,113 @@ For best results:

Retriever can be deployed to any platform that supports Python applications.

### Docker

**Prerequisites:**
- Docker and docker-compose compatible container tool installed
- `.env` file configured with your API keys

**Build and run:**

```bash
# Build the production image
docker build -t retriever:latest .

# Run with docker-compose (recommended)
docker-compose up -d

# Check logs
docker-compose logs -f retriever

# Check health
curl http://localhost:8000/health
```

**Alternative: Run with docker directly**

```bash
docker run -d \
--name retriever \
-p 8000:8000 \
--env-file .env \
-v retriever-data:/app/data \
-v retriever-documents:/app/documents \
retriever:latest
```

**Create a user:**

The database is inside the container, so you need to execute the script within the running container:

```bash
# Using docker-compose (recommended)
docker-compose exec retriever uv run python scripts/create_user.py

# Or using docker directly
docker exec -it retriever uv run python scripts/create_user.py
```

**Volume Management:**

```bash
# List volumes
docker volume ls

# Backup data
docker run --rm \
-v retriever-data:/data \
-v $(pwd):/backup \
alpine tar czf /backup/retriever-data-backup.tar.gz /data

# Restore data
docker run --rm \
-v retriever-data:/data \
-v $(pwd):/backup \
alpine tar xzf /backup/retriever-data-backup.tar.gz -C /

# Stop containers (preserves volumes)
docker-compose down

# Stop and DELETE volumes (CAUTION: destroys all data)
docker-compose down -v
```

**Troubleshooting:**

| Issue | Solution |
|-------|----------|
| Port 8000 already in use | Change port: `docker run -p 8001:8000 ...` |
| Health check failing | Check logs: `docker-compose logs retriever` |
| Cannot write to `/app/data` | Verify container runs as `appuser` (uid 1000) |
| Missing environment variables | Ensure `.env` file exists with all required keys |
| Old code running after changes | Rebuild image: `docker-compose build --no-cache` |

**Environment Variables:**

See `.env.example` for the complete list. Required:
- `OPENROUTER_API_KEY` — OpenRouter API key
- `OPENAI_API_KEY` — OpenAI API key
- `JWT_SECRET_KEY` — Generate with `openssl rand -base64 32`

**What gets persisted:**
- `retriever-data` volume → SQLite database + Chroma vector store
- `retriever-documents` volume → Uploaded policy documents

### Railway / Render

1. Connect your repository
2. Set environment variables in the dashboard
3. Deploy

### Docker

```dockerfile
# Dockerfile example
FROM python:3.12-slim
WORKDIR /app
COPY . .
RUN pip install uv && uv sync
CMD ["uv", "run", "uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"]
```

### Production Checklist

- [ ] Set `DEBUG=false`
- [ ] Use a strong `JWT_SECRET_KEY`
- [ ] Configure rate limiting appropriately
- [ ] Use a strong `JWT_SECRET_KEY` (32+ characters, random)
- [ ] Configure rate limiting appropriately for your traffic
- [ ] Set up monitoring (Sentry DSN in `SENTRY_DSN`)
- [ ] Use persistent storage for `data/` directory
- [ ] Use persistent storage for `data/` directory (volumes in Docker, mounted storage on cloud platforms)
- [ ] Test the Docker image locally before cloud deployment
- [ ] Enable HTTPS in production (handled by Cloud Run, Railway, Render)

## Architecture

Expand All @@ -188,7 +271,7 @@ Retriever uses a modular monolith architecture with clean separation of concerns
```

**Tech Stack:**
- **Backend:** Python 3.12+, FastAPI, Pydantic
- **Backend:** Python 3.13+, FastAPI, Pydantic
- **LLM:** Claude via OpenRouter
- **Vector DB:** Chroma (embedded)
- **Frontend:** Jinja2 + HTMX + Tailwind CSS
Expand Down
Loading
Loading