diff --git a/Dockerfile b/Dockerfile index 5ec6356..19211d5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,25 +1,38 @@ -# Build-Phase -FROM node:20-slim AS builder +# STAGE 1: Dependencies & Build +FROM node:20-alpine AS builder WORKDIR /app + COPY package*.json ./ -RUN npm install --legacy-peer-deps +RUN npm ci + COPY . . +ENV NODE_ENV=production RUN npm run build -# Production-Phase (Node SSR Runner) -FROM node:20-slim AS runner +# STAGE 2: Production Runner +FROM node:20-alpine AS runner WORKDIR /app ENV NODE_ENV=production ENV HOST=0.0.0.0 -ENV PORT=4321 +ENV PORT=3000 ENV DATA_DIR=/app/data -COPY --from=builder /app/package*.json ./ -COPY --from=builder /app/node_modules ./node_modules -COPY --from=builder /app/dist ./dist -COPY --from=builder /app/data_template ./data_template +# Erstelle Kunden-Volume Ordner +RUN mkdir -p /app/data && chown -R node:node /app -EXPOSE 4321 +COPY package*.json ./ +RUN npm ci --only=production + +COPY --from=builder /app/dist ./dist +COPY --from=builder /app/public ./public + +USER node + +EXPOSE 3000 + +# Healthcheck für Docker / Nginx Proxy +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD wget --no-verbose --tries=1 --spider http://localhost:3000/api/health || exit 1 CMD ["node", "./dist/server/entry.mjs"] diff --git a/docker-compose.yml b/docker-compose.yml index 2a54aaa..9a982e3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,15 +1,25 @@ +version: '3.8' + services: - website: - build: . - image: gitea.hephex.de/daniel/websitedummy:latest - container_name: websitedummy_app + kunden_app: + build: + context: . + dockerfile: Dockerfile + image: nd-waas-engine:latest + container_name: kunden_instanz_app restart: always - ports: - - "8080:4321" environment: - - HOST=0.0.0.0 - - PORT=4321 + - NODE_ENV=production + - PORT=3000 - DATA_DIR=/app/data volumes: - - ./app/data:/app/data - + # Kunden-Konfigurationsdaten & Uploads bleiben bei Updates persistent + - ./data:/app/data + ports: + # Interne Port-Anbindung für den händischen Nginx Reverse Proxy + - "127.0.0.1:8080:3000" + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "3" diff --git a/src/pages/api/health.ts b/src/pages/api/health.ts new file mode 100644 index 0000000..cf26784 --- /dev/null +++ b/src/pages/api/health.ts @@ -0,0 +1,37 @@ +import type { APIRoute } from 'astro'; +import fs from 'node:fs/promises'; +import path from 'node:path'; + +export const prerender = false; + +const DATA_DIR = process.env.DATA_DIR || path.join(process.cwd(), 'app', 'data'); + +export const GET: APIRoute = async () => { + let volumeStatus = 'ok'; + + try { + // Prüfe Schreib- und Lesezugriff auf /app/data/ + await fs.mkdir(DATA_DIR, { recursive: true }); + const testFile = path.join(DATA_DIR, '.healthcheck'); + await fs.writeFile(testFile, 'ok', 'utf-8'); + await fs.unlink(testFile); + } catch { + volumeStatus = 'error_data_volume_unwritable'; + } + + const isHealthy = volumeStatus === 'ok'; + + return new Response( + JSON.stringify({ + status: isHealthy ? 'healthy' : 'unhealthy', + uptime: process.uptime(), + timestamp: new Date().toISOString(), + volume_status: volumeStatus, + node_version: process.version, + }), + { + status: isHealthy ? 200 : 503, + headers: { 'Content-Type': 'application/json' }, + } + ); +}; diff --git a/tests/health.test.ts b/tests/health.test.ts new file mode 100644 index 0000000..e9e7400 --- /dev/null +++ b/tests/health.test.ts @@ -0,0 +1,18 @@ +import { describe, it, expect } from 'vitest'; +import fs from 'node:fs/promises'; +import path from 'node:path'; + +const DATA_DIR = path.join(process.cwd(), 'app', 'test_data_health'); + +describe('Healthcheck API Test', () => { + it('soll Volume-Ordner erfolgreich erstellen und beschreiben können', async () => { + await fs.mkdir(DATA_DIR, { recursive: true }); + const testFile = path.join(DATA_DIR, '.vitest_health'); + + await fs.writeFile(testFile, 'test', 'utf-8'); + const content = await fs.readFile(testFile, 'utf-8'); + await fs.unlink(testFile); + + expect(content).toBe('test'); + }); +});