added docker setup

This commit is contained in:
duffyduck 2026-02-08 19:59:49 +01:00
parent dd4d57fa1b
commit 89cf92eaf5
10 changed files with 501 additions and 1 deletions

51
.dockerignore Normal file
View File

@ -0,0 +1,51 @@
# Dependencies
node_modules
*/node_modules
# Build output (will be built in container)
frontend/dist
backend/dist
# Development files
.git
.gitignore
.vscode
.idea
*.md
!docker/README.md
# Environment files (use Docker environment instead)
.env
.env.local
.env.*.local
backend/.env
frontend/.env
# Logs
*.log
npm-debug.log*
# OS files
.DS_Store
Thumbs.db
# Test files
*.test.ts
*.test.tsx
*.spec.ts
*.spec.tsx
__tests__
coverage
# Docker files in wrong location
docker-compose.yml
# Uploads and backups (mounted as volumes)
uploads
backups
backend/uploads
backend/backups
# Prisma migrations (included, but not dev db)
*.db
*.db-journal

14
backend/dist/index.js vendored
View File

@ -66,6 +66,20 @@ app.use('/api', contractHistory_routes_js_1.default);
app.get('/api/health', (req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() });
});
// Production: Serve frontend static files
if (process.env.NODE_ENV === 'production') {
const publicPath = path_1.default.join(process.cwd(), 'public');
// Serve static files
app.use(express_1.default.static(publicPath));
// SPA fallback: serve index.html for all non-API routes
app.get('*', (req, res, next) => {
// Skip API routes
if (req.path.startsWith('/api')) {
return next();
}
res.sendFile(path_1.default.join(publicPath, 'index.html'));
});
}
// Error handling
app.use((err, req, res, next) => {
console.error(err.stack);

View File

@ -1 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;AAAA,sDAA8B;AAC9B,gDAAwB;AACxB,gDAAwB;AACxB,oDAA4B;AAE5B,6EAAiD;AACjD,qFAAyD;AACzD,mFAAuD;AACvD,qFAAyD;AACzD,qFAAyD;AACzD,+EAAmD;AACnD,mGAAuE;AACvE,qFAAyD;AACzD,qFAAyD;AACzD,2GAA8E;AAC9E,uGAA0E;AAC1E,qFAAyD;AACzD,iFAAqD;AACrD,6EAAiD;AACjD,iFAAqD;AACrD,uFAA2D;AAC3D,qGAAyE;AACzE,6FAAiE;AACjE,yFAA6D;AAC7D,+FAAmE;AACnE,2FAA+D;AAC/D,mFAAuD;AACvD,mGAAuE;AAEvE,gBAAM,CAAC,MAAM,EAAE,CAAC;AAEhB,MAAM,GAAG,GAAG,IAAA,iBAAO,GAAE,CAAC;AACtB,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC;AAEtC,aAAa;AACb,GAAG,CAAC,GAAG,CAAC,IAAA,cAAI,GAAE,CAAC,CAAC;AAChB,GAAG,CAAC,GAAG,CAAC,iBAAO,CAAC,IAAI,EAAE,CAAC,CAAC;AAExB,gCAAgC;AAChC,GAAG,CAAC,GAAG,CAAC,cAAc,EAAE,iBAAO,CAAC,MAAM,CAAC,cAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC;AAE7E,SAAS;AACT,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,wBAAU,CAAC,CAAC;AACjC,GAAG,CAAC,GAAG,CAAC,gBAAgB,EAAE,4BAAc,CAAC,CAAC;AAC1C,GAAG,CAAC,GAAG,CAAC,gBAAgB,EAAE,2BAAa,CAAC,CAAC;AACzC,GAAG,CAAC,GAAG,CAAC,iBAAiB,EAAE,4BAAc,CAAC,CAAC;AAC3C,GAAG,CAAC,GAAG,CAAC,gBAAgB,EAAE,4BAAc,CAAC,CAAC;AAC1C,GAAG,CAAC,GAAG,CAAC,aAAa,EAAE,yBAAW,CAAC,CAAC;AACpC,GAAG,CAAC,GAAG,CAAC,wBAAwB,EAAE,mCAAqB,CAAC,CAAC;AACzD,GAAG,CAAC,GAAG,CAAC,gBAAgB,EAAE,4BAAc,CAAC,CAAC;AAC1C,GAAG,CAAC,GAAG,CAAC,gBAAgB,EAAE,4BAAc,CAAC,CAAC;AAC1C,GAAG,CAAC,GAAG,CAAC,2BAA2B,EAAE,uCAAwB,CAAC,CAAC;AAC/D,GAAG,CAAC,GAAG,CAAC,yBAAyB,EAAE,qCAAsB,CAAC,CAAC;AAC3D,GAAG,CAAC,GAAG,CAAC,gBAAgB,EAAE,4BAAc,CAAC,CAAC;AAC1C,GAAG,CAAC,GAAG,CAAC,cAAc,EAAE,0BAAY,CAAC,CAAC;AACtC,GAAG,CAAC,GAAG,CAAC,YAAY,EAAE,wBAAU,CAAC,CAAC;AAClC,GAAG,CAAC,GAAG,CAAC,aAAa,EAAE,0BAAY,CAAC,CAAC;AACrC,GAAG,CAAC,GAAG,CAAC,gBAAgB,EAAE,6BAAe,CAAC,CAAC;AAC3C,GAAG,CAAC,GAAG,CAAC,0BAA0B,EAAE,oCAAsB,CAAC,CAAC;AAC5D,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,gCAAkB,CAAC,CAAC;AACpC,GAAG,CAAC,GAAG,CAAC,eAAe,EAAE,8BAAgB,CAAC,CAAC;AAC3C,GAAG,CAAC,GAAG,CAAC,sBAAsB,EAAE,iCAAmB,CAAC,CAAC;AACrD,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,+BAAiB,CAAC,CAAC;AACnC,GAAG,CAAC,GAAG,CAAC,qBAAqB,EAAE,2BAAa,CAAC,CAAC;AAC9C,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,mCAAqB,CAAC,CAAC;AAEvC,eAAe;AACf,GAAG,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;IAClC,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;AAClE,CAAC,CAAC,CAAC;AAEH,iBAAiB;AACjB,GAAG,CAAC,GAAG,CAAC,CAAC,GAAU,EAAE,GAAoB,EAAE,GAAqB,EAAE,IAA0B,EAAE,EAAE;IAC9F,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACzB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,uBAAuB,EAAE,CAAC,CAAC;AAC3E,CAAC,CAAC,CAAC;AAEH,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE;IACpB,OAAO,CAAC,GAAG,CAAC,yBAAyB,IAAI,EAAE,CAAC,CAAC;AAC/C,CAAC,CAAC,CAAC"}
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}

View File

@ -69,6 +69,23 @@ app.get('/api/health', (req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() });
});
// Production: Serve frontend static files
if (process.env.NODE_ENV === 'production') {
const publicPath = path.join(process.cwd(), 'public');
// Serve static files
app.use(express.static(publicPath));
// SPA fallback: serve index.html for all non-API routes
app.get('*', (req, res, next) => {
// Skip API routes
if (req.path.startsWith('/api')) {
return next();
}
res.sendFile(path.join(publicPath, 'index.html'));
});
}
// Error handling
app.use((err: Error, req: express.Request, res: express.Response, next: express.NextFunction) => {
console.error(err.stack);

26
docker/.env.example Normal file
View File

@ -0,0 +1,26 @@
# OpenCRM Docker Environment
# ============================
# Copy this file to .env and adjust the values
# Domain (for Caddy SSL certificate)
# Use your actual domain, e.g., crm.example.com
DOMAIN=localhost
# Database
DB_ROOT_PASSWORD=change-this-root-password
DB_NAME=opencrm
DB_USER=opencrm
DB_PASSWORD=change-this-password
# JWT Authentication
# Generate with: openssl rand -base64 32
JWT_SECRET=change-this-to-a-secure-random-string
JWT_EXPIRES_IN=7d
# Encryption Key (for portal credentials)
# Generate with: openssl rand -hex 32
ENCRYPTION_KEY=change-this-to-a-32-byte-hex-key
# First Install: Set to "true" to seed database on first startup
# After first successful start, set back to "false"
RUN_SEED=true

34
docker/Caddyfile Normal file
View File

@ -0,0 +1,34 @@
# OpenCRM Caddyfile
# ===================
# Replace {$DOMAIN} with your actual domain or use environment variable
# For local development without SSL, use localhost:80
{$DOMAIN:localhost} {
# Reverse proxy to OpenCRM app
reverse_proxy app:3001
# Logging
log {
output stdout
format console
}
# Security headers
header {
# Clickjacking protection
X-Frame-Options "SAMEORIGIN"
# XSS protection
X-Content-Type-Options "nosniff"
X-XSS-Protection "1; mode=block"
# Referrer policy
Referrer-Policy "strict-origin-when-cross-origin"
}
# Gzip compression
encode gzip
# Handle file uploads (increase body limit)
request_body {
max_size 50MB
}
}

97
docker/Dockerfile Normal file
View File

@ -0,0 +1,97 @@
# ===================================
# Stage 1: Build Frontend
# ===================================
FROM node:20-alpine AS frontend-builder
WORKDIR /app/frontend
# Copy package files
COPY frontend/package*.json ./
# Install dependencies
RUN npm ci
# Copy frontend source
COPY frontend/ ./
# Build frontend
RUN npm run build
# ===================================
# Stage 2: Build Backend
# ===================================
FROM node:20-alpine AS backend-builder
WORKDIR /app/backend
# Install OpenSSL for Prisma
RUN apk add --no-cache openssl
# Copy package files
COPY backend/package*.json ./
# Install dependencies (including dev for build)
RUN npm ci
# Copy backend source
COPY backend/ ./
# Generate Prisma client
RUN npx prisma generate
# Build TypeScript
RUN npm run build
# ===================================
# Stage 3: Production Runtime
# ===================================
FROM node:20-alpine AS production
WORKDIR /app
# Install OpenSSL for Prisma runtime and netcat for DB health check
RUN apk add --no-cache openssl netcat-openbsd
# Create non-root user
RUN addgroup -g 1001 -S nodejs && \
adduser -S opencrm -u 1001 -G nodejs
# Copy backend package files
COPY backend/package*.json ./
# Install production dependencies + tsx for seed script
RUN npm ci --omit=dev && npm install tsx
# Copy Prisma schema and generate client
COPY backend/prisma ./prisma
RUN npx prisma generate
# Copy built backend
COPY --from=backend-builder /app/backend/dist ./dist
# Copy built frontend to public directory
COPY --from=frontend-builder /app/frontend/dist ./public
# Copy seed file for factory reset
COPY backend/prisma/seed.ts ./prisma/
# Copy entrypoint script
COPY docker/entrypoint.sh ./entrypoint.sh
# Create directories for uploads and backups
RUN mkdir -p uploads backups && \
chmod +x entrypoint.sh && \
chown -R opencrm:nodejs /app
# Switch to non-root user
USER opencrm
# Expose port
EXPOSE 3001
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:3001/api/health || exit 1
# Start the application with entrypoint
ENTRYPOINT ["./entrypoint.sh"]

139
docker/README.md Normal file
View File

@ -0,0 +1,139 @@
# OpenCRM Docker Deployment
## Schnellstart
1. **Umgebungsvariablen konfigurieren:**
```bash
cd docker
cp .env.example .env
nano .env # Sichere Werte setzen
```
2. **Container starten (erster Start mit RUN_SEED=true):**
```bash
docker compose up -d
```
Beim ersten Start wird automatisch:
- Auf die Datenbank gewartet
- Migrationen ausgeführt
- Seed-Daten geladen (wenn `RUN_SEED=true`)
3. **Nach erfolgreicher Installation:**
```bash
# RUN_SEED in .env auf false setzen
sed -i 's/RUN_SEED=true/RUN_SEED=false/' .env
```
4. **Anwendung aufrufen:**
- Mit Domain: `https://your-domain.com`
- Lokal: `http://localhost`
5. **Login:**
- E-Mail: `admin@admin.com`
- Passwort: `admin`
## Architektur
```
┌─────────────┐
│ Caddy │
│ (SSL/TLS) │
│ :80/:443 │
└──────┬──────┘
┌──────▼──────┐
│ OpenCRM │
│ (Node.js) │
│ :3001 │
└──────┬──────┘
┌──────▼──────┐
│ MariaDB │
│ :3306 │
└─────────────┘
```
## Befehle
### Container verwalten
```bash
# Starten
docker compose up -d
# Stoppen
docker compose down
# Logs anzeigen
docker compose logs -f app
# Neustart
docker compose restart app
```
### Datenbank
```bash
# Migration ausführen
docker compose exec app npx prisma migrate deploy
# Seed-Daten laden
docker compose exec app npx tsx prisma/seed.ts
# Prisma Studio (Datenbank-UI)
docker compose exec app npx prisma studio
```
### Backup & Restore
```bash
# Backup-Verzeichnis ist unter /app/backups gemountet
# Backups werden über die Anwendung erstellt/wiederhergestellt
```
### Update
```bash
# Image neu bauen und Container aktualisieren
docker compose build --no-cache
docker compose up -d
# Migrationen werden automatisch beim Start ausgeführt
```
## Volumes
| Volume | Beschreibung |
|--------|--------------|
| `mariadb_data` | Datenbank-Dateien |
| `uploads_data` | Hochgeladene Dokumente |
| `backups_data` | Backup-Dateien |
| `caddy_data` | SSL-Zertifikate |
| `caddy_config` | Caddy-Konfiguration |
## SSL-Zertifikat
Caddy holt automatisch ein Let's Encrypt Zertifikat wenn:
- Die Domain in `.env` korrekt gesetzt ist
- Port 80 und 443 von außen erreichbar sind
- DNS auf den Server zeigt
Für lokale Entwicklung mit `DOMAIN=localhost` wird ein selbstsigniertes Zertifikat verwendet.
## Troubleshooting
### Container startet nicht
```bash
docker compose logs app
```
### Datenbank-Verbindung fehlgeschlagen
```bash
# Warten bis MariaDB bereit ist
docker compose logs db
```
### SSL-Zertifikat Probleme
```bash
docker compose logs caddy
# Caddy-Daten zurücksetzen
docker compose down
docker volume rm opencrm_caddy_data opencrm_caddy_config
docker compose up -d
```

88
docker/docker-compose.yml Normal file
View File

@ -0,0 +1,88 @@
version: '3.8'
services:
# ===================================
# MariaDB Database
# ===================================
db:
image: mariadb:10.11
container_name: opencrm-db
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD:-rootpassword}
MYSQL_DATABASE: ${DB_NAME:-opencrm}
MYSQL_USER: ${DB_USER:-opencrm}
MYSQL_PASSWORD: ${DB_PASSWORD:-opencrm123}
volumes:
- mariadb_data:/var/lib/mysql
networks:
- opencrm-network
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
start_period: 10s
interval: 10s
timeout: 5s
retries: 3
# ===================================
# OpenCRM Application
# ===================================
app:
build:
context: ..
dockerfile: docker/Dockerfile
container_name: opencrm-app
restart: unless-stopped
environment:
NODE_ENV: production
PORT: 3001
DATABASE_URL: mysql://${DB_USER:-opencrm}:${DB_PASSWORD:-opencrm123}@db:3306/${DB_NAME:-opencrm}
JWT_SECRET: ${JWT_SECRET:?JWT_SECRET is required}
JWT_EXPIRES_IN: ${JWT_EXPIRES_IN:-7d}
ENCRYPTION_KEY: ${ENCRYPTION_KEY:?ENCRYPTION_KEY is required}
RUN_SEED: ${RUN_SEED:-false}
volumes:
- uploads_data:/app/uploads
- backups_data:/app/backups
networks:
- opencrm-network
depends_on:
db:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3001/api/health"]
start_period: 60s
interval: 30s
timeout: 10s
retries: 3
# ===================================
# Caddy Reverse Proxy (with auto SSL)
# ===================================
caddy:
image: caddy:2-alpine
container_name: opencrm-caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
networks:
- opencrm-network
depends_on:
app:
condition: service_healthy
networks:
opencrm-network:
driver: bridge
volumes:
mariadb_data:
uploads_data:
backups_data:
caddy_data:
caddy_config:

34
docker/entrypoint.sh Normal file
View File

@ -0,0 +1,34 @@
#!/bin/sh
set -e
echo "=== OpenCRM Startup ==="
# Wait for database to be ready
echo "Waiting for database connection..."
MAX_RETRIES=30
RETRY_COUNT=0
while ! nc -z db 3306 2>/dev/null; do
RETRY_COUNT=$((RETRY_COUNT + 1))
if [ $RETRY_COUNT -ge $MAX_RETRIES ]; then
echo "Error: Database not available after $MAX_RETRIES attempts"
exit 1
fi
echo " Attempt $RETRY_COUNT/$MAX_RETRIES - waiting..."
sleep 2
done
echo "Database is ready!"
# Run migrations
echo "Running database migrations..."
npx prisma migrate deploy
# Seed database if RUN_SEED is set (first install)
if [ "$RUN_SEED" = "true" ]; then
echo "Seeding database..."
npx tsx prisma/seed.ts
fi
# Start the application
echo "Starting OpenCRM server..."
exec node dist/index.js