-
Notifications
You must be signed in to change notification settings - Fork 3
Running in a Container
In the tree since 11 September 2026; the first image is published by the 6.3.2 release's own workflow, and the page describes that image. Until then there is nothing at the registry address below.
The Linux server (Installing on Linux) as a container image:
ghcr.io/progressiverobot/hmailserver:<version>, and :latest for a stable
release only, so latest never points at an alpha. It is the x86-64 .deb the
release's workflow built, installed on Ubuntu 24.04 (the distribution release the
package was built on; since Boost is linked statically the package also installs on
later releases, but the image stays on the release it was built for), run as the
hmailserver user the package creates, with one capability on the binary so that
user can bind the mail ports. About 40 MB. Everything the Linux page says about
what the server is and is not applies unchanged: it is the server, there is no
Control Panel and no COM, and the REST API is the administration.
- What is in the image, and what is not
- The environment is the configuration
- Reaching the API
- First start, with Compose
- Volumes, ports and the health check
- Upgrading
- Backups
- Auto-ban in a container
- How the image is built and proven
In: /usr/bin/hmailserver, the packaged hMailServer.ini at
/etc/hmailserver/, the database scripts, the Control Deck, the fail2ban filter
and jail the package carries, ca-certificates for the TLS the server does as a
client, and an entrypoint. The process runs as hmailserver; ports below 1024
are bound through cap_net_bind_service on the binary, not through root.
Not in: the database. It never will be. Point the image at a PostgreSQL or
MariaDB server, in another container or anywhere else; on first start the
entrypoint creates the schema there, and on every later start upgrades it if it
is behind. Also not in: curl, an editor, or a shell of any consequence beyond
bash; the image is the server and nothing else.
The entrypoint writes hMailServer.ini from these on every start, so the
environment is the truth and a stale file cannot disagree with it. Each is
optional; what is not set is left as the packaged file has it.
| Variable | Writes | Notes |
|---|---|---|
HM_DB_TYPE |
[Database] Type |
PostgreSQL or MySQL (MariaDB is MySQL). Setting this is what makes the section be written at all |
HM_DB_HOST, HM_DB_PORT
|
Server, Port
|
Defaults db, and the type's port (5432 or 3306) |
HM_DB_NAME, HM_DB_USER, HM_DB_PASSWORD
|
Database, Username, Password
|
Defaults hmailserver, hmailserver, empty. The user needs the right to create the database on first start |
HM_ADMIN_PASSWORD |
[Security] AdministratorPassword |
Through the server's own --set-admin-password: the file holds a PBKDF2 hash, never the password. Without one the REST API refuses to start, and the REST API is the only administration this build has |
HM_REST_PORT |
[Settings] RestApiPort |
The packaged file has 0, which is off. 8045 is the conventional port |
HM_REST_BIND_ADDRESS |
RestApiBindAddress |
The packaged file has 127.0.0.1, which inside a container is the container's own loopback - see the next section |
HM_REST_CERTIFICATE_FILE, HM_REST_PRIVATE_KEY_FILE
|
RestApiCertificateFile, RestApiPrivateKeyFile
|
Paths inside the container, readable by the hmailserver user |
HM_DB_PASSWORD_FILE, HM_ADMIN_PASSWORD_FILE
|
the same two secrets | Read from a file instead: a Docker or Compose secret. Setting both a variable and its _FILE is refused |
HM_CREATE_DATABASE, HM_UPGRADE_DATABASE
|
nothing |
0 skips --create-database or --upgrade-database at start. Both are no-ops when there is nothing to do, so the default of 1 is right for almost everyone |
HM_INI |
which file all of this is written to | Default /etc/hmailserver/hMailServer.ini
|
Anything the table does not cover - listeners, directories, LDAP, logging - is
the packaged hMailServer.ini in the /etc/hmailserver volume, edited as on any
Linux installation (Settings Reference); the entrypoint rewrites only the
keys above and leaves the rest of the file as it finds it.
The server requires TLS on a REST listener that is not bound to loopback. Inside
a container, 127.0.0.1 is the container's loopback, so a listener left at the
packaged bind address answers only to processes inside the container - which is
none, since the image carries no client. Two ways out, and one is enough:
-
Bind it and give it a certificate.
HM_REST_BIND_ADDRESS=0.0.0.0,HM_REST_CERTIFICATE_FILEandHM_REST_PRIVATE_KEY_FILEpointing at a certificate and key mounted into the container (readable by thehmailserveruser), and the port published. The example below does this. Publish it on the host's loopback (127.0.0.1:8045:8045) or a private network unless the certificate is one clients will trust. -
Host networking (
--network host). The container's127.0.0.1is the host's, the packaged bind address is right as it is, no certificate is needed, and the API is reachable from the host athttp://127.0.0.1:8045and from elsewhere through an SSH tunnel, exactly as on a package installation. The mail ports are then the host's ports directly, with no publishing.
services:
db:
image: postgres:16
environment:
POSTGRES_USER: hmailserver
POSTGRES_PASSWORD: change-me
POSTGRES_DB: postgres
volumes:
- pgdata:/var/lib/postgresql/data
restart: unless-stopped
mail:
image: ghcr.io/progressiverobot/hmailserver:6.3.2
depends_on:
- db
environment:
HM_DB_TYPE: PostgreSQL
HM_DB_HOST: db
HM_DB_NAME: hmailserver
HM_DB_USER: hmailserver
HM_DB_PASSWORD: change-me
HM_ADMIN_PASSWORD: change-me-too
HM_REST_PORT: 8045
HM_REST_BIND_ADDRESS: 0.0.0.0
HM_REST_CERTIFICATE_FILE: /certs/rest.pem
HM_REST_PRIVATE_KEY_FILE: /certs/rest.key
volumes:
- ./certs:/certs:ro
- hm-data:/var/lib/hmailserver
- hm-logs:/var/log/hmailserver
- hm-etc:/etc/hmailserver
ports:
- "25:25"
- "587:587"
- "993:993"
- "127.0.0.1:8045:8045"
restart: unless-stopped
volumes:
pgdata:
hm-data:
hm-logs:
hm-etc:restart: unless-stopped matters on the first start: depends_on starts the
database container first but does not wait for it to accept connections, and the
entrypoint's --create-database fails, and the container exits, if the database
is not there yet. The restart tries again a moment later and succeeds. What the
first start logs, in order:
[Database] written from the environment: PostgreSQL on db:5432, database hmailserver
[Settings] REST API written from the environment: port 8045 bind 0.0.0.0 certificate /certs/rest.pem key /certs/rest.key
The administrator password was written to /etc/hmailserver/hMailServer.ini as a PBKDF2 hash.
Database hmailserver created on PostgreSQL at schema version 6031, from /usr/share/hmailserver/DBScripts/CreateTablesPGSQL.sql.
The database is at schema version 6031, which is what this build needs. Nothing to do.
Then the server is up, and the first domain is one request away, as on the Linux page:
curl -k -u Administrator https://127.0.0.1:8045/api/v1/status
curl -k -u Administrator -H 'Content-Type: application/json' \
-d '{"name":"example.com"}' https://127.0.0.1:8045/api/v1/domains-k because the example's certificate is self-signed; drop it when the
certificate is a real one. Passwords in a Compose file are passwords in a file:
for anything beyond a first look, use HM_DB_PASSWORD_FILE and
HM_ADMIN_PASSWORD_FILE with Compose secrets.
| Path | What lives there |
|---|---|
/var/lib/hmailserver |
The mail store, and .hmailserver-secret-key, the key under which the server protects stored secrets. The key travels with this directory: a database restored beside a fresh data volume cannot open the secrets it holds |
/var/log/hmailserver |
The logs. Bind-mount it to a host path if fail2ban on the host is to read it |
/etc/hmailserver |
hMailServer.ini. Owned by the hmailserver user in the image, so the entrypoint can write it |
The image exposes 25, 110, 143, 465, 587, 993, 995 (mail), 4190 (ManageSieve) and 8045 (the REST API and the Control Deck); publish the ones you serve. The health check opens a connection to the container's own port 25 every 30 seconds after a 60-second start period, and three failures in a row mark the container unhealthy - which is what an orchestrator restarts on.
Pull the new tag and recreate the container. The entrypoint runs
--upgrade-database on every start, so a schema that is behind is brought
forward before the server opens a port, and one that is current is reported as
such and left alone. The upgrade is the same one the Linux package runs
(Upgrading hMailServer); take the backup first.
Three things, together: the database (its own tools), the /var/lib/hmailserver
volume (the mail store and the secret key), and /etc/hmailserver if you edited
it beyond what the environment writes. Backup and Restore applies; the
server's own backup writes into the data volume.
The server's own auto-ban works as everywhere: an address that fails to log on
repeatedly is refused by the listeners. What the Installing on Linux page
describes below that - the hook that adds the address to the host's firewall -
cannot run from inside a container, which has no authority over the host's
tables. Run fail2ban on the host instead: bind-mount /var/log/hmailserver
to a host path, copy the filter and jail the package carries out of the image
(docker cp <container>:/etc/fail2ban/filter.d/hmailserver.conf … and the jail
beside it), point the jail's logpath at the mounted directory, and enable it.
The ban then lands in the host's firewall, ahead of the published ports.
The image job of the Linux workflow runs on every v* tag after the build:
it takes the amd64 .deb that run made, checks its version is the tag's, builds
the image with Buildx from a base pinned by digest, pushes it, and then
smoke-tests the pushed image the way the example above runs it: a bridge
network with a PostgreSQL container beside it, a throwaway certificate, the
database, listener and password from the environment. It passes only if the
container reports healthy, both listeners are inside the container's own network
namespace, the SMTP banner through the published port is the server's
(220 <host> ESMTP), the API answers 200 to the administrator, 401 to a wrong
password and 201 to a domain created through it, and the administrator password
appears in no file in clear. A tag whose image fails any of that is a release
without an image, not a release with a broken one.
hMailServer 6.3.2 · AGPL-3.0-or-later · Repository · Report a documentation error
Hmail Server — full index
Start here
1. Install and run
- Before You Install
- Installing hMailServer
- Installing on Linux
- Running in a Container
- The Control Panel
- Your First Domain and Mailbox
- Connecting a Mail Client
- DNS for Your Domain
2. Secure it
3. Operate it
- Monitoring and Health
- Backup and Restore
- Troubleshooting
- Diagnosing Stalled Mail
- Relocating an Installation
- Upgrading hMailServer
- Upgrading Guide
- Migrating the Database Backend
- High Availability Runbook
- Warm Standby
- Runbooks Digest
4. Extend it
- Rules and Sieve
- Aliases Lists and Public Folders
- Routes and Relays
- The COM API and Scripting
- The REST API
- APIs Reference
5. Contribute to it
- Project Handbook
- Architecture
- Contributing
- Release Process
- Governance
- Assurance Case
- Regression Test Environment
- Fuzzing
- Regulatory Scope
- Third-Party Binaries
Look it up — from any journey