From ecad447d60862bf2117770101d281652f1f746ff Mon Sep 17 00:00:00 2001 From: blaisadmin Date: Tue, 21 Jul 2026 22:46:16 -0400 Subject: [PATCH] Add deploy-ready health checks --- .gitea/workflows/deploy.yml | 8 +++++++- backend/src/app.ts | 19 +++++++++++++++++++ docker-compose.yml | 7 +++++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 3cf3d66..d2048b0 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -85,4 +85,10 @@ jobs: run: | set -e cd /docker/FlockPal - docker compose -f docker-compose.prod.yml up -d --build + docker compose -f docker-compose.prod.yml up -d --build --wait --wait-timeout 180 + + - name: Verify production API readiness + run: | + set -e + cd /docker/FlockPal + docker compose -f docker-compose.prod.yml exec -T backend node dist/healthcheck.js api-ready diff --git a/backend/src/app.ts b/backend/src/app.ts index cffef15..7a8f52e 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -12,6 +12,7 @@ import nodemailer, { type SendMailOptions } from 'nodemailer'; import Stripe from 'stripe'; import { z } from 'zod'; +import { db } from './db/client.js'; import { ensureSchema } from './db/schema.js'; import { adoptionReportQueueEvents, enqueueAdoptionReportJob } from './queues/adoptionReportQueue.js'; import { enqueueBirdMilestoneReminderJob, getBirdMilestoneReminderQueueCounts } from './queues/birdMilestoneReminderQueue.js'; @@ -2514,6 +2515,24 @@ app.get('/api/health', (_req: Request, res: Response) => { res.json({ ok: true }); }); +app.get('/api/health/live', (_req: Request, res: Response) => { + res.json({ ok: true, status: 'live' }); +}); + +app.get('/api/health/ready', async (_req: Request, res: Response) => { + try { + await Promise.all([ + db.query('SELECT 1'), + getBirdMilestoneReminderQueueCounts(), + getMedicationReminderQueueCounts(), + ]); + res.json({ ok: true, status: 'ready' }); + } catch (error) { + console.error('Readiness check failed', error); + res.status(503).json({ ok: false, status: 'not_ready' }); + } +}); + app.get('/api/metrics', requireAuth, requireAdmin, async (_req: Request, res: Response, next: NextFunction) => { const memoryUsage = process.memoryUsage(); const averageDurationMs = requestMetrics.totalRequests > 0 ? requestMetrics.totalDurationMs / requestMetrics.totalRequests : 0; diff --git a/docker-compose.yml b/docker-compose.yml index b7f4446..09d3a1e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -95,6 +95,12 @@ services: condition: service_healthy redis: condition: service_healthy + healthcheck: + test: ["CMD-SHELL", "node -e \"fetch('http://127.0.0.1:5000/api/health/ready').then(r => { if (!r.ok) process.exit(1) }).catch(() => process.exit(1))\""] + interval: 30s + timeout: 10s + retries: 3 + start_period: 30s ports: - "5000:5000" command: > @@ -103,6 +109,7 @@ services: volumes: - ./backend:/app - /app/node_modules + restart: unless-stopped worker: build: