# Quorla: one image with the dashboard, the publishing queue, the Meta connector and PDF export. # Build: docker compose build Run: docker compose up -d (see docs/hosting.html) # ---- 1. Build the web app ---- FROM node:22-bookworm-slim AS web WORKDIR /app/dashboard COPY dashboard/package.json dashboard/package-lock.json ./ RUN npm ci COPY dashboard/web ./web RUN npm run build # ---- 2. Runtime ---- FROM node:22-bookworm-slim ENV NODE_ENV=production \ DASHBOARD_HOST=0.0.0.0 \ DASHBOARD_PORT=4310 \ REPORT_PDF_BROWSER=/usr/bin/chromium # Chromium for branded PDF reports; fonts so PDFs don't render as boxes; tini as PID 1. RUN apt-get update \ && apt-get install -y --no-install-recommends chromium fonts-dejavu-core fonts-liberation fonts-noto-color-emoji ca-certificates tini \ && rm -rf /var/lib/apt/lists/* WORKDIR /app COPY connector/package.json connector/package-lock.json ./connector/ RUN cd connector && npm ci --omit=dev COPY dashboard/package.json dashboard/package-lock.json ./dashboard/ RUN cd dashboard && npm ci --omit=dev COPY tools/report-pdf/package.json tools/report-pdf/package-lock.json ./tools/report-pdf/ RUN cd tools/report-pdf && npm ci --omit=dev # App code and the AI's working files (skills, playbooks, instructions). COPY connector ./connector COPY dashboard/server ./dashboard/server COPY tools ./tools COPY .claude ./.claude COPY playbooks ./playbooks COPY CLAUDE.md THIRD_PARTY_SKILLS.md ./ COPY deploy/mcp.json ./.mcp.json COPY --from=web /app/dashboard/web/dist ./dashboard/web/dist # Defaults for folders that are normally mounted from the server (see docker-compose.yml). COPY branding ./branding COPY clients/_template ./clients/_template # Only the folders the app writes to need to belong to the runtime user. RUN mkdir -p data clients team reports && chown -R node:node data clients team reports branding .claude && chown node:node /app USER node EXPOSE 4310 HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \ CMD node -e "fetch('http://127.0.0.1:4310/api/auth/me').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))" ENTRYPOINT ["/usr/bin/tini", "--"] CMD ["node", "dashboard/server/index.js"]