Safeguard
Security Guides•

Should database migrations run as a different role than the app?

Yes. Run schema migrations as an owner role and the running app as a separate role with only data privileges, so an injection cannot drop tables.

Safeguard Security Team
4 min read

Yes. Use one role to run schema migrations and a different role to run the application. In PostgreSQL the owner of an object can alter or drop it, and that right cannot be granted or revoked on its own. If your app logs in as the owner, every SQL injection bug inherits the power to drop tables.

Why ownership matters more than grants

PostgreSQL treats the right to modify or destroy an object as part of owning it. The documentation says it is inherent in being the owner and cannot be granted or revoked in itself. The GRANT reference adds that there is no need to grant privileges to the owner because the owner has all privileges by default.

That is convenient in a tutorial and risky in production. Many frameworks create tables on first run using the same login that serves requests. The result is an application role that owns every table. A single unsafe query then has the same reach as your deploy script.

The two roles, side by side

TaskMigration roleRuntime role
Create or alter tablesYesNo
Own the schema objectsYesNo
Read and write rowsNot neededYes, on named tables
Used byDeploy job onlyThe running service
Credential lives inCI or deploy secret storeApp secret store

The migration credential should never be present in the running container. If a request handler can read it, you have not separated anything.

Steps to set it up

  1. Create two login roles with different credentials.
  2. Make the migration role the owner of the application schema.
  3. Grant the runtime role USAGE on the schema and data privileges only on the tables it needs.
  4. Set default privileges for the owner so new tables are usable by the runtime role without a manual grant.
  5. Point the deploy job at the migration credential and the service at the runtime credential.
  6. Test by trying a DROP TABLE as the runtime role. It should fail.

A minimal sketch looks like this:

CREATE ROLE app_migrator LOGIN;
CREATE ROLE app_runtime LOGIN;
ALTER SCHEMA app OWNER TO app_migrator;
GRANT USAGE ON SCHEMA app TO app_runtime;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA app TO app_runtime;
ALTER DEFAULT PRIVILEGES FOR ROLE app_migrator IN SCHEMA app
  GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO app_runtime;

Set the passwords through your secret store rather than in the script. Our post on default privileges for future tables covers why the last statement matters, and one database user that can do everything explains the failure this avoids.

What about migration tools that need to run at startup?

Some teams run migrations when the service boots. That ties the migration credential to the runtime container. Move the step to a separate job that runs before the new version rolls out. If you need backward compatible changes while old and new versions overlap, see expand and contract migrations.

Review the migration itself

A migration is code that runs with high privilege. Treat grants inside migration files as reviewable changes. A pull request that adds a broad GRANT or makes the runtime role an owner should stop for a human look.

Safeguard scans dependencies, including database drivers and migration libraries, and can gate pull requests with policies. You can try a free scan at gold.safeguard.sh/scan.

Try it yourself

Open a pull request in the demo and watch the gate run. Look for how a policy can stop a risky change before it merges, which is where a migration that grants broad rights should be caught.

Click through it at your own pace, or open it full screen.

Quick check

Can the app role create its own tables?

It can if you grant CREATE on the schema, but you should not. Let the migration role create objects so ownership stays out of the runtime login.

Does a separate role stop SQL injection?

No. It limits what an injection can do. Parameterized queries still prevent the injection itself.

Is a superuser ever right for the app?

No. A superuser bypasses the privilege system, so the separation above would mean nothing.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.