Introduction
In Docker Compose, services start concurrently. Even with depends_on, Docker only waits for the container to start, not for the service inside to be ready. When a Node.js app tries to connect to PostgreSQL, Redis, or another service immediately on startup, the connection is refused because the service is still initializing.
Symptoms
Error: connect ECONNREFUSED 127.0.0.1:5432on application startupAggregateError: connect ECONNREFUSED ::1:6379- App crashes on first start, works on restart (
docker compose restart) npm startworks locally but fails in Docker- Error only during initial deployment, not during normal operation
``` $ docker compose up [+] Running 3/3 ✔ Container db Started ✔ Container redis Started ✔ Container app Started
app | Error: connect ECONNREFUSED 172.20.0.2:5432 app | at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1555:16) app | at TCPConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) app | Exited with code 1 ```
Common Causes
depends_ononly waits for container start, not service readiness- Database needs time to initialize (run migrations, accept connections)
- Node.js app connects immediately on startup without retry
- Docker Compose version 2
depends_ondoes not support health checks - Service bound to wrong network interface in container
Step-by-Step Fix
- 1.Use Docker Compose health checks:
- 2.```yaml
- 3.# docker-compose.yml
- 4.services:
- 5.db:
- 6.image: postgres:16
- 7.healthcheck:
- 8.test: ["CMD-SHELL", "pg_isready -U postgres"]
- 9.interval: 5s
- 10.timeout: 5s
- 11.retries: 5
- 12.start_period: 30s
app: build: . depends_on: db: condition: service_healthy environment: DATABASE_URL: postgresql://postgres:password@db:5432/myapp ```
- 1.Add retry logic in the application:
- 2.```javascript
- 3.const { Client } = require('pg');
async function connectWithRetry(retries = 10, delay = 3000) { const client = new Client({ connectionString: process.env.DATABASE_URL, });
for (let i = 0; i < retries; i++) {
try {
await client.connect();
console.log('Database connected successfully');
return client;
} catch (err) {
console.log(Connection attempt ${i + 1}/${retries} failed: ${err.message});
if (i === retries - 1) throw err;
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
// Usage const client = await connectWithRetry(); ```
- 1.Use wait-for-it script in Dockerfile:
- 2.```dockerfile
- 3.FROM node:20-alpine
WORKDIR /app COPY package*.json ./ RUN npm ci --only=production COPY . .
# Install wait-for-it RUN apk add --no-cache bash COPY wait-for-it.sh /usr/local/bin/ RUN chmod +x /usr/local/bin/wait-for-it.sh
CMD ["wait-for-it.sh", "db:5432", "--timeout=60", "--strict", "--", "node", "server.js"] ```
- 1.Use Docker Compose wait-for-it in command:
- 2.```yaml
- 3.# docker-compose.yml
- 4.services:
- 5.app:
- 6.build: .
- 7.command: >
- 8.sh -c "
- 9.until nc -z db 5432; do
- 10.echo 'Waiting for database...';
- 11.sleep 2;
- 12.done;
- 13.echo 'Database is ready!';
- 14.node server.js
- 15."
- 16.depends_on:
- 17.- db
- 18.
` - 19.Implement application-level readiness check:
- 20.```javascript
- 21.const express = require('express');
- 22.const app = express();
let isReady = false;
// Check database connection on startup async function initialize() { try { await db.connect(); await db.runMigrations(); isReady = true; console.log('Application initialized'); } catch (err) { console.error('Initialization failed:', err.message); process.exit(1); } }
// Health check endpoint app.get('/health', (req, res) => { res.status(isReady ? 200 : 503).json({ status: isReady ? 'ready' : 'initializing', }); });
// Readiness probe for Kubernetes app.get('/ready', (req, res) => { res.status(isReady ? 200 : 503).json({ ready: isReady }); });
initialize().then(() => { app.listen(3000); }); ```
Prevention
- Always use
depends_onwithcondition: service_healthyin Docker Compose - Add retry logic to all external service connections
- Implement
/healthand/readyendpoints for orchestration - Use
start_periodin health checks for slow-starting services - Test Docker Compose startup order in CI pipeline
- Use
docker compose up --waitto wait for all services to be healthy - Set appropriate connection timeouts in database clients