Skip to content
  • Categories
  • Recent
  • Popular
  • Tags
  • Users
  • Groups
  • Documentation
  • Downloads
  • Report a bug
  • GitHub mirror
  • Paid support
Skins
  • Light
  • Brite
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dark
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Default (No Skin)
  • No Skin
Collapse
hMailServer Community Forum
hMailServer 6.3.8 is the current release, signed 2 November 2026. Windows and Linux, and an in-place upgrade from 5.3 or later, but for the original hMailServer's 5.7.Download 6.3.8 Documentation FAQ
  1. Support and discussion for hMailServer
  2. Running hMailServer
  3. Linux
  4. In a container or on Kubernetes: the image, Compose, Helm and the environment

In a container or on Kubernetes: the image, Compose, Helm and the environment

Scheduled Pinned Locked Moved Linux
linuxinstallationpackagingadministrationlimitations
1 Posts 1 Posters 143 Views 1 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • ProgressiverobotP
    ProgressiverobotP
    Progressiverobot
    hMailServer Team
    wrote on last edited by chris
    #1

    Written at 6.3.4, when no image was published. hMailServer 6.3.6 (6 October 2026) publishes the first: registry.gitlab.com/hmailserver/hmailserver:6.3.6, also tagged 6.3 and latest, one index over linux/amd64 and linux/arm64, signed with cosign as the release's pipeline; the Helm chart is a file of the release, and the packaging Compose file now runs PostgreSQL 18. See the 6.3.6 announcement and ContainerImage.md at the v6.3.6 tag. hMailServer 6.3.7 (17 October 2026), a security release, is registry.gitlab.com/hmailserver/hmailserver:6.3.7, for linux/amd64 and linux/arm64; in the container the update check names the image to pull and installs nothing, and the schema stays at 6108, so a container recreated on it from 6.3.6 changes no table. Its IMAP and POP3 listeners take the client's address from a PROXY protocol header, as the SMTP listener does, each from the peers on a list of its own, both off by default. See the 6.3.7 announcement. hMailServer 6.3.8 (2 November 2026), a security release, is registry.gitlab.com/hmailserver/hmailserver:6.3.8, and 6.3 and latest now name it; the schema stays at 6108. Its image carries MariaDB Connector/C, so HM_DB_TYPE=MySQL reaches a MariaDB server, where the images of 6.3.7 and earlier stopped at the first start, and its entrypoint secures a new installation at its first start by itself, whatever the container's command, so the run by hand below is left for a database its --create-database made more than a day before. The Helm charts of 6.3.6 and 6.3.7 never started a pod; 6.3.8's runs it as 999:999, fills new volumes in an init container and leaves database.port to the type's own port. Move a release of the older chart with helm repo update, helm upgrade <release> hmailserver/hmailserver --reset-then-reuse-values (Helm 3.14 or later) and then kubectl delete pod <release>-hmailserver-0, since the StatefulSet never replaces the stuck pod by itself (--reuse-values would keep the old chart's fsGroup 1000 and database.port 5432); where a cluster's policy refuses every sysctl, set unprivilegedPortsSysctl: false. For a Compose deployment made from an earlier Compose file, keep its HM_REST_CERTIFICATE_FILE, HM_REST_PRIVATE_KEY_FILE and ./certs mount in the new file, the key readable by 999, or its REST API does not start. See the 6.3.8 announcement.

    The Linux server is also a container image: the x86-64 .deb installed on Ubuntu 24.04 and run as the hmailserver user, with the database connection, the REST listener, the administrator password and, since 6.3.4, every other setting taken from the environment at start. This topic describes the image, the Compose file and the Helm chart as they are in the tree at the v6.3.4 tag, and the HM_* variables the entrypoint reads. The one thing to know first, from the 6.3.4 changelog: no container image of 6.3.4 is published. The images published on ghcr.io/progressiverobot/hmailserver before GitHub's suspension answer 404, and registry.gitlab.com/progressiverobot/hmailserver, which the Compose file and the chart now name, holds nothing. Meanwhile, build the image from the release's .deb and use your own tag wherever the files say registry.gitlab.com/.... What a Linux server is and is not is in hMailServer on Linux.

    Building the image yourself

    Use the docker directory of the same tag as the package: 6.3.4's entrypoint runs verbs a 6.3.3 binary does not have.

    git clone https://gitlab.com/hmailserver/hmailserver.git
    cd hmailserver
    git checkout v6.3.4
    cd hmailserver/source/Server/platform/packaging/docker
    mkdir packages
    curl -L -o packages/hmailserver_6.3.4_amd64.deb \
      https://gitlab.com/hmailserver/hmailserver/-/releases/v6.3.4/downloads/hmailserver_amd64.deb
    docker build -t hmailserver:6.3.4 .
    

    The Dockerfile copies packages/hmailserver_*_amd64.deb (the HM_DEB build argument names another path) and pins its Ubuntu base by digest. 6.3.4's package carries no Sigstore bundle to check it against, as 6.3.3's did. The image is x86-64 only: the Dockerfile takes the amd64 package.

    What is in the image, and what is not

    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. About 40 MB. The process runs as hmailserver; ports below 1024 are bound through cap_net_bind_service on the binary, not through root. Since 6.3.4 the Dockerfile installs the package's two Recommends, 7zip and python3: without them a backup failed with "Could not add the backup settings to the archive", and hmctl had no Python 3.

    Not in: the database, and 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. There is no Control Panel and no COM; the REST API is the administration.

    The variables the entrypoint reads itself

    Sixteen of these are older than 6.3.4 and keep their names and meanings.

    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_DB_TLS_MODE, HM_DB_TLS_CA TlsMode, TlsCaFile Since 6.3.4. disable, prefer (empty, the default), require or verify-full, and the path of a PEM file of CA certificates mounted into the container
    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
    HM_REST_PORT 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
    HM_REST_CERTIFICATE_FILE, HM_REST_PRIVATE_KEY_FILE RestApiCertificateFile, RestApiPrivateKeyFile Paths inside the container, readable by the hmailserver user. Paths the server reads, not _FILE forms
    HM_DB_PASSWORD_FILE, HM_ADMIN_PASSWORD_FILE the same two secrets Read from a file: 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
    HM_INI which file is written Default /etc/hmailserver/hMailServer.ini
    HM_PROFILE, HM_PROFILE_CONFIRM nothing Since 6.3.4: the setting profile applied once, and its confirmation

    On 6.3.3 and earlier the entrypoint writes these into hMailServer.ini on every start, and every other setting is the packaged file in the /etc/hmailserver volume. Since 6.3.4 the database is the settings store, so the four HM_REST_* variables are aliases of HM_SETTING_RestApiPort and its three neighbours, stored like any other setting; [Database] is still written into the file at every start, because it says where the database is. The image's /etc/hmailserver is owned by the hmailserver user, so there, unlike on a package installation, the server keeps its copy of each stored value in the file, and a --create-database that creates the schema also makes the stored-secrets key.

    Every setting from the environment (since 6.3.4)

    After --create-database and --upgrade-database, and before the server, the entrypoint runs the server's own verb, hmailserver --apply-environment, whenever there is anything for it to do. HM_SETTING_<key> sets a [Settings] key and HM_PROPERTY_<name> a database setting (HM_SETTING_MaxSubmissionsPerIPPerMinute=30, HM_PROPERTY_maxmessagesize=20480), and either with _FILE reads the value from a file, a Compose secret or a Kubernetes Secret mounted as one.

    • The environment wins, at every start. An administrator cannot change a setting the environment names until the variable is unset and the container restarted, and a ConfigMap change, too, is applied by a restart and not live.
    • Everything is checked before anything is stored. When anything is refused, nothing is stored and the container does not start; docker compose logs or kubectl logs says which variable and why.

    The naming rule, every refusal, what the REST API and the Control Deck show for a setting the environment manages, and how a value from a file stays secret are in configuration from the environment.

    HM_PROFILE names one of the four setting profiles, office, gateway, relay or sender, which the entrypoint applies once, with --apply-profile NAME --once, after the environment's settings and before the server starts. HM_PROFILE=none records that none is wanted. A profile that closes a listener, trusts an address range, turns a filter or a check off or weakens it, or shortens a window of days needs HM_PROFILE_CONFIRM=yes, and without it the container does not start. A profile skips a setting the environment manages, naming its variable.

    Not yet run in a container. The verb has been run as hMailServer.exe --apply-environment on Windows, and its POSIX half compiles; the entrypoint as a whole, the POSIX verb against a database, the chart and the Compose file have been read and not executed. HM_PROFILE is in the same state. Try it on a scratch database first.

    Reaching the API

    The server requires TLS on a REST listener that is not bound to loopback. A listener left at the packaged bind address answers only processes inside the container, which is none, since the image carries no client. Two ways out; either will do:

    • Bind it and give it a certificate. HM_REST_BIND_ADDRESS=0.0.0.0, with HM_REST_CERTIFICATE_FILE and HM_REST_PRIVATE_KEY_FILE pointing at a certificate and key mounted into the container, and the port published. Publish it on the host's loopback (127.0.0.1:8045:8045) unless the certificate is one clients will trust.
    • Host networking (--network host). The container's 127.0.0.1 is the host's, the packaged bind address is right as it is, no certificate is needed, and the API is at http://127.0.0.1:8045 from the host and through an SSH tunnel from elsewhere. The mail ports are then the host's ports directly.

    First start, with Compose

    The file to use is in the repository, at hmailserver/source/Server/platform/packaging/docker/docker-compose.yml. It reads the two passwords as Compose secrets from files, mounts the REST certificate and key in, and carries the HM_SETTING_ and HM_PROPERTY_ examples commented out. A workflow resolved it with docker compose config on every change until 18 September 2026, so it is known to parse. Below is the mail service of the wiki's shorter example, which puts it under services: beside a postgres:16 service named db and declares the named volumes. It names the image built above, not the empty registry:

      mail:
        image: hmailserver:6.3.4
        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
    

    restart: unless-stopped matters on the first start: depends_on starts the database container first but does not wait for it to accept connections, so the entrypoint's --create-database fails and the container exits if the database is not there yet; the restart tries again a moment later. The first start logs the [Database] section written, the administrator password hashed and the database created at schema 6104, and also what --create-database did about the stored-secrets key and one line per HM_SETTING_ or HM_PROPERTY_ variable stored.

    The first domain is then one request away (First install on Linux). 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.

    The secure defaults of a new installation are not applied in a container by themselves. On a package installation the systemd unit's first start gives a database --create-database made a self-signed certificate, STARTTLS and TLS required for authentication on the Internet IP range. The image has no unit, so that step is run by hand, as the service's user, with the server stopped. SecureDefaults.md gives the verb and no container command for it, and lists its refusal of a running service (exit 4) for Windows only:

    hmailserver --apply-new-install-defaults
    

    A run by hand has no one-day deadline, so an image first booted days after its --create-database is secured this way. What it changes, its exit codes and the checklist item that says what is left are in Hardening a public mail server.

    Kubernetes, with the chart

    Since 6.3.4 the chart is at hmailserver/source/Server/platform/packaging/helm/hmailserver. It is not published to a chart repository; install it from a checkout. It renders a StatefulSet of one, because a message store is not something two pods may share: the three volumes the image declares become claim templates, and the database is pointed at, never bundled. The chart refuses to render without database.host. Its image.repository names the empty registry, so set it to the image you built, pushed to a registry the cluster's nodes can pull from:

    helm install mail ./hmailserver \
      --set image.repository=<your registry>/hmailserver \
      --set database.host=postgres.databases.svc.cluster.local \
      --set database.password=... \
      --set administrator.password=... \
      --set rest.tlsSecretName=mail-tls
    
    Value What it is
    image.repository registry.gitlab.com/progressiverobot/hmailserver as shipped, which holds no image: set it to where you pushed the image you built
    database.host, .type, .name, .user The PostgreSQL or MariaDB the server uses; required. database.create and database.upgrade make the schema on first start and move it forward on an upgrade
    database.password, administrator.password Go into a Secret the chart makes. Point existingSecret at a Secret you made and the chart writes none; its two keys are named by existingSecretKeys
    rest.enabled, .port, .tlsSecretName The one listener that serves the REST API, the Control Deck and the webmail. Off loopback the server requires TLS, so without a kubernetes.io/tls Secret named here the listener will not start; the chart's notes say so on install
    settings, properties, settingsFrom Since 6.3.4: any [Settings] key (rendered as HM_SETTING_<key>), any database setting (HM_PROPERTY_<name>), and a key of a Secret you made, projected into /run/secrets/hmailserver-settings/<n> and named through _FILE, so a secret value is in no environment variable, rendered manifest or log line
    service.type, .ports, .externalTrafficPolicy A LoadBalancer with the mail ports, and the REST port when the listener is on. Local keeps the client's address, which auto-ban, greylisting and the Received header all need
    persistence.* The message store, the logs and the configuration, one claim each
    Probes Port 25, as the image's own health check is, or the metrics listener's /livez and /readyz when that listener is on

    settings, properties and settingsFrom look like this in a values file; a settingsFrom entry says setting: in place of property: for a [Settings] key:

    settings:
      MaxSubmissionsPerIPPerMinute: "30"
    properties:
      maxmessagesize: "20480"
    settingsFrom:
      - secretName: mail-relay
        key: password
        property: smtprelayerpassword
    

    A switch is 1 or 0 for a [Settings] key; true, false, yes, no, on and off are taken as well for a setting whose REST settings row is a switch, and only there: maxmessagesize=off is refused rather than stored as 0, which is "no limit".

    Volumes, ports and the health check

    Path What lives there
    /var/lib/hmailserver The mail store, and .hmailserver-secret-key, the key under which this machine's stored secrets are protected. 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; since 6.3.4 the only home of PasswordPepper and [Security] StoredSecretsKey, which no database and no backup holds

    The image exposes 25, 110, 143, 465, 587, 993, 995, 4190 (ManageSieve) and 8045; 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.

    Upgrades, backups and auto-ban

    Upgrading is building the image of the new tag, as no 6.3.4 image is published to pull, and recreating 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. On MySQL and MariaDB the 6102 step rebuilds every table as utf8mb4, which takes time on a large database and needs the database server version that Databases on Linux gives. An upgraded database gets no stored-secrets key until one is made with hmailserver --create-stored-secrets-key, and never the new installation's defaults. Take the backup first (Upgrading to 6.3.4).

    Backups are three things together: the database, the /var/lib/hmailserver volume, and /etc/hmailserver. Two things are only in /etc/hmailserver/hMailServer.ini: a PasswordPepper, if one is set, without which a restored database refuses the password of every account hashed with Argon2id or scrypt, and the stored-secrets key, without which a restored database opens no account's second factor and no [Settings] secret. Export the key once, and keep the file with the database backups, apart from them:

    docker exec <container> hmailserver --export-stored-secrets-key /var/lib/hmailserver/stored-secrets.key
    docker cp <container>:/var/lib/hmailserver/stored-secrets.key ./stored-secrets.key
    

    A replacement container takes it back with --import-stored-secrets-key FILE.

    Auto-ban works as everywhere: an address that fails to log on repeatedly is refused by the listeners. The hook that adds the address to the host's firewall cannot run from inside a container, so run fail2ban on the host instead: bind-mount /var/log/hmailserver, copy the filter and jail out of the image, point the jail's logpath at the mounted directory, and enable it.

    What was proven, and what has not been

    Until 18 September 2026 the image job of the GitHub workflow built the image on every release tag, pushed it, and smoke-tested the pushed image as the example above runs it: the container healthy, the SMTP banner and the API answering, and the administrator password in no file in clear. Nothing has run it since. GitLab CI's linux-image job is written to build the image from the .deb with kaniko and inspect it; it neither pushes nor runs the image, so the smoke test has no replacement yet. The chart-and-compose job is written to lint and render the chart and resolve the Compose file. Neither had run when ContinuousIntegration.md was written, on 25 September 2026. The changelog's 6.3.4 section records the first full pipeline the day after: it finished failed, the image job among those that passed, and no pipeline has run on the fixed tree since. 6.3.4 was made by hand.

    Where to read more

    • Running in a Container and Profiles and Environment on the wiki.
    • EnvironmentConfiguration.md for the naming rule and every refusal, Profiles.md for every entry of the four profiles, SecureDefaults.md for the verb run by hand, and DatabaseTls.md for HM_DB_TLS_MODE.
    • The 6.3.4 section of CHANGELOG.md for the state of the image.
    • On this forum: the 6.3.4 announcement, hMailServer on Linux, First install on Linux and Upgrading to 6.3.4; configuration from the environment for HM_SETTING_ and HM_PROPERTY_ in full, and the hmailserver command line on Linux for the verbs the entrypoint runs.
    1 Reply Last reply
    0

    Hello! It looks like you're interested in this conversation, but you don't have an account yet.

    Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.

    With your input, this post could be even better 💗

    Register Login
    Reply
    • Reply as topic
    Log in to reply
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes


    hMailServer Community

    The community support forum for hMailServer — the free, open-source mail server for Windows and Linux — maintained by Progressive Robot Ltd. The software is AGPL-3.0 and stays that way; this is where it is discussed, debugged and documented.

    The software

    • Documentation
    • Downloads
    • hMailServer 6.3
    • Source on GitLab

    Getting help

    • Forum rules
    • About this forum
    • Support plans
    • Pay monthly support
    • Talk to Progressive Robot

    Legal

    • Privacy policy
    • Terms and conditions
    • Cookie policy

    Progressive Robot Ltd · 220 Chester Business Park, Heronsway, Chester, Cheshire CH4 9GB, United Kingdom · +44 (0)1244 911212 · [email protected] · VAT 506152326

    Powered by NodeBB

    • Login

    • Don't have an account? Register

    • Login or register to search.
    • First post
      Last post
    0
    • Categories
    • Recent
    • Popular
    • Tags
    • Users
    • Groups
    • Documentation
    • Downloads
    • Report a bug
    • GitHub mirror
    • Paid support