Table of Contents
Overview
Changing PostgreSQL credentials in a Docker Compose stack requires more than editing environment variables. The official postgres image only uses POSTGRES_USER/POSTGRES_PASSWORD on first initialization of the data directory. After data exists, you must change credentials inside PostgreSQL with SQL, then update your services to use the new values.
This guide shows how to rotate a password, change a username, and (if needed) reinitialize the cluster, while keeping downtime low.
Minimal working example
A small docker-compose.yml you can run locally.
a version: "3.9"
services:
db:
image: postgres:16
container_name: db
environment:
POSTGRES_DB: mydb
POSTGRES_USER: myapp
POSTGRES_PASSWORD: initialSecret123
# Optional: use *_FILE to read from a mounted secret file
# POSTGRES_PASSWORD_FILE: /run/secrets/db_password
volumes:
- db_data:/var/lib/postgresql/data
ports:
- "5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U myapp -d mydb"]
interval: 5s
timeout: 3s
retries: 10
volumes:
db_data:
Quick start:
# Start PostgreSQL
docker compose up -d
# Connect (interactive shell)
docker compose exec db psql -U myapp -d mydb
Note: Changing POSTGRES_USER or POSTGRES_PASSWORD in this file later will NOT change the existing database credentials because the data volume persists.
Quickstart: rotate only the password
- Connect as a superuser or the role you want to change
docker compose exec -e PGPASSWORD=initialSecret123 db \
psql -U myapp -d mydb -c "SELECT current_user;"
- Change the password inside PostgreSQL
-- inside psql or via -c
ALTER ROLE myapp WITH PASSWORD 'NewStrongerSecret!';
- Update dependent services
- Update your app’s connection string (e.g., DATABASE_URL) with the new password.
- Redeploy/restart apps that connect to the DB.
- (Optional) Update compose for documentation
- You may update POSTGRES_PASSWORD in docker-compose.yml to match your new password for future reinitializations. It does not affect the current running database.
Change the username (recommended zero-downtime approach)
Changing a role name directly can break connections. A safer pattern is to create a new role, grant it privileges, reassign ownership, then drop the old role.
- Create a new login role with a new password
docker compose exec db psql -U postgres -d mydb -c \
"CREATE ROLE newapp WITH LOGIN PASSWORD 'NewSecret'";
- Grant privileges and set role as owner
- Set database owner, and reassign objects owned by the old role to the new role.
-- As superuser (postgres or a superuser role)
ALTER DATABASE mydb OWNER TO newapp;
REASSIGN OWNED BY myapp TO newapp; -- moves ownership of schemas, tables, etc.
-- Optional cleanup of privileges
DROP OWNED BY myapp; -- removes privileges granted to old role
- Update your applications to use the new username and password
- Update connection strings to use newapp/NewSecret.
- Roll your services (restart) to pick up new credentials.
- Drop the old role when safe
DROP ROLE myapp; -- only after all dependencies are moved
Notes:
- REASSIGN OWNED requires superuser or membership with privileges. Run as postgres (created by the image) or another superuser.
- For multiple databases, repeat ALTER DATABASE and run REASSIGN OWNED in each database context.
Alternative: reinitialize the cluster (destructive)
If you want the environment variables to take effect and you can afford to wipe data:
- Stop and remove containers
docker compose down
- Remove the data volume
docker volume rm $(basename $(pwd))_db_data
# Or: docker volume rm your_project_db_data
- Edit docker-compose.yml
- Set new POSTGRES_USER and POSTGRES_PASSWORD (or *_FILE).
- Start fresh
docker compose up -d
Warning: This destroys all data in the old volume. Backup before doing this.
End-to-end example: rotate password non-interactively
Use psql -c with environment to avoid prompts and scripts without TTY.
NEWPASS="S3cure-$(openssl rand -hex 8)"
# Change password
docker compose exec -T db bash -lc \
"psql -U postgres -d mydb -c \"ALTER ROLE myapp WITH PASSWORD '${NEWPASS}';\""
# Update a .env for your app
sed -i.bak "s/^DATABASE_URL=.*/DATABASE_URL=postgres:\/\/myapp:${NEWPASS}@db:5432\/mydb/" .env
# Restart dependent service(s)
docker compose up -d --force-recreate app
Pitfalls and gotchas
- Environment variables only on first init: POSTGRES_USER/POSTGRES_PASSWORD are read once when PGDATA is empty. Changing them later won’t update existing roles.
- Persisted volumes: The data directory in a volume survives container recreation. That’s why env changes don’t apply automatically.
- Use the right user: ALTER ROLE requires permission. Use postgres (superuser) if your app role lacks privileges.
- Escape secrets in shells: Quotes and special characters can be interpreted by the shell. Prefer single quotes or use environment variables.
- Avoid leaking secrets: Don’t put plain passwords in shell history or commit them to version control. Consider *_FILE and mounted secrets.
- Healthchecks and restarts: After rotating credentials, dependent services may fail until they reload configuration. Restart them in a controlled order.
- Multiple databases/schemas: You may need REASSIGN OWNED in each database. ALTER DATABASE OWNER is per database.
Performance notes
- ALTER ROLE ... PASSWORD is instantaneous; it updates catalog metadata. It doesn’t rewrite table data.
- REASSIGN OWNED and ALTER DATABASE OWNER are metadata operations and typically fast. In large schemas, catalog updates can still take seconds.
- Rotations don’t increase runtime CPU/IO load meaningfully. The main risk is brief connection errors if apps aren’t restarted promptly.
Using files for secrets
The postgres image supports reading credentials from files (useful with Docker secrets or bind mounts):
- POSTGRES_PASSWORD_FILE and POSTGRES_USER_FILE point to files containing the values.
- This only applies at first init, like the env vars. For existing data, still use SQL to rotate.
Example snippet:
services:
db:
image: postgres:16
environment:
POSTGRES_DB: mydb
POSTGRES_USER_FILE: /run/secrets/db_user
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_user
- db_password
secrets:
db_user:
file: ./secrets/db_user
db_password:
file: ./secrets/db_password
FAQ
- Can I just edit docker-compose.yml and restart? No. For an existing volume, PostgreSQL ignores POSTGRES_USER/POSTGRES_PASSWORD. Use SQL to change credentials.
- How do I rotate without downtime? Create a new role with the desired password, grant access, let apps switch over, then drop the old role. Coordinate app restarts.
- I lost the superuser password. What now? Temporarily start with trusted auth via a custom pg_hba.conf or mount a config that permits local trust, reset the password, then restore secure settings.
- Does changing the password impact performance? No. It’s a quick metadata update. Any slowdown comes from app restarts, not PostgreSQL.