diff --git a/Dockerfile b/Dockerfile index 5d7bb67700..17587325fa 100644 --- a/Dockerfile +++ b/Dockerfile @@ -65,4 +65,23 @@ RUN curl -sSL https://railpack.com/install.sh | bash COPY --from=buildpacksio/pack:0.39.1 /usr/local/bin/pack /usr/local/bin/pack EXPOSE 3000 + +# Install redis-tools: the Debian package that provides redis-cli. +# This is a small, targeted addition (much smaller than adding full DB client stacks) +RUN apt-get update && apt-get install -y --no-install-recommends redis-tools + +# Add a docker-entrypoint to manage startup dependencies +COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh +RUN chmod +x /usr/local/bin/docker-entrypoint.sh + +ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"] + +# Add Healthcheck to allow for service and container restart by orchestrator restart policies +HEALTHCHECK \ + --interval=10s \ + --timeout=3s \ + --start-period=30s \ + --retries=5 \ + CMD curl -fsS http://127.0.0.1:3000/api/health >/dev/null || exit 1 + CMD [ "pnpm", "start" ] diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh new file mode 100644 index 0000000000..d33eca808a --- /dev/null +++ b/docker-entrypoint.sh @@ -0,0 +1,127 @@ +#!/bin/sh +set -eu + +# Enable/disable waiting logic +: "${DOKPLOY_WAIT_FOR_DEPS:=1}" + +# Total wait budget + retry pacing +: "${DOKPLOY_WAIT_TIMEOUT_SECONDS:=600}" # 10 min default +: "${DOKPLOY_WAIT_INTERVAL_SECONDS:=2}" + +# Optional override (otherwise derived from DATABASE_URL and REDIS_HOST) +: "${POSTGRES_URL:=}" # if set, takes precedence over DATABASE_URL +: "${REDIS_URL:=}" # optional; otherwise uses REDIS_HOST/REDIS_PORT +: "${REDIS_PORT:=6379}" + +log() { echo "[entrypoint] $*"; } + +require_env() { + name="$1" + eval "val=\${$name:-}" + if [ -z "$val" ]; then + log "Missing required environment variable: $name" + exit 1 + fi +} + +# Uses Node + runtime deps in node_modules to test REAL connectivity: +# - Postgres: require('postgres') and connect using DATABASE_URL/POSTGRES_URL +wait_for_postgres() { + require_env "DATABASE_URL" + + node <<'NODE' +const postgres = require('postgres'); + +const url = process.env.POSTGRES_URL || process.env.DATABASE_URL; +const timeoutSeconds = Number(process.env.DOKPLOY_WAIT_TIMEOUT_SECONDS || 600); +const intervalSeconds = Number(process.env.DOKPLOY_WAIT_INTERVAL_SECONDS || 2); + +function sleep(ms) { return new Promise(r => setTimeout(r, ms)); } + +function redact(u) { + try { + const x = new URL(u); + if (x.password) x.password = '***'; + if (x.username) x.username = '***'; + return x.toString(); + } catch { + return '(invalid url)'; + } +} + +(async () => { + const start = Date.now(); + let attempt = 0; + + while (true) { + attempt += 1; + const sql = postgres(url, { + // keep timeouts tight so retries are responsive + connect_timeout: 5, // seconds + idle_timeout: 5, // seconds + max_lifetime: 10, // seconds + max: 1 + }); + + try { + await sql`select 1`; + await sql.end({ timeout: 5 }); + console.log(`[entrypoint] Postgres ready: ${redact(url)}`); + process.exit(0); + } catch (e) { + try { await sql.end({ timeout: 5 }); } catch {} + const elapsed = Math.floor((Date.now() - start) / 1000); + const remaining = timeoutSeconds - elapsed; + const msg = (e && e.code) ? `${e.code}` : (e && e.message ? e.message : 'unknown error'); + console.log(`[entrypoint] Waiting for Postgres... attempt=${attempt} elapsed=${elapsed}s remaining=${remaining}s err=${msg}`); + if (remaining <= 0) { + console.error(`[entrypoint] Timeout waiting for Postgres after ${elapsed}s: ${redact(url)}`); + process.exit(1); + } + await sleep(intervalSeconds * 1000); + } + } +})(); +NODE +} + +## Using redis-cli as in the dokploy build, there is no reliable way to use ioredis/redis node libs. +wait_for_redis() { + : "${REDIS_HOST:?REDIS_HOST is required}" + : "${REDIS_PORT:=6379}" + + start_ts="$(date +%s)" + attempt=0 + + while true; do + attempt=$((attempt + 1)) + if redis-cli -h "${REDIS_HOST}" -p "${REDIS_PORT}" ping 2>/dev/null | grep -q PONG; then + echo "[entrypoint] Redis ready: ${REDIS_HOST}:${REDIS_PORT}" + return 0 + fi + + now="$(date +%s)" + elapsed="$((now - start_ts))" + remaining="$((DOKPLOY_WAIT_TIMEOUT_SECONDS - elapsed))" + + echo "[entrypoint] Waiting for Redis... attempt=${attempt} elapsed=${elapsed}s remaining=${remaining}s" + if [ "$remaining" -le 0 ]; then + echo "[entrypoint] Timeout waiting for Redis after ${elapsed}s" + return 1 + fi + + sleep "${DOKPLOY_WAIT_INTERVAL_SECONDS}" + done +} + + +if [ "$DOKPLOY_WAIT_FOR_DEPS" = "1" ]; then + log "Dependency wait enabled. timeout=${DOKPLOY_WAIT_TIMEOUT_SECONDS}s interval=${DOKPLOY_WAIT_INTERVAL_SECONDS}s" + wait_for_postgres + wait_for_redis +else + log "Dependency wait disabled (DOKPLOY_WAIT_FOR_DEPS=0)." +fi + +log "Starting: $*" +exec "$@"