2026.10 / mailbox permissions 076
Docker Mailserver Dovecot permission denied opening a mailbox
A Docker Mailserver user authenticates successfully, but Dovecot cannot open, create, rename, or update the mailbox. Logs may mention Permission denied beside a path under /var/mail, Maildir, dovecot.index, dovecot-uidlist, tmp, new, or cur. Delivery may fail too, or SMTP delivery may continue while IMAP cannot update indexes and flags.
The safe fix is not a recursive chmod 777 or a guessed chown. First prove the exact failing operation, Docker’s effective mail-data mount, Dovecot’s user lookup and numeric identity, and every parent directory. Back up the mail volume before changing metadata, then repair only the boundary the evidence identifies.
Docker Mailserver’s mail-storage volume is /var/mail. It contains real mailbox data. Do not delete, replace, or test broad permission commands against it.
Fast diagnosis order
- Match one controlled mailbox action to one Dovecot log event.
- Separate authentication success from mailbox-open failure.
- Inspect Docker’s effective
/var/mailmount and read/write mode. - Ask Dovecot for the user’s actual home, mail path, UID and GID.
- Inspect numeric ownership and traversal along the complete failing path.
- Rule out a full filesystem, exhausted inodes, ACLs, labels, and unsupported storage.
- Stop writes, back up the mail volume, and make one scoped repair.
- Verify login, delivery, flag changes, folder creation, and persistence after recreation.
1. Prove this is a storage error, not a bad password
Watch a short log window, then perform one harmless action: open INBOX, mark a test message read, or create a temporary folder.
docker compose logs --since 10m --follow mailserver
Use the real Compose service name. Keep complete logs private because they can expose mailbox addresses, message metadata, client addresses, and internal paths. Classify the first relevant event:
- Authentication failed: diagnose the identity or credential before touching mailbox storage.
- Login succeeded, then open or stat failed: continue with the mail path and filesystem checks.
- Read works but rename, index, flag, or folder creation fails: the path may be readable but not writable.
- No space left on device: inspect blocks and inodes; do not misdiagnose it as ownership.
- Read-only file system: inspect the mount and host filesystem state before file modes.
If the client only reports “login failed”, the separate Docker Mailserver IMAP diagnosis guide shows how to prove authentication independently.
2. Record the image and effective mail-data mount
Inspect the running container rather than assuming Compose was applied as intended:
docker inspect mailserver \
--format 'image={{.Config.Image}} id={{.Image}}'
docker inspect mailserver \
--format '{{range .Mounts}}{{println .Type .Source "->" .Destination .RW}}{{end}}'
Confirm that the intended bind mount or named volume reaches /var/mail and reports true for write access. Keep host paths private. A typo can cause Docker to create an empty host directory, while a Compose override can replace the intended mount or mark it read-only.
Do not upgrade the image during recovery. Keep the exact image reference and ID so a storage repair is not mixed with new startup logic, Dovecot defaults, or mailbox-format changes.
3. Ask Dovecot where this user’s mailbox really lives
Do not infer the mailbox path only from the email address. Query the effective user database:
docker compose exec mailserver \
doveadm user [email protected]
docker compose exec mailserver \
doveconf -n
Review both outputs privately. The user lookup should expose the effective home, mail location, UID, and GID used for mailbox access. doveconf -n shows active overrides, namespaces, index paths, and account-provider settings. LDAP or another provisioner can return a different identity or location from the default file provisioner.
Names such as docker, mail, or vmail are not portable evidence. Record numeric UID and GID values from inside the pinned image. Host and container account databases may display different names for the same number.
4. Inspect the complete path, not just the final file
Substitute the exact path reported by Dovecot. If namei is available in the image:
docker compose exec mailserver \
namei -l /var/mail/example.com/person
docker compose exec mailserver \
stat -c '%A %u:%g %n' \
/var/mail \
/var/mail/example.com \
/var/mail/example.com/person
A process needs execute—or traversal—permission on every parent directory. Correct ownership on the final Maildir does not help if /var/mail or the domain directory blocks the effective identity. Also inspect the exact object from the error, including its parent: Dovecot must create temporary files and atomically rename them when updating indexes, flags, subscriptions, and Maildir state.
If the service container exits too quickly, use a one-off container from the same Compose service and image with the real volume mounted, but do not run write tests yet:
docker compose run --rm --no-deps \
--entrypoint sh mailserver -c '
namei -l /var/mail/example.com/person
stat -c "%A %u:%g %n" \
/var/mail /var/mail/example.com \
/var/mail/example.com/person
'
5. Check capacity and policy boundaries
Permissions are only one reason an operation can fail. Capture these read-only checks:
docker compose exec mailserver df -h /var/mail
docker compose exec mailserver df -i /var/mail
docker compose exec mailserver \
findmnt -T /var/mail
Then inspect host-side evidence for the backing mount: ACLs, filesystem errors, read-only remounts, SELinux or AppArmor denials, rootless/user-namespace mapping, NFS root squashing, and volume-plugin behavior. Docker bind mounts retain host filesystem semantics. A mount can look writable in container metadata while the backing filesystem still rejects a particular UID, ownership change, lock, hard link, or rename.
| Evidence | Likely boundary | Safe direction |
|---|---|---|
| One mailbox path has a different numeric owner | Restore or manual-copy drift | Back up, compare with a healthy sibling, repair only that mailbox |
| Domain parent lacks traversal | Parent mode or ACL | Correct the parent narrowly |
| Mount reports read-only | Compose or host storage | Restore a writable mount; do not chmod content |
| Blocks or inodes are exhausted | Capacity | Free measured safe space, then re-test |
| Unix modes look correct but SELinux denies access | Security label | Use the documented mount relabel policy |
| Local named-volume control works, remote storage fails | Storage semantics or ID mapping | Fix the backend or move mail-data to supported storage |
| Only index files fail after copied restoration | Index ownership or restore metadata | Preserve mail first; repair measured index paths, not all mail |
6. Stop writes and back up mail-data
Stop the mail service cleanly before archiving or changing ownership. This avoids a backup that races with delivery, index writes, or IMAP changes:
docker compose down
# Bind-mount example; replace the path privately
sudo tar --numeric-owner -cpf \
mail-data-before-permission-repair.tar \
-C ./docker-data/dms mail-data
tar -tf mail-data-before-permission-repair.tar >/dev/null
Restrict access to the archive: it contains private mail. For a named volume, mount it read-only in a temporary backup container and write the archive to a separate protected destination. Keep the backup until the repaired service has survived verification and at least one deliberate recreation.
The broader Docker Mailserver backup and restore runbook covers mail-data, state, configuration, image identity, and restore testing together.
7. Make only a measured correction
Compare the affected path with a healthy mailbox created by the same pinned DMS image and the same account provisioner. Compare numeric owners, groups, directory modes, ACLs, and relevant labels. Do not copy a UID or mode from a forum post.
If evidence proves that one restored mailbox has the wrong owner, apply the measured owner only to that mailbox while the service is stopped:
# Example only: use the UID:GID returned by your own user lookup
sudo chown -R MAIL_UID:MAIL_GID \
./docker-data/dms/mail-data/example.com/person
MAIL_UID:MAIL_GID is intentionally a placeholder, not a command to paste unchanged. Resolve the host path from Docker’s mount inspection and the numeric identity from Dovecot’s effective user lookup. If user namespaces or rootless Docker are enabled, translate IDs according to that runtime instead of assuming container and host numbers match directly.
If only a parent ACL or mode blocks traversal, repair that parent rather than recursively rewriting every mailbox. If the mount is read-only, full, incorrectly labelled, or backed by incompatible storage, fix that boundary instead of changing file ownership.
8. Treat indexes carefully
Dovecot index errors do not automatically justify deleting index files. First prove that message files under cur, new, and tmp are preserved and readable, and that the error is limited to regenerable index state. A wrong mailbox owner, read-only mount, or full disk will simply make regenerated indexes fail again.
If official guidance for your pinned Dovecot release supports rebuilding the affected index, do it only after the storage boundary is fixed and the mailbox backup is verified. Never delete message files, dovecot-uidlist, or subscription metadata casually: UID stability and client state matter.
9. Verify more than a successful login
Start the pinned service and exercise every operation that previously crossed the broken boundary:
docker compose up -d mailserver
docker compose ps
docker compose logs --since 5m mailserver
- Authenticate to IMAPS with one controlled account.
- Open INBOX and an existing folder.
- Deliver one harmless message and confirm it reaches
neworcur. - Mark the message read and confirm the flag survives reconnect.
- Create, rename, and remove a temporary folder.
- Check that no fresh permission, read-only, capacity, or index errors appear.
- Recreate the container once and repeat the read/write check.
- Retain the pre-repair archive through the rollback window.
Test a second healthy mailbox too. A repair that makes one account work by broadening access can silently damage isolation or hide a wrong per-user lookup.
Unsafe fixes to avoid
chmod -R 777 /var/mail: exposes private mailbox data and destroys useful permission evidence.- Guessing
chown -R 5000:5000: account backends, image versions, and user mappings differ. - Deleting and recreating
mail-data: removes the actual mailboxes rather than fixing access. - Deleting every Dovecot index first: adds churn before the underlying write failure is fixed.
- Running the whole container as root or privileged: weakens isolation and can create more root-owned files.
- Disabling SELinux or AppArmor globally: replaces a scoped labelling problem with a host-wide regression.
- Testing writes on production mail during diagnosis: risks changing UID lists, flags, and message state without a backup.
- Upgrading during recovery: makes rollback and root-cause analysis harder.
Compact recovery checklist
- Capture the first Dovecot permission error from one controlled action.
- Prove authentication succeeds before touching mailbox storage.
- Record the pinned image and effective writable
/var/mailmount. - Query Dovecot for the real home, mail path, UID, and GID.
- Inspect numeric ownership and traversal on every parent.
- Check capacity, inodes, ACLs, labels, mappings, and storage semantics.
- Stop the service and make a verified numeric-owner mail-data backup.
- Repair only the proven mailbox, parent, mount, or policy boundary.
- Verify delivery, reads, flags, folders, a second user, and container recreation.
Authoritative references: Docker Mailserver’s volume documentation identifies /var/mail as the delivered-mail storage boundary; its basic Compose example shows the intended mail-data mount; Dovecot’s mail-location documentation explains how mailbox paths and index paths are resolved; Docker’s bind-mount documentation explains host-backed mount behavior; and DMS’s Podman guidance documents SELinux relabelling for these volumes. Match all commands and repair decisions to the exact DMS, Dovecot, provisioner, and container-runtime versions you operate.