Skip to content
Open
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
36 changes: 30 additions & 6 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# Core image: report_analyst Streamlit app (RPL only, no enterprise deps)
# Unified Report Analyst image — same codebase, runtime selected via REPORT_ANALYST_RUNTIME:
# core — Streamlit only (customer / standalone)
# enterprise — Streamlit + FastAPI + NATS worker (Climate+Tech internal)
FROM python:3.12-slim

WORKDIR /app
Expand All @@ -13,18 +15,40 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
&& rm -rf /var/lib/apt/lists/*

# Python dependencies (core only)
ENV OPENBLAS_NUM_THREADS=1
ENV REPORT_ANALYST_RUNTIME=core
ENV STORAGE_PATH=/app/storage

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Application code
COPY report_analyst_enterprise/requirements.txt report_analyst_enterprise/requirements.txt
RUN pip install --no-cache-dir -r report_analyst_enterprise/requirements.txt

COPY report_analyst_api/requirements.txt report_analyst_api/requirements.txt
RUN pip install --no-cache-dir -r report_analyst_api/requirements.txt

COPY report_analyst_search_backend/requirements.txt report_analyst_search_backend/requirements.txt
RUN pip install --no-cache-dir -r report_analyst_search_backend/requirements.txt

RUN pip install --no-cache-dir "nats-py>=2.7.0" "aiohttp>=3.9.0" "pandas>=2.0.0" "numpy>=1.24.0"

COPY report_analyst/ report_analyst/
COPY report_analyst_enterprise/ report_analyst_enterprise/
COPY report_analyst_api/ report_analyst_api/
COPY report_analyst_jobs/ report_analyst_jobs/
COPY report_analyst_search_backend/ report_analyst_search_backend/
COPY prompts/ prompts/
COPY .streamlit/ .streamlit/
COPY alembic.ini .
COPY alembic/ alembic/

COPY docker-entrypoint.sh /docker-entrypoint.sh
RUN chmod +x /docker-entrypoint.sh

EXPOSE 8080
EXPOSE 8080 8001

HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
HEALTHCHECK --interval=30s --timeout=10s --start-period=15s --retries=3 \
CMD curl -f http://localhost:8080/_stcore/health || exit 1

ENTRYPOINT ["streamlit", "run", "report_analyst/streamlit_app.py", "--server.port=8080", "--server.address=0.0.0.0"]
ENTRYPOINT ["/docker-entrypoint.sh"]
12 changes: 10 additions & 2 deletions INSTALL.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,14 @@ Two images are available: **core** (RPL, Streamlit only) and **enterprise** (cor
# Core image (Streamlit app only)
docker build -t report-analyst:core .

# Enterprise image (adds report_analyst_enterprise, Alembic)
docker build -f Dockerfile.enterprise -t report-analyst:enterprise .
# Enterprise image (report_analyst_enterprise module)
docker build -f report_analyst_enterprise/Dockerfile -t report-analyst:enterprise .

# REST API (report_analyst_api module)
docker build -f report_analyst_api/Dockerfile -t report-analyst:api .

# NATS jobs worker (report_analyst_jobs module)
docker build -f report_analyst_jobs/Dockerfile -t report-analyst:jobs .
```

On **Apple Silicon (ARM)** use `--platform linux/amd64` so `sqlite-vss` installs (no Linux ARM wheel):
Expand All @@ -138,6 +144,8 @@ docker run -p 8080:8080 -e OPENAI_API_KEY=your_key -e DATABASE_URL=postgresql://

App is at `http://localhost:8080`.

For API, jobs worker, and platform integration (NATS, search backend, S3), see [`docs/DOCKER-DEPLOY.md`](docs/DOCKER-DEPLOY.md).

---

## Usage Summary
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,7 @@ The repository uses a **module-based licensing model**:
| `report_analyst_api/` | FastAPI API module | **Climate+Tech Open License for Good** |
| `report_analyst_jobs/` | Jobs, NATS, integration toolkit | **Climate+Tech Open License for Good** |
| `report_analyst_search_backend/` | Search/upload backend integration | **Climate+Tech Open License for Good** |
| `report_analyst_enterprise/` | Enterprise edition (Postgres/pgvector, deploy) | **Climate+Tech Open License for Good** |

The core analysis module `report_analyst/` is open source under the RPL (Reciprocal Public License). All other modules (API, jobs, search backend, etc.) are provided under the Climate+Tech Open License for Good, and can be dual-licensed for commercial or special use cases upon request.

Expand Down
46 changes: 46 additions & 0 deletions docker-compose.coolify-customer.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Coolify customer deploy: Streamlit + persistent SQLite/vector storage (Heroku-style disk).
# Traefik routes to `report-analyst-auth:8080` when HTTP Basic Auth is enabled
# (set-app-http-basic-auth.sh), else `report-analyst:8080`.
# Auth uses nginx + Coolify app env HTTP_BASIC_AUTH_* (not Traefik label substitution).
services:
report-analyst-auth:
build:
context: ./docker/nginx-basic-auth
dockerfile: Dockerfile
environment:
HTTP_BASIC_AUTH_USERNAME: ${HTTP_BASIC_AUTH_USERNAME:-}
HTTP_BASIC_AUTH_PASSWORD: ${HTTP_BASIC_AUTH_PASSWORD:-}
UPSTREAM_HOST: report-analyst
UPSTREAM_PORT: "8080"
expose:
- "8080"
depends_on:
report-analyst:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s

report-analyst:
build:
context: .
dockerfile: Dockerfile
environment:
REPORT_ANALYST_RUNTIME: ${REPORT_ANALYST_RUNTIME:-core}
STORAGE_PATH: /app/storage
volumes:
- storage-data:/app/storage
expose:
- "8080"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/_stcore/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 15s

volumes:
storage-data:
29 changes: 29 additions & 0 deletions docker-entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/usr/bin/env bash
# Start processes based on REPORT_ANALYST_RUNTIME:
# core — Streamlit only (default, customer deployments)
# enterprise — Streamlit + FastAPI + NATS worker (internal Climate+Tech)
set -euo pipefail

RUNTIME="${REPORT_ANALYST_RUNTIME:-core}"
pids=()

cleanup() {
for pid in "${pids[@]}"; do
kill "$pid" 2>/dev/null || true
done
}
trap cleanup EXIT TERM INT

if [ "$RUNTIME" = "enterprise" ]; then
echo "Starting enterprise runtime (Streamlit + API + NATS worker)"
python report_analyst_jobs/nats_integration.py worker &
pids+=($!)
uvicorn report_analyst_api.main:app --host 0.0.0.0 --port 8001 &
pids+=($!)
else
echo "Starting core runtime (Streamlit only)"
fi

exec streamlit run report_analyst/streamlit_app.py \
--server.port=8080 \
--server.address=0.0.0.0
9 changes: 9 additions & 0 deletions docker/nginx-basic-auth/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FROM nginx:1.27-alpine

RUN apk add --no-cache apache2-utils wget

COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

EXPOSE 8080
ENTRYPOINT ["/entrypoint.sh"]
45 changes: 45 additions & 0 deletions docker/nginx-basic-auth/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#!/bin/sh
# Optional HTTP Basic Auth in front of Streamlit. Credentials from Coolify app env
# (HTTP_BASIC_AUTH_USERNAME / HTTP_BASIC_AUTH_PASSWORD). When unset, proxies without auth.
set -eu

UPSTREAM="${UPSTREAM_HOST:-report-analyst}:${UPSTREAM_PORT:-8080}"
# nginx default is 1m; Streamlit uploads use PUT /_stcore/upload_file/ (often multi-MB PDFs).
CLIENT_MAX_BODY_SIZE="${NGINX_CLIENT_MAX_BODY_SIZE:-200m}"
AUTH_FILE=/etc/nginx/auth.htpasswd
CONF=/etc/nginx/conf.d/default.conf

if [ -n "${HTTP_BASIC_AUTH_USERNAME:-}" ] && [ -n "${HTTP_BASIC_AUTH_PASSWORD:-}" ]; then
htpasswd -nbB "$HTTP_BASIC_AUTH_USERNAME" "$HTTP_BASIC_AUTH_PASSWORD" > "$AUTH_FILE"
AUTH_DIRECTIVES="auth_basic \"Report Analyst\";
auth_basic_user_file ${AUTH_FILE};"
else
rm -f "$AUTH_FILE"
AUTH_DIRECTIVES=""
fi

cat > "$CONF" <<EOF
server {
listen 8080;
client_max_body_size ${CLIENT_MAX_BODY_SIZE};
location /health {
auth_basic off;
return 200 'ok';
add_header Content-Type text/plain;
}
location / {
${AUTH_DIRECTIVES}
proxy_pass http://${UPSTREAM};
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_set_header X-Forwarded-Proto \$scheme;
proxy_read_timeout 86400;
}
}
EOF

exec nginx -g 'daemon off;'
44 changes: 0 additions & 44 deletions docs/COOLIFY.md

This file was deleted.

74 changes: 74 additions & 0 deletions docs/DOCKER-DEPLOY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Docker deployment

One image from the **repository root**. Runtime mode is selected with `REPORT_ANALYST_RUNTIME`:

| Mode | Env | Processes |
|------|-----|-----------|
| **core** (default) | `REPORT_ANALYST_RUNTIME=core` | Streamlit on `:8080` |
| **enterprise** | `REPORT_ANALYST_RUNTIME=enterprise` | Streamlit `:8080`, FastAPI `:8001`, NATS worker |

```bash
docker build -t report-analyst .
```

## Customer (Streamlit only)

```bash
docker run -p 8080:8080 \
-v report-analyst-storage:/app/storage \
-e REPORT_ANALYST_RUNTIME=core \
-e STORAGE_PATH=/app/storage \
-e OPENAI_API_KEY=your_key \
-e OPENAI_API_MODEL=gpt-4o-mini \
report-analyst
```

On **Coolify**, use `docker-compose.coolify-customer.yml` (declares `storage-data:/app/storage`) or add a **Persistent Storage** volume mount at `/app/storage` (directory, not the `.sqlite` file alone). Coolify prefixes volume names with the app UUID.

Uploaded PDFs default to **`/app/storage/uploads/`** when `STORAGE_PATH=/app/storage` (same volume as SQLite cache). Override with `REPORT_ANALYST_UPLOAD_DIR`.

### Coolify customer compose layout

`docker-compose.coolify-customer.yml` runs Streamlit as service **`report-analyst`** on `:8080` (what Traefik routes to).

**HTTP Basic Auth (optional):** compose service labels + `TRAEFIK_BASIC_AUTH_USERS` env (set by `set-app-http-basic-auth.sh`). Credentials in `.env.customer.<slug>`:

```bash
HTTP_BASIC_AUTH_USERNAME=demo-user
HTTP_BASIC_AUTH_PASSWORD=...
```

Use `coolify-provisioning/scripts/set-app-http-basic-auth.sh` — see `REPORT-ANALYST-DEPLOY.md` § HTTP Basic Auth. Follows [Coolify Basic Auth](https://coolify.io/docs/knowledge-base/proxy/traefik/basic-auth) + [Custom Middlewares](https://coolify.io/docs/knowledge-base/proxy/traefik/custom-middlewares) (`coolify.traefik.middlewares` shorthand).

Health: `GET /_stcore/health`. Unauthenticated requests return **401** when basic auth is enabled.

When basic auth uses the **`report-analyst-auth` nginx sidecar**, set `NGINX_CLIENT_MAX_BODY_SIZE` (default `200m`) so PDF uploads via Streamlit `/_stcore/upload_file/` are not rejected with **413** (nginx’s built-in limit is 1 MB).

Optional Postgres (enterprise module features in UI):

```bash
docker run -p 8080:8080 \
-e REPORT_ANALYST_RUNTIME=core \
-e DATABASE_URL=postgresql://... \
-e USE_ALEMBIC_MIGRATIONS=true \
report-analyst
```

## Internal enterprise (Climate+Tech)

```bash
docker run -p 8080:8080 -p 8001:8001 \
-e REPORT_ANALYST_RUNTIME=enterprise \
-e DATABASE_URL=postgresql://... \
-e USE_BACKEND=true \
-e USE_CENTRALIZED_LLM=true \
-e NATS_URL=nats://nats:4222 \
report-analyst
```

- Streamlit health: `GET /_stcore/health`
- API health: `GET /health` on port 8001

Licensed modules (`report_analyst_enterprise/`, `report_analyst_api/`, `report_analyst_jobs/`, `report_analyst_search_backend/`) ship in the same image; no separate container builds.

See [`INSTALL.md`](../INSTALL.md) for local install without Docker.
Loading
Loading