Introduction
When a Node.js application starts alongside its dependencies (database, Redis, external APIs) in Docker, the application may attempt to connect before the dependency is ready to accept connections. This results in ECONNREFUSED errors because the port exists but no process is listening yet.
This is a classic startup ordering problem in containerized environments, especially with docker-compose where containers start in parallel.
Symptoms
- Node.js throws "Error: connect ECONNREFUSED 127.0.0.1:5432" at startup
- Application crashes immediately while the database container is still initializing
- Restarting the Node.js container manually after a few seconds works fine
Common Causes
- Node.js application starts before the database/service container is ready
- Docker-compose starts all containers simultaneously with no startup ordering
- Service takes longer to initialize than the Node.js connection timeout
Step-by-Step Fix
- 1.Implement connection retry with exponential backoff: Retry until the service is ready.
- 2.```javascript
- 3.const { Client } = require('pg');
async function connectWithRetry(config, maxRetries = 10) { const client = new Client(config);
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
await client.connect();
console.log('Database connected successfully');
return client;
} catch (err) {
if (err.code === 'ECONNREFUSED') {
const delay = Math.min(1000 * Math.pow(2, attempt - 1), 30000);
console.log(Connection refused, retrying in ${delay}ms (attempt ${attempt}/${maxRetries}));
await new Promise(resolve => setTimeout(resolve, delay));
} else {
throw err; // Non-connection errors fail immediately
}
}
}
throw new Error(Failed to connect after ${maxRetries} retries);
}
const db = await connectWithRetry({ host: 'postgres', port: 5432, database: 'myapp', user: 'myuser', password: 'mypass', }); ```
- 1.Use wait-for-it or dockerize to delay startup: Block until the dependency is ready.
- 2.```yaml
- 3.# docker-compose.yml
- 4.version: '3.8'
- 5.services:
- 6.app:
- 7.build: .
- 8.command: ["./wait-for-it.sh", "postgres:5432", "--", "node", "server.js"]
- 9.depends_on:
- 10.postgres:
- 11.condition: service_healthy
postgres: image: postgres:15 healthcheck: test: ["CMD-SHELL", "pg_isready -U myuser"] interval: 5s timeout: 5s retries: 5 ```
- 1.Use Docker health checks for proper startup ordering: Define when a service is truly ready.
- 2.```yaml
- 3.# docker-compose.yml
- 4.services:
- 5.redis:
- 6.image: redis:7
- 7.healthcheck:
- 8.test: ["CMD", "redis-cli", "ping"]
- 9.interval: 3s
- 10.timeout: 3s
- 11.retries: 5
- 12.start_period: 10s
app: build: . depends_on: redis: condition: service_healthy postgres: condition: service_healthy ```
Prevention
- Always use depends_on with condition: service_healthy in docker-compose
- Implement connection retry logic in your application, not just in startup scripts
- Use health checks for all dependency services
- Set appropriate start_period in health checks to allow for slow initial startup