diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..3dd3e0b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,21 @@ +# Version control +.git +.gitignore + +# Dependencies +node_modules +npm-debug.log + +# Environment files +.env +.env.* + +# Development files +.vscode +.idea +*.test.ts +*.spec.ts + +# Docker files +Dockerfile +docker-compose.yml diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..454e5c8 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,44 @@ +# Build stage +FROM node:18 AS builder + +WORKDIR /app + +# Copy all source files first +COPY . . + +# Install dependencies and build +RUN npm ci && \ + npm run build + +# Production stage +FROM node:18 + +# Create app directory and non-root user with proper home directory +WORKDIR /app +RUN groupadd -r appgroup && \ + useradd -r -g appgroup -m -d /home/appuser appuser && \ + chown -R appuser:appgroup /app + +# Copy package files and install production dependencies +COPY package*.json tsconfig.json ./ +ENV NODE_ENV=production + +# Install production dependencies and rebuild native modules +RUN npm ci --omit=dev --ignore-scripts && \ + npm rebuild && \ + chown -R appuser:appgroup /app + +# Copy built files from builder stage +COPY --from=builder /app/dist ./dist +RUN chown -R appuser:appgroup /app/dist + +# Set environment variables +ENV LOG_LEVEL=info +ENV HOME=/home/appuser + +# Switch to non-root user +USER appuser + +# Use ENTRYPOINT and CMD to allow argument overrides +ENTRYPOINT ["npm", "start", "--"] +CMD [] diff --git a/src/transports/sse.ts b/src/transports/sse.ts index 2f576f4..34b275f 100644 --- a/src/transports/sse.ts +++ b/src/transports/sse.ts @@ -19,7 +19,7 @@ export class SSETransport { constructor(options: SSETransportOptions = {}) { this.options = { port: options.port || 3001, - host: options.host || 'localhost', + host: options.host || '0.0.0.0', // Changed from localhost to 0.0.0.0 keepAliveInterval: options.keepAliveInterval || 30000 // Default: 30 seconds }; @@ -31,6 +31,14 @@ export class SSETransport { allowedHeaders: ['Content-Type'] })); + // Health check endpoint + this.app.get('/healthz', (_req: Request, res: Response) => { + res.status(200).json({ + status: 'ok', + timestamp: new Date().toISOString() + }); + }); + // SSE endpoint this.app.get('/sse', (req: Request, res: Response) => { const transport = new SSEServerTransport('/messages', res); @@ -86,7 +94,7 @@ export class SSETransport { async start(server: Server): Promise { this.server = server; const port = this.options.port || 3001; - const host = this.options.host || 'localhost'; + const host = this.options.host || '0.0.0.0'; // Changed from localhost to 0.0.0.0 return new Promise((resolve) => { this.app.listen(port, host, () => { @@ -95,4 +103,4 @@ export class SSETransport { }); }); } -} \ No newline at end of file +}