Skip to content

feat: add support for external PostgreSQL with pgbouncer (Supabase, Neon, managed databases) #139

@organicnz

Description

@organicnz

Problem

When using Remnawave with an external managed database through pgbouncer (Supabase, Neon, etc.), prisma migrate deploy hangs indefinitely during container startup.

This is a known Prisma issue — advisory locks used by prisma migrate are incompatible with pgbouncer's transaction pooling mode:

There is currently no documentation or built-in support for running Remnawave against an external database with connection pooling, which is the standard setup for Supabase, Neon, and most managed PostgreSQL providers.

Proposed Fix: docker-entrypoint.sh

Add an optional MIGRATION_TIMEOUT environment variable that wraps the migration command with a timeout. Without it set, behavior is identical to current — fully backward-compatible.

#!/bin/sh

echo "Starting entrypoint script..."

echo "Migrating database..."

if [ -n "$MIGRATION_TIMEOUT" ]; then
    echo "MIGRATION_TIMEOUT=${MIGRATION_TIMEOUT}s — running migration with timeout..."
    if ! timeout "${MIGRATION_TIMEOUT}" npm run migrate:deploy; then
        EXIT_CODE=$?
        if [ "$EXIT_CODE" -eq 143 ]; then
            echo "⚠️  Migration timed out after ${MIGRATION_TIMEOUT}s (advisory lock issue with pgbouncer)."
            echo "If schema is already up-to-date this is safe to ignore. Otherwise run migrations via a direct connection."
        else
            echo "Database migration failed! Exiting container..."
            exit 1
        fi
    fi
else
    if ! npm run migrate:deploy; then
        echo "Database migration failed! Exiting container..."
        exit 1
    fi
fi

echo "Migrations deployed successfully!"

echo "Seeding database..."
if ! npm run migrate:seed; then
    echo "Database seeding failed! Exiting container..."
    exit 1
fi

echo "Entrypoint script completed."
exec "$@"

Key behavior:

MIGRATION_TIMEOUT Behavior
Not set Identical to current — waits indefinitely for migration
Set (e.g. 30) Kills hung migration after N seconds. Exit code 143 (timeout) is treated as non-fatal so the container still starts

The timeout approach works because in practice, if the schema is already current, the migration command hangs on the advisory lock rather than failing — so timing out and proceeding is safe for day-to-day container restarts. For actual schema changes, users run migrations separately via a direct (non-pooled) connection.

Configuration Guide for External Database with pgbouncer

Required DATABASE_URL parameters

DATABASE_URL=postgresql://user:password@host:6543/postgres?pgbouncer=true&connection_limit=13&pool_timeout=10&statement_cache_size=0
Parameter Why
pgbouncer=true Tells Prisma to use pgbouncer-compatible mode
connection_limit=13 Avoids exhausting pooler connections (adjust to your plan)
pool_timeout=10 Prevents indefinite waits for a connection
statement_cache_size=0 Required for pgbouncer — prepared statements don't work across pooled connections

.env additions

# Timeout for prisma migrate deploy (seconds). Set when using pgbouncer.
# Recommended: 30
MIGRATION_TIMEOUT=30

docker-compose adjustments for external DB

When using an external database, users should:

  1. Remove or comment out the remnawave-db service
  2. Remove depends_on: remnawave-db from the remnawave backend service
  3. Increase the health check start_period to 120s (first migration through a remote pooler takes longer)
  4. Add MIGRATION_TIMEOUT to the environment

Suggested .env.sample additions

### External database (Supabase / Neon / managed PostgreSQL) ###
# If using pgbouncer, append: ?pgbouncer=true&connection_limit=13&pool_timeout=10&statement_cache_size=0
# DATABASE_URL=postgresql://user:password@pooler-host:6543/postgres?pgbouncer=true&connection_limit=13&pool_timeout=10&statement_cache_size=0

# Timeout for migration through pgbouncer (seconds). Unset = no timeout.
# MIGRATION_TIMEOUT=30

Testing

  • Production-tested with Supabase pgbouncer (transaction pooling, port 6543) — running stable
  • Local PostgreSQL without MIGRATION_TIMEOUT set: no behavior change, identical to current entrypoint
  • Timeout value of 30 works reliably — migrations either complete in <5s or hang indefinitely (advisory lock), no false positives observed

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions