Skip to content
Draft
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
48 changes: 48 additions & 0 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,54 @@ This requires `uv` to be installed (see backend setup).
npm run test -- --run --coverage
```

## Docker Workflow (Alternative)

As an alternative to running services locally, Docker provides a consistent development environment.

### Starting Docker Environment

```bash
docker compose up
```

### Running Backend Checks in Docker

```bash
# Format, lint, typecheck, test
docker compose exec backend make fmt
docker compose exec backend make lint
docker compose exec backend make typecheck
docker compose exec backend make test

# All checks
docker compose exec backend make --keep-going check
```

### Running Frontend Checks in Docker

```bash
docker compose exec frontend npm run lint
docker compose exec frontend npm run format
docker compose exec frontend npm run test -- --run
docker compose exec frontend npm run test -- --run --coverage
```

### Building Production Images Locally

```bash
# Backend
docker build --build-arg TYPE=deploy -t backend:prod ./backend

# Frontend
docker build --build-arg TYPE=deploy -t frontend:prod ./frontend
```

### Notes

- GitHub Actions automatically builds and pushes multi-arch images on push to main
- Use `docker compose down` to stop services
- Use `docker compose down -v` to also remove volumes

## Style notes

- Write comments as full sentences and end them with a period.
Expand Down
28 changes: 28 additions & 0 deletions .env.docker.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Google Cloud configuration
GOOGLE_CLOUD_PROJECT=tenantfirstaid
GOOGLE_CLOUD_LOCATION=global
VERTEX_AI_DATASTORE=<YOUR_DATASTORE_ID>

# Path to Google credentials on your HOST machine
# Default location after 'gcloud auth application-default login'
GOOGLE_APPLICATION_CREDENTIALS=/home/<USERNAME>/.config/gcloud/application_default_credentials.json

# Model configuration
MODEL_NAME=gemini-2.5-pro
SHOW_MODEL_THINKING=true
LOG_LEVEL=DEBUG

# Flask configuration
FLASK_SECRET_KEY=dev-secret-key-unsafe
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security: Unsafe default for production

The example includes FLASK_SECRET_KEY=dev-secret-key-unsafe which could accidentally be used in production.

Recommendation:

# Flask configuration
# WARNING: Generate a secure random key for production!
# python -c 'import secrets; print(secrets.token_hex(32))'
FLASK_SECRET_KEY=dev-secret-key-CHANGE-THIS-IN-PRODUCTION

Consider adding a startup check in the Flask app that errors if the secret key matches known insecure defaults.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "unsafe" suffix is easy to miss. Add an explicit generation command and make the warning harder to skip:

Suggested change
FLASK_SECRET_KEY=dev-secret-key-unsafe
# WARNING: Replace with a strong random value before any non-local use.
# Generate one with: python -c 'import secrets; print(secrets.token_hex(32))'
FLASK_SECRET_KEY=dev-secret-key-CHANGE-THIS


# LangSmith (optional)
LANGSMITH_API_KEY=<YOUR_LANGSMITH_KEY>
LANGSMITH_TRACING=true
LANGCHAIN_TRACING_V2=true
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If LANGSMITH_TRACING defaults to false in the compose file (see suggestion there), the values here should match to avoid confusing developers who copy the example as-is.

Suggested change
LANGCHAIN_TRACING_V2=true
LANGSMITH_TRACING=false
LANGCHAIN_TRACING_V2=false


# SMTP (optional for local dev)
#MAIL_SERVER=smtp.gmail.com
#MAIL_PORT=587
#SENDER_EMAIL=your_email@gmail.com
#APP_PASSWORD=your_app_password
#RECIPIENT_EMAIL=recipient@example.com
77 changes: 77 additions & 0 deletions Architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,83 @@ graph TB
ReactQuery --> API[Backend API]
```

## Containerization

The application supports Docker containerization for consistent development and deployment environments across Mac, Linux, and Windows.

### Container Architecture

```mermaid
graph TB
subgraph "Development"
DevCompose[docker-compose.yml]
BackendDev[Backend Container<br/>Flask Dev Server<br/>Hot Reload]
FrontendDev[Frontend Container<br/>Vite Dev Server<br/>Hot Reload]
end

subgraph "CI/CD Pipeline"
GHA[GitHub Actions<br/>Multi-arch Build]
BuildBackend[Build Backend<br/>amd64 + arm64]
BuildFrontend[Build Frontend<br/>amd64 + arm64]
end

subgraph "Registry"
GHCR[ghcr.io<br/>Container Registry]
end

subgraph "Production"
ProdCompose[docker-compose.prod.yml]
BackendProd[Backend Container<br/>Gunicorn]
FrontendProd[Frontend Container<br/>Nginx]
end

DevCompose --> BackendDev
DevCompose --> FrontendDev

GHA --> BuildBackend
GHA --> BuildFrontend
BuildBackend --> GHCR
BuildFrontend --> GHCR

GHCR --> BackendProd
GHCR --> FrontendProd
ProdCompose --> BackendProd
ProdCompose --> FrontendProd
```

### Image Types

**Development Images (`TYPE=dev`):**
- Include all dev dependencies (pytest, ruff, mypy, etc.)
- Include tests, scripts, and Makefile
- Run Flask/Vite dev servers with hot reload
- Optimized for developer experience

**Production Images (`TYPE=deploy`):**
- Minimal dependencies (production only)
- No tests or dev tools
- Run Gunicorn/nginx for production serving
- Optimized for size and security

### Multi-Architecture Support

Images are built for:
- `linux/amd64` - x86_64 processors (Intel/AMD)
- `linux/arm64` - ARM processors (Apple Silicon, AWS Graviton)

Enables deployment to cost-effective ARM-based cloud instances.

### Container Registry

Production images published to:
- `ghcr.io/codeforpdx/tenantfirstaid/backend:latest`
- `ghcr.io/codeforpdx/tenantfirstaid/frontend:latest`

Tagged with:
- `latest` - Latest from main branch
- `main-<sha>` - Specific commit from main
- `<branch>` - Latest from feature branch

## Deployment

For full deployment documentation — environments, CI/CD pipeline, secrets management, debugging, permissions, and observability — see [Deployment.md](Deployment.md).
141 changes: 141 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,147 @@ Live at https://tenantfirstaid.com/
% npm run test -- --run
```

## Containerized Development Setup (Alternative)

As an alternative to the local development setup above, you can use containers for a consistent development environment across Mac, Linux, and Windows.

### Prerequisites

- Container runtime
- [Docker](https://docs.docker.com/get-docker/) (24.0+)
- [Docker Compose](https://docs.docker.com/compose/install/) (2.0+)
- or [Apple/Container](https://github.com/apple/container) (0.9.0+)
- Google Cloud credentials (same as local setup)

### Quick Start

1. Copy environment template:
```bash
cp .env.docker.example .env
```

2. Update `.env` with your credentials path:
```bash
# Edit GOOGLE_APPLICATION_CREDENTIALS_HOST to point to your credentials
GOOGLE_APPLICATION_CREDENTIALS_HOST=/home/<USERNAME>/.config/gcloud/application_default_credentials.json
```

3. Start the application:
```bash
docker compose up
```

On MacOS with [`apple/container`](https://github.com/apple/container):
```bash
container system start
# build frontend-dev image
cd frontend && container build --build-arg TYPE=dev --tag frontend-dev
# start frontend container
container run \
--name frontend \
--remove \
--env VITE_API_URL=http://localhost:3000 \
-v ./frontend/src:/app/src:ro \
-v ./frontend/public:/app/public:ro \
-v ./frontend/index.html:/app/index.html:ro \
-v ./frontend/vite.config.ts:/app/vite.config.ts:ro \
--publish 5173:5173 \
frontend-dev:latest &
# execute interactive shell in frontend container
container exec -it frontend /bin/sh

# build backend-dev image
container build --build-arg TYPE=dev --tag backend-dev
# set env from .env and then start backend container
env `grep -e '^\w.*=\S*' .env | cut -d\ -f1` && container run \
--name backend \
--remove \
--env FLASK_ENV=development \
--env SHOW_MODEL_THINKING=${SHOW_MODEL_THINKING:-true} \
--env GOOGLE_CLOUD_PROJECT=${GOOGLE_CLOUD_PROJECT} \
--env GOOGLE_CLOUD_LOCATION=${GOOGLE_CLOUD_LOCATION:-global} \
--env GOOGLE_APPLICATION_CREDENTIALS=/app/secrets/google-creds.json \
--env VERTEX_AI_DATASTORE=${VERTEX_AI_DATASTORE} \
--env MODEL_NAME=${MODEL_NAME:-gemini-2.5-pro} \
--env LOG_LEVEL=${LOG_LEVEL:-DEBUG} \
--env LANGSMITH_API_KEY=${LANGSMITH_API_KEY} \
--env LANGSMITH_TRACING=${LANGSMITH_TRACING:-true} \
--env LANGCHAIN_TRACING_V2=${LANGCHAIN_TRACING_V2:-true} \
-v ./backend/tenantfirstaid:/app/tenantfirstaid:ro \
-v ./backend/tests:/app/tests:ro \
-v ./backend/scripts:/app/scripts:ro \
-v ${GOOGLE_APPLICATION_CREDENTIALS}:/app/secrets/google-creds.json:ro \
--publish 3000:5000 \
backend-dev:latest &
# execute interactive shell in backend container
container exec -it backend /bin/sh
```

4. Access the application:
- Frontend: http://localhost:5173
- Backend API: http://localhost:5000

### Development Workflow

- **Hot reload enabled** - Changes to source files are reflected immediately
- **Run backend checks:**
```bash
docker compose exec backend make lint
docker compose exec backend make typecheck
docker compose exec backend make test
```

on MacOS with `apple/container`
```bash
cd backend && make clean # __pycache__ dirs are bind'd-in to containers as read-only so delete them
container exec backend make lint
container exec backend make typecheck
container exec backend make test
```

- **Run frontend checks:**
```bash
docker compose exec frontend npm run lint
docker compose exec frontend npm run test -- --run
```

on MacOS with `apple/container`
```bash
container exec frontend npm run lint
container exec frontend npm run test -- --run
```

- **View logs:**
```bash
docker compose logs -f backend
docker compose logs -f frontend
```

### Stopping

```bash
docker compose down
```

On MacOS with [`apple/container`](https://github.com/apple/container) (>= 0.9.0):
```bash
container stop --all
container system stop
```

### Troubleshooting

**Windows/WSL2 Performance:**
- Keep source code inside WSL2 filesystem, not Windows filesystem
- Add `consistency: delegated` to volume mounts if experiencing slow performance

**Permission Issues:**
- Ensure Google credentials file is readable: `chmod 644 <credentials-file>`
- On Linux, ensure your user is in the `docker` group: `sudo usermod -aG docker $USER`

**Port Already in Use:**
- Change ports in `docker-compose.yml` if 5000 or 5173 are taken

## Contributing

We currently have regular project meetups: https://www.meetup.com/codepdx/ . Also check out https://www.codepdx.org/ to find our Discord server.
Expand Down
21 changes: 21 additions & 0 deletions backend/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
__pycache__/
*.pyc
*.pyo
*.pyd
.Python
*.so
*.egg
*.egg-info/
dist/
build/
.venv/
venv/
.env
.env.*
!.env.example
*.log
.pytest_cache/
.mypy_cache/
.ruff_cache/
.coverage
htmlcov/
Loading