# ── Builder stage ────────────────────────────────────────────── FROM node:22-alpine AS builder # Enable corepack for pnpm RUN corepack enable && corepack prepare pnpm@latest --activate # Install native build tools (needed for better-sqlite3) RUN apk add --no-cache python3 make g++ WORKDIR /app # Copy dependency manifests first (layer caching) COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ # Install dependencies (frozen lockfile for reproducibility) RUN pnpm install --frozen-lockfile # Copy source and config COPY tsconfig.json ./ COPY src/ src/ COPY config/ config/ # Build TypeScript RUN pnpm build # ── Runtime stage ───────────────────────────────────────────── FROM node:22-alpine # Label LABEL org.opencontainers.image.title="Flynn" \ org.opencontainers.image.description="Self-hosted personal AI agent" \ org.opencontainers.image.source="https://github.com/will666/flynn" WORKDIR /app # Copy node_modules from builder (includes compiled native deps like better-sqlite3) COPY --from=builder /app/node_modules/ node_modules/ # Copy compiled output COPY --from=builder /app/dist/ dist/ # Copy gateway UI static files into dist/gateway/ui so import.meta.dirname # resolution from dist/daemon/index.js (../gateway/ui) resolves correctly COPY --from=builder /app/src/gateway/ui/ dist/gateway/ui/ # Copy default config COPY --from=builder /app/config/ config/ # Copy package.json (needed for bin resolution / metadata) COPY --from=builder /app/package.json ./ # Copy SOUL.md if it exists (prompt template loaded at runtime) COPY --from=builder /app/SOUL.md ./ # Create data directories and ship a default config at /config/config.yaml so # the image is runnable without an external bind-mount (compose can still # override /config/config.yaml). RUN mkdir -p /data/memory /data/sessions /config && \ cp -f /app/config/paas.yaml /config/config.yaml # Environment ENV NODE_ENV=production \ FLYNN_CONFIG=/config/config.yaml \ FLYNN_DATA_DIR=/data # Gateway port EXPOSE 18800 # Health check — verify the gateway is responding HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \ CMD wget -qO- http://localhost:18800/ || exit 1 ENTRYPOINT ["node", "dist/cli/index.js"] CMD ["start"]