# ── Build-Stage ─────────────────────────────────────────────────────────────── # Abhängigkeiten separat installieren, damit der finale Layer schlanker bleibt. FROM node:22-alpine AS deps WORKDIR /app COPY package.json package-lock.json* ./ # Nur Produktions-Abhängigkeiten; kein devDependencies RUN npm ci --omit=dev && npm cache clean --force # ── Runtime-Stage ───────────────────────────────────────────────────────────── FROM node:22-alpine AS runtime # Metadaten LABEL org.opencontainers.image.title="serienbrief-backend" LABEL org.opencontainers.image.description="Serienbrief-Backend für Directus + Gotenberg" LABEL org.opencontainers.image.licenses="MIT" # LibreOffice nur wenn RENDERER=carbone (Gotenberg bringt eigenes LibreOffice mit) # ARG INSTALL_LIBREOFFICE=false # RUN if [ "$INSTALL_LIBREOFFICE" = "true" ]; then apk add --no-cache libreoffice font-freefont; fi # Sicherheit: kein root RUN addgroup -g 1001 sbapp && adduser -u 1001 -G sbapp -s /bin/sh -D sbapp WORKDIR /app # Nur nötige Dateien kopieren COPY --from=deps /app/node_modules ./node_modules COPY --chown=sbapp:sbapp src/ ./src/ COPY --chown=sbapp:sbapp package.json ./ # Verzeichnisse für Templates und Secrets (werden als Volumes gemountet) RUN mkdir -p /templates /run/secrets && chown sbapp:sbapp /templates USER sbapp # Kein EXPOSE nötig (wird im Compose-File definiert) # Port dokumentarisch: EXPOSE 3001 # Healthcheck (auch ohne externen curl/wget über Node) HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ CMD node -e "fetch('http://localhost:3001/health').then(r=>r.ok?process.exit(0):process.exit(1)).catch(()=>process.exit(1))" CMD ["node", "src/server.js"]