← Back home

2026.10 / mail-state permissions 075

Docker Mailserver Postfix master.lock permission denied

Docker Mailserver starts, but Postfix exits with an error such as fatal: open lock file /var/lib/postfix/master.lock: cannot create file exclusively: Permission denied. Restarting does not help because the failure is normally in the persistent state mount, not the disposable container layer.

The safe repair is not chmod -R 777, a privileged container, or deleting mail-state. First resolve the lock path, preserve the state volume and pending queue, then prove whether the failed boundary is the parent directory, the Postfix state directory, the storage backend, or a security label.

/var/lib/postfix may be a symlink into /var/mail-state. Diagnose the resolved volume path, not only the name printed in the error.

Fast diagnosis order

  1. Confirm Postfix—not another service—is the component failing.
  2. Record the exact image reference and resolved state-volume mount.
  3. Resolve /var/lib/postfix and inspect every parent directory.
  4. Check numeric ownership from inside the container.
  5. Test whether the storage supports ownership, modes, symlinks, and file creation.
  6. Back up mail-state before changing it.
  7. Repair only the proven boundary, recreate once, and verify the queue.

1. Preserve the first useful evidence

Capture the service state and a narrow startup log window. Do not paste the complete output into a public issue: mail logs and Compose configuration can expose domains, addresses, message metadata, and environment values.

docker compose ps

docker compose logs --since 20m mailserver

Use your actual Compose service name. Look for the first Postfix failure, not only Supervisor’s later restart messages. Distinguish these cases:

2. Record the image and effective mount

Before modifying persistent data, identify the image and the mount Docker actually supplied:

docker inspect mailserver \
  --format 'image={{.Config.Image}} id={{.Image}}'

docker inspect mailserver \
  --format '{{range .Mounts}}{{println .Type .Source "->" .Destination .RW}}{{end}}'

Keep host paths private. Confirm that the intended state volume is mounted at /var/mail-state and is writable. A path typo can silently create a new empty bind-mount directory; a Compose override can also replace the expected volume definition.

Do not pull latest during diagnosis. Keep the exact image that produced the state so that a recovery attempt does not mix a permission repair with an unreviewed upgrade.

3. Resolve the lock path and every parent

If the container stays up long enough for exec, inspect the path from inside it:

docker compose exec mailserver \
  readlink -f /var/lib/postfix

docker compose exec mailserver \
  namei -l /var/lib/postfix/master.lock

docker compose exec mailserver \
  stat -c '%A %u:%g %n' \
  /var/mail-state /var/mail-state/lib-postfix

If the normal container exits too quickly, use a one-off container from the same Compose service and image:

docker compose run --rm --no-deps \
  --entrypoint sh mailserver -c '
    readlink -f /var/lib/postfix
    namei -l /var/lib/postfix/master.lock
    stat -c "%A %u:%g %n" \
      /var/mail-state /var/mail-state/lib-postfix
    id postfix
  '

namei -l matters because a correctly owned final directory is still unreachable when a parent lacks execute—or traversal—permission for the service identity. The official DMS issue tracker has documented this exact parent-directory failure on /var/mail-state.

Read numeric UID and GID values inside the container. Names displayed on the host can be misleading because host and container account databases do not need to assign the same names to the same numbers.

4. Decide which boundary is actually broken

EvidenceLikely boundaryNext action
/var/mail-state lacks traversalParent directory mode or ACLRepair that parent narrowly, then recreate
lib-postfix has an unrelated ownerStale or altered state ownershipBack up, derive the expected Postfix IDs, repair only that directory
Mount is read-onlyCompose or storage configurationRestore a writable state mount; do not chmod files
chown, symlink, or lock creation is unsupportedNAS/network/plugin filesystem semanticsMove state to supported local storage or fix the storage class
SELinux denial accompanies normal Unix modesHost security labelApply the documented relabel option; do not disable host policy globally
Fresh disposable local volume worksExisting volume or backendKeep production stopped and repair/restore the real state deliberately

5. Preserve the state volume and queue

Docker Mailserver’s state volume is not merely disposable locks. Its documentation lists the Postfix queue, Fail2Ban bans, antivirus updates, and Rspamd Redis data among the state that may be worth retaining across a crash or power loss.

Stop the service cleanly before copying state so queue files do not change during the archive:

docker compose down

# Example bind-mount backup; replace the path privately
sudo tar --numeric-owner -cpf \
  mail-state-before-permission-repair.tar \
  -C ./docker-data/dms mail-state

Store the archive with restricted permissions. If you use a named volume, use a temporary container that mounts the volume read-only and writes the archive to a separate protected backup directory. Verify that the archive can be listed before proceeding.

Removing the state mount may make a disposable test container start, but it is not a production fix: recreating the container can then discard queued mail and other useful state.

6. Let a supported fresh start repair normal ownership first

For ordinary rootful Docker on local Linux storage, DMS startup logic sets up service state. After the backup, recreate the same pinned image and inspect the first startup:

docker compose up -d --force-recreate mailserver

docker compose logs --since 5m mailserver

If it starts and the resolved directory now has the expected ownership, the durable volume is repaired. If ownership remains wrong, do not loop recreations. Something in the host, remote filesystem, ACL, security policy, rootless mapping, or volume plugin is preventing the startup correction.

7. Make a scoped manual correction only from measured IDs

When evidence proves that only lib-postfix has the wrong owner, derive the service IDs from the exact pinned image instead of copying numbers from a forum post:

POSTFIX_UID=$(docker compose run --rm --no-deps \
  --entrypoint id mailserver -u postfix)
POSTFIX_GID=$(docker compose run --rm --no-deps \
  --entrypoint id mailserver -g postfix)

printf 'Postfix numeric identity: %s:%s\n' \
  "$POSTFIX_UID" "$POSTFIX_GID"

With the mail service stopped and the backup verified, apply ownership only to the confirmed Postfix state directory on the host bind mount:

# Example path only; verify your actual Mounts output first
sudo chown -R "$POSTFIX_UID:$POSTFIX_GID" \
  ./docker-data/dms/mail-state/lib-postfix

Do not recursively apply that owner to all of mail-state. Different sub-services require different identities, and the Postfix spool contains paths with deliberate ownership and mode differences. Likewise, do not change all mail storage to the Postfix user: mailbox data belongs to DMS’s virtual-mail identity, not Postfix.

If the parent alone lacks traversal, compare it with a fresh DMS local volume and grant only the required directory traversal. Check ACLs as well as mode bits. A broad recursive chmod 777 exposes mail state and hides the real incompatibility.

8. Test storage semantics before trusting the repair

Permission failures that return after every restart often point to the backing filesystem. NAS shares, network mounts, volume plugins, rootless mappings, and desktop file-sharing layers may reject or translate ownership, Unix sockets, symlinks, set-group-ID bits, or locks.

Use a disposable directory or volume on the same backend to test the required operations. Never experiment on the live queue. Compare against a disposable local named volume as a control. If local storage works and the production backend does not, move mail-state to a supported local filesystem or reconfigure the storage backend; do not compensate with privileged: true.

On SELinux hosts, Docker Mailserver’s Podman guidance documents relabelling bind mounts with :Z. Confirm an actual SELinux denial first and use the appropriate private/shared label for your deployment. Do not disable SELinux globally. On AppArmor systems, preserve and inspect the denial evidence rather than assuming ordinary mode bits are responsible.

9. Verify Postfix, queue preservation, and real delivery

Start the same pinned release and verify the daemon rather than stopping at “container is running”:

docker compose up -d mailserver

docker compose exec mailserver supervisorctl status postfix

docker compose exec mailserver postfix status

docker compose exec mailserver postqueue -p

Compare the queue with the private pre-repair record. Then test the actual boundaries:

  1. Confirm Postfix remains running through a full health-check interval.
  2. Connect to port 25 and the configured submission port from an authorised test path.
  3. Send one harmless message between controlled accounts.
  4. Follow its queue ID to successful delivery.
  5. Confirm the message appears in the intended mailbox.
  6. Recreate the container once more and confirm the permission failure does not return.
  7. Recheck the queue after recreation and retain the backup through the rollback window.

If Postfix starts but deferred mail remains, diagnose each queue ID’s latest response instead of flushing everything. The Docker Mailserver deferred-queue guide covers that evidence-led path.

Unsafe fixes to avoid

Compact recovery checklist

  1. Capture the exact first Postfix error and running image.
  2. Inspect Docker’s effective /var/mail-state mount.
  3. Resolve /var/lib/postfix and run namei -l.
  4. Compare numeric directory ownership with id postfix in the pinned image.
  5. Check read-only flags, ACLs, labels, free blocks, and inodes.
  6. Stop DMS and make a numeric-owner state backup.
  7. Try one normal forced recreation on supported local storage.
  8. If necessary, repair only the measured parent or lib-postfix boundary.
  9. Start Postfix and compare the preserved queue.
  10. Prove one end-to-end delivery and one further recreation.

Authoritative references: Docker Mailserver’s state-volume documentation explains what /var/mail-state retains and how service paths are redirected into it; the official debugging guide covers clean recreation and compatibility boundaries; and the project’s parent traversal and master.lock issue threads show real resolved-path and storage-permission failures. Match all advice to the exact DMS version and runtime you operate.