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
+173
View File
@@ -0,0 +1,173 @@
# Coolify Deployment Configuration — ERP Nutzfahrzeuge
---
## Overview
This document describes how to deploy the ERP Nutzfahrzeuge system on Coolify using Docker Compose.
The system consists of 4 services:
| Service | Image | Port | Health Check |
|---------|-------|------|-------------|
| Backend | Custom (Python 3.13-slim) | 8000 | `GET /api/v1/health` |
| Frontend | Custom (Node 18-alpine) | 3000 | `GET /` |
| PostgreSQL | `postgres:16-alpine` | 5432 | `pg_isready` |
| Redis | `redis:7-alpine` | 6379 | `redis-cli ping` |
---
## Coolify Resource Setup
### Step 1: Create Resource
1. In Coolify, create a new resource
2. Select **Docker Compose** as the resource type
3. Connect your Forgejo/Git repository
4. Set the base directory to the project root
5. Coolify will detect `docker-compose.yml` automatically
### Step 2: Configure Environment Variables
1. Navigate to resource → **Environment Variables**
2. Add all variables from `.env.example` (see `deploy/env.md` for descriptions)
3. **Critical variables:**
- `JWT_SECRET` — generate with `openssl rand -hex 32`
- `POSTGRES_PASSWORD` — strong password for database
- `CORS_ORIGINS` — production frontend URL (e.g., `https://erp.your-domain.com`)
- `NEXT_PUBLIC_API_URL` — production backend URL (e.g., `https://api.your-domain.com/api/v1`)
4. **Optional but feature-dependent:**
- `OPENROUTER_API_KEY` — required for AI features (OCR, retouch, copilot)
- `MOBILE_DE_API_KEY` + `MOBILE_DE_SELLER_ID` — required for mobile.de integration
5. Click **Save** and redeploy
### Step 3: Configure Health Check
In the resource settings:
| Setting | Value |
|---------|-------|
| Health check path | `/api/v1/health` |
| Health check port | `8000` |
| Health check interval | `30s` |
| Health check timeout | `5s` |
| Health check retries | `3` |
> **Note:** The health check targets the backend service. Docker Compose health checks are also defined per-service in `docker-compose.yml`.
### Step 4: Configure Persistent Storage
Docker Compose volumes are defined in `docker-compose.yml`:
| Volume | Mount Point | Purpose |
|--------|-------------|---------|
| `uploads` | `/data/uploads` (backend) | User file uploads (images, documents) |
| `pgdata` | `/var/lib/postgresql/data` (postgres) | Database data |
| `redisdata` | `/data` (redis) | Redis persistence (AOF) |
In Coolify:
1. Ensure persistent storage is enabled for the resource
2. Verify volume names match the compose file
3. For production, consider using a managed PostgreSQL instance instead of the containerized one
### Step 5: Configure Domains / Routing
1. **Backend API:**
- Set domain: `api.your-domain.com` (or subpath)
- Port: `8000`
- This is the primary entry point for Coolify health checks
2. **Frontend:**
- Set domain: `erp.your-domain.com` (or root domain)
- Port: `3000`
- Next.js standalone server
3. **CORS:**
- Set `CORS_ORIGINS` to the frontend domain
- Set `NEXT_PUBLIC_API_URL` to the backend domain
### Step 6: Deploy
1. Click **Deploy** in Coolify
2. Wait for all services to build and start
3. Verify health checks pass (green status for all services)
4. Test endpoints:
- `GET https://api.your-domain.com/api/v1/health` → 200 `{"status":"ok"}`
- `GET https://erp.your-domain.com/` → 200 (frontend loads)
- `GET https://api.your-domain.com/openapi.json` → 200 (API schema)
---
## Coolify Service Type
- **Type:** Docker Compose (multi-service)
- **Compose file:** `docker-compose.yml` (project root)
- **Build:** Coolify builds images from `backend/Dockerfile` and `frontend/Dockerfile`
- **Pre-built images:** `postgres:16-alpine`, `redis:7-alpine`
---
## Port Mapping
| Service | Internal Port | External Port | Notes |
|---------|---------------|---------------|-------|
| Backend | 8000 | 8000 | FastAPI + uvicorn (4 workers) |
| Frontend | 3000 | 3000 | Next.js standalone server |
| PostgreSQL | 5432 | — (internal only) | Not exposed externally |
| Redis | 6379 | — (internal only) | Not exposed externally |
> In Coolify, only expose backend (8000) and frontend (3000) via domains. PostgreSQL and Redis should remain internal to the Docker network.
---
## Build Configuration
### Backend
- **Base image:** `python:3.13-slim`
- **System packages:** libpango, libcairo, libjpeg, libpng (for WeasyPrint + Pillow)
- **Python venv:** `/opt/venv`
- **Entry:** `uvicorn app.main:app --host 0.0.0.0 --port 8000 --workers 4`
### Frontend
- **Base image:** `node:18-alpine` (multi-stage)
- **Build:** `npm ci && npm run build` (standalone output)
- **Entry:** `node server.js`
- **Requires:** `output: 'standalone'` in `next.config.js`
---
## Post-Deploy Verification
1. All 4 containers are running and healthy
2. `GET /api/v1/health` → 200 `{"status":"ok"}`
3. Frontend loads at production URL
4. Login flow works: `POST /api/v1/auth/login`
5. CORS headers present for frontend origin
6. No errors in container logs
7. File uploads work (test upload to `/data/uploads`)
8. Database queries return expected data
---
## Troubleshooting
| Issue | Cause | Solution |
|-------|-------|----------|
| Backend health check fails | DB not ready | Check `postgres` container health; increase `start_period` |
| WeasyPrint errors | Missing system libs | Verify `libpango`, `libcairo` installed in backend image |
| Frontend build fails | Missing `output: 'standalone'` | Add `output: 'standalone'` to `next.config.js` |
| CORS errors | Wrong `CORS_ORIGINS` | Set to exact frontend domain (no trailing slash) |
| AI features fail | Missing `OPENROUTER_API_KEY` | Set API key in Coolify environment variables |
| mobile.de fails | Missing credentials | Set `MOBILE_DE_API_KEY` and `MOBILE_DE_SELLER_ID` |
---
## Rollback
See `deploy/rollback.md` for the full rollback procedure.
Quick rollback in Coolify:
1. Navigate to resource → **Deployments**
2. Find last known-good deployment
3. Click **Redeploy**
4. Verify health check passes
+56
View File
@@ -0,0 +1,56 @@
# Environment Configuration — ERP Nutzfahrzeuge
**Source:** `backend/.env.example` (safe reference — no live values)
---
## Required Variables
These variables MUST be set before the application can start.
| Variable | Description | Example (redacted) |
|----------|-------------|---------------------|
| `DATABASE_URL` | PostgreSQL async connection string | `postgresql+asyncpg://user:***@host:5432/dbname` |
| `JWT_SECRET` | Secret key for JWT signing (≥256 bits) | `<redacted — generate with openssl rand -hex 32>` |
## Important Variables
| Variable | Default | Description |
|----------|---------|-------------|
| `REDIS_URL` | `redis://localhost:6379/0` | Redis connection for async task queues. Not required for JWT (stateless). Required for OCR/retouch/mobile.de async processing. |
| `JWT_ALGORITHM` | `HS256` | JWT signing algorithm |
| `JWT_ACCESS_TTL_MINUTES` | `15` | Access token TTL in minutes |
| `JWT_REFRESH_TTL_DAYS` | `7` | Refresh token TTL in days |
| `CORS_ORIGINS` | `http://localhost:3000,http://localhost:3001` | Comma-separated allowed origins. Add production frontend URL. |
| `UPLOAD_DIR` | `/tmp/uploads` | File upload directory. **Must be a persistent volume in production.** |
| `APP_NAME` | `ERP Nutzfahrzeuge` | Application display name |
| `APP_ENV` | `development` | Set to `production` for deployment |
## Optional Integration Variables
| Variable | Default | Description |
|----------|---------|-------------|
| `MOBILE_DE_API_KEY` | (empty) | mobile.de API key for vehicle listing push |
| `MOBILE_DE_SELLER_ID` | (empty) | mobile.de seller ID |
| `OPENROUTER_API_KEY` | (not set) | OpenRouter API key for AI Copilot features. Without this, Copilot endpoints will fail. |
| `BZST_API_ENABLED` | `False` | Enable BZSt USt-IdNr. verification API |
## Production Checklist
- [ ] `DATABASE_URL` points to production PostgreSQL (managed or backed up)
- [ ] `JWT_SECRET` is a cryptographically secure random string (not placeholder)
- [ ] `APP_ENV=production`
- [ ] `CORS_ORIGINS` includes production frontend origin
- [ ] `UPLOAD_DIR` points to a persistent volume mount
- [ ] `REDIS_URL` set if async features (OCR, retouch, mobile.de) are needed
- [ ] `OPENROUTER_API_KEY` set if AI Copilot is needed
- [ ] `MOBILE_DE_API_KEY` and `MOBILE_DE_SELLER_ID` set if mobile.de integration is needed
## Coolify Configuration
In Coolify, set these as environment variables (or via `.env` file in the resource):
1. Navigate to resource → Environment Variables
2. Add each variable with its production value
3. Redeploy after changes
**Never commit real secret values to the repository.** Use Coolify's secret management or an external secrets manager.
+63
View File
@@ -0,0 +1,63 @@
# Health Check Configuration — ERP Nutzfahrzeuge
---
## Health Endpoint
**URL:** `GET /api/v1/health`
**Expected response:**
- HTTP Status: `200 OK`
- Body: `{"status":"ok"}`
**No authentication required.**
---
## Coolify Health Check
Configure in Coolify resource settings:
| Setting | Value |
|---------|-------|
| Health check path | `/api/v1/health` |
| Health check port | `8000` |
| Health check interval | `30s` (recommended) |
| Health check timeout | `5s` (recommended) |
| Health check retries | `3` (recommended) |
---
## Additional Monitoring Endpoints
| Endpoint | Method | Auth | Purpose |
|----------|--------|------|---------|
| `/` | GET | No | Root info — returns app name, version, docs URL |
| `/openapi.json` | GET | No | OpenAPI schema — useful for API monitoring |
| `/docs` | GET | No | Swagger UI (disable in production via `APP_ENV=production` if needed) |
---
## Docker Compose Health Check
If using Docker Compose, add to the backend service:
```yaml
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/api/v1/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
```
---
## Troubleshooting
| Symptom | Likely Cause | Action |
|---------|-------------|--------|
| Health check returns 502 | Backend not started or crashed | Check container logs, verify `DATABASE_URL` is reachable |
| Health check returns 503 | App startup in progress | Increase `start_period` in health check config |
| Health check times out | Network/firewall issue | Verify port mapping and firewall rules |
| Health check returns 500 | Internal error (DB connection, config) | Check application logs for traceback |
+137
View File
@@ -0,0 +1,137 @@
# Rollback Procedure — ERP Nutzfahrzeuge
---
## Rollback Strategy
The rollback strategy depends on the deployment method used.
---
## Coolify Rollback
### Option A: Redeploy Previous Image
1. In Coolify, navigate to the resource
2. Go to **Deployments** tab
3. Find the last known-good deployment
4. Click **Redeploy** on that deployment
5. Wait for health check to pass
6. Verify endpoints:
- `GET /api/v1/health` → 200
- `GET /` → 200
### Option B: Rollback via Git
1. In the repository, identify the last known-good commit:
```bash
git log --oneline -10
```
2. Revert to the previous commit:
```bash
git revert <commit_hash> # or git reset --hard <commit_hash>
git push origin main
```
3. Trigger a new deployment in Coolify (auto-deploy or manual)
4. Wait for health check to pass
---
## Docker Compose Rollback
### Step 1: Stop Current Deployment
```bash
docker-compose down
```
### Step 2: Revert Code
```bash
git checkout <last-known-good-tag-or-commit>
```
### Step 3: Rebuild and Restart
```bash
docker-compose build
docker-compose up -d
```
### Step 4: Verify
```bash
curl -s http://localhost:8000/api/v1/health
docker-compose logs --tail 50 backend
```
---
## Database Rollback
### If Alembic migrations are used:
```bash
cd backend
/opt/venv/bin/python -m alembic downgrade -1 # rollback one revision
# or
/opt/venv/bin/python -m alembic downgrade <revision_id> # rollback to specific revision
```
### If no migration tool:
- Database schema changes are applied on app startup via SQLAlchemy `create_all`
- Rollback requires manual SQL or restoring from a database backup
- **Always take a database backup before deployment:**
```bash
pg_dump -U erp_user erp_test > backup_$(date +%Y%m%d_%H%M%S).sql
```
### Restore from backup:
```bash
psql -U erp_user erp_test < backup_YYYYMMDD_HHMMSS.sql
```
---
## Rollback Decision Tree
| Situation | Action |
|-----------|--------|
| App won't start after deploy | Redeploy previous image (Coolify) or `git checkout` + rebuild (Docker) |
| Health check fails | Redeploy previous image; check DB connectivity |
| Database migration broke schema | `alembic downgrade` or restore from `pg_dump` backup |
| Frontend broken | Rebuild frontend from previous commit; redeploy |
| Performance regression | Redeploy previous image; investigate in staging |
| Security issue found | Rollback immediately; patch in staging; redeploy |
---
## Post-Rollback Verification
After any rollback:
1. ✅ Health check passes: `GET /api/v1/health` → 200 `{"status":"ok"}`
2. ✅ Root endpoint: `GET /` → 200
3. ✅ Login works: `POST /api/v1/auth/login` → 200 with valid credentials
4. ✅ No errors in logs: `docker logs <container> --tail 50`
5. ✅ Frontend loads at production URL
6. ✅ Database queries return expected data
---
## Emergency Rollback (Quick)
If the deployment is causing immediate issues:
```bash
# Coolify: use the Redeploy button on the last good deployment
# Docker Compose:
docker-compose down
git checkout <last-good-commit>
docker-compose build && docker-compose up -d
curl -s http://localhost:8000/api/v1/health
```
**Time to rollback:** < 5 minutes (if image is cached) or < 15 minutes (if rebuild needed).
+189
View File
@@ -0,0 +1,189 @@
# Deployment Runbook — ERP Nutzfahrzeuge
---
## Prerequisites
1. **PostgreSQL** — running and accessible (managed or self-hosted)
2. **Redis** (optional) — if async features (OCR, retouch, mobile.de push) are needed
3. **Node.js** — for frontend build (if building at deploy time)
4. **Python 3.11+** — for backend runtime
5. **Coolify** (or Docker) — for containerized deployment
---
## Pre-Deployment Steps
### 1. Verify Environment Variables
Ensure all required variables are configured in Coolify or `.env`:
```bash
# Required
DATABASE_URL=postgresql+asyncpg://<user>:<password>@<host>:5432/<dbname>
JWT_SECRET=<generate with: openssl rand -hex 32>
# Important
APP_ENV=production
CORS_ORIGINS=https://your-frontend-domain.com
UPLOAD_DIR=/app/uploads # persistent volume
# Optional (feature-dependent)
REDIS_URL=redis://<host>:6379/0
OPENROUTER_API_KEY=<key>
MOBILE_DE_API_KEY=<key>
MOBILE_DE_SELLER_ID=<id>
```
### 2. Build Frontend
```bash
cd frontend
npm ci
npm run build
```
Build artifacts are output to `frontend/.next/`.
### 3. Verify Backend Dependencies
```bash
cd backend
/opt/venv/bin/pip install -r requirements.txt
/opt/venv/bin/python -c "from app.main import app; print('Import OK')"
```
### 4. Database Migration (if applicable)
```bash
cd backend
/opt/venv/bin/python -m alembic upgrade head # if Alembic is configured
# Or ensure tables are created via app startup
```
---
## Deployment Steps (Coolify)
### Step 1: Create Resource
1. In Coolify, create a new resource (Docker Compose or Dockerfile-based)
2. Configure the source repository
3. Set the build/pack type
### Step 2: Configure Environment
1. Navigate to resource → Environment Variables
2. Add all variables from `deploy/env.md`
3. Ensure `JWT_SECRET` is a secure random value
4. Set `APP_ENV=production`
### Step 3: Configure Health Check
1. Set health check path to `/api/v1/health`
2. Set port to `8000`
3. See `deploy/healthcheck.md` for full configuration
### Step 4: Configure Persistent Storage
1. Mount a persistent volume for `UPLOAD_DIR` (e.g., `/app/uploads`)
2. Ensure database volume is persistent
### Step 5: Deploy
1. Click **Deploy** in Coolify
2. Wait for build and startup to complete
3. Verify health check passes (green status)
4. Test endpoints:
- `GET /api/v1/health` → 200 `{"status":"ok"}`
- `GET /` → 200 app metadata
- `GET /openapi.json` → 200 OpenAPI schema
### Step 6: Post-Deployment Verification
1. Verify frontend is accessible at the configured domain
2. Test login flow: `POST /api/v1/auth/login` with valid credentials
3. Verify CORS headers are present for frontend origin
4. Check logs for any errors:
```bash
docker logs <container_name> --tail 50
```
---
## Deployment Steps (Docker Compose)
### docker-compose.yml (template)
```yaml
version: '3.8'
services:
backend:
build: ./backend
ports:
- "8000:8000"
environment:
- DATABASE_URL=postgresql+asyncpg://user:pass@db:5432/erp
- JWT_SECRET=${JWT_SECRET}
- APP_ENV=production
- CORS_ORIGINS=https://your-domain.com
- UPLOAD_DIR=/app/uploads
- REDIS_URL=redis://redis:6379/0
volumes:
- uploads:/app/uploads
depends_on:
- db
- redis
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/api/v1/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
db:
image: postgres:16-alpine
environment:
- POSTGRES_USER=erp_user
- POSTGRES_PASSWORD=${DB_PASSWORD}
- POSTGRES_DB=erp
volumes:
- pgdata:/var/lib/postgresql/data
redis:
image: redis:7-alpine
volumes:
- redisdata:/data
frontend:
build: ./frontend
ports:
- "3000:3000"
depends_on:
- backend
volumes:
uploads:
pgdata:
redisdata:
```
---
## Start Command (standalone)
```bash
cd backend && /opt/venv/bin/python -m uvicorn app.main:app --host 0.0.0.0 --port 8000
```
For production with workers:
```bash
cd backend && /opt/venv/bin/python -m uvicorn app.main:app --host 0.0.0.0 --port 8000 --workers 4
```
---
## Rollback
See `deploy/rollback.md` for rollback procedure.