The "chmod: changing permissions: Operation not permitted" error occurs when PostgreSQL fails to change file permissions during initialization, typically in Docker containers or when using NFS volumes with root_squash enabled. Fix by adjusting volume permissions, using Docker named volumes instead of bind mounts, or configuring NFS properly.
The "chmod: changing permissions: Operation not permitted" error is a filesystem permission issue that occurs when PostgreSQL tries to change directory permissions during database initialization. This typically happens when the postgres process lacks sufficient privileges to modify permissions on database files or directories. PostgreSQL initialization scripts run chmod commands to set correct permissions on the data directory and socket directories (/var/lib/postgresql/data, /var/run/postgresql). When these commands fail with EACCES (Operation not permitted), it signals that the current user cannot modify the permission bits on those directories—usually because they do not own the files. This error is extremely common in containerized environments (Docker, Kubernetes) when: 1. PostgreSQL attempts to chmod directories on bind-mounted host volumes, where file ownership mismatches between host and container 2. NFS volumes are mounted with root_squash enabled, which prevents the root (or postgres) user inside the container from changing permissions 3. The data directory is pre-populated with files owned by a different user 4. Running the postgres container with an arbitrary user that differs from the standard postgres user The error is a safety mechanism—PostgreSQL refuses to start if it cannot set proper permissions. The fix is not to bypass chmod, but to ensure the container and volume are configured so that chmod succeeds.
First, examine the full error message to identify which directory is causing the permission issue.
# If running Docker, check the container logs:
docker logs <container_name>
# Look for the specific path in the error message:
# chmod: changing permissions of '/var/lib/postgresql/data': Operation not permitted
# or
# chmod: changing permissions of '/var/run/postgresql': Operation not permitted
# If running locally, check the PostgreSQL initialization logs:
sudo -u postgres /usr/lib/postgresql/13/bin/initdb /var/lib/postgresql/13/main 2>&1Identify whether the issue is with /var/lib/postgresql/data (data directory) or /var/run/postgresql (socket directory).
The most common cause is using Docker bind mounts instead of named volumes. Docker bind mounts can have permission mismatches because they directly reference the host filesystem.
Bind mount (problematic):
docker run -v /host/path:/var/lib/postgresql/data postgresNamed volume (recommended):
docker volume create pgdata
docker run -v pgdata:/var/lib/postgresql/data postgresNamed volumes are managed by Docker and have proper permission handling. If you currently use bind mounts and experience this error, switch to named volumes.
Replace bind mounts with Docker named volumes, which properly handle file ownership and permissions.
# Create a named volume:
docker volume create postgresql-data
# Update your docker-compose.yml:
version: '3.9'
services:
postgres:
image: postgres:15
volumes:
- postgresql-data:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: password
volumes:
postgresql-data:
driver: local# Or run directly with Docker:
docker run -d \
--name postgres \
-v postgresql-data:/var/lib/postgresql/data \
-e POSTGRES_PASSWORD=password \
postgres:15
# Start the container:
docker-compose up -dNamed volumes are persistent and properly managed by Docker, eliminating permission issues.
If you must use bind mounts, ensure the host directory has correct ownership before starting the container.
# Create the directory on the host with correct ownership:
sudo mkdir -p /data/postgresql
sudo chown -R 999:999 /data/postgresql
sudo chmod -R 700 /data/postgresql
# Note: uid 999 is the postgres user in the official postgres Docker image
# Verify the postgres user's uid in the image:
docker run --rm postgres id postgres
# Now run the container with the bind mount:
docker run -d \
--name postgres \
-v /data/postgresql:/var/lib/postgresql/data \
-e POSTGRES_PASSWORD=password \
postgres:15The postgres user inside the container (uid 999) must own the host directory. If the uid differs, adjust the chown command accordingly.
If using NFS storage (common in Kubernetes), the root_squash setting prevents permission changes. Update the NFS export configuration.
On the NFS server, edit /etc/exports:
/exported/path *(rw,sync,no_root_squash,no_all_squash)Or use IP-specific rules:
/exported/path 192.168.1.0/24(rw,sync,no_root_squash)Then apply the changes:
sudo exportfs -raWARNING: no_root_squash allows the root user inside containers to act as root on the NFS share. Use cautiously and only for trusted networks. As an alternative, see the next solution.
For Kubernetes, use an init container to pre-initialize the data directory with correct permissions before the main PostgreSQL container starts.
apiVersion: v1
kind: Pod
metadata:
name: postgres
spec:
initContainers:
- name: init-postgres-dir
image: postgres:15
command:
- /bin/sh
- -c
- |
mkdir -p /var/lib/postgresql/data
chmod 700 /var/lib/postgresql/data
volumeMounts:
- name: pgdata
mountPath: /var/lib/postgresql/data
containers:
- name: postgres
image: postgres:15
volumeMounts:
- name: pgdata
mountPath: /var/lib/postgresql/data
env:
- name: POSTGRES_PASSWORD
value: "password"
volumes:
- name: pgdata
persistentVolumeClaim:
claimName: pgdata-pvcThe init container runs with appropriate permissions (or with no_root_squash temporarily) to set up directories before PostgreSQL starts.
If the data directory is in a partially initialized state, clean it up and restart.
# Stop the container:
docker stop <container_name>
# Remove the container:
docker rm <container_name>
# Delete the volume (WARNING: This deletes data!):
docker volume rm postgresql-data
# Create a fresh volume:
docker volume create postgresql-data
# Run the container again:
docker run -d \
--name postgres \
-v postgresql-data:/var/lib/postgresql/data \
-e POSTGRES_PASSWORD=password \
postgres:15For bind mounts:
# Delete the directory content:
sudo rm -rf /data/postgresql/*
# Ensure correct permissions:
sudo chown -R 999:999 /data/postgresql
sudo chmod -R 700 /data/postgresql
# Restart the container:
docker-compose up -dA fresh initialization avoids corruption from partial/failed attempts.
After applying a fix, verify the PostgreSQL container is running and accessible.
# Check container status:
docker ps | grep postgres
# View logs for errors:
docker logs <container_name> | tail -20
# Connect to PostgreSQL from the host:
psql -h localhost -U postgres -c "SELECT version();"
# If no host tools available, exec into the container:
docker exec -it <container_name> psql -U postgres -c "SELECT version();"Verify that initialization completed without "Operation not permitted" errors and the database is accepting connections.
The chmod operation is critical to PostgreSQL security. The /var/lib/postgresql/data directory must have permissions 700 (rwx------) so only the postgres user can access database files. Similarly, /var/run/postgresql must be 775 (rwxrwxr-x) for socket access. These permissions prevent unauthorized users from reading database files directly from disk.
When using Kubernetes with StatefulSets, use PersistentVolumes backed by local storage or properly configured network storage (NFS, EBS) instead of emptyDir volumes. StatefulSets combined with named volumes provide reliable persistence and permission handling.
Docker Compose simplifies volume management. Always use the volumes: section with named volumes rather than raw host paths in production setups.
For development, you can work around this by running the postgres container as root temporarily (add --user root to the docker run command), but never do this in production—it violates principle of least privilege.
Linux AppArmor or SELinux policies can also interfere with chmod operations. If you have SELinux enabled (check with getenforce), you may need to adjust policies or disable SELinux for the volume mount using the :Z option in Docker: -v pgdata:/var/lib/postgresql/data:Z.
When migrating PostgreSQL from non-containerized to containerized deployments, test volume setup thoroughly on a staging environment first. File permission issues can silently corrupt a database if left unchecked.
insufficient columns in unique constraint for partition key
How to fix "insufficient columns in unique constraint for partition key" in PostgreSQL
ERROR 42501: must be owner of table
How to fix "must be owner of table" in PostgreSQL
trigger cannot change partition destination
How to fix "Trigger cannot change partition destination" in PostgreSQL
SSL error: certificate does not match host name
SSL error: certificate does not match host name in PostgreSQL
No SSL connection
No SSL connection to PostgreSQL