fix(selfhost): pin MinIO images and add application health checks

This commit is contained in:
2026-09-23 16:39:17 +02:00
parent 74c0cf40b3
commit f51f2b13a1
5 changed files with 59 additions and 8 deletions

View File

@@ -98,10 +98,6 @@ async function main() {
done()
})
app.get('/health', async (req, res) => {
return res.send({ status: 'ok' })
})
//Plugin nur auf bestimmten Routes
await app.register(queryConfigPlugin, {
routes: ['/api/resource/:resource/paginated']

View File

@@ -2,6 +2,16 @@ import { FastifyInstance } from "fastify";
import { getBuildInfo } from "../utils/buildInfo";
export default async function routes(server: FastifyInstance) {
server.get("/health", async (_request, reply) => {
reply.header("Cache-Control", "no-store");
try {
await server.db.execute("SELECT 1");
return { status: "ok", ...getBuildInfo() };
} catch {
return reply.code(503).send({ status: "unavailable" });
}
});
server.get("/version", async (_request, reply) => {
reply.header("Cache-Control", "no-store");
return getBuildInfo();

View File

@@ -3,6 +3,28 @@ import { test } from 'node:test';
import Fastify from 'fastify';
import healthRoutes from '../src/routes/health';
test('health checks the database and reports a failure without leaking connection details', async () => {
const app = Fastify();
let available = true;
app.decorate('db', { execute: async () => {
if (!available) throw new Error('postgres://secret@database');
return { rows: [{ result: 1 }] };
} } as any);
try {
await app.register(healthRoutes);
const healthy = await app.inject('/health');
assert.equal(healthy.statusCode, 200);
assert.equal(healthy.json().status, 'ok');
assert.equal(healthy.headers['cache-control'], 'no-store');
available = false;
const unhealthy = await app.inject('/health');
assert.equal(unhealthy.statusCode, 503);
assert.deepEqual(unhealthy.json(), { status: 'unavailable' });
} finally {
await app.close();
}
});
test('version endpoint reports image metadata without a database or authentication', async () => {
const previousVersion = process.env.FEDEO_VERSION;
const previousRevision = process.env.FEDEO_REVISION;

View File

@@ -48,7 +48,7 @@ services:
- internal
minio:
image: minio/minio:latest
image: quay.io/minio/minio:RELEASE.2025-09-07T16-13-09Z@sha256:14cea493d9a34af32f524e538b8346cf79f3321eff8e708c1e2960462bd8936e
container_name: fedeo-minio
restart: unless-stopped
command: server /data --console-address ":9001"
@@ -66,13 +66,13 @@ services:
- internal
createbuckets:
image: minio/mc:latest
image: quay.io/minio/mc:RELEASE.2025-08-13T08-35-41Z@sha256:a7fe349ef4bd8521fb8497f55c6042871b2ae640607cf99d9bede5e9bdf11727
container_name: fedeo-minio-init
depends_on:
minio:
condition: service_healthy
entrypoint: >
/bin/sh -c "
/bin/sh -ec "
mc alias set local http://minio:9000 ${MINIO_ROOT_USER} ${MINIO_ROOT_PASSWORD};
mc mb --ignore-existing local/${MINIO_BUCKET};
mc anonymous set private local/${MINIO_BUCKET};
@@ -143,6 +143,12 @@ services:
FEDEO_BOOTSTRAP_TENANT_NAME: ${FEDEO_BOOTSTRAP_TENANT_NAME:-FEDEO}
FEDEO_BOOTSTRAP_TENANT_SHORT: ${FEDEO_BOOTSTRAP_TENANT_SHORT:-FEDEO}
NODE_EXPORTER_URL: ${NODE_EXPORTER_URL:-http://node-exporter:9100}
healthcheck:
test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:' + (process.env.PORT || '3100') + '/health', {signal: AbortSignal.timeout(4000)}).then(r => {if (!r.ok) process.exit(1)}).catch(() => process.exit(1))"]
interval: 10s
timeout: 5s
retries: 6
start_period: 120s
labels:
- traefik.enable=true
- traefik.http.routers.fedeo-backend.rule=Host(`${DOMAIN}`) && PathPrefix(`/backend`)
@@ -178,11 +184,18 @@ services:
container_name: fedeo-frontend
restart: unless-stopped
depends_on:
- backend
backend:
condition: service_healthy
environment:
NODE_ENV: production
NUXT_PUBLIC_API_BASE: https://${DOMAIN}/backend
NUXT_PUBLIC_PDF_LICENSE: ${NUXT_PUBLIC_PDF_LICENSE}
healthcheck:
test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:3000/api/health', {signal: AbortSignal.timeout(4000)}).then(r => {if (!r.ok) process.exit(1)}).catch(() => process.exit(1))"]
interval: 10s
timeout: 5s
retries: 6
start_period: 30s
labels:
- traefik.enable=true
- traefik.http.routers.fedeo-frontend.rule=Host(`${DOMAIN}`)

View File

@@ -0,0 +1,10 @@
export default defineEventHandler((event) => {
setHeader(event, 'Cache-Control', 'no-store')
const config = useRuntimeConfig(event)
return {
status: 'ok',
product: 'FEDEO',
version: config.public.fedeoVersion,
revision: config.public.fedeoRevision,
}
})