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/postfixmay be a symlink into/var/mail-state. Diagnose the resolved volume path, not only the name printed in the error.
Fast diagnosis order
- Confirm Postfix—not another service—is the component failing.
- Record the exact image reference and resolved state-volume mount.
- Resolve
/var/lib/postfixand inspect every parent directory. - Check numeric ownership from inside the container.
- Test whether the storage supports ownership, modes, symlinks, and file creation.
- Back up
mail-statebefore changing it. - 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:
Permission deniedonmaster.lock: continue with the storage checks below.Resource temporarily unavailableor an existing lock owner: check for a second Postfix process rather than changing permissions.Read-only file system: inspect the mount mode and host storage state.No space left on device: check bytes and inodes; ownership is not the primary fault.
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
| Evidence | Likely boundary | Next action |
|---|---|---|
/var/mail-state lacks traversal | Parent directory mode or ACL | Repair that parent narrowly, then recreate |
lib-postfix has an unrelated owner | Stale or altered state ownership | Back up, derive the expected Postfix IDs, repair only that directory |
| Mount is read-only | Compose or storage configuration | Restore a writable state mount; do not chmod files |
chown, symlink, or lock creation is unsupported | NAS/network/plugin filesystem semantics | Move state to supported local storage or fix the storage class |
| SELinux denial accompanies normal Unix modes | Host security label | Apply the documented relabel option; do not disable host policy globally |
| Fresh disposable local volume works | Existing volume or backend | Keep 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:
- Confirm Postfix remains running through a full health-check interval.
- Connect to port 25 and the configured submission port from an authorised test path.
- Send one harmless message between controlled accounts.
- Follow its queue ID to successful delivery.
- Confirm the message appears in the intended mailbox.
- Recreate the container once more and confirm the permission failure does not return.
- 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
chmod -R 777: grants broad access across sensitive state and erases useful evidence.chown -Ron all state: breaks the deliberately different identities used by Postfix, Dovecot, Rspamd, Fail2Ban, and other services.- Deleting
mail-state: can discard the Postfix queue and other persistent operational data. - Removing the volume permanently: makes state dependent on the replaceable container layer.
- Adding
privileged: true: weakens host isolation without proving why the filesystem rejected the operation. - Hard-coding a UID from another machine: image versions and host mappings can differ.
- Disabling SELinux or AppArmor globally: trades one scoped fault for a host-wide security regression.
- Upgrading during recovery: combines a storage repair with unrelated image changes and complicates rollback.
Compact recovery checklist
- Capture the exact first Postfix error and running image.
- Inspect Docker’s effective
/var/mail-statemount. - Resolve
/var/lib/postfixand runnamei -l. - Compare numeric directory ownership with
id postfixin the pinned image. - Check read-only flags, ACLs, labels, free blocks, and inodes.
- Stop DMS and make a numeric-owner state backup.
- Try one normal forced recreation on supported local storage.
- If necessary, repair only the measured parent or
lib-postfixboundary. - Start Postfix and compare the preserved queue.
- 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.