fix: Add support for external PostgreSQL and Redis instances

Implements smart detection that allows users to provide external DATABASE_URL
or REDIS_URL. When external services are detected, internal instances are
automatically disabled to save resources. Maintains full backward compatibility
with existing setup
This commit is contained in:
Joe Harrison
2026-02-12 15:04:09 +00:00
parent eca24e46a8
commit 7addb1dc70
6 changed files with 220 additions and 75 deletions
+18
View File
@@ -53,6 +53,24 @@ services:
# CONFIG_ENCRYPTION_KEY: "your-custom-encryption-key-here" # CONFIG_ENCRYPTION_KEY: "your-custom-encryption-key-here"
# POSTGRES_PASSWORD: "your-custom-postgres-password-here" # POSTGRES_PASSWORD: "your-custom-postgres-password-here"
# ========================================================================
# OPTIONAL: External PostgreSQL and Redis
# ========================================================================
# To use external PostgreSQL or Redis instances instead of the internal ones,
# uncomment and configure the appropriate URL(s):
#
# External PostgreSQL example:
# DATABASE_URL: "postgresql://username:password@postgres.example.com:5432/readmeabook"
#
# External Redis example:
# REDIS_URL: "redis://redis.example.com:6379"
# REDIS_URL: "redis://:password@redis.example.com:6379" # With password
#
# Note: When using external services:
# - The internal PostgreSQL/Redis will NOT start (smart detection)
# - You do NOT need to mount ./pgdata or ./redis volumes
# - Ensure your external services are accessible from the container
# ======================================================================== # ========================================================================
# OPTIONAL: Rootless Podman Support # OPTIONAL: Rootless Podman Support
# ======================================================================== # ========================================================================
+68 -10
View File
@@ -157,8 +157,33 @@ export PLEX_PRODUCT_NAME="${PLEX_PRODUCT_NAME:-ReadMeABook}"
export LOG_LEVEL="${LOG_LEVEL:-info}" export LOG_LEVEL="${LOG_LEVEL:-info}"
# ============================================================================ # ============================================================================
# INITIALIZE POSTGRESQL # DETECT EXTERNAL SERVICES
# ============================================================================ # ============================================================================
# Check if user provided external DATABASE_URL or REDIS_URL
USE_EXTERNAL_POSTGRES=false
USE_EXTERNAL_REDIS=false
if [ -n "$DATABASE_URL" ]; then
DB_HOST=$(echo "$DATABASE_URL" | sed -n 's|.*@\([^:/]*\).*|\1|p')
if [ "$DB_HOST" != "127.0.0.1" ] && [ "$DB_HOST" != "localhost" ]; then
USE_EXTERNAL_POSTGRES=true
echo "️ External PostgreSQL detected at $DB_HOST"
fi
fi
if [ -n "$REDIS_URL" ]; then
REDIS_HOST=$(echo "$REDIS_URL" | sed -n 's|redis://\([^:@]*@\)\?\([^:/]*\).*|\2|p')
if [ "$REDIS_HOST" != "127.0.0.1" ] && [ "$REDIS_HOST" != "localhost" ]; then
USE_EXTERNAL_REDIS=true
echo "️ External Redis detected at $REDIS_HOST"
fi
fi
# ============================================================================
# INITIALIZE POSTGRESQL (only if using internal PostgreSQL)
# ============================================================================
if [ "$USE_EXTERNAL_POSTGRES" = "false" ]; then
echo "📦 Configuring internal PostgreSQL..."
PGDATA="/var/lib/postgresql/data" PGDATA="/var/lib/postgresql/data"
PG_WAS_EMPTY=0 PG_WAS_EMPTY=0
@@ -208,8 +233,12 @@ else
chmod 700 "$PGDATA" chmod 700 "$PGDATA"
chmod 775 /var/run/postgresql chmod 775 /var/run/postgresql
fi fi
else
echo "⏭️ Skipping internal PostgreSQL setup (using external database)"
fi
# Redis directory - owned by redis user (remapped to PUID:PGID if set) # Redis directory - owned by redis user (remapped to PUID:PGID if set)
if [ "$USE_EXTERNAL_REDIS" = "false" ]; then
if ! chown -R redis:redis /var/lib/redis 2>/dev/null; then if ! chown -R redis:redis /var/lib/redis 2>/dev/null; then
echo "" echo ""
echo "❌ ERROR: Failed to set ownership on Redis directory" echo "❌ ERROR: Failed to set ownership on Redis directory"
@@ -218,6 +247,9 @@ if ! chown -R redis:redis /var/lib/redis 2>/dev/null; then
exit 1 exit 1
fi fi
chmod 770 /var/lib/redis chmod 770 /var/lib/redis
else
echo "⏭️ Skipping internal Redis setup (using external Redis)"
fi
# App directories - owned by node user (remapped to PUID:PGID if set) # App directories - owned by node user (remapped to PUID:PGID if set)
# These need group write permissions for shared access # These need group write permissions for shared access
@@ -232,6 +264,8 @@ chmod 775 /app/config /app/cache
echo "✅ Directory permissions configured" echo "✅ Directory permissions configured"
if [ "$USE_EXTERNAL_POSTGRES" = "false" ]; then
# Only initialize/setup PostgreSQL if using internal instance
if [ ! -f "$PGDATA/PG_VERSION" ]; then if [ ! -f "$PGDATA/PG_VERSION" ]; then
PG_WAS_EMPTY=1 PG_WAS_EMPTY=1
echo "📦 Initializing PostgreSQL database..." echo "📦 Initializing PostgreSQL database..."
@@ -259,9 +293,9 @@ else
echo "✅ PostgreSQL data directory already exists" echo "✅ PostgreSQL data directory already exists"
fi fi
# ============================================================================ # ========================================================================
# START POSTGRESQL TEMPORARILY TO CREATE USER/DATABASE # START POSTGRESQL TEMPORARILY TO CREATE USER/DATABASE
# ============================================================================ # ========================================================================
echo "🔧 Starting PostgreSQL for setup..." echo "🔧 Starting PostgreSQL for setup..."
su - postgres -c "/usr/lib/postgresql/16/bin/pg_ctl -D $PGDATA -w start -o '-c listen_addresses=127.0.0.1'" su - postgres -c "/usr/lib/postgresql/16/bin/pg_ctl -D $PGDATA -w start -o '-c listen_addresses=127.0.0.1'"
@@ -302,13 +336,33 @@ else
echo "✅ Database user and permissions verified" echo "✅ Database user and permissions verified"
fi fi
# Stop PostgreSQL (supervisord will start it via wrapper)
echo "🔧 Stopping temporary PostgreSQL instance..."
su - postgres -c "/usr/lib/postgresql/16/bin/pg_ctl -D $PGDATA stop -m fast"
fi
# ============================================================================ # ============================================================================
# SET ENVIRONMENT VARIABLES FOR APP # SET ENVIRONMENT VARIABLES FOR APP
# ============================================================================ # ============================================================================
# Set DATABASE_URL and REDIS_URL based on whether we're using internal or external services
if [ "$USE_EXTERNAL_POSTGRES" = "false" ]; then
# URL-encode the password to handle special characters # URL-encode the password to handle special characters
ENCODED_PASSWORD=$(urlencode "$POSTGRES_PASSWORD") ENCODED_PASSWORD=$(urlencode "$POSTGRES_PASSWORD")
export DATABASE_URL="postgresql://$POSTGRES_USER:$ENCODED_PASSWORD@127.0.0.1:5432/$POSTGRES_DB" export DATABASE_URL="postgresql://$POSTGRES_USER:$ENCODED_PASSWORD@127.0.0.1:5432/$POSTGRES_DB"
echo "✅ Using internal PostgreSQL (127.0.0.1:5432)"
else
# DATABASE_URL already set by user - do not modify
echo "✅ Using external DATABASE_URL: ${DATABASE_URL%%@*}@***"
fi
if [ "$USE_EXTERNAL_REDIS" = "false" ]; then
export REDIS_URL="redis://127.0.0.1:6379" export REDIS_URL="redis://127.0.0.1:6379"
echo "✅ Using internal Redis (127.0.0.1:6379)"
else
# REDIS_URL already set by user - do not modify
echo "✅ Using external REDIS_URL: ${REDIS_URL}"
fi
export NODE_ENV="production" export NODE_ENV="production"
export PORT="3030" export PORT="3030"
export HOSTNAME="0.0.0.0" export HOSTNAME="0.0.0.0"
@@ -335,16 +389,12 @@ EOF
echo "✅ Environment configured" echo "✅ Environment configured"
# ============================================================================ # ============================================================================
# RUN PRISMA MIGRATIONS (while PostgreSQL is still running) # RUN PRISMA MIGRATIONS
# ============================================================================ # ============================================================================
echo "🔄 Running Prisma migrations..." echo "🔄 Running Prisma migrations..."
cd /app cd /app
su - node -c "cd /app && DATABASE_URL='$DATABASE_URL' npx prisma db push --skip-generate --accept-data-loss" || echo "⚠️ Migrations may have failed, continuing..." su - node -c "cd /app && DATABASE_URL='$DATABASE_URL' npx prisma db push --skip-generate --accept-data-loss" || echo "⚠️ Migrations may have failed, continuing..."
# Stop PostgreSQL (supervisord will start it)
echo "🔧 Stopping temporary PostgreSQL instance..."
su - postgres -c "/usr/lib/postgresql/16/bin/pg_ctl -D $PGDATA stop -m fast"
# ============================================================================ # ============================================================================
# DISPLAY STARTUP INFO # DISPLAY STARTUP INFO
# ============================================================================ # ============================================================================
@@ -361,8 +411,16 @@ if [ "$POSTGRES_PASSWORD" = "$(generate_secret)" ]; then
fi fi
echo "" echo ""
echo "📊 Services starting:" echo "📊 Services starting:"
echo " - PostgreSQL (internal, user=postgres)" if [ "$USE_EXTERNAL_POSTGRES" = "false" ]; then
echo " - Redis (internal, UID:GID=${PUID:-102}:${PGID:-102})" echo " - PostgreSQL (internal, 127.0.0.1:5432)"
else
echo " - PostgreSQL (external - local instance disabled)"
fi
if [ "$USE_EXTERNAL_REDIS" = "false" ]; then
echo " - Redis (internal, 127.0.0.1:6379, UID:GID=${PUID:-102}:${PGID:-102})"
else
echo " - Redis (external - local instance disabled)"
fi
echo " - Next.js App (port 3030, UID:GID=${PUID:-1000}:${PGID:-1000})" echo " - Next.js App (port 3030, UID:GID=${PUID:-1000}:${PGID:-1000})"
if [ "${ROOTLESS_CONTAINER}" = "true" ]; then if [ "${ROOTLESS_CONTAINER}" = "true" ]; then
echo "" echo ""
+39
View File
@@ -0,0 +1,39 @@
#!/bin/bash
# PostgreSQL startup wrapper for unified container
# Smart supervisor: detects external PostgreSQL and sleeps instead of starting local instance
#
# Behavior:
# - If DATABASE_URL points to external host (not 127.0.0.1/localhost), sleep infinity
# - Otherwise, start local PostgreSQL instance
set -e
# Load environment from /etc/environment (set by entrypoint)
if [ -f /etc/environment ]; then
set -a
source /etc/environment
set +a
fi
echo "[PostgreSQL] Checking for external database configuration..."
# Extract host from DATABASE_URL
# Format: postgresql://user:pass@host:port/db
if [ -n "$DATABASE_URL" ]; then
# Extract the host part (between @ and :port or /)
DB_HOST=$(echo "$DATABASE_URL" | sed -n 's|.*@\([^:/]*\).*|\1|p')
echo "[PostgreSQL] Detected DATABASE_URL host: $DB_HOST"
# Check if host is external (not localhost or 127.0.0.1)
if [ "$DB_HOST" != "127.0.0.1" ] && [ "$DB_HOST" != "localhost" ]; then
echo "[PostgreSQL] ✅ External PostgreSQL detected at $DB_HOST"
echo "[PostgreSQL] Skipping local PostgreSQL startup - sleeping to keep supervisord happy"
exec sleep infinity
fi
fi
echo "[PostgreSQL] Starting local PostgreSQL server..."
# Start PostgreSQL as postgres user
exec /usr/lib/postgresql/16/bin/postgres -D /var/lib/postgresql/data
+26 -1
View File
@@ -1,5 +1,11 @@
#!/bin/bash #!/bin/bash
# Redis startup wrapper for unified container # Redis startup wrapper for unified container
# Smart supervisor: detects external Redis and sleeps instead of starting local instance
#
# Behavior:
# - If REDIS_URL points to external host (not 127.0.0.1/localhost), sleep infinity
# - Otherwise, start local Redis instance
#
# Uses gosu to ensure correct PUID:PGID for file operations # Uses gosu to ensure correct PUID:PGID for file operations
# #
# Supports: # Supports:
@@ -15,11 +21,30 @@ if [ -f /etc/environment ]; then
set +a set +a
fi fi
echo "[Redis] Checking for external Redis configuration..."
# Extract host from REDIS_URL
# Format: redis://host:port or redis://:password@host:port
if [ -n "$REDIS_URL" ]; then
# Extract the host part (between :// or @, and :port or end)
REDIS_HOST=$(echo "$REDIS_URL" | sed -n 's|redis://\([^:@]*@\)\?\([^:/]*\).*|\2|p')
echo "[Redis] Detected REDIS_URL host: $REDIS_HOST"
# Check if host is external (not localhost or 127.0.0.1)
if [ "$REDIS_HOST" != "127.0.0.1" ] && [ "$REDIS_HOST" != "localhost" ]; then
echo "[Redis] ✅ External Redis detected at $REDIS_HOST"
echo "[Redis] Skipping local Redis startup - sleeping to keep supervisord happy"
exec sleep infinity
fi
fi
echo "[Redis] Starting local Redis server..."
# Get PUID/PGID (default to redis user's current IDs if not set) # Get PUID/PGID (default to redis user's current IDs if not set)
PUID=${PUID:-$(id -u redis)} PUID=${PUID:-$(id -u redis)}
PGID=${PGID:-$(id -g redis)} PGID=${PGID:-$(id -g redis)}
echo "[Redis] Starting Redis server..."
echo "[Redis] Process will run as UID:GID = $PUID:$PGID" echo "[Redis] Process will run as UID:GID = $PUID:$PGID"
# ============================================================================= # =============================================================================
+1 -1
View File
@@ -7,7 +7,7 @@ loglevel=info
pidfile=/var/run/supervisord.pid pidfile=/var/run/supervisord.pid
[program:postgresql] [program:postgresql]
command=/usr/lib/postgresql/16/bin/postgres -D /var/lib/postgresql/data command=/app/postgres-start.sh
user=postgres user=postgres
autostart=true autostart=true
autorestart=true autorestart=true
+5
View File
@@ -115,6 +115,11 @@ COPY --chown=root:root docker/unified/redis-start.sh /app/redis-start.sh
# Convert line endings and make executable # Convert line endings and make executable
RUN sed -i 's/\r$//' /app/redis-start.sh && chmod +x /app/redis-start.sh RUN sed -i 's/\r$//' /app/redis-start.sh && chmod +x /app/redis-start.sh
# Copy postgres startup wrapper
COPY --chown=root:root docker/unified/postgres-start.sh /app/postgres-start.sh
# Convert line endings and make executable
RUN sed -i 's/\r$//' /app/postgres-start.sh && chmod +x /app/postgres-start.sh
# Expose app port # Expose app port
EXPOSE 3030 EXPOSE 3030