6.5 KiB
Docker Mailserver User Management Panel
A small full-stack admin tool for managing Docker Mailserver accounts with Bun, ElysiaJS, and React.
1. Proposed stack
Backend
- Bun
- ElysiaJS
- Bun SQLite
- Docker CLI through fixed argument arrays
- Zod for small, explicit validation
Why this backend:
- Bun keeps the runtime, package manager, and SQLite support simple.
- ElysiaJS is a good fit for a small internal API without extra framework weight.
- SQLite is enough for admin sessions and a small amount of panel metadata, and keeps deployment straightforward.
- Docker CLI integration stays close to Docker Mailserver's real operational model instead of hiding it behind unnecessary layers.
Frontend
- React
- Vite
- Plain CSS
Why React:
- It is easy for most full-stack teams to read and maintain.
- This panel only needs basic local state and forms, so React works well without routers or global state libraries.
- The UI stays minimal while still being easy to grow later.
2. Folder structure
apps/
api/
src/
auth/
docker/
lib/
mail/
users/
web/
src/
api/
components/
pages/
styles/
packages/
shared/
data/
app.db
infra/
nginx/
3. Backend implementation
The backend is intentionally flat:
apps/api/src/index.tsDefines the HTTP routes and keeps request flow obvious.apps/api/src/auth/service.tsHandles admin login, bootstrap admin seeding, session cookies, and session lookup.apps/api/src/docker/Wraps Docker CLI calls with fixed argument arrays only. No shell interpolation.apps/api/src/users/service.tsContains the mailbox user operations, pending mailbox invites, and the one-time link flow for password changes.apps/api/src/lib/Holds config parsing, SQLite access, validation, HTTP helpers, and small security utilities.
API design
GET /api/auth/sessionPOST /api/auth/loginPOST /api/auth/logoutGET /api/usersPOST /api/usersDELETE /api/users/:emailPOST /api/users/:email/password-resetPOST /api/password-resets/inspectPOST /api/password-resets/consume
4. Frontend implementation
The frontend is a small React app with three views:
- Login page
- Dashboard page
- Public reset-password page
The dashboard includes:
- mailbox listing
- simple search
- create user modal
- 48-hour setup link generation for new mailboxes
- temporary link generation for password changes on existing mailboxes
- delete action
- no direct password change from the admin panel
The create flow now generates a 48-hour setup link. The mailbox does not exist yet at that point. It is created only when the user opens the link and chooses a password.
5. Password reset flow
The reset flow is built to stay secure without becoming heavy:
- For a new mailbox, the admin generates a 48-hour setup link.
- The mailbox is not created yet.
- The backend stores only the SHA-256 hash of the invite token.
- The admin shares the one-time setup link manually with the user.
- When the user chooses a password through the link, the backend creates the mailbox in Docker Mailserver.
- For existing mailboxes, the same public page is reused for password reset links.
- Every link is one-time and expires automatically.
6. Docker CLI integration strategy
The app talks to Docker Mailserver through docker exec with explicit argument arrays:
setup email listsetup email add <email> <password>setup email update <email> <password>setup email del <email>sendmail -t -i
Security choices here:
- No
sh -c - No user input inserted into shell strings
- Input is validated before command execution
- Docker commands have a timeout
- Errors are returned as API failures instead of leaking raw implementation detail to the browser
7. Security notes
- Admin authentication uses an Argon2id-hashed bootstrap account from environment variables.
- Panel login uses a single access key instead of an email/password pair.
- Access key login is protected by an intentionally aggressive in-memory rate limit with escalating lockouts.
- Sessions are server-side, opaque, and stored in an
HttpOnlycookie withSameSite=Strict. - Password reset tokens are random, one-time, time-limited, and stored only as hashes.
- New mailbox setup links are also one-time, time-limited, and stored only as hashes.
- The admin panel never changes mailbox passwords directly; it only generates one-time links.
- Admin-only routes require a valid session cookie.
- Validation is applied to logins, emails, passwords, and reset payloads.
- Allowed origins are restricted by
APP_ORIGINS. - Setup emails for new accounts are sent from the backend only. No secrets are exposed to the frontend.
- Docker Mailserver access is treated as privileged infrastructure access.
8. Run locally
Prerequisites
- Bun installed
- Docker available locally
- A running Docker Mailserver container
- The API process must be allowed to access Docker
Setup
- Copy
.env.exampleto.env. - Update at least:
HOSTED_DOMAINSMAILSERVER_CONTAINERAPP_ADMIN_EMAILAPP_ADMIN_ACCESS_KEYMAILBOX_INVITE_TTL_HOURS=48
- Install dependencies:
bun install
- Start the API:
bun run dev:api
- In another terminal, start the frontend:
bun run dev:web
- Open
http://localhost:5173.
Production-style local run
If you want the built frontend to be served by the API itself:
bun run build
bun run dev:api
Then open http://localhost:3001.
The API serves files from apps/web/dist by default, configurable with WEB_DIST_PATH.
Docker permission note
If you see:
permission denied while trying to connect to the docker API at unix:///var/run/docker.sock
the app itself is fine, but the API process cannot talk to Docker.
Common fixes:
- Run the API with a user that belongs to the
dockergroup - If the API runs in a container, mount
/var/run/docker.sock:/var/run/docker.sock - Make sure the Docker daemon is actually running
- Make sure
MAILSERVER_CONTAINERmatches the real Docker Mailserver container name
Notes and assumptions
- This panel assumes Docker Mailserver is using the file-based account provisioner and supports the standard
setup emailcommands. APP_ADMIN_EMAILis now an internal admin identity for sessions and audit trails. The actual panel login usesAPP_ADMIN_ACCESS_KEY.- The app stores only panel-specific metadata in SQLite. Mailbox accounts themselves remain managed by Docker Mailserver.