← Back to list

Debugging PoolTimedOut with YugabyteDB, Docker, Rust, and GitHub Actions

Setting up an ephemeral database for integration tests sounds simple — until Docker, CI containers, and database drivers all collide.

ShahNilay · 2025-12-15 19:17 · 4 claps · 2.3 min read
#yugabytedb #rust #github-actions #cicd
Open on Medium ↗
Wiki topics: 💻 · Programming ☁️ · DevOps & Cloud 🔓 · Open Source

Debugging PoolTimedOut with YugabyteDB, Docker, Rust, and GitHub Actions

Setting up an ephemeral database for integration tests sounds simple — until Docker, CI containers, and database drivers all collide.

This post walks through a real debugging journey of running Rust unit tests against YugabyteDB in GitHub Actions, where everything worked locally but consistently failed in CI with:

Unable to Create Connection With Postgres: PoolTimedOut

If you’ve ever dealt with Postgres-compatible databases, Docker Compose, and GitHub Actions job containers, this story will probably feel familiar.

The Goal

  • Run Rust unit tests (cargo test)
  • Use YugabyteDB as a PostgreSQL-compatible test database
  • Start YugabyteDB using Docker Compose
  • Run tests inside a GitHub Actions job container
  • No volumes, fully ephemeral DB
  • Tests pass locally and in CI

Locally, everything worked perfectly.

CI was a different story.

The Initial Symptom

In GitHub Actions, tests consistently failed with:

Unable to Create Connection With Postgres: PoolTimedOut

Important observations:

  • YugabyteDB container was running
  • Healthcheck passed
  • ysqlsh worked from inside the Yugabyte container
  • cargo test still timed out

This ruled out:

  • Yugabyte not starting
  • Wrong credentials
  • Port not exposed
  • SSL misconfiguration (explicitly disabled)

So what was going on?

Understanding the CI Network Topology

The key realization came from understanding where the Rust tests were running.

In GitHub Actions, the setup looked like this:

┌──────────────────────────────┐
│ GitHub Actions Job Container │
│                              │
│   cargo test                 │
│                              │
└───────────────┬──────────────┘
                │
                │ Docker network
                │
┌───────────────▼──────────────┐
│ YugabyteDB Container         │
│ (started via docker-compose) │
└──────────────────────────────┘

This is very different from local development.

The Critical Mistake: localhost

The Rust application was configured like this:

database:
  host: "localhost"
  port: 5433
  name: "yugabyte"
  user: "yugabyte"
  password: "yugabyte"
  ssl_mode: "disable"

Why this works locally

  • YugabyteDB is exposed on the host
  • localhost:5433 correctly routes to the database

Why this fails in CI

Inside a job container:

localhost = the job container itself

But YugabyteDB is running in a different container.

So the Rust app was trying to connect to:

job-container → localhost:5433 ❌

Nothing was listening there.

Why the Error Was PoolTimedOut

This made the issue harder to spot.

Most Rust Postgres pools:

  • Retry connection acquisition
  • Wait until timeout
  • Do not fail immediately on ECONNREFUSED

So instead of a clear “connection refused,” the error surfaced as: “PoolTimedOut”

Which misleadingly suggests a pool configuration issue.

The Fix: Stop Using localhost

In Docker-based CI:

Containers must talk to each other using container names, not localhost.

The correct host was the Docker service name:

yugabytedb

Correct connection string for CI

postgres://yugabyte:yugabyte@yugabytedb:5433/yugabyte?sslmode=disable

In the GitHub Actions workflow:

- name: Run unit tests
  env:
    DATABASE_URL: postgres://yugabyte:yugabyte@yugabytedb:5433/yugabyte?sslmode=disable
  run: |
    make test

Once this change was applied:

  • ✅ Database connections succeeded
  • ✅ Pools initialized correctly
  • cargo test passed reliably

Key Takeaways

1. localhost is context-dependent

  • Local machine → host OS
  • Docker container → that container only
  • GitHub Actions job container → never the host

2. Prefer DATABASE_URL in CI

It removes ambiguity and avoids env parsing mistakes.

3. PoolTimedOut often means “can’t reach the DB”

Not authentication. Not SSL. Not max connections. Just networking.

4. Healthchecks aren’t enough

A healthy DB container doesn’t mean your test container can reach it.

Final Checklist for DB-backed CI Tests

  • ❌ Don’t use localhost between containers
  • ✅ Use Docker service names
  • ✅ Disable SSL explicitly for YugabyteDB
  • ✅ Connect containers to the same Docker network
  • ✅ Use DATABASE_URL when possible

Conclusion

The bug wasn’t YugabyteDB. It wasn’t Rust. It wasn’t Docker Compose.

It was a single word:

**localhost**

Once that was fixed, everything else fell into place.

If you’re debugging database timeouts in CI, always ask yourself:

“Where is this code actually running?”

It might save you days.


메타데이터
post_id
fcda0db2013f
slug
debugging-pooltimedout-with-yugabytedb-docker-rust-and-github-actions-fcda0db2013f
url
https://medium.com/@shahnilay/debugging-pooltimedout-with-yugabytedb-docker-rust-and-github-actions-fcda0db2013f
canonical_url
https://medium.com/@shahnilay/debugging-pooltimedout-with-yugabytedb-docker-rust-and-github-actions-fcda0db2013f
author_url
https://medium.com/@shahnilay
status
ok
fetched_at
2026-08-29 00:48:47