# Stage 1: Builder FROM node:20-alpine AS builder WORKDIR /app # Copy package files COPY package*.json ./ # Install dependencies RUN npm ci --only=production # Stage 2: Production FROM node:20-alpine # Install dumb-init for proper signal handling RUN apk add --no-cache dumb-init # Create non-root user RUN addgroup -g 1001 -S gds && \ adduser -S -D -H -u 1001 -h /app -s /sbin/nologin -G gds -g gds gds WORKDIR /app # Copy dependencies from builder COPY --from=builder --chown=gds:gds /app/node_modules ./node_modules # Copy application source COPY --chown=gds:gds src ./src COPY --chown=gds:gds package.json ./ # Set environment variables ENV NODE_ENV=production \ LOG_LEVEL=info \ VALKEY_HOST=valkey \ VALKEY_PORT=6379 # Health check HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \ CMD node -e "const Redis = require('ioredis'); const client = new Redis({ host: process.env.VALKEY_HOST, port: process.env.VALKEY_PORT, lazyConnect: true }); client.connect().then(() => client.ping()).then(() => process.exit(0)).catch(() => process.exit(1));" # Switch to non-root user USER gds # Expose MCP server (stdio, no network port) # MCP servers use stdio transport, not network ports # Start server with dumb-init ENTRYPOINT ["/usr/bin/dumb-init", "--"] CMD ["node", "src/index.js"]