6.0 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 password reset tokens, 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 and password reset flow.apps/api/src/mail/service.tsSends reset emails throughsendmailinside the Docker Mailserver container.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/usersPATCH /api/users/:email/passwordDELETE /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
- edit password modal
- delete action
- send reset action
The create flow intentionally generates a temporary password on the server and immediately emails a one-time reset link to the user. That means the admin panel never needs to expose an initial mailbox password in the UI.
5. Password reset flow
The reset flow is built to stay secure without becoming heavy:
- An admin triggers a reset for an existing mailbox.
- The backend generates a random opaque token.
- Only the SHA-256 hash of that token is stored in SQLite.
- The user receives an email with the one-time reset link.
- The reset page validates the token before showing the password form.
- On submit, the token is claimed for one use only.
- The backend updates the mailbox password through Docker Mailserver.
- Used or expired tokens are rejected.
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.
- 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.
- 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. - Reset emails 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_PASSWORDAPP_MAIL_FROM
- 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. - The admin account for the panel is separate from mailbox users.
- The app stores only panel-specific metadata in SQLite. Mailbox accounts themselves remain managed by Docker Mailserver.