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:5432 on application startup
  • AggregateError: connect ECONNREFUSED ::1:6379
  • App crashes on first start, works on restart (docker compose restart)
  • npm start works 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_on only 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_on does not support health checks
  • Service bound to wrong network interface in container

Step-by-Step Fix

  1. 1.Use Docker Compose health checks:
  2. 2.```yaml
  3. 3.# docker-compose.yml
  4. 4.services:
  5. 5.db:
  6. 6.image: postgres:16
  7. 7.healthcheck:
  8. 8.test: ["CMD-SHELL", "pg_isready -U postgres"]
  9. 9.interval: 5s
  10. 10.timeout: 5s
  11. 11.retries: 5
  12. 12.start_period: 30s

app: build: . depends_on: db: condition: service_healthy environment: DATABASE_URL: postgresql://postgres:password@db:5432/myapp ```

  1. 1.Add retry logic in the application:
  2. 2.```javascript
  3. 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. 1.Use wait-for-it script in Dockerfile:
  2. 2.```dockerfile
  3. 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. 1.Use Docker Compose wait-for-it in command:
  2. 2.```yaml
  3. 3.# docker-compose.yml
  4. 4.services:
  5. 5.app:
  6. 6.build: .
  7. 7.command: >
  8. 8.sh -c "
  9. 9.until nc -z db 5432; do
  10. 10.echo 'Waiting for database...';
  11. 11.sleep 2;
  12. 12.done;
  13. 13.echo 'Database is ready!';
  14. 14.node server.js
  15. 15."
  16. 16.depends_on:
  17. 17.- db
  18. 18.`
  19. 19.Implement application-level readiness check:
  20. 20.```javascript
  21. 21.const express = require('express');
  22. 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_on with condition: service_healthy in Docker Compose
  • Add retry logic to all external service connections
  • Implement /health and /ready endpoints for orchestration
  • Use start_period in health checks for slow-starting services
  • Test Docker Compose startup order in CI pipeline
  • Use docker compose up --wait to wait for all services to be healthy
  • Set appropriate connection timeouts in database clients