feat(T06): deployment config - Dockerfiles, docker-compose, Coolify guide, env example

This commit is contained in:
2026-07-18 15:47:26 +02:00
parent fbb1b39b57
commit 1620a35d71
10 changed files with 1140 additions and 0 deletions
+305
View File
@@ -0,0 +1,305 @@
# Runtime Report — ERP Nutzfahrzeuge
**Date:** 2026-07-17
**Phase:** 5 — Runtime Verification
**Runtime:** `/opt/venv/bin/python` (execution environment)
---
## Summary
| Check | Status |
|-------|--------|
| Backend startup | ✅ PASS |
| Health endpoint | ✅ PASS (200, `{"status":"ok"}`) |
| Root endpoint | ✅ PASS (200, app metadata) |
| Auth login (GET) | ✅ PASS (405 Method Not Allowed — POST-only) |
| OpenAPI/Swagger | ✅ PASS (200, full schema) |
| Frontend build artifacts | ✅ PASS (`.next/` with BUILD_ID, server/, static/) |
| Runtime errors | ✅ NONE (clean logs, no tracebacks) |
| PostgreSQL | ✅ Running (localhost:5432, accepting connections) |
| Redis | ⚠️ NOT installed (JWT-based refresh tokens, no Redis storage needed) |
**Overall verdict:** Backend starts and runs cleanly. All public endpoints respond as expected. No runtime errors detected.
---
## 1. Startup Verification
**Start command:**
```bash
cd backend && /opt/venv/bin/python -m uvicorn app.main:app --host 0.0.0.0 --port 8000
```
**Result:** Server started successfully (PID 3383733).
**Startup log:**
```
INFO: Started server process [3383733]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
```
**Import check:** `from app.main import app` — OK (no ImportError)
**Core dependencies verified:**
- `uvicorn`
- `fastapi`
- `sqlalchemy`
- `asyncpg`
---
## 2. Health Check
**Endpoint:** `GET http://localhost:8000/api/v1/health`
**Response:**
- HTTP Status: `200 OK`
- Body: `{"status":"ok"}`
**Verdict:** ✅ Health endpoint reachable and returning expected payload.
---
## 3. Endpoint Verification
### 3.1 Root Endpoint
- **URL:** `GET http://localhost:8000/`
- **HTTP Status:** `200 OK`
- **Body:** `{"name":"ERP Nutzfahrzeuge","version":"1.0.0","docs":"/docs"}`
### 3.2 Auth Login (GET — should fail)
- **URL:** `GET http://localhost:8000/api/v1/auth/login`
- **HTTP Status:** `405 Method Not Allowed`
- **Body:** `{"detail":"Method Not Allowed"}`
- **Expected behavior:** Login is POST-only; GET correctly rejected.
### 3.3 OpenAPI Schema
- **URL:** `GET http://localhost:8000/openapi.json`
- **HTTP Status:** `200 OK`
- **Schema version:** OpenAPI 3.1.0
- **Title:** ERP Nutzfahrzeuge Backend API
- **Version:** 1.0.0
---
## 4. Available API Endpoints (from OpenAPI)
### Auth (`/api/v1/auth`)
| Method | Path | Description | Auth |
|--------|------|-------------|------|
| POST | `/api/v1/auth/login` | Login with email+password → JWT tokens | No |
| POST | `/api/v1/auth/refresh` | Exchange refresh token for new token pair | No |
| GET | `/api/v1/auth/me` | Get current user profile | Bearer |
### Users (`/api/v1/users`)
| Method | Path | Description | Auth |
|--------|------|-------------|------|
| GET | `/api/v1/users/` | List users (paginated, admin only) | Bearer |
| POST | `/api/v1/users/` | Create user (admin only) | Bearer |
| PUT | `/api/v1/users/{user_id}` | Update user (admin only) | Bearer |
| DELETE | `/api/v1/users/{user_id}` | Soft-delete user (admin only) | Bearer |
### Vehicles (`/api/v1/vehicles`)
| Method | Path | Description | Auth |
|--------|------|-------------|------|
| GET | `/api/v1/vehicles/` | List vehicles (filter, sort, paginate) | Bearer |
| POST | `/api/v1/vehicles/` | Create vehicle | Bearer |
| GET | `/api/v1/vehicles/{vehicle_id}` | Get single vehicle | Bearer |
| PUT | `/api/v1/vehicles/{vehicle_id}` | Update vehicle | Bearer |
| DELETE | `/api/v1/vehicles/{vehicle_id}` | Soft-delete vehicle | Bearer |
| POST | `/api/v1/vehicles/{vehicle_id}/mobile-de/push` | Push to mobile.de (async, 202) | Bearer |
| GET | `/api/v1/vehicles/{vehicle_id}/mobile-de/status` | Get mobile.de sync status | Bearer |
### Contacts (`/api/v1/contacts`)
| Method | Path | Description | Auth |
|--------|------|-------------|------|
| GET | `/api/v1/contacts/` | List contacts (filter, sort, paginate) | Bearer |
| POST | `/api/v1/contacts/` | Create contact (admin/verkaeufer) | Bearer |
| GET | `/api/v1/contacts/{contact_id}` | Get contact with persons | Bearer |
| PUT | `/api/v1/contacts/{contact_id}` | Update contact | Bearer |
| DELETE | `/api/v1/contacts/{contact_id}` | Soft-delete contact | Bearer |
| POST | `/api/v1/contacts/{contact_id}/persons` | Add contact person | Bearer |
| DELETE | `/api/v1/contacts/{contact_id}/persons/{person_id}` | Remove contact person | Bearer |
### Files (`/api/v1/vehicles/{vehicle_id}/files`)
| Method | Path | Description | Auth |
|--------|------|-------------|------|
| GET | `/api/v1/vehicles/{vehicle_id}/files` | List files for vehicle | Bearer |
| POST | `/api/v1/vehicles/{vehicle_id}/files` | Upload file (multipart, max 20MB) | Bearer |
| GET | `/api/v1/vehicles/{vehicle_id}/files/{file_id}` | Download file | Bearer |
| DELETE | `/api/v1/vehicles/{vehicle_id}/files/{file_id}` | Delete file | Bearer |
### OCR (`/api/v1/ocr`)
| Method | Path | Description | Auth |
|--------|------|-------------|------|
| POST | `/api/v1/ocr/upload` | Upload scan for OCR (async, 202) | Bearer |
| GET | `/api/v1/ocr/results/{result_id}` | Get OCR result | Bearer |
| GET | `/api/v1/ocr/results` | List OCR results | Bearer |
| POST | `/api/v1/ocr/results/{result_id}/apply` | Apply OCR data to vehicle | Bearer |
### Sales (`/api/v1/sales`)
| Method | Path | Description | Auth |
|--------|------|-------------|------|
| GET | `/api/v1/sales/` | List sales (filter by status/date) | Bearer |
| POST | `/api/v1/sales/` | Create sale (sets vehicle to 'sold') | Bearer |
| GET | `/api/v1/sales/{sale_id}` | Get sale with nested data | Bearer |
| PUT | `/api/v1/sales/{sale_id}` | Update sale | Bearer |
| DELETE | `/api/v1/sales/{sale_id}` | Cancel sale (soft delete) | Bearer |
| POST | `/api/v1/sales/{sale_id}/contract` | Regenerate contract PDF | Bearer |
| GET | `/api/v1/sales/{sale_id}/contract` | Download contract PDF | Bearer |
| POST | `/api/v1/sales/{sale_id}/verify-ust-id` | Verify USt-IdNr. via BZSt API | Bearer |
### DATEV (`/api/v1/datev`)
| Method | Path | Description | Auth |
|--------|------|-------------|------|
| POST | `/api/v1/datev/export` | Create DATEV export (CSV) | Bearer |
| GET | `/api/v1/datev/exports` | List DATEV exports | Bearer |
| GET | `/api/v1/datev/exports/{export_id}/download` | Download DATEV CSV | Bearer |
### Copilot (`/api/v1/copilot`)
| Method | Path | Description | Auth |
|--------|------|-------------|------|
| POST | `/api/v1/copilot/chat` | Send message to AI Copilot | Bearer |
| POST | `/api/v1/copilot/action` | Execute confirmed action | Bearer |
| GET | `/api/v1/copilot/history` | Get chat history | Bearer |
| POST | `/api/v1/copilot/voice` | Voice input (base64 audio) | Bearer |
### Retouch (`/api/v1/retouch`)
| Method | Path | Description | Auth |
|--------|------|-------------|------|
| POST | `/api/v1/retouch/process` | Upload image for retouching (async, 202) | Bearer |
| GET | `/api/v1/retouch/results/{result_id}` | Get retouch result | Bearer |
| POST | `/api/v1/retouch/price-compare` | Compare prices for vehicle | Bearer |
### Health
| Method | Path | Description | Auth |
|--------|------|-------------|------|
| GET | `/api/v1/health` | Health check | No |
| GET | `/` | Root info | No |
---
## 5. Frontend Build Verification
**Location:** `frontend/.next/`
**Build artifacts confirmed:**
- `BUILD_ID` — present (20 bytes)
- `build-manifest.json` — present
- `app-build-manifest.json` — present
- `routes-manifest.json` — present
- `server/` — directory present
- `static/` — directory present
- `prerender-manifest.json` — present
- `required-server-files.json` — present
- `trace/` — build trace present
**Verdict:** ✅ Frontend build artifacts are complete and present.
---
## 6. Environment Verification
### Required Environment Variables (from `.env.example`)
| Variable | Required | Status | Notes |
|----------|----------|--------|-------|
| `DATABASE_URL` | ✅ Yes | Set (local PostgreSQL) | `postgresql+asyncpg://erp_test_user:***@localhost:5432/erp_test` |
| `REDIS_URL` | ✅ Yes | Set in .env.example | Redis NOT installed; JWT refresh tokens are stateless (no Redis storage needed) |
| `JWT_SECRET` | ✅ Yes | Placeholder in .env.example | Must be set to a secure random string for production |
| `JWT_ALGORITHM` | Optional | Default HS256 | — |
| `JWT_ACCESS_TTL_MINUTES` | Optional | Default 15 | — |
| `JWT_REFRESH_TTL_DAYS` | Optional | Default 7 | — |
| `CORS_ORIGINS` | ✅ Yes | Set | `http://localhost:3000,http://localhost:3001` |
| `UPLOAD_DIR` | Optional | Default `/tmp/uploads` | Must be persistent volume in production |
| `APP_NAME` | Optional | Default `ERP Nutzfahrzeuge` | — |
| `APP_ENV` | Optional | Default `development` | Set to `production` for deployment |
| `MOBILE_DE_API_KEY` | Optional | Empty | Required for mobile.de integration |
| `MOBILE_DE_SELLER_ID` | Optional | Empty | Required for mobile.de integration |
| `OPENROUTER_API_KEY` | Optional | Not in .env.example | Required for AI Copilot features (currently empty/non-functional) |
### Infrastructure Status
| Service | Status | Notes |
|---------|--------|-------|
| PostgreSQL | ✅ Running | localhost:5432, accepting connections |
| Redis | ⚠️ Not installed | Not required for JWT; may be needed for async task queues (OCR, retouch, mobile.de push) |
| OpenRouter API | ⚠️ Not configured | AI Copilot features will not work without API key |
---
## 7. Errors Found
**No errors, tracebacks, warnings, or critical messages found in runtime logs.**
Log output was clean:
```
INFO: Started server process [3383733]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
INFO: 127.0.0.1:48688 - "GET /api/v1/health HTTP/1.1" 200 OK
INFO: 127.0.0.1:48700 - "GET / HTTP/1.1" 200 OK
INFO: 127.0.0.1:48706 - "GET /api/v1/auth/login HTTP/1.1" 405 Method Not Allowed
INFO: 127.0.0.1:48710 - "GET /openapi.json HTTP/1.1" 200 OK
```
---
## 8. Known Risks
| # | Risk | Severity | Mitigation |
|---|------|----------|------------|
| 1 | **Redis not installed** — async task processing (OCR, retouch, mobile.de push) may fail or fall back to synchronous processing | Medium | Install Redis or verify async tasks have graceful fallback. Check if background tasks use in-memory queues. |
| 2 | **OPENROUTER_API_KEY not set** — AI Copilot (`/api/v1/copilot/*`) will not function | Medium | Set `OPENROUTER_API_KEY` in production environment for Copilot features. |
| 3 | **JWT_SECRET is placeholder**`change-me-to-a-secure-random-string` in .env.example | High | Generate a cryptographically secure secret (≥256 bits) for production. |
| 4 | **PostgreSQL is local only** — no connection pooling or HA configured | Medium | Configure PgBouncer or use managed PostgreSQL (e.g., Coolify managed DB) for production. |
| 5 | **UPLOAD_DIR = `/tmp/uploads`** — ephemeral storage, files lost on container restart | High | Mount a persistent volume for `UPLOAD_DIR` in production. |
| 6 | **CORS_ORIGINS** — currently allows localhost:3000,3001 only | Low | Add production frontend origin to `CORS_ORIGINS` for deployment. |
| 7 | **MOBILE_DE_API_KEY empty** — mobile.de push/status endpoints will fail | Low | Set API key when mobile.de integration is needed. |
| 8 | **No HTTPS/TLS** — backend runs on plain HTTP | Medium | Use reverse proxy (nginx/Traefik) or Coolify proxy for TLS termination. |
| 9 | **No rate limiting** — no rate limiting middleware detected | Medium | Add rate limiting (e.g., slowapi) before public exposure. |
---
## 9. Recommendations
### Immediate (before deployment)
1. Generate a secure `JWT_SECRET` and configure in production environment
2. Set `APP_ENV=production` for deployment
3. Configure persistent volume for `UPLOAD_DIR`
4. Add production frontend origin to `CORS_ORIGINS`
5. Install Redis if async task processing (OCR, retouch, mobile.de) is required
6. Set `OPENROUTER_API_KEY` if AI Copilot features are needed
### Deployment preparation
7. Create Dockerfile / docker-compose.yml for containerized deployment
8. Configure reverse proxy with TLS termination (Coolify proxy or nginx)
9. Set up PostgreSQL as managed service or with backup strategy
10. Add health check to Coolify resource configuration (`GET /api/v1/health`)
11. Configure log rotation and monitoring
### Post-deployment
12. Run smoke tests against production endpoints
13. Verify JWT auth flow end-to-end
14. Test file upload with persistent volume
15. Monitor for async task failures (OCR, retouch)
---
## 10. Sign-off
| Item | Status |
|------|--------|
| App starts without errors | ✅ Yes |
| Health endpoint returns 200 | ✅ Yes |
| Bounded error output shows no critical runtime failure | ✅ Yes (zero errors) |
| Frontend build artifacts present | ✅ Yes |
| Deployment docs ready | ✅ Yes (see `deploy/` directory) |
| Ready for deployment | ⚠️ Conditional — requires secure JWT_SECRET, persistent storage, and Redis decision |
**Runtime verification: PASSED**
**Deployment readiness: CONDITIONAL** (env vars + infrastructure must be configured)