Fix 'Database Connection Refused' in Supabase Local Dev (2025 Guide)
Supabase connection refused? This comprehensive guide covers the Docker dependency trap, systematic troubleshooting for containerized databases, and when to escalate beyond DIY debugging.
FlowQL Team
AI Search Optimization Experts
You ran supabase start, watched Docker pull seven different containers, saw "Started supabase local development setup" in your terminal... and then your app throws "Connection refused." The Supabase CLI said everything's fine, but your Next.js app can't connect to the database. No helpful error message, no obvious fix, just a vague network failure.
We've all been there. Supabase promises to handle 80% of the backend infrastructure work, letting you focus on building features. But when that Docker dependency trap springs - when the containerized stack (PostgreSQL, PostgREST, GoTrue, Kong) creates multiple failure points - you're stuck in the frustrating 20% where AI tools can't debug Docker networking and your app is dead in the water.
This guide walks through systematic troubleshooting for Supabase connection refused errors, explains the root causes behind Docker container failures, and shows you exactly when it's time to escalate beyond DIY debugging. Whether you're dealing with port conflicts, environment variable mismatches, or Docker networking on WSL2, we'll cover the diagnostic workflow that actually works.
Here's what we'll cover: understanding the Supabase local stack architecture, diagnosing which of the seven services failed, fixing Docker and container issues, correcting connection strings and environment variables, resolving port conflicts, and advanced troubleshooting when standard fixes don't work. By the end, you'll know not just how to fix connection refused, but why it happened in the first place.
Understanding the Problem: The Docker Dependency Trap
Before diving into fixes, let's get clear on what "connection refused" actually means in a Supabase context. This isn't just semantic hair-splitting. Understanding which layer failed helps you choose the right solution faster.
What 'Connection Refused' Actually Means in Supabase Context
"Connection refused" is a network-level error that means your application tried to connect to a TCP port, but nothing was listening on that port. In the Supabase local development context, this typically means one of three things: the Docker container didn't start, it started but crashed immediately, or it's running but not listening on the expected port.
The error surfaces differently depending on where you're connecting from. Your Next.js app might show "ECONNREFUSED 127.0.0.1:54322" when trying to access the database. The Supabase client library might report "Failed to fetch" or "Network error" when hitting the API endpoint. If you're testing with psql, you'll see "could not connect to server: Connection refused."
The frustrating part? supabase start often completes successfully even when containers aren't actually healthy. The CLI starts the Docker Compose stack, waits a few seconds for containers to initialize, then reports success. It doesn't deeply validate that PostgreSQL is accepting connections or that Kong's API gateway is routing requests correctly.
The Supabase Local Stack: 7 Services That Must Work Together
Supabase local development isn't a single database - it's a complete backend-as-a-service running on your machine via Docker Compose. Understanding this architecture is crucial because connection failures can occur at any layer.
The stack consists of seven core services: PostgreSQL (the actual database on port 54322), PostgREST (auto-generates REST API from your schema), GoTrue (authentication service), Kong (API gateway that routes requests on port 54321), Realtime (WebSocket server for subscriptions), Storage (file upload handling), and Inbucket (local email testing on port 54324). Plus you get Studio (the admin UI on port 54323).
When you run supabase start, Docker Compose orchestrates all seven containers, sets up networking between them, and exposes specific ports to your host machine. Your application never talks to PostgreSQL directly. It connects to Kong on port 54321, which routes API requests to PostgREST, which queries PostgreSQL. Authentication flows through GoTrue first.
This is what I call the Docker dependency trap. Each service depends on others being healthy. If PostgreSQL fails to initialize, PostgREST can't generate its API routes. If Kong misconfigures, your app can't reach any service even though they're all running. You have multiple failure points, and the error messages often don't tell you which layer broke.
Visual Architecture: Where Connection Failures Occur
Connection failures typically happen at three points in the architecture. First, between your app and Kong (the API gateway). If Kong isn't running or isn't bound to port 54321, you'll get connection refused when your app tries to hit http://localhost:54321.
Second, between Kong and the internal services (PostgREST, GoTrue, Storage). These communicate over Docker's internal network. If Docker's network bridge is broken, Kong can't route requests even though it's running. You'll see successful connection to Kong followed by 502 Bad Gateway errors.
Third, between PostgREST and PostgreSQL. PostgREST connects to the database using Docker's internal networking (usually postgresql://postgres:postgres@db:5432/postgres inside the container network). If PostgreSQL didn't fully initialize, PostgREST throws errors and Kong returns 503 Service Unavailable.
The Supabase local development documentation shows the intended architecture, but doesn't explain these failure modes in detail. That's where systematic troubleshooting comes in.
Root Causes: Why Supabase Local Connection Fails
Connection failures in Supabase local dev typically fall into six categories. Each has distinct symptoms and requires different fixes. Understanding the root cause prevents trial-and-error troubleshooting.
Docker Not Running or Insufficient Resources
The most common culprit: Docker Desktop isn't running at all. You installed it months ago, haven't opened it since, and forgot that Supabase relies on it. When you run supabase start, the CLI can't communicate with the Docker daemon and either hangs indefinitely or reports cryptic errors.
Even if Docker is running, insufficient resource allocation causes silent failures. Docker Desktop defaults to 2GB of RAM and 2 CPU cores on many systems. Supabase's seven-container stack needs at least 4GB of RAM to run reliably. With less, PostgreSQL might fail to initialize fully, containers restart in loops, or the stack starts but performs so poorly it times out on connections.
You'll know it's a Docker resource issue if containers show "Restarting" status or if supabase start takes longer than 2 minutes. Check Docker Desktop's Resources settings and allocate at least 4GB RAM, 2 CPUs, and 20GB disk space.
Port Conflicts (54322, 54323, 54321 Already in Use)
Supabase claims specific ports on your local machine: 54321 (API/Kong), 54322 (PostgreSQL), 54323 (Studio), 54324 (Inbucket). If another application is using any of these ports, Docker can't bind to them, and the affected container either fails to start or starts but isn't accessible.
Port conflicts manifest as "address already in use" errors in Docker logs, or containers showing as running but inaccessible. The most common conflict: you're running multiple Supabase projects simultaneously, or you have PostgreSQL installed globally and it's using port 5432 (which conflicts if Supabase tries to map there).
On macOS, AirPlay Receiver uses port 5000 by default and sometimes conflicts with Supabase edge functions. On Windows, Hyper-V's networking can reserve port ranges that collide with Supabase. The port forwarding documentation explains how ports work in containerized environments.
Incorrect Connection String or Environment Variables
Your .env file has the wrong Supabase URL, or you're mixing production and local development credentials. This is especially common in Next.js projects where you need separate NEXT_PUBLIC_SUPABASE_URL for client-side code and DATABASE_URL for server-side database access.
The correct local development URL is http://localhost:54321 for the API endpoint, not https://your-project.supabase.co. Using the production URL in your local .env means your app tries to connect to the cloud database instead of your local Docker stack. It works, but you're not testing locally.
Another common mistake: using 127.0.0.1 instead of localhost, or vice versa. On some systems, especially Windows WSL2, these resolve differently. Docker binds to one but not the other. If your app uses 127.0.0.1:54321 but Docker bound to localhost, you get connection refused.
The Next.js environment variables docs explain the NEXT_PUBLIC prefix requirement for client-side access. Missing this prefix means your frontend can't see the Supabase URL at all.
Supabase CLI Version Mismatch or Outdated Config
You installed Supabase CLI six months ago and haven't updated it. The local development Docker images have changed, but your CLI is trying to start outdated containers. Or your config.toml has settings from an old Supabase CLI version that are no longer compatible.
Breaking changes happen. Supabase CLI v1.50+ changed how environment variables are loaded. Older projects might have .env files that the new CLI ignores in favor of config.toml settings. If you're not aware of this change, your connection strings get loaded incorrectly.
Check your CLI version with supabase --version. Compare it to the supabase start command reference for the latest version. If you're more than a few versions behind, update with brew upgrade supabase (macOS), scoop update supabase (Windows), or re-download from GitHub.
Network Bridge Issues (Docker Desktop on Mac/Windows)
Docker Desktop on macOS and Windows uses a virtual machine to run Linux containers. The networking between your host OS and that VM sometimes breaks, especially after OS updates, Docker Desktop updates, or when VPN software interferes.
On macOS, Docker's network bridge can get into a bad state where containers see each other fine (internal networking works), but the host machine can't reach exposed ports. You'll see all containers running healthy in docker ps, but connection refused when your app tries to connect.
On Windows WSL2, the network bridge between Windows and WSL creates additional complexity. Your Next.js app might run in Windows while Supabase containers run in WSL's Docker. The ports exposed from WSL don't always forward correctly to Windows localhost. Using host.docker.internal instead of localhost sometimes fixes this, but it's inconsistent.
The Docker Desktop documentation has OS-specific networking troubleshooting, but the practical fix is usually restarting Docker Desktop or switching between WSL2 and Hyper-V backends on Windows.
Database Container Failed to Initialize (Silent Failure)
PostgreSQL initialization is the longest and most fragile part of supabase start. The database container needs to create the postgres database, apply migrations, set up schemas, and start accepting connections. If any step fails, you get a broken database that appears to be running but doesn't actually work.
Silent failures happen when PostgreSQL starts, encounters an error during schema setup (maybe a bad migration in your supabase/migrations folder), logs the error internally, but doesn't crash. Docker sees the container as "running" because the process is alive, but the database isn't accepting connections on port 5432 inside the container.
You'll know it's a database initialization issue if docker logs supabase_db_[project-name] shows errors like "relation does not exist," "syntax error in SQL," or "database system is shut down." These indicate PostgreSQL started but failed to complete initialization.
Quick Diagnosis: 5-Minute Health Check (Before You Debug)
Before diving into fixes, run through this diagnostic workflow to understand what's actually broken. These five steps take under five minutes total and pinpoint the failure layer, saving you from trial-and-error troubleshooting.
Step 1: Verify Docker Desktop is Running and Healthy
Open Docker Desktop and confirm the whale icon shows green (running). If it's not running at all, start it and wait for the "Docker Desktop is running" notification. This sounds obvious, but it's the single most common cause of connection refused errors.
Check Docker Desktop's status in the UI. If it shows "Docker Desktop starting..." for more than two minutes, you have a Docker initialization problem separate from Supabase. Restart Docker Desktop entirely - quit completely (not just close the window) and relaunch.
# Verify Docker daemon is accessible
docker ps
# Expected: List of running containers (might be empty, but command should succeed)
# If you see "Cannot connect to Docker daemon," Docker isn't running properly
Look for resource warnings in Docker Desktop's dashboard. If you see "Low memory" or "Disk space critical," allocate more resources in Settings > Resources. Supabase needs at least 4GB RAM to run reliably.
Step 2: Check All Supabase Containers Are Up
Run docker ps and filter for Supabase containers. You should see at least seven containers all in "Up" status, not "Restarting" or "Exited."
# List all running Supabase containers
docker ps --filter "name=supabase"
# Expected: You should see 7+ containers:
# - supabase_db_[project]
# - supabase_kong_[project]
# - supabase_auth_[project]
# - supabase_rest_[project]
# - supabase_realtime_[project]
# - supabase_storage_[project]
# - supabase_edge_[project]
# Check container logs for errors
docker logs supabase_db_[your-project-id]
If any container is missing or shows "Restarting" status, that's your problem layer. A restarting container indicates initialization failure - the process crashes, Docker restarts it, it crashes again. Check logs with docker logs [container-name] to see the specific error.
If all containers show "Up" status, the issue isn't container startup. Move to testing actual connectivity.
Step 3: Test Database Connection with psql
Test direct PostgreSQL access to verify the database is actually accepting connections. This bypasses all the Supabase service layers (Kong, PostgREST) and tests the foundation.
# Test connection using psql (install via: brew install postgresql)
psql postgresql://postgres:postgres@localhost:54322/postgres
# If successful, you'll see:
psql (14.5)
Type "help" for help.
postgres=#
# Test query
SELECT version();
# Exit
\q
If psql connects successfully, your database is working and the issue is in a higher layer (PostgREST, Kong, or your app's connection string). If psql gets connection refused, the database container itself is the problem.
Common psql errors: "connection refused" means the database isn't listening on port 54322. "FATAL: password authentication failed" means the port is open but credentials are wrong (default should be postgres:postgres). "could not translate host name" means DNS resolution failed, try 127.0.0.1 instead of localhost.
Step 4: Validate Your .env Connection String
Check your application's environment variables to ensure you're pointing at the local Supabase stack, not production. This is especially critical in Next.js projects.
# .env.local (for Next.js)
# API URL (for client-side requests)
NEXT_PUBLIC_SUPABASE_URL=http://localhost:54321
# Anon key (for client-side auth)
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
# Service role key (for server-side admin operations - NEVER expose to client)
SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
# Database URL (for direct Postgres access if needed)
DATABASE_URL=postgresql://postgres:postgres@localhost:54322/postgres
The keys come from the output of supabase start. Copy them exactly, including the long JWT strings. The URL must be http://localhost:54321 (not HTTPS, not your production URL).
Verify your app is actually loading these variables. Add a console.log in your code: console.log(process.env.NEXT_PUBLIC_SUPABASE_URL). If it prints your production URL or undefined, your .env.local isn't being loaded correctly.
Step 5: Confirm Port Availability
Verify that nothing else is using Supabase's required ports. Port conflicts prevent Docker from binding containers to host ports, causing connection refused even though containers are running internally.
# macOS/Linux: Find what's using port 54322
lsof -i :54322
# Windows: Find what's using port 54322
netstat -ano | findstr :54322
# If something is using the port, you'll see output like:
# postgres 12345 username 6u IPv4 0x... TCP localhost:54322 (LISTEN)
If lsof or netstat returns nothing, the port is free (good). If it shows another process, that's your conflict. Note the PID (process ID) and decide whether to kill that process or configure Supabase to use different ports.
Common port conflicts: system PostgreSQL on 54322, another Supabase project, or in Windows/macOS, system services that reserve port ranges. Check all four critical ports: 54321, 54322, 54323, 54324.
Solution 1: Fix Docker and Container Issues
If your diagnostic workflow showed Docker or container problems (Step 1-2 failures), these fixes address the infrastructure layer. Start here before troubleshooting application-level issues.
Restart Docker Desktop with Proper Resource Allocation
Completely quit Docker Desktop - don't just close the window, actually quit the application. On macOS, right-click the whale icon and select "Quit Docker Desktop." On Windows, quit from the system tray.
Open Docker Desktop settings before restarting. Navigate to Resources and set:
- Memory: 4GB minimum, 8GB recommended
- CPUs: 2 minimum, 4 recommended
- Disk space: 20GB minimum
These aren't arbitrary numbers. PostgreSQL initialization needs at least 1GB, the seven Supabase containers combined use ~2GB at idle, and you need headroom for your actual application and database content.
Click "Apply & Restart" and wait for Docker to fully initialize. This takes 30-60 seconds. Once the whale icon is green again, try supabase start.
If Docker Desktop won't start even with proper resources, check for OS-level conflicts. On macOS Ventura+, privacy settings sometimes block Docker's kernel extensions. On Windows, Hyper-V and WSL2 need to be properly enabled in Windows Features.
Stop Conflicting Containers and Reset Supabase
If containers are stuck in "Restarting" status or you've modified Docker state manually, cleanly stop everything and start fresh.
# Stop all Supabase containers
supabase stop
# Verify they're actually stopped
docker ps --filter "name=supabase"
# Remove any orphaned containers
docker container prune
# Remove Supabase volumes (CAUTION: Deletes local database data)
docker volume ls | grep supabase
docker volume rm [volume-name] # Only if you want to wipe database
# Start fresh
supabase start
⚠️ Warning: Warning: Removing Docker volumes deletes your local database content. Only do this if you're okay losing local test data, or if you've confirmed your migrations can rebuild the schema.
The supabase stop command is supposed to cleanly shut down containers, but sometimes they get stuck. Using docker ps to verify and docker container prune to clean up ensures a truly clean state.
Update Supabase CLI to Latest Version
CLI version mismatches cause all sorts of initialization issues. Update to the latest version to ensure compatibility with current Docker images and configurations.
# macOS (Homebrew)
brew upgrade supabase
# Windows (Scoop)
scoop update supabase
# Linux (or if Homebrew/Scoop aren't available)
# Download latest from https://github.com/supabase/cli/releases
# Verify version
supabase --version
After updating, reinitialize your project's Supabase config if prompted. The CLI sometimes migrates config.toml formats between major versions. If you see warnings about deprecated settings, address them before troubleshooting further.
Check the Supabase CLI GitHub issues for known bugs in your specific version. Sometimes a recent release introduces regressions that are fixed in the next patch. Pinning to a known-good version is valid while waiting for fixes.
Verify Docker Network Bridge Configuration
Docker's network bridge can get corrupted, especially after OS updates or VPN software changes. This manifests as containers running but unreachable from host.
# Inspect Docker networks
docker network ls
# Look for the Supabase network (usually named after your project)
docker network inspect supabase_network_[project-id]
# Check if containers are attached and have IPs assigned
# You should see "Containers" section with all seven services listed
If the network exists but containers aren't attached, or if containers have no IP addresses assigned, the network is broken. Remove it and let Supabase recreate:
# Stop Supabase first
supabase stop
# Remove the network
docker network rm supabase_network_[project-id]
# Start again - Supabase will recreate the network
supabase start
On Windows WSL2, if issues persist, try switching between WSL2 and Hyper-V backends in Docker Desktop settings. Each has different networking behaviors, and one might work better with your system configuration.
Solution 2: Fix Connection String and Environment Variables
If Docker and containers are healthy (diagnostic steps 1-2 passed) but your app still can't connect (step 4 failed), the issue is configuration - how your application is trying to reach Supabase.
Correct Local Connection String Format
Your application needs to connect to http://localhost:54321 for the API and postgresql://postgres:postgres@localhost:54322/postgres for direct database access. Using production URLs or incorrect ports causes connection failures.
The complete set of local development credentials comes from supabase start output. Don't hardcode these - copy from the terminal:
# Start Supabase (first time or after stopping)
supabase start
# Expected output:
Started supabase local development setup.
API URL: http://localhost:54321
DB URL: postgresql://postgres:postgres@localhost:54322/postgres
Studio URL: http://localhost:54323
Inbucket URL: http://localhost:54324
anon key: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
service_role key: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Copy these values directly into your .env.local or .env file. Don't modify them, don't abbreviate the keys, don't add additional environment variables that might override them.
The URL scheme matters: http:// not https:// for local development. Supabase local stack doesn't use TLS. If your HTTP client enforces HTTPS (some Vercel/Next.js configurations do), you'll get connection failures or protocol errors.
Understanding service_role vs anon Keys for Local Dev
Supabase uses JWT-based authentication with two key types. The anon key is for client-side code - it's safe to expose in frontend bundles and has limited permissions. The service_role key is for server-side admin operations - it bypasses Row Level Security and should never be exposed to clients.
In local development, both keys are public (they're right in the terminal output), so security is less critical. But understanding the distinction prevents logic errors. If your frontend tries to use the service_role key, it will work in dev but break in production when that key is actually secret.
For Next.js apps, NEXT_PUBLIC_SUPABASE_ANON_KEY goes in .env.local and gets bundled into client JavaScript. SUPABASE_SERVICE_ROLE_KEY (without the NEXT_PUBLIC prefix) stays server-side and is used in API routes or server components.
Using the wrong key type won't cause connection refused, but it will cause auth failures or permission errors that feel like connection issues. If you can connect but get 401 Unauthorized on every request, check which key you're using.
Common .env Mistakes (Localhost vs 127.0.0.1 vs host.docker.internal)
localhost and 127.0.0.1 should be equivalent, but they're not always. On some systems, especially Windows WSL2, Docker binds to IPv4 (127.0.0.1) but not IPv6 (which localhost might resolve to). Or vice versa.
If localhost:54321 gives connection refused, try 127.0.0.1:54321. If that works, you have a DNS resolution issue. Force IPv4 by using the numeric IP. If 127.0.0.1 fails but localhost works, you have the opposite problem.
host.docker.internal is Docker's special hostname that resolves to the host machine from inside a container. You usually don't need this in .env for your application, but if your app itself runs in Docker and needs to reach Supabase on the host, use host.docker.internal:54321 instead of localhost.
Another mistake: mixing local and production URLs in the same environment. You have NEXT_PUBLIC_SUPABASE_URL=http://localhost:54321 but DATABASE_URL=postgresql://...your-project.supabase.co/postgres. Your frontend connects locally but your backend connects to production. Pick one environment per .env file.
Next.js Specific: NEXT_PUBLIC vs Server-Side Variables
Next.js has a critical distinction for environment variables. Variables prefixed with NEXT_PUBLIC_ are bundled into client-side JavaScript and accessible in browser code. Variables without this prefix are only available server-side (API routes, server components).
For Supabase, you need both:
# Client-side (bundled into browser JavaScript)
NEXT_PUBLIC_SUPABASE_URL=http://localhost:54321
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
# Server-side only (not exposed to browser)
SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
DATABASE_URL=postgresql://postgres:postgres@localhost:54322/postgres
If you forget the NEXT_PUBLIC_ prefix on the URL and anon key, your client-side Supabase client initializes with undefined and can't connect. If you accidentally use NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY, you expose admin credentials to the browser (bad security, even in local dev).
For detailed environment variable handling in Next.js, see the Next.js environment variables documentation. The practical rule: API URL and anon key need NEXT_PUBLIC_, everything else should not have it.
Solution 3: Resolve Port Conflicts
If Docker and environment variables are correct but connection still fails, port conflicts are likely. Something else on your system is using Supabase's ports, preventing Docker from binding.
Identifying What's Using Supabase Ports (lsof, netstat)
The diagnostic command from earlier tells you if ports are in use, but doesn't explain what's using them or whether it's safe to kill.
# macOS/Linux: Detailed port check
lsof -i :54322
lsof -i :54321
lsof -i :54323
lsof -i :54324
# Windows: Detailed port check (run as Administrator)
netstat -ano | findstr :54322
netstat -ano | findstr :54321
The output shows process name and PID. Look for:
- Another Supabase project (safe to stop)
- System PostgreSQL (you can keep it running on default port 5432, it won't conflict)
- Random application you forgot about (investigate before killing)
- Nothing (port is free, conflict isn't the issue)
On macOS, AirPlay Receiver can conflict if Apple changed their port usage. Check System Preferences > Sharing > AirPlay Receiver and disable it if you don't use it.
On Windows, Hyper-V and Windows Subsystem for Android reserve port ranges. Use netsh interface ipv4 show excludedportrange protocol=tcp to see reserved ranges. If Supabase ports fall in those ranges, you'll need to either configure Supabase to use different ports or disable the reserving service.
Killing Conflicting Processes Safely
Once you've identified the conflicting process, decide if it's safe to kill. Don't blindly kill system processes - you might break your OS.
# macOS/Linux: Kill process by PID
kill -9 [PID]
# Windows: Kill process by PID (run as Administrator)
taskkill /PID [PID] /F
# Verify port is now free
lsof -i :54322 # Should return nothing
⚠️ Warning: Warning:
kill -9andtaskkill /Fforce-terminate processes without cleanup. Only use on processes you recognize and can safely stop. If you're killing another Supabase project, usesupabase stopin that project directory instead.
After killing the conflicting process, try supabase start again. Docker should now successfully bind to the freed port. If you see the same port conflict immediately, something is auto-restarting the process (system service, daemon, etc.) and you'll need to disable it at the OS level.
Changing Supabase Default Ports in config.toml
If you can't or don't want to kill the conflicting process, configure Supabase to use different ports. This is common when running multiple Supabase projects simultaneously or when you have a system PostgreSQL installation you can't stop.
Edit supabase/config.toml in your project directory:
[api]
port = 54321 # Change to 54421, 54521, etc. if 54321 is taken
[db]
port = 54322 # Change to 54422, 54522, etc. if 54322 is taken
[studio]
port = 54323 # Change to 54423, etc.
[inbucket]
port = 54324 # Change to 54424, etc.
After changing ports, update your .env file to match the new ports. If you set db.port = 54422, your DATABASE_URL becomes postgresql://postgres:postgres@localhost:54422/postgres.
Run supabase stop then supabase start to apply the new configuration. Verify the start output shows your custom ports. This approach lets you run multiple Supabase projects side-by-side, each on different port ranges.
Running Multiple Supabase Projects Simultaneously
Official Supabase local dev doesn't support multiple projects simultaneously by default because they fight over ports. But with custom port configuration, you can run several projects.
Create a spreadsheet or note tracking which project uses which ports:
- Project A: 54321, 54322, 54323, 54324
- Project B: 54421, 54422, 54423, 54424
- Project C: 54521, 54522, 54523, 54524
Each project gets its own config.toml with unique ports. When you supabase start in each directory, they'll coexist without conflicts. Just remember which ports map to which project when connecting.
The downside: you'll use significant RAM (4GB+ per project) and CPU. Docker Desktop's resource allocation needs to accommodate all running projects simultaneously. This is viable for two projects, gets painful with three or more.
Alternatively, use supabase stop in one project before supabase start in another. You can only work on one project at a time, but you don't need port juggling or massive resource allocation.
Advanced Troubleshooting: When Standard Fixes Don't Work
If you've exhausted Docker fixes, environment variables, and port conflicts but still see connection refused, these advanced techniques dig deeper into edge cases and system-specific issues.
Inspecting Docker Container Logs for Database Errors
Container logs reveal initialization failures that don't surface in supabase start output. PostgreSQL might start the process but fail during schema setup, leaving you with a zombie database.
# Get exact container name
docker ps --filter "name=supabase_db"
# View recent logs
docker logs supabase_db_[project-id]
# Follow logs in real-time (useful during startup)
docker logs -f supabase_db_[project-id]
# Search logs for error keywords
docker logs supabase_db_[project-id] 2>&1 | grep -i error
docker logs supabase_db_[project-id] 2>&1 | grep -i fatal
Look for specific error patterns:
- "FATAL: database system is shut down" - PostgreSQL crashed during initialization
- "ERROR: relation does not exist" - Migration references a table that wasn't created
- "syntax error at or near" - Bad SQL in your migrations
- "permission denied" - Docker volume permissions problem (common on Linux)
If logs show migration errors, the issue is your supabase/migrations/*.sql files, not Supabase itself. Comment out problematic migrations, reset the database (supabase db reset), and fix the SQL syntax.
For permission errors on Linux, Docker volumes might have wrong ownership. Fix with sudo chown -R $USER ~/.docker or run Supabase with appropriate permissions. This is a common issue in the PostgreSQL connection strings context.
WSL2 Networking Issues on Windows
Windows Subsystem for Linux 2 (WSL2) adds a virtualization layer between Windows and Linux containers. This creates unique networking challenges that don't exist on macOS or native Linux.
The most common WSL2 issue: your Next.js app runs in Windows (PowerShell, Windows Terminal) but Supabase runs in WSL's Docker. Ports exposed from WSL don't automatically forward to Windows localhost. You connect to localhost:54321 from Windows and get connection refused because that port only exists in WSL's network namespace.
Solutions:
- Run your entire development environment inside WSL2 (recommended) - cd into
/mnt/c/Users/...or move your project to~/projectsin WSL - Use WSL's IP address from Windows: run
wsl hostname -Ito get WSL's IP, then connect to[WSL-IP]:54321instead oflocalhost:54321 - Configure Docker Desktop to use Hyper-V backend instead of WSL2 (Settings > General > uncheck "Use WSL2 based engine")
WSL2 networking issues are notoriously finicky. Restarting WSL (wsl --shutdown from PowerShell, then restart your WSL terminal) sometimes fixes transient routing problems. Or restart Docker Desktop entirely.
For systematic debugging of environment issues like this, see our systematic debugging approach guide.
Resetting Supabase Local Database (supabase db reset)
When database schema gets corrupted, migrations fail, or you just want a clean slate, supabase db reset wipes everything and rebuilds from migrations.
# Reset database (CAUTION: Deletes all data)
supabase db reset
# This will:
# 1. Drop all tables, functions, schemas
# 2. Re-run migrations in supabase/migrations/ in order
# 3. Seed database if seed.sql exists
⚠️ Warning: Warning:
supabase db resetpermanently deletes all data in your local database. Only use this in local dev, never against production. If you have test data you need to keep, export it first withsupabase db dump.
Reset is useful when:
- You've been experimenting with schema changes and want to start fresh
- Migrations are failing and you can't figure out why - reset and apply them cleanly
- Database state is corrupted (duplicate keys, broken constraints)
- You want to test that migrations actually recreate your schema correctly
After reset, verify the database works with psql postgresql://postgres:postgres@localhost:54322/postgres and run a test query. If reset fails with errors, the problem is in your migration files. Check each .sql file in supabase/migrations/ for syntax errors.
When to Use Production Supabase Instead of Local Dev
Local development isn't always worth the Docker complexity. For certain scenarios, connecting directly to a Supabase production (or staging) project is more pragmatic.
Use production/staging when:
- You're on a deadline and Docker issues are blocking progress
- Your machine doesn't have enough resources for Docker (8GB+ RAM recommended)
- You're collaborating with a team and want shared database state
- You're debugging production-specific issues that don't reproduce locally
- Docker Desktop licensing is a concern (commercial use requires paid license)
To switch, update your .env to use production credentials:
# Production Supabase connection
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=[production anon key from dashboard]
SUPABASE_SERVICE_ROLE_KEY=[production service role key from dashboard]
The downside: you're testing against live data (or creating test data in production), slower iteration (network latency vs local), and potential cost implications if you exceed free tier limits.
A hybrid approach: use local dev for backend/database work where you need fast iteration, but use a staging Supabase project (free tier) for frontend development where you just need API access. This avoids local Docker complexity for frontend-only developers.
Reporting Issues to Supabase Team (What Info They Need)
If you've exhausted all troubleshooting and believe you've found a Supabase CLI or Docker image bug, report it to help the community. The Supabase team is responsive to well-documented issues.
Before filing on Supabase CLI GitHub issues, gather:
- OS and version (macOS 14.2, Windows 11 WSL2, Ubuntu 22.04, etc.)
- Supabase CLI version (
supabase --version) - Docker version (
docker --version) - Docker Desktop version and backend (WSL2 vs Hyper-V)
- Full output of
supabase start(including errors) - Container status (
docker ps --filter "name=supabase") - Container logs for failed services (
docker logs supabase_db_...) - Steps to reproduce in a fresh project
Vague issues like "connection refused doesn't work" won't get traction. Specific issues like "connection refused on Windows 11 WSL2 with Docker Desktop 4.25.0 when using custom ports in config.toml - containers start but ports don't forward to Windows host" give maintainers something actionable.
Search existing issues first - your problem might already be documented with workarounds. If you find a matching issue, add your specific details as a comment rather than creating a duplicate.
Conclusion
When Supabase local development throws "connection refused," the root cause is usually one of six issues: Docker not running or under-resourced, port conflicts with the default Supabase ports (54321, 54322, 54323, 54324), incorrect environment variables or connection strings, Supabase CLI version mismatches, Docker networking bridge failures, or silent PostgreSQL initialization failures.
Start with the 5-minute diagnostic workflow: verify Docker Desktop is running and healthy, check that all seven Supabase containers show "Up" status, test direct database connection with psql, validate your .env file has correct local URLs, and confirm no port conflicts with lsof or netstat. This pinpoints which layer failed.
For persistent issues, move to targeted solutions: fix Docker resource allocation and restart containers cleanly, update Supabase CLI, repair Docker network bridges, correct environment variables with proper NEXT_PUBLIC_ prefixes for Next.js, resolve port conflicts by killing processes or changing Supabase ports in config.toml, inspect container logs for initialization errors, and handle WSL2 networking quirks on Windows.
Prevention matters as much as fixing. Allocate sufficient Docker resources (4GB+ RAM), use consistent environment variable patterns, keep Supabase CLI updated, understand localhost vs 127.0.0.1 vs host.docker.internal for your OS, and know when to use Composer for complex changes versus inline apply for quick iterations.
But recognize when you've hit the 20% that needs human debugging expertise. If you've spent more than 30 minutes troubleshooting Docker networking, WSL2 port forwarding, or mysterious container initialization failures without progress, that's the signal to escalate. FlowQL's live debugging sessions with backend experts who've configured Supabase on every OS combination typically resolve connection refused issues in under 30 minutes.
The promise of Supabase local development is real: a complete backend-as-a-service running on your machine for fast iteration and offline work. But when the Docker dependency trap springs - when seven containerized services create multiple failure points - you need systematic debugging skills and sometimes expert help. Start with the diagnostic workflow above, apply targeted fixes based on what failed, and remember that needing help isn't a failure - it's efficient problem-solving.
Still stuck on connection refused after trying these solutions? Book a live debugging session with FlowQL. Our backend experts have debugged Supabase Docker configurations on macOS, Windows WSL2, and Linux. We'll diagnose your specific environment and get your local development working.
For more on debugging containerized environments and when AI tools can't help, see our AI debugging limitations guide. And for understanding systematic approaches to diagnosing multi-service failures, check out our debugging workflow guide.
Frequently Asked Questions
Why is Supabase local development connection refused?
Connection refused in Supabase local dev typically means Docker containers aren't running, PostgreSQL failed to initialize, or your app is connecting to the wrong port. Run docker ps --filter "name=supabase" to verify all seven containers show "Up" status, then check your .env uses http://localhost:54321 for the API URL and postgresql://postgres:postgres@localhost:54322/postgres for database access.
How do I fix database connection refused in Supabase?
Start by verifying Docker Desktop is running and has allocated at least 4GB RAM. Run supabase stop && supabase start to cleanly restart containers, then test direct database access with psql postgresql://postgres:postgres@localhost:54322/postgres. If psql connects but your app doesn't, check your environment variables for correct local URLs instead of production URLs.
What is the correct Supabase local connection string?
For API access use http://localhost:54321 (not HTTPS, not your production URL). For direct database access use postgresql://postgres:postgres@localhost:54322/postgres. The anon key and service_role key come from supabase start output - copy them exactly into your .env file.
How do I check if Supabase Docker containers are running?
Run docker ps --filter "name=supabase" and verify you see at least seven containers in "Up" status: supabase_db, supabase_kong, supabase_auth, supabase_rest, supabase_realtime, supabase_storage, and supabase_edge. If any show "Restarting" or are missing, check container logs with docker logs supabase_db_[project-name] for error messages.
Why does supabase start fail silently?
Silent failures usually indicate PostgreSQL initialization errors that don't crash the container but prevent the database from accepting connections. Check container logs with docker logs supabase_db_[project-id] for errors like "FATAL: database system is shut down" or migration syntax errors. Also verify Docker Desktop has allocated sufficient resources (4GB+ RAM).
How do I resolve port conflicts with Supabase local dev?
Use lsof -i :54322 (macOS/Linux) or netstat -ano | findstr :54322 (Windows) to identify what's using Supabase ports. Either kill the conflicting process with kill -9 [PID] or configure Supabase to use different ports by editing supabase/config.toml and changing the [api] port, [db] port, etc. to unused values.
What's the difference between Supabase local and production URLs?
Local development uses http://localhost:54321 and runs entirely on your machine via Docker with public test keys. Production uses https://your-project.supabase.co and connects to Supabase's cloud infrastructure with private keys. Never commit production keys to git, and ensure your .env.local uses local URLs for development to avoid accidentally modifying production data.
For backend debugging strategies applicable to diagnosing database connection issues, or systematic troubleshooting methodologies, our guides provide detailed approaches to isolating problems in complex environments.
Subscribe to our blog
Get the latest guides and insights delivered to your inbox.
Join the FlowQL waitlist
Get early access to our AI search optimization platform.
Related Articles
Fix: 'Duplicate key value violates unique constraint' in Supabase
Supabase unique constraint error? Learn how to fix 'Duplicate key value' by mastering upserts, conflict resolution, and preventing race conditions in your app.
Fix: Supabase Realtime Subscription Not Receiving Updates
Supabase Realtime not working? Learn how to fix 'no updates received' by enabling replication, auditing RLS, and debugging your WebSocket connection.
Fix: 'RLS policy violates row security' in Supabase (2025)
Supabase RLS error? Learn how to fix 'RLS policy violates row security' by solving infinite recursion and properly using auth.uid() in your Postgres policies.