← Back home

2026.10 / database migrations 069

Coolify PostgreSQL “relation does not exist” after deploy

A new Coolify deployment starts successfully, its health check turns green, and then a database-backed route fails:

ERROR: relation "orders" does not exist
LINE 1: SELECT ... FROM "orders" ...

PostgreSQL uses relation for tables and several table-like objects, including views, materialized views, sequences, and indexes. The message does not automatically mean the table was deleted. It means the current session could not resolve the referenced name in the database and schema context used by that query.

After a deploy, the usual causes are a wrong database URL, a migration that never completed, a schema or search_path mismatch, quoted-name case drift, or application code released before its database change was safe. Recreating PostgreSQL or restoring a backup before identifying which case applies can turn a release fault into data loss.

Do not create an empty replacement table just to clear the error. First prove the session identity, locate any existing relation, and compare the application release with the recorded migration state.

What this error proves

ObservationWhat it tells you
relation "orders" does not existThe query reached PostgreSQL and name resolution failed in that session.
permission denied for table ordersThe relation resolved, but the role lacks an object privilege; that is a different repair.
database "..." does not existThe connection request named a missing database before the query ran.
connection refused or timeoutThe request has not reached PostgreSQL query processing.
Homepage or /health returns 200The web process may be healthy; it does not prove migrations or database-backed routes.

If the failure is at connection or authentication level, use the Coolify PostgreSQL connection checklist or the password-authentication guide first. Continue here only when a query reaches a database.

1. Freeze the evidence from one failing release

Record the deployment identifier, expected Git commit, first failure time, exact unqualified or qualified relation name, application process that issued it, and migration command or job associated with the release. Keep credentials and the full database URL out of incident notes.

Correlate the application error with PostgreSQL logs by time and, where available, application name or backend PID. Do not assume the web process and migration process have the same environment. A release can run migrations against one database and serve traffic against another when build, runtime, worker, and one-off command variables have drifted.

2. Ask the failing session where it is

Run diagnostics through the same application role and connection settings as the failing process. An administrative shell inside the PostgreSQL container may connect as a different role, database, or socket path and produce a misleading success.

SELECT current_database() AS database_name,
       current_user AS role_name,
       current_schema() AS current_schema,
       current_schemas(true) AS effective_search_path;

SHOW search_path;

Compare only the non-secret results with the intended Coolify resource. Coolify’s official PostgreSQL documentation says its Internal URL includes the container hostname, internal port, role, password, and selected database. For an application on the same destination network, that managed private URL is the baseline. A copied URL with the wrong final path component can authenticate successfully to the same PostgreSQL server while selecting a different database.

Check the running web container, every worker, and the migration job separately. Confirm the expected environment variable is present and sourced at runtime without printing its value. If the framework accepts separate host, database, role, and password settings, verify their precedence too; an old DB_NAME can override a correct URL.

3. Locate the relation without guessing

to_regclass performs a relation lookup and returns NULL instead of throwing an error when the name is not visible. Test both the application’s unqualified name and the intended qualified name:

SELECT to_regclass('orders') AS visible_unqualified,
       to_regclass('app.orders') AS visible_qualified;

Then inspect accessible matching objects in the current database:

SELECT table_catalog, table_schema, table_name, table_type
FROM information_schema.tables
WHERE lower(table_name) = lower('orders')
ORDER BY table_schema, table_name;

PostgreSQL documents that information_schema.tables contains tables and views in the current database that the current user can access. Therefore an empty result can mean the relation is absent or the role has no privilege through which it is visible. If an authorised administrator sees it but the app role does not, inspect schema and object privileges rather than creating another table.

Interpret the results carefully:

4. Prove the migration state

Use the application framework’s read-only migration-status command with the production runtime environment. Examples differ by framework, so use the command documented for the project rather than copying an unrelated tool:

# Run the project's read-only status command in the release context.
FRAMEWORK_MIGRATION_STATUS_COMMAND

# Compare with the deployed commit and expected migration files.
git show --stat EXPECTED_COMMIT -- path/to/migrations

Check four facts:

  1. The deployed image contains the expected migration file.
  2. The migration process connected to the same database as the failing application.
  3. The framework’s migration ledger records the expected migration as applied.
  4. The intended relation and its required columns, constraints, indexes, or view definition actually exist.

A ledger row alone is not proof that the schema is complete. A migration may have been marked, manually edited, partially applied outside a transaction, or followed by a failed concurrent index operation. Conversely, do not rerun an apparently missing migration until you understand whether it is idempotent and what it does to existing data.

5. Read the deployment logs for release-order failures

Find the exact migration invocation and terminal result in the Coolify deployment logs. Common patterns include:

Build time is the wrong place for a production database migration. A Docker build should be reproducible without mutating a live database. Run migrations as an explicit release step or controlled one-off process with a clear failure status, then make the compatible application release eligible for traffic.

6. Repair the right failure class

Wrong database

Correct the application’s Coolify runtime setting to the intended Internal URL or database components. Recreate the affected web and worker containers so long-lived processes receive it. Do not rename databases or copy production tables into the accidental target. Also update any scheduled backup configuration if the authoritative database identity changed.

Migration did not run

Take or verify a current backup before a state-changing migration. Read the migration, estimate locks and runtime, decide whether writes must be paused, and execute it once through the project’s normal migration mechanism. Capture its exit status. Avoid marking a migration as applied merely to silence the framework.

Wrong schema or search path

PostgreSQL resolves an unqualified name by searching schemas in order. It reports an error if no matching object appears in that path, even when the relation exists elsewhere in the same database. Prefer explicit schema qualification where the application supports it, or set a narrowly defined persistent search_path for the application role or database:

-- Example shape: substitute the verified role and trusted schemas.
ALTER ROLE APP_ROLE IN DATABASE APP_DATABASE
  SET search_path = app, pg_catalog;

Do not add arbitrary writable schemas. PostgreSQL warns that putting a schema in search_path effectively trusts users who can create objects there. Confirm ownership, revoke unnecessary CREATE rights, reconnect the application so the role setting takes effect, and retest.

Quoted case mismatch

PostgreSQL folds unquoted identifiers to lower case. A migration that creates "Orders" requires that exact quoted spelling, while most frameworks expect orders. Choose one deliberate naming convention and repair it through a reviewed migration. Do not scatter ad-hoc quoting changes through queries.

Privilege visibility

If the relation exists under the intended schema but the app role cannot see or use it, inspect USAGE on the schema and the specific privileges required on the table, view, or sequence. Grant only the application operations needed. Do not make the app a superuser or transfer ownership of the entire database as a shortcut.

7. Make the release compatible before routing traffic

The safest general pattern is expand and contract:

  1. Add new tables, columns, or compatible views first.
  2. Deploy code that works with both the old and new shape when a rolling window exists.
  3. Backfill in bounded, observable batches if required.
  4. Switch reads and writes after the new shape is proven.
  5. Remove the old shape in a later release after rollback windows close.

If the new code requires a new relation immediately, the migration must complete successfully before that code receives traffic. If the migration is destructive or has already changed data semantics, blindly rolling back the app image may be unsafe. The Coolify rollback and database-state guide explains why images and persistent state need separate recovery decisions.

8. Verify through the real application boundary

After the narrow repair:

  1. Confirm the current deployment uses the intended Git commit and image.
  2. Run the migration-status command and verify the expected relation with a qualified lookup.
  3. Confirm the web process and workers report the intended database, role, and schema context without exposing credentials.
  4. Load the exact route that previously failed and assert unique expected content.
  5. Perform one controlled application-level write, read it back, and remove it through the normal application path if safe.
  6. Check application, worker, and PostgreSQL logs for repeated relation, privilege, or migration errors.
  7. Verify scheduled backups still target the authoritative database.

A green container, an HTTP 200 from a static route, or a successful administrator query does not complete this test. The same application identity and code path that failed must now resolve and use the relation correctly.

Compact troubleshooting order

  1. Capture the exact relation name, deployment, commit, process, and failed query context.
  2. From the app identity, query current_database(), current_user, and the effective schemas.
  3. Use to_regclass and information_schema.tables to locate the object.
  4. Compare the image’s migration files with the framework’s migration ledger.
  5. Read the exact migration command and result in deployment logs.
  6. Classify wrong database, missing migration, wrong schema, case drift, or privilege failure.
  7. Back up, then make one narrow persistent repair.
  8. Verify a database-backed read, controlled write, workers, logs, and backups.
  9. Adopt a release order that prevents the same compatibility gap.

The important distinction is between an absent object and an invisible object. By proving the current database, role, search path, migration ledger, and actual catalog state in that order, you can repair the release without inventing a second schema or destroying the first one.

Authoritative references: Coolify’s official PostgreSQL guide documents the generated Internal URL, selected initial database, private networking, and managed connection settings. PostgreSQL documents schemas and search-path name resolution, session identity and schema functions, and the accessible objects exposed by information_schema.tables.