
如果你使用 DeepSeek、元宝 AI、Copilot、ChatGPT 等 AI 工具,协助配置轩辕镜像、编写 docker pull 命令、修改 Docker Compose 镜像地址、配置镜像加速、排查镜像拉取失败、分析报错日志等问题,请先让 AI 阅读并遵守轩辕镜像的规则文档。
只需在 AI 对话中先发送下面这段话即可:
请先阅读并遵守:https://xuanyuan.cloud/agents.md
未读文档前不要生成 pull 命令或排错方案。查看 agents.md 用法指南与完整示范。国内用户首推 元宝 AI、DeepSeek 的深度思考模式,不推荐豆包 AI;Cursor 等编辑器可在对话 @ 该链接,或加入 User Rules。 若 AI 无法访问外链,可 打开说明文档 复制全文粘贴。文档会随站点更新,复制内容可能过期,建议定期检查。
A small, fully-unprivileged, defence-in-depth Docker image for Roundcube webmail. PID 1 and the whole PHP/web tree run as a non-root user (uid ***), the container holds zero Linux capabilities, the root filesystem is read-only, and an Angie WAF sits in front of PHP.
You bring an IMAP/SMTP server (your own mail host) and a database; the image
brings the webmail front-end. The bundled docker-compose.yml includes a
hardened MariaDB so you can be running in two commands.
Just want it running? Jump to Quick start. Curious how it's locked down? See Security & hardening. Want to know which plugins/skins ship? See Bundled plugins & skins.
| Base | eilandert/debian-base:stable (trixie + deb.myguard.nl repo + hardening) |
| Web | angie-minimal — single self-contained angie.conf with a WAF gate in front of PHP |
| Runtime | php8.5-fpm, minimal extension set, Snuffleupagus + a strict source-audited Roundcube ruleset |
| Cache | tuned OPcache + APCu |
| DB | external MariaDB/MySQL or PostgreSQL (mandatory; no SQLite) |
| Init | rootless s6-overlay PID1 — init-bootstrap.sh oneshot (setup + DB schema) then php-fpm/angie/fpm-watch longruns (s6-rc.d/) |
| Config | one file each: angie.conf (incl. loopback healthz), phpfpm.conf |
Defence in depth: the runtime is hardened by Snuffleupagus + the FPM pool, the edge by an Angie WAF, and the container by the measures below.
S6_READ_ONLY_ROOT=1); it, the Angie and PHP-FPM masters, every
worker — all of it runs as the unprivileged roundcube user (USER roundcube:roundcube in the image; user: "10001:10001" in compose). No root
process at any point. s6 supervises php-fpm + angie, so a killed master is
reaped and restarted instead of leaving a silent outage.cap_drop: [ALL] with no cap_add. Angie
binds a high port (:8080, no CAP_NET_BIND_SERVICE); nothing does runtime
setuid/chown (the masters are already unprivileged; writable mounts are
pre-owned — see Mount ownership).no-new-privileges + AppArmor (apparmor=docker-default).read_only: true). Only the config volume and
the /tmp tmpfs are writable; Angie's pid/sockets/temp + the FPM error log
are redirected there so the rest of the rootfs stays immutable.roundcube:roundcube
0440/0550. The web/PHP processes cannot modify a line of code.des_key and the Snuffleupagus
secret_key are each generated on first boot, unique per deployment, persisted
on the config volume and never in an image layer (override with a Docker secret
or ROUNDCUBEMAIL_DES_KEY / ROUNDCUBEMAIL_SP_SECRET).php_admin_*, not overridable by userland):
open_basedir jail, expose_php off, allow_url_fopen/allow_url_include
off, a wide disable_functions (exec/system/proc_open/passthru/… gone), and
Secure+HttpOnly+SameSite=Strict session cookies with 64-char IDs.real_ip from the trusted private proxy ranges (reads X-Forwarded-For)
so the throttle + RC's failed-login tracking see the real client.return 444 (nikto/sqlmap/nmap/nuclei/wpscan/…).limit_req, 12 r/min + burst) keyed on the
real client IP, applied to login POSTs only (an empty-key map keeps normal
browsing through index.php un-throttled).X-Content-Type-Options, X-Frame-Options.index.php and static.php ever execute;
any other .php (a dropped webshell) returns 404.Pre-installed; enable with ROUNDCUBEMAIL_PLUGINS (comma-separated). Nothing
loads unless you list it.
| Plugin | Version | What it does |
|---|---|---|
contextmenu | 3.3.1 | Right-click context menus (message list / folders) |
contextmenu_folder | 2.0.2 | Folder management context menu |
swipe | 0.6 | Touch swipe gestures (mobile) |
show_folder_size | 0.7.22 | Folder size column |
quota | git | Mailbox/IMAP quota display |
persistent_login | 1.0.3 | "Keep me logged in" |
identity_switch | 3.0.5 | Switch between separate IMAP/SMTP accounts (toteph42 fork) |
advanced_search | 3.7 | Extended search form |
account_details | 5.0.0 | Per-account info pane |
message_highlight | 1.0.5 | Colour-highlight messages by rule |
authres (authres_status) | 0.7.1 | Show SPF/DKIM/DMARC results |
thunderbird_labels | 1.6.2 | Thunderbird-compatible coloured labels |
responses | 1.3.13 | Canned-response templates |
easy_unsubscribe | git | One-click List-Unsubscribe |
rcguard | 1.3.2 | reCAPTCHA after failed logins |
twofactor_gauthenticator | composer | Two-factor auth (TOTP / Google Authenticator) |
scheduled_sending | composer | Queue messages for later delivery |
customizr | composer | Custom logo / watermark / stylesheets via config |
dovecot_client_ip | composer | Forward client IP to Dovecot on IMAP connect |
carddav | composer | CardDAV address-book sync |
Roundcube core plugins (archive, zipdownload, managesieve, password,
newmail_notifier, new_user_dialog) ship with RC and are on by default.
Pick one with ROUNDCUBEMAIL_SKIN (default elastic).
| Skin | Source | Notes |
|---|---|---|
elastic | Roundcube core | Default, responsive |
elastic4mobile | eilandert/roundcube-elastic4mobile (fork) | Mobile-tuned elastic |
elastic-dark | tborychowski/elastic-dark | Dark theme |
elastic2025 | bijanbina/Elastic2025 | Refreshed elastic look |
gmail | bundled (this image) | Gmail look-alike |
outlook365 | bundled (this image) | Outlook 365 look-alike |
larry | roundcube/larry | Classic RC 1.x skin |
classic | roundcube/classic | Minimal classic skin |
shcurl -fsSLO https://raw.githubusercontent.com/eilandert/dockerized/master/src/roundcube/docker-compose.yml $EDITOR docker-compose.yml # set MARIADB_* + ROUNDCUBEMAIL_DB_PASSWORD, # and ROUNDCUBEMAIL_DEFAULT_HOST / SMTP_SERVER docker compose up -d # Browse http://localhost:8080/ -> log in with an IMAP account.
docker-compose.ymlyamlservices: db: image: docker.io/eilandert/mariadb:debian restart: unless-stopped environment: MARIADB_DATABASE: roundcube MARIADB_USER: roundcube MARIADB_PASSWORD: change-me MARIADB_ROOT_PASSWORD: change-me-too volumes: - db:/var/lib/mysql networks: [rc] security_opt: - no-new-privileges:true cap_drop: [ALL] cap_add: [CHOWN, SETUID, SETGID, DAC_OVERRIDE] deploy: resources: limits: memory: 512M pids: 256 healthcheck: test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] interval: 10s timeout: 5s retries: 12 roundcube: image: docker.io/eilandert/roundcube:latest restart: unless-stopped depends_on: db: condition: service_healthy # Unprivileged: cap_drop ALL -> the container CANNOT chown, so the config # mount must already be owned 10001:10001 (a named volume inherits it). user: "10001:10001" environment: TZ: Europe/Amsterdam # ---- IMAP (reading mail) ---- ROUNDCUBEMAIL_DEFAULT_HOST: ssl://imap.example.org ROUNDCUBEMAIL_DEFAULT_PORT: 993 # ---- SMTP (sending mail) ---- ROUNDCUBEMAIL_SMTP_SERVER: tls://smtp.example.org ROUNDCUBEMAIL_SMTP_PORT: 587 # TLS to your mail server is VERIFIED by default. If the cert won't match: # pin a CA: ROUNDCUBEMAIL_SSL_CA=/etc/ssl/mail-ca.pem (mount it :ro) # trusted LAN only (allows MITM): ROUNDCUBEMAIL_SSL_VERIFY: 0 # ---- database (points at the bundled `db` service) ---- ROUNDCUBEMAIL_DB_TYPE: mysql ROUNDCUBEMAIL_DB_HOST: db ROUNDCUBEMAIL_DB_PORT: 3306 ROUNDCUBEMAIL_DB_USER: roundcube ROUNDCUBEMAIL_DB_PASSWORD: change-me # must match MARIADB_PASSWORD ROUNDCUBEMAIL_DB_NAME: roundcube # ---- app ---- ROUNDCUBEMAIL_PLUGINS: archive,zipdownload,managesieve,newmail_notifier,password,new_user_dialog,contextmenu,persistent_login ROUNDCUBEMAIL_UPLOAD_MAX_FILESIZE: 25M ROUNDCUBEMAIL_SKIN: elastic CLEAN_INACTIVE_USERS_DAYS: 365 ports: # Loopback only — :8080 trusts X-Forwarded-For from private ranges, so do # not expose it to an untrusted network; terminate TLS at your edge proxy. - "127.0.0.1:8080:8080" networks: [rc] # ---- hardening ---- read_only: true # rootfs is immutable; writes go to the mounts below volumes: - conf:/var/roundcube/config tmpfs: - /tmp:uid=10001,gid=10001,mode=1770,noexec,nosuid,nodev - /run:uid=10001,gid=10001,mode=0750,exec,nosuid,nodev # s6 scratch — exec REQUIRED security_opt: - no-new-privileges:true - apparmor=docker-default cap_drop: [ALL] # angie binds :8080 -> ZERO capabilities required ulimits: nofile: soft: 10240 hard: 10240 deploy: resources: limits: memory: 512M pids: 256 logging: driver: json-file options: max-size: "10m" max-file: "3" networks: rc: volumes: db: conf:
The compose file bundles a hardened MariaDB; point ROUNDCUBEMAIL_DEFAULT_HOST
/ ROUNDCUBEMAIL_SMTP_SERVER at your own IMAP/SMTP servers. Run behind TLS in
production (terminate at your edge proxy, forward the real client IP via
X-Forwarded-For). The container listens on :8080 only.
cap_drop: [ALL] removes CAP_CHOWN, so any writable mount must already be
owned by uid 10001:
sudo chown -R 10001:10001 /your/bind/dir.--tmpfs /tmp:uid=10001,gid=10001,mode=1770,noexec,nosuid,nodev.A Permission denied on boot = a writable mount not owned 10001:10001. The
fix is always the chown — never add a capability back. (The container prints
these same instructions in its startup logs.)
Driven by ROUNDCUBEMAIL_* env vars (IMAP/SMTP hosts+ports, DB DSN, plugin
list, skin, upload size, TLS verification). Per-deployment PHP overrides go in a
phpfpm.conf.override in the config volume; extra Roundcube config in a
config.inc.php.user. TLS to your mail server is verified by default — set
ROUNDCUBEMAIL_SSL_CA to pin a private CA, or (trusted LAN only, allows MITM)
ROUNDCUBEMAIL_SSL_VERIFY=0.
您可以使用以下命令拉取该镜像。请将 <标签> 替换为具体的标签版本。如需查看所有可用标签版本,请访问 标签列表页面。
来自真实用户的反馈,见证轩辕镜像的优质服务
以下是 eilandert/roundcube 相关的常用 Docker 镜像,适用于 不同场景 等不同场景: