mirror of
https://github.com/runyanjake/cooking.git
synced 2026-06-25 08:04:51 -07:00
Add health check endpoint and Jenkins CI
This commit is contained in:
parent
c6b23d32b0
commit
8483353ac3
@ -9,7 +9,14 @@
|
|||||||
"Bash(ls *)",
|
"Bash(ls *)",
|
||||||
"WebFetch(*)",
|
"WebFetch(*)",
|
||||||
"Bash(mkdir -p \"c:/Users/runya/Documents/repositories/cooking/public/recipes/mexican/fajita-vegetables/assets\")",
|
"Bash(mkdir -p \"c:/Users/runya/Documents/repositories/cooking/public/recipes/mexican/fajita-vegetables/assets\")",
|
||||||
"Bash(cp \"c:/Users/runya/Documents/repositories/cooking/public/recipes/mexican/birria/assets/not-found.svg\" \"c:/Users/runya/Documents/repositories/cooking/public/recipes/mexican/fajita-vegetables/assets/not-found.svg\")"
|
"Bash(cp \"c:/Users/runya/Documents/repositories/cooking/public/recipes/mexican/birria/assets/not-found.svg\" \"c:/Users/runya/Documents/repositories/cooking/public/recipes/mexican/fajita-vegetables/assets/not-found.svg\")",
|
||||||
|
"Bash(ls -la && echo \"=== next.config ===\" && ls next.config* 2>/dev/null; echo \"=== applications infra ===\" && ls -la /c/Users/runya/Documents/repositories/applications/ | grep -iE 'docker|compose|nginx|next.config')",
|
||||||
|
"Read(//c/Users/runya/Documents/repositories/applications//**)",
|
||||||
|
"Bash(ls /c/Users/runya/Documents/repositories/cooking/app)",
|
||||||
|
"Read(//c/Users/runya/Documents/repositories/cooking/app/**)",
|
||||||
|
"Bash(find /c/Users/runya/Documents/repositories/cooking/app/api -type f)",
|
||||||
|
"Read(//c/Users/runya/Documents/repositories/cooking/app/api/**)",
|
||||||
|
"Bash(npx tsc *)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
10
Dockerfile
10
Dockerfile
@ -3,6 +3,11 @@ WORKDIR /app
|
|||||||
COPY package.json package-lock.json ./
|
COPY package.json package-lock.json ./
|
||||||
RUN npm ci
|
RUN npm ci
|
||||||
|
|
||||||
|
# Lint + type-check target, used by CI (docker build --target ci).
|
||||||
|
FROM deps AS ci
|
||||||
|
COPY . .
|
||||||
|
RUN npm run lint && npx tsc --noEmit
|
||||||
|
|
||||||
FROM node:20-alpine AS build
|
FROM node:20-alpine AS build
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --from=deps /app/node_modules ./node_modules
|
COPY --from=deps /app/node_modules ./node_modules
|
||||||
@ -25,4 +30,9 @@ COPY --from=build --chown=nextjs:nodejs /app/.next/static ./.next/static
|
|||||||
USER nextjs
|
USER nextjs
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
||||||
|
# Liveness probe against the dedicated /healthz route. Use 127.0.0.1 (not
|
||||||
|
# localhost) so busybox wget hits the IPv4 address the server binds to.
|
||||||
|
HEALTHCHECK --interval=5s --timeout=3s --start-period=15s --retries=3 \
|
||||||
|
CMD wget -q -O /dev/null http://127.0.0.1:3000/healthz || exit 1
|
||||||
|
|
||||||
CMD ["node", "server.js"]
|
CMD ["node", "server.js"]
|
||||||
|
|||||||
102
Jenkinsfile
vendored
Normal file
102
Jenkinsfile
vendored
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
pipeline {
|
||||||
|
agent any
|
||||||
|
|
||||||
|
environment {
|
||||||
|
COMPOSE_FILE = 'docker-compose.yml'
|
||||||
|
SERVICE = 'recipes'
|
||||||
|
APP_PORT = '3000'
|
||||||
|
}
|
||||||
|
|
||||||
|
options {
|
||||||
|
timestamps()
|
||||||
|
disableConcurrentBuilds()
|
||||||
|
timeout(time: 30, unit: 'MINUTES')
|
||||||
|
}
|
||||||
|
|
||||||
|
stages {
|
||||||
|
stage('Checkout') {
|
||||||
|
steps {
|
||||||
|
checkout scm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Preflight') {
|
||||||
|
steps {
|
||||||
|
sh '''
|
||||||
|
set -eu
|
||||||
|
docker network inspect traefik >/dev/null 2>&1 || { echo "missing docker network 'traefik'" >&2; exit 1; }
|
||||||
|
docker compose -f "$COMPOSE_FILE" config -q
|
||||||
|
'''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Lint & Type-check') {
|
||||||
|
steps {
|
||||||
|
sh 'docker build --target ci -t cooking-ci:$BUILD_NUMBER .'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Teardown') {
|
||||||
|
steps {
|
||||||
|
sh 'docker compose -f "$COMPOSE_FILE" down --remove-orphans'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Build & Deploy') {
|
||||||
|
steps {
|
||||||
|
sh 'docker compose -f "$COMPOSE_FILE" up -d --build'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Health Check') {
|
||||||
|
steps {
|
||||||
|
sh '''
|
||||||
|
set -eu
|
||||||
|
cid="$(docker compose -f "$COMPOSE_FILE" ps -q "$SERVICE")"
|
||||||
|
[ -n "$cid" ] || { echo "$SERVICE container not found" >&2; exit 1; }
|
||||||
|
|
||||||
|
# The image defines a HEALTHCHECK (wget against 127.0.0.1/healthz); wait
|
||||||
|
# for Docker to report healthy, failing fast on terminal states.
|
||||||
|
deadline=$(( $(date +%s) + 90 ))
|
||||||
|
while :; do
|
||||||
|
status="$(docker inspect -f '{{.State.Status}}' "$cid")"
|
||||||
|
health="$(docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' "$cid")"
|
||||||
|
[ "$status" = "running" ] && [ "$health" = "healthy" ] && break
|
||||||
|
[ "$health" = "unhealthy" ] && { echo "$SERVICE reported unhealthy" >&2; exit 1; }
|
||||||
|
case "$status" in
|
||||||
|
exited|dead) echo "$SERVICE container $status before becoming healthy" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
[ "$(date +%s)" -ge "$deadline" ] && { echo "timed out waiting for healthy (status=$status, health=$health)" >&2; exit 1; }
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
echo "$SERVICE healthy"
|
||||||
|
'''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Smoke Test') {
|
||||||
|
steps {
|
||||||
|
sh '''
|
||||||
|
set -eu
|
||||||
|
cid="$(docker compose -f "$COMPOSE_FILE" ps -q "$SERVICE")"
|
||||||
|
[ -n "$cid" ] || { echo "$SERVICE container not found" >&2; exit 1; }
|
||||||
|
|
||||||
|
# /healthz proves the server is up; this proves the build actually deployed:
|
||||||
|
# GET / must return 200 and serve the rendered homepage, not an error page.
|
||||||
|
if ! body="$(docker exec "$cid" wget -q -O - "http://127.0.0.1:$APP_PORT/")"; then
|
||||||
|
echo "GET / did not return a successful response" >&2; exit 1
|
||||||
|
fi
|
||||||
|
echo "$body" | grep -q '<title>PWS Recipes</title>' || { echo "GET / response missing expected homepage markup" >&2; exit 1; }
|
||||||
|
echo "smoke test passed"
|
||||||
|
'''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
failure {
|
||||||
|
sh 'docker compose -f "$COMPOSE_FILE" ps || true'
|
||||||
|
sh 'docker compose -f "$COMPOSE_FILE" logs --tail=200 || true'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
8
app/healthz/route.ts
Normal file
8
app/healthz/route.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export const dynamic = "force-static";
|
||||||
|
|
||||||
|
export function GET() {
|
||||||
|
return new Response("ok\n", {
|
||||||
|
status: 200,
|
||||||
|
headers: { "Content-Type": "text/plain" },
|
||||||
|
});
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user