Healthchecks and fixes for QR API
This commit is contained in:
@@ -34,6 +34,11 @@ services:
|
||||
volumes:
|
||||
- /mnt/config/docker/kutt/redis:/data
|
||||
command: redis-server --appendonly yes
|
||||
healthcheck:
|
||||
test: ['CMD', 'redis-cli', 'ping']
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
kutt:
|
||||
image: kutt/kutt:latest
|
||||
@@ -46,7 +51,17 @@ services:
|
||||
kutt_db:
|
||||
condition: service_healthy
|
||||
kutt_redis:
|
||||
condition: service_started
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
'CMD-SHELL',
|
||||
"node -e \"require('http').get('http://127.0.0.1:3000/', (r) => { r.resume(); process.exit(r.statusCode >= 200 && r.statusCode < 500 ? 0 : 1); }).on('error', () => process.exit(1))\"",
|
||||
]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
environment:
|
||||
ADMIN_EMAILS: ${ADMIN_EMAILS:?Set ADMIN_EMAILS}
|
||||
DEFAULT_DOMAIN: mifi.me
|
||||
@@ -85,7 +100,8 @@ services:
|
||||
- 'traefik.http.routers.kutt-mifi.tls.certresolver=letsencrypt'
|
||||
- 'traefik.http.routers.kutt-mifi.service=kutt-short'
|
||||
- 'traefik.http.services.kutt-short.loadbalancer.server.port=3000'
|
||||
- 'traefik.http.services.kutt-short.loadbalancer.serversTransport=kutt-long-timeout'
|
||||
# Backend timeout: use transport from file provider (see traefik-kutt-timeout.example.yml).
|
||||
- 'traefik.http.services.kutt-short.loadbalancer.serversTransport=kutt-long-timeout@file'
|
||||
|
||||
qr_api:
|
||||
image: ${REGISTRY:-git.mifi.dev}/mifi-holdings/shorty-qr-api:${IMAGE_TAG:-latest}
|
||||
@@ -103,6 +119,18 @@ services:
|
||||
KUTT_API_KEY: ${KUTT_API_KEY:-}
|
||||
KUTT_BASE_URL: http://kutt:3000
|
||||
SHORT_DOMAIN: https://mifi.me
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
'CMD',
|
||||
'node',
|
||||
'-e',
|
||||
"require('http').get('http://127.0.0.1:8080/health', (r) => { r.resume(); process.exit(r.statusCode === 200 ? 0 : 1); }).on('error', () => process.exit(1))",
|
||||
]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 10s
|
||||
|
||||
qr_web:
|
||||
image: ${REGISTRY:-git.mifi.dev}/mifi-holdings/shorty-qr-web:${IMAGE_TAG:-latest}
|
||||
@@ -112,9 +140,20 @@ services:
|
||||
- marina-net
|
||||
- backend
|
||||
depends_on:
|
||||
- qr_api
|
||||
qr_api:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
QR_API_URL: http://qr_api:8080
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
'CMD-SHELL',
|
||||
"node -e \"require('http').get('http://127.0.0.1:3000/', (r) => { r.resume(); process.exit(r.statusCode >= 200 && r.statusCode < 500 ? 0 : 1); }).on('error', () => process.exit(1))\"",
|
||||
]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 15s
|
||||
labels:
|
||||
- 'traefik.enable=true'
|
||||
- 'docker.network=marina-net'
|
||||
|
||||
@@ -28,6 +28,11 @@ services:
|
||||
volumes:
|
||||
- /mnt/config/docker/kutt/redis:/data
|
||||
command: redis-server --appendonly yes
|
||||
healthcheck:
|
||||
test: ['CMD', 'redis-cli', 'ping']
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
kutt:
|
||||
image: kutt/kutt:latest
|
||||
@@ -39,7 +44,17 @@ services:
|
||||
kutt_db:
|
||||
condition: service_healthy
|
||||
kutt_redis:
|
||||
condition: service_started
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
'CMD-SHELL',
|
||||
"node -e \"require('http').get('http://127.0.0.1:3000/', (r) => { r.resume(); process.exit(r.statusCode >= 200 && r.statusCode < 500 ? 0 : 1); }).on('error', () => process.exit(1))\"",
|
||||
]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
environment:
|
||||
DB_CLIENT: pg
|
||||
DB_HOST: kutt_db
|
||||
@@ -85,6 +100,18 @@ services:
|
||||
KUTT_API_KEY: ${KUTT_API_KEY:-}
|
||||
KUTT_BASE_URL: http://kutt:3000
|
||||
SHORT_DOMAIN: https://mifi.me
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
'CMD',
|
||||
'node',
|
||||
'-e',
|
||||
"require('http').get('http://127.0.0.1:8080/health', (r) => { r.resume(); process.exit(r.statusCode === 200 ? 0 : 1); }).on('error', () => process.exit(1))",
|
||||
]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 10s
|
||||
|
||||
qr_web:
|
||||
build:
|
||||
@@ -95,9 +122,20 @@ services:
|
||||
- marina-net
|
||||
- backend
|
||||
depends_on:
|
||||
- qr_api
|
||||
qr_api:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
QR_API_URL: http://qr_api:8080
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
'CMD-SHELL',
|
||||
"node -e \"require('http').get('http://127.0.0.1:3000/', (r) => { r.resume(); process.exit(r.statusCode >= 200 && r.statusCode < 500 ? 0 : 1); }).on('error', () => process.exit(1))\"",
|
||||
]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 15s
|
||||
labels:
|
||||
- 'traefik.enable=true'
|
||||
- 'docker.network=marina-net'
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
FROM node:20-alpine AS builder
|
||||
# Use Debian-based image so better-sqlite3 prebuilds (glibc) work; Alpine/musl has no prebuilds.
|
||||
FROM node:20-bookworm-slim AS builder
|
||||
RUN corepack enable && corepack prepare pnpm@latest --activate
|
||||
WORKDIR /app
|
||||
COPY package.json pnpm-lock.yaml* ./
|
||||
@@ -6,7 +7,7 @@ RUN pnpm install
|
||||
COPY . .
|
||||
RUN pnpm run build
|
||||
|
||||
FROM node:20-alpine
|
||||
FROM node:20-bookworm-slim
|
||||
RUN corepack enable && corepack prepare pnpm@latest --activate
|
||||
WORKDIR /app
|
||||
ENV NODE_ENV=production
|
||||
|
||||
@@ -18,7 +18,13 @@ export async function GET(
|
||||
}
|
||||
return Response.json(data);
|
||||
} catch (e) {
|
||||
return Response.json({ error: String(e) }, { status: 502 });
|
||||
return Response.json(
|
||||
{
|
||||
error: 'QR API unreachable',
|
||||
detail: e instanceof Error ? e.message : String(e),
|
||||
},
|
||||
{ status: 502 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +49,13 @@ export async function PUT(
|
||||
}
|
||||
return Response.json(data);
|
||||
} catch (e) {
|
||||
return Response.json({ error: String(e) }, { status: 502 });
|
||||
return Response.json(
|
||||
{
|
||||
error: 'QR API unreachable',
|
||||
detail: e instanceof Error ? e.message : String(e),
|
||||
},
|
||||
{ status: 502 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +77,12 @@ export async function DELETE(
|
||||
{ status: res.status },
|
||||
);
|
||||
} catch (e) {
|
||||
return Response.json({ error: String(e) }, { status: 502 });
|
||||
return Response.json(
|
||||
{
|
||||
error: 'QR API unreachable',
|
||||
detail: e instanceof Error ? e.message : String(e),
|
||||
},
|
||||
{ status: 502 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,13 @@ export async function GET() {
|
||||
}
|
||||
return Response.json(Array.isArray(data) ? data : data);
|
||||
} catch (e) {
|
||||
return Response.json({ error: String(e) }, { status: 502 });
|
||||
return Response.json(
|
||||
{
|
||||
error: 'QR API unreachable',
|
||||
detail: e instanceof Error ? e.message : String(e),
|
||||
},
|
||||
{ status: 502 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +39,12 @@ export async function POST(request: Request) {
|
||||
}
|
||||
return Response.json(data);
|
||||
} catch (e) {
|
||||
return Response.json({ error: String(e) }, { status: 502 });
|
||||
return Response.json(
|
||||
{
|
||||
error: 'QR API unreachable',
|
||||
detail: e instanceof Error ? e.message : String(e),
|
||||
},
|
||||
{ status: 502 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,13 @@ export async function GET(
|
||||
}
|
||||
return Response.json(data);
|
||||
} catch (e) {
|
||||
return Response.json({ error: String(e) }, { status: 502 });
|
||||
return Response.json(
|
||||
{
|
||||
error: 'QR API unreachable',
|
||||
detail: e instanceof Error ? e.message : String(e),
|
||||
},
|
||||
{ status: 502 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +61,13 @@ export async function PUT(
|
||||
}
|
||||
return Response.json(data);
|
||||
} catch (e) {
|
||||
return Response.json({ error: String(e) }, { status: 502 });
|
||||
return Response.json(
|
||||
{
|
||||
error: 'QR API unreachable',
|
||||
detail: e instanceof Error ? e.message : String(e),
|
||||
},
|
||||
{ status: 502 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,6 +89,12 @@ export async function DELETE(
|
||||
{ status: res.status },
|
||||
);
|
||||
} catch (e) {
|
||||
return Response.json({ error: String(e) }, { status: 502 });
|
||||
return Response.json(
|
||||
{
|
||||
error: 'QR API unreachable',
|
||||
detail: e instanceof Error ? e.message : String(e),
|
||||
},
|
||||
{ status: 502 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,13 @@ export async function GET() {
|
||||
}
|
||||
return Response.json(Array.isArray(data) ? rewriteLogoUrl(data) : data);
|
||||
} catch (e) {
|
||||
return Response.json({ error: String(e) }, { status: 502 });
|
||||
return Response.json(
|
||||
{
|
||||
error: 'QR API unreachable',
|
||||
detail: e instanceof Error ? e.message : String(e),
|
||||
},
|
||||
{ status: 502 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +48,12 @@ export async function POST(request: Request) {
|
||||
}
|
||||
return Response.json(data);
|
||||
} catch (e) {
|
||||
return Response.json({ error: String(e) }, { status: 502 });
|
||||
return Response.json(
|
||||
{
|
||||
error: 'QR API unreachable',
|
||||
detail: e instanceof Error ? e.message : String(e),
|
||||
},
|
||||
{ status: 502 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,12 @@ export async function POST(request: Request) {
|
||||
}
|
||||
return Response.json(data);
|
||||
} catch (e) {
|
||||
return Response.json({ error: String(e) }, { status: 502 });
|
||||
return Response.json(
|
||||
{
|
||||
error: 'QR API unreachable',
|
||||
detail: e instanceof Error ? e.message : String(e),
|
||||
},
|
||||
{ status: 502 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
# Place this file in the same directory as your other dynamic config (e.g. /etc/traefik/conf.d/)
|
||||
# so the file provider picks it up.
|
||||
#
|
||||
# Then uncomment the serversTransport label on the kutt service in docker-compose.portainer.yml.
|
||||
# In docker-compose.portainer.yml set the kutt service label to use this transport with @file:
|
||||
# traefik.http.services.kutt-short.loadbalancer.serversTransport=kutt-long-timeout@file
|
||||
#
|
||||
# responseHeaderTimeout: time to wait for backend response headers (0 = no timeout).
|
||||
# 300s helps avoid 504 Gateway Timeout when Kutt or OIDC is slow.
|
||||
|
||||
Reference in New Issue
Block a user