Fix prod startup: add start.sh for backend, fix documents proxy base route

- backend/Dockerfile: run migrations via start.sh before uvicorn instead
  of launching uvicorn directly (prod was skipping Alembic)
- backend/scripts/start.sh: alembic upgrade head + uvicorn exec
- documents_proxy.py: add explicit "" route so GET /api/documents (no
  trailing slash) returns 200 instead of 307 redirect
- README.md: update Containers table, volumes section, and Current State
  to reflect the new 4-container architecture with doc-service

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
curo1305
2026-04-14 05:32:43 +02:00
parent 0d34867a69
commit b8238e03ea
4 changed files with 56 additions and 18 deletions
+43 -16
View File
@@ -7,22 +7,23 @@ A fullstack SaaS web application built with FastAPI, React, and PostgreSQL.
| Layer | Tech |
|---|---|
| Backend | FastAPI (async), SQLAlchemy 2, Alembic, PostgreSQL 16 |
| Auth | JWT bearer tokens, bcrypt password hashing |
| Auth | JWT bearer tokens (RS256), bcrypt password hashing |
| Frontend | React 18, TypeScript, Vite, React Router v6, TanStack Query |
## Current State
- User registration and login (JWT auth)
- Protected dashboard with nav bar (Dashboard | Profile | Logout)
- User registration and login (JWT RS256 auth, 8-hour expiry)
- Protected dashboard with nav bar
- `/api/users/me` — authenticated user info
- `/api/profile/me` — GET/PUT personal profile (position, phone, date of birth, address)
- Profile data stored in a dedicated `profiles` table; auto-created on first access
- Admin role flag (`is_superuser`) stored in `users` table; exposed as `is_admin` in API (false for regular users, true for admins)
- Admin-only user management at `/admin`: list all users, add users, delete users, toggle active status
- Admin-only user management at `/admin`: list, add, delete, toggle active
- All input sanitized before reaching the DB (null-byte rejection, length caps, format validation)
- 3 separate Docker containers: `db` (PostgreSQL), `backend` (FastAPI), `frontend` (nginx)
- All containers run as non-root users (UID 1001 for backend and frontend, UID 70 for db)
- Network-isolated: only the frontend exposes a host port (80/5173); db and backend are unreachable from outside Docker
- **PDF Documents app** (`/apps/documents`): upload PDFs, async text extraction (pdfplumber), AI classification via Anthropic / Ollama / LM Studio, per-user categories, file download
- Admin settings per app at `/apps/documents/settings/admin`: AI provider (cloud or local), upload limits; config stored in `/config/doc_service_config.json` on a shared Docker volume
- `/apps` launcher hub — one card per installed app with Open + Settings links
- 4 separate Docker containers: `db`, `backend`, `doc-service`, `frontend`
- All containers run as non-root users (UID 1001 for app containers, UID 70 for db)
- Network-isolated: only the frontend exposes a host port (80/5173); all backend services are unreachable from outside Docker
- Dev environment seeds a test user automatically on startup (`test@example.com` / `Test123!`)
- Password policy: min 8 chars, upper + lowercase, digit, special character, no common words
- Pre-commit security hook (`scripts/security_check.py`) runs inside Docker on every commit
@@ -32,14 +33,20 @@ A fullstack SaaS web application built with FastAPI, React, and PostgreSQL.
| Container | Image | Host port | Network | User (UID:GID) | Description |
|---|---|---|---|---|---|
| `db` | postgres:16-alpine | none | backend-net | 70:70 | PostgreSQL database |
| `backend` | custom (python:3.12-slim) | none | backend-net | 1001:1001 | FastAPI management API |
| `backend` | custom (python:3.12-slim) | none | backend-net | 1001:1001 | FastAPI management API + proxy to doc-service |
| `doc-service` | custom (python:3.12-slim) | none | backend-net | 1001:1001 | PDF extraction microservice |
| `frontend` | custom (nginxinc/nginx-unprivileged:alpine) | 80 (prod) / 5173 (dev) | backend-net + frontend-net | 1001:1001 | React UI + nginx reverse proxy |
**Networks:**
- `backend-net` (`internal: true`) — db, backend, and frontend reverse proxy communicate here; no host routing
- `backend-net` — db, backend, doc-service, and frontend reverse proxy; no host ports bound; outbound internet access allowed (needed for cloud AI API calls)
- `frontend-net` — frontend only; this is where the single host port (80/5173) is bound
The frontend nginx proxies `/api/*` to `backend:8000` via `backend-net`. No backend or database port is ever exposed to the host.
**Volumes:**
- `postgres_data` — PostgreSQL data files
- `doc_data` — uploaded PDF files (mounted into doc-service at `/data/documents`)
- `app_config` — per-service runtime config JSON files (mounted into backend and doc-service at `/config`)
The frontend nginx proxies `/api/*` to `backend:8000` via `backend-net`. The backend proxies `/api/documents/*` and `/api/documents/categories/*` to `doc-service:8001`. No backend, doc-service, or database port is ever exposed to the host.
## Installation
@@ -58,7 +65,9 @@ docker compose up --build -d
```
- Frontend: http://localhost
- API docs: not directly accessible from host (backend port not exposed); access via `docker compose exec backend` or add a dev-only port mapping
- API docs: not directly accessible from host (backend port not exposed)
After first start, configure the AI provider at `/apps/documents/settings/admin` (admin login required).
### Development (hot reload)
@@ -81,14 +90,24 @@ docker compose up db -d
```bash
cd backend
python -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\activate
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
cp ../.env.example .env
alembic upgrade head
uvicorn app.main:app --reload
```
**3. Frontend**
**3. doc-service**
```bash
cd features/doc-service
python -m venv .venv && source .venv/bin/activate
pip install -e .
alembic upgrade head
uvicorn app.main:app --host 0.0.0.0 --port 8001 --reload
```
**4. Frontend**
```bash
cd frontend && npm install && npm run dev
@@ -104,6 +123,10 @@ Copy `.env.example` to `backend/.env` and adjust:
| `JWT_PRIVATE_KEY` | — | RS256 private key PEM (generate with `scripts/generate_jwt_keys.py`) |
| `JWT_PUBLIC_KEY` | — | RS256 public key PEM (generate with `scripts/generate_jwt_keys.py`) |
| `CORS_ORIGINS` | `["http://localhost:5173"]` | Allowed frontend origins |
| `APP_CONFIG_DIR` | `/config` | Directory for per-service runtime config JSON files |
| `DOC_SERVICE_URL` | `http://doc-service:8001` | Internal URL of the doc-service (set by docker-compose) |
`doc-service` reads `DATABASE_URL`, `DATA_DIR`, and `CONFIG_PATH` from its own environment (set in `docker-compose.yml`).
## Development
@@ -117,7 +140,11 @@ cd backend && pytest
# Frontend type check + lint
cd frontend && npm run typecheck && npm run lint
# New DB migration (after changing models)
# New DB migration — main backend
cd backend && alembic revision --autogenerate -m "describe change"
cd backend && alembic upgrade head
# New DB migration — doc-service
cd features/doc-service && alembic revision --autogenerate -m "describe change"
cd features/doc-service && alembic upgrade head
```
+3 -1
View File
@@ -24,9 +24,11 @@ COPY --from=builder /install /usr/local
COPY --chown=appuser:appuser app ./app
COPY --chown=appuser:appuser alembic ./alembic
COPY --chown=appuser:appuser alembic.ini .
COPY --chown=appuser:appuser scripts ./scripts
RUN chmod +x scripts/start.sh
USER appuser
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
CMD ["sh", "scripts/start.sh"]
+2 -1
View File
@@ -46,11 +46,12 @@ def _forward_headers(request: Request, user_id: str) -> dict:
return headers
@router.api_route("", methods=["GET", "POST"])
@router.api_route("/{path:path}", methods=["GET", "POST", "PUT", "PATCH", "DELETE"])
async def proxy_documents(
path: str,
request: Request,
current_user: User = Depends(get_current_user),
path: str = "",
) -> StreamingResponse:
url = f"/documents/{path}" if path else "/documents"
headers = _forward_headers(request, str(current_user.id))
+8
View File
@@ -0,0 +1,8 @@
#!/bin/sh
set -e
echo "[start] running migrations..."
alembic upgrade head
echo "[start] starting uvicorn..."
exec uvicorn app.main:app --host 0.0.0.0 --port 8000