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*\)/,