diff --git a/docker-compose.yaml b/docker-compose.yaml index 297eab85e..6d2176c97 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -19,7 +19,7 @@ services: - ${WORKSPACE_DIR:-./workspace}:/workspace:ro restart: unless-stopped healthcheck: - test: ['CMD', 'curl', '-fsSI', 'http://localhost:4747/api/heartbeat'] + test: ['CMD', 'curl', '-f', 'http://localhost:4747/api/health'] interval: 30s timeout: 5s retries: 3 diff --git a/gitnexus/src/server/api.ts b/gitnexus/src/server/api.ts index 376ab5876..69ffb6d44 100644 --- a/gitnexus/src/server/api.ts +++ b/gitnexus/src/server/api.ts @@ -183,6 +183,7 @@ a.ext:hover{text-decoration:underline}
Endpoints

/api/info — Server version & context

/api/repos — Indexed repositories

+

/api/health — Docker/orchestrator healthcheck

/api/heartbeat — SSE heartbeat

/api/graph /api/query /api/search — Data

/api/mcp — MCP over StreamableHTTP

@@ -777,6 +778,13 @@ export const createServer = async (port: number, host: string = '127.0.0.1') => return found; }; + // Lightweight healthcheck for Docker/orchestrator probes (#1147). + // Returns immediately so container managers do not confuse a long-lived + // SSE stream with an unhealthy server. + app.get('/api/health', (_req, res) => { + res.json({ status: 'ok' }); + }); + // SSE heartbeat — clients connect to detect server liveness instantly. // When the server shuts down, the TCP connection drops and the client's // EventSource fires onerror immediately (no polling delay). diff --git a/gitnexus/test/unit/rate-limit.test.ts b/gitnexus/test/unit/rate-limit.test.ts index 1802d57d0..5a79e6085 100644 --- a/gitnexus/test/unit/rate-limit.test.ts +++ b/gitnexus/test/unit/rate-limit.test.ts @@ -242,6 +242,10 @@ describe('production routes — rate-limit middleware wiring', () => { expect(apiSource).toMatch(/app\.get\(SPA_FALLBACK_REGEX,\s*createRouteLimiter\(/); }); + it('GET /api/health is registered (Docker healthcheck, #1147)', () => { + expect(apiSource).toMatch(/app\.get\('\/api\/health',\s*\(_req,\s*res\)\s*=>/); + }); + it('createServer wires trust proxy to loopback/linklocal/uniquelocal', () => { expect(apiSource).toMatch( /app\.set\(\s*'trust proxy'\s*,\s*'loopback,\s*linklocal,\s*uniquelocal'\s*\)/,