fixed coolify health checks

This commit is contained in:
2026-04-21 12:22:49 +02:00
parent 6347b53863
commit 8f2455026d
8 changed files with 52 additions and 6 deletions

View File

@@ -325,7 +325,7 @@ docker compose restart
# health-check.sh erstellen
cat > health-check.sh << 'EOF'
#!/bin/bash
if curl -s -o /dev/null -w "%{http_code}" http://localhost:8000/login | grep -q "200"; then
if curl -s -o /dev/null -w "%{http_code}" http://localhost:8001/health | grep -q "200"; then
echo "$(date): OK"
else
echo "$(date): FEHLER - Neustart..."

View File

@@ -41,7 +41,7 @@ EXPOSE 8001
# Health check (uses PORT env var with 8001 fallback)
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD python -c "import os, httpx; port = os.getenv('PORT', '8001'); httpx.get(f'http://localhost:{port}/login', timeout=5)" || exit 1
CMD python -c "import os, httpx; port = os.getenv('PORT', '8001'); r = httpx.get(f'http://127.0.0.1:{port}/health', timeout=5); raise SystemExit(0 if r.status_code == 200 else 1)" || exit 1
# Run the application (uses PORT env var with 8001 fallback)
CMD sh -c "python -m uvicorn src.web.app:app --host 0.0.0.0 --port ${PORT:-8001}"

View File

@@ -27,6 +27,7 @@ services:
interval: 10s
timeout: 5s
retries: 3
start_period: 5s
linkedin-scheduler:
build: .
@@ -34,10 +35,17 @@ services:
command: python -m src.services.scheduler_runner
labels:
- traefik.enable=false
healthcheck:
test: ["CMD", "python", "-c", "import os, time; p=os.getenv('SCHEDULER_HEALTH_FILE','/tmp/scheduler_heartbeat'); raise SystemExit(0 if os.path.exists(p) and time.time() - os.path.getmtime(p) < 120 else 1)"]
interval: 30s
timeout: 5s
retries: 3
start_period: 20s
environment:
- PYTHONPATH=/app
- SCHEDULER_ENABLED=true
- REDIS_URL=redis://redis:6379/0
- SCHEDULER_HEALTH_FILE=/tmp/scheduler_heartbeat
volumes:
- logs:/app/logs
depends_on:
@@ -61,7 +69,7 @@ services:
redis:
condition: service_healthy
healthcheck:
test: ["CMD", "python", "-c", "import httpx; httpx.get('http://localhost:8001/login', timeout=5)"]
test: ["CMD", "python", "-c", "import httpx; r = httpx.get('http://127.0.0.1:8001/health', timeout=5); raise SystemExit(0 if r.status_code == 200 else 1)"]
interval: 30s
timeout: 10s
retries: 3

View File

@@ -11,17 +11,25 @@ services:
interval: 10s
timeout: 5s
retries: 3
start_period: 5s
linkedin-scheduler:
build: .
container_name: linkedin-scheduler
restart: unless-stopped
command: sh -c "python -m src.services.scheduler_runner"
healthcheck:
test: ["CMD", "python", "-c", "import os, time; p=os.getenv('SCHEDULER_HEALTH_FILE','/tmp/scheduler_heartbeat'); raise SystemExit(0 if os.path.exists(p) and time.time() - os.path.getmtime(p) < 120 else 1)"]
interval: 30s
timeout: 5s
retries: 3
start_period: 20s
env_file: .env
environment:
- PYTHONPATH=/app
- SCHEDULER_ENABLED=true
- REDIS_URL=redis://redis:6379/0
- SCHEDULER_HEALTH_FILE=/tmp/scheduler_heartbeat
volumes:
- ./logs:/app/logs
depends_on:
@@ -47,7 +55,7 @@ services:
redis:
condition: service_healthy
healthcheck:
test: ["CMD", "python", "-c", "import httpx; httpx.get('http://localhost:8001/login', timeout=5)"]
test: ["CMD", "python", "-c", "import httpx; r = httpx.get('http://127.0.0.1:8001/health', timeout=5); raise SystemExit(0 if r.status_code == 200 else 1)"]
interval: 30s
timeout: 10s
retries: 3

View File

@@ -18,7 +18,7 @@ services:
volumes:
- ./logs:/app/logs
healthcheck:
test: ["CMD", "python", "-c", "import httpx; httpx.get('http://localhost:8001/login', timeout=5)"]
test: ["CMD", "python", "-c", "import httpx; r = httpx.get('http://127.0.0.1:8001/health', timeout=5); raise SystemExit(0 if r.status_code == 200 else 1)"]
interval: 30s
timeout: 10s
retries: 3

View File

@@ -16,7 +16,7 @@ services:
# Optional: Mount logs directory
- ./logs:/app/logs
healthcheck:
test: ["CMD", "python", "-c", "import httpx; httpx.get('http://localhost:8001/login', timeout=5)"]
test: ["CMD", "python", "-c", "import httpx; r = httpx.get('http://127.0.0.1:8001/health', timeout=5); raise SystemExit(0 if r.status_code == 200 else 1)"]
interval: 30s
timeout: 10s
retries: 3

View File

@@ -8,16 +8,29 @@ SchedulerService so it can run in its own container without duplicating work in
the main web-worker containers (which set SCHEDULER_ENABLED=false).
"""
import asyncio
import os
import signal
from pathlib import Path
from loguru import logger
from src.database.client import DatabaseClient
from src.services.scheduler_service import init_scheduler
async def heartbeat_loop(path: Path, interval_seconds: int = 30):
"""Write a heartbeat file so Docker can healthcheck the scheduler process."""
while True:
try:
path.write_text(str(asyncio.get_running_loop().time()), encoding="utf-8")
except Exception as exc:
logger.warning(f"Failed to write scheduler heartbeat: {exc}")
await asyncio.sleep(interval_seconds)
async def main():
db = DatabaseClient()
scheduler = init_scheduler(db, check_interval=60)
health_file = Path(os.getenv("SCHEDULER_HEALTH_FILE", "/tmp/scheduler_heartbeat"))
stop_event = asyncio.Event()
@@ -30,10 +43,16 @@ async def main():
loop.add_signal_handler(sig, handle_signal)
await scheduler.start()
heartbeat_task = asyncio.create_task(heartbeat_loop(health_file))
logger.info("Scheduler started (dedicated process)")
await stop_event.wait()
heartbeat_task.cancel()
try:
await heartbeat_task
except asyncio.CancelledError:
pass
await scheduler.stop()
logger.info("Scheduler stopped")

View File

@@ -116,6 +116,17 @@ app.add_middleware(GZipMiddleware, minimum_size=500)
app.mount("/static", StaticFiles(directory=Path(__file__).parent / "static"), name="static")
@app.api_route("/health", methods=["GET", "HEAD"], include_in_schema=False)
async def health():
"""Container health endpoint for Docker/Coolify.
Keep this endpoint intentionally shallow: it verifies the ASGI app is
serving requests without depending on auth, templates, Redis, Supabase or
external APIs.
"""
return {"status": "ok"}
def _load_env_file(path: Path) -> dict:
"""Load key=value pairs from a simple .env file."""
data = {}