← Back home

2026.09 / database networking 062

Coolify app cannot connect to PostgreSQL? Stop using localhost

A web application can build successfully in Coolify and then crash with ECONNREFUSED 127.0.0.1:5432, connection refused, or could not translate host name. The first error already gives away the most common cause: inside the application container, localhost means that application container—not the separate PostgreSQL container.

The reliable fix is not to publish PostgreSQL to the internet. It is to give the app Coolify’s database Internal URL, make sure both resources share the intended destination network, and then test each layer without printing the password.

Use an internal database hostname from the application container. Reserve localhost for a database process running in that same container, which is not the normal Coolify layout.

Read the exact error before changing settings

Error shapeWhat it proves
ECONNREFUSED 127.0.0.1:5432 or ::1:5432The app tried its own loopback interface; the host is almost certainly wrong.
could not translate host name / ENOTFOUNDThe configured database hostname did not resolve through container DNS.
Connection timeoutA route, network, firewall, wrong address, or unresponsive endpoint needs investigation.
password authentication failedThe app reached PostgreSQL; focus on credentials and the selected user.
database ... does not existThe server answered, but the database name is wrong or was never created.
no pg_hba.conf entryPostgreSQL received the request but its host/authentication policy rejected it.
TLS or certificate errorTCP reachability exists; check the client’s SSL mode and the endpoint it is using.

Those are different boundaries. Rotating a password cannot fix DNS. Opening port 5432 cannot fix an app pointed at its own loopback. Disabling certificate verification cannot make a stopped database ready.

1. Use Coolify’s Internal URL for same-destination traffic

Open the PostgreSQL resource in Coolify and copy its Internal URL from the database configuration. Coolify’s database documentation explicitly recommends that URL when an application runs on the same Coolify destination network. Add it to the application as the variable your framework actually reads, commonly DATABASE_URL.

# Shape only — use the value generated by Coolify
DATABASE_URL=postgresql://APP_USER:ENCODED_PASSWORD@INTERNAL_DB_HOST:5432/APP_DB

Do not copy a real URL into a ticket, commit, shell history, screenshot, or public log: it contains credentials. Set it in Coolify’s environment-variable UI, save it, and redeploy or restart according to how the application consumes the variable. If a frontend build can see DATABASE_URL, correct that build configuration immediately; database credentials belong only in server-side runtime code.

The External URL is for an authorised client that genuinely connects from outside the destination network. It may require a published port, firewall controls, TLS, and a different threat model. Using it from a colocated app adds unnecessary exposure and hairpin routing. Prefer the private path.

2. Confirm the app reads the variable you changed

A correct Coolify value has no effect if the process reads DB_HOST, a framework-specific variable, an env file baked into the image, or a different deployment environment. Inspect source and startup configuration, then confirm only whether the expected variable exists inside the replacement container:

# Prints only presence, never the value
if docker exec APP_CONTAINER sh -lc 'test -n "$DATABASE_URL"'; then
  printf '%s\n' 'DATABASE_URL is present'
else
  printf '%s\n' 'DATABASE_URL is missing'
fi

Do not run env, docker inspect, or debug endpoints into a shared log without redaction. They can expose the complete URL. If the setting was changed after the current container started, replace or restart that container; an existing process does not acquire a new environment automatically. For build-time versus runtime confusion, use the focused Coolify environment-variable guide.

3. Prove Docker DNS resolves the internal hostname

Docker networks provide service discovery by name. Compose documents that services on the same network can reach one another by service name even when a container is recreated with a different IP. Test the hostname from the application container or a short-lived diagnostic container on the same network:

docker exec APP_CONTAINER getent hosts INTERNAL_DB_HOST

# If the runtime image deliberately includes a DNS tool:
docker exec APP_CONTAINER nslookup INTERNAL_DB_HOST

Use the hostname portion of the Internal URL, not the full URL. A successful result should return a private container address. Do not replace the name with that address in configuration: container addresses can change on redeploy, while the network alias is designed to remain useful.

If resolution fails, verify that the app and database use the same Coolify server and destination, and inspect their attached Docker network names privately. Separate Coolify projects or environments do not automatically guarantee isolation or connectivity; the effective network attachments decide the path.

docker inspect APP_CONTAINER --format '{{json .NetworkSettings.Networks}}'
docker inspect DATABASE_CONTAINER --format '{{json .NetworkSettings.Networks}}'

# Redact names and addresses before sharing the output.

4. Test TCP port 5432 without exposing it

After DNS succeeds, test the PostgreSQL socket from the application’s network. Use tools already present in an authorised diagnostic environment:

nc -vz INTERNAL_DB_HOST 5432

# Better when PostgreSQL client tools are available:
pg_isready --host=INTERNAL_DB_HOST --port=5432 --timeout=5

pg_isready distinguishes an accepting server from one that is rejecting connections or giving no response. It does not prove that the application’s credentials, database, migrations, and queries work. A refused connection usually means the name resolves but no listener is accepting at that address and port, or the database is still starting. A timeout points more strongly at the path or endpoint.

Do not add a public ports: - "5432:5432" mapping as a diagnostic shortcut. Containers on the same Docker network use the database’s container port directly. Host publishing creates a separate, potentially internet-facing path and does not repair the internal network.

5. Check database state and readiness

Inspect the Coolify database resource and its recent logs. PostgreSQL should be running, not restarting, restoring, upgrading, or failing because its volume is full. If the app starts faster than PostgreSQL, use retry logic with bounded backoff and a real database healthcheck rather than assuming container start order means readiness.

# Run privately with the actual managed container name
docker ps --filter name=DATABASE_CONTAINER \
  --format 'table {{.Names}}\t{{.Status}}'

docker logs --since 10m DATABASE_CONTAINER

A healthcheck such as pg_isready improves startup orchestration, but the application should still tolerate a brief dependency restart. For a Compose deployment, see the related guide to volumes, PostgreSQL readiness, and health-aware startup.

6. Separate credentials from connectivity

Once PostgreSQL returns an authentication error, the network is working. Compare the username and initial database in Coolify with the app’s intended values. Coolify warns that if those values are changed directly inside the running database, the matching General settings must also be updated so generated connection URLs and database automation stay accurate.

Passwords containing reserved URL characters must be percent-encoded when embedded in a URI. A password can be valid in PostgreSQL and still break a connection URL if characters such as @, :, /, ?, #, or % are interpreted as URI syntax. Prefer the exact URL Coolify generates, or let a trusted URL library encode components. Do not hand-log the transformed value to “check” it.

Test with the application’s real driver or migration command so URI parsing matches production. If using psql interactively, avoid putting the password on the command line where process listings and shell history may retain it.

7. Treat TLS errors as a separate final layer

Some frameworks default to requiring SSL in production; others disable it unless requested. The appropriate mode depends on whether the connection stays on the trusted internal Coolify network or crosses an external boundary with a valid server certificate. Do not solve a certificate-name error with a permanent “accept any certificate” option.

First confirm which endpoint the app is using. Then configure the database client’s documented SSL mode deliberately. If encrypted remote access is required, issue a certificate for the name clients use and validate its chain and hostname. If the app uses Coolify’s private internal endpoint, do not accidentally force assumptions copied from a managed public database.

Redeploy once, then prove an application query

  1. Save the correct internal connection setting in the intended Coolify application environment.
  2. Trigger one deployment or restart and follow it to a terminal state.
  3. Confirm the new container has the variable without printing it.
  4. Confirm internal DNS and port 5432 from the app network.
  5. Read the app and database logs for the same attempt.
  6. Run the framework’s migration-status or read-only database check.
  7. Load one public application route that depends on PostgreSQL and assert unique expected content.
  8. If safe in that environment, perform one controlled write and read it back.

A green Coolify deployment and an HTTP 200 on a static health endpoint do not prove database access. Choose a route or API response that cannot succeed without the expected schema and data. Keep rollback and backups in mind before allowing migrations; the Coolify rollback guide explains why reverting an image does not reverse database state.

Compact troubleshooting order

  1. Capture the exact error, target hostname and target port without credentials.
  2. Replace application-container localhost with Coolify’s Internal URL.
  3. Confirm the process reads the changed runtime variable.
  4. Resolve the internal hostname from the application network.
  5. Test port 5432 and database readiness privately.
  6. Verify shared destination/network attachments.
  7. Only after connectivity succeeds, repair user, password, database, URI encoding, or pg_hba.conf policy.
  8. Configure TLS for the actual internal or external boundary.
  9. Redeploy once and prove a real database-backed application path.

The central distinction is simple: an app connecting to 127.0.0.1:5432 is looking inside itself, while a normal Coolify PostgreSQL resource is another container. Point the app at the managed internal hostname, prove the shared network, and let each error tell you which layer to inspect next.

Authoritative references: Coolify’s official database overview explains when to use Internal and External URLs, and its PostgreSQL guide covers generated connection details and configuration consistency. Docker’s Compose networking documentation explains service-name discovery on shared networks. PostgreSQL’s connection-string reference and authentication diagnostics distinguish connection parameters from server-side rejection.